智能指针类的一个简单实现



在书C++ Primer 13.5.1中,它使用Use Count class实现了智能指针类。它们的实现如下:

  • 使用计数类

    // private class for use by HasPtr only
    class U_Ptr {
    friend class HasPtr;
    int *ip;
    size_t use;
    U_Ptr(int *p): ip(p), use(1) { }
    ~U_Ptr() { delete ip; }
    };
    
  • 智能指针类

    /* 
    smart pointer class: takes ownership of the dynamically allocated
    object to which it is bound
    User code must dynamically allocate an object to initialize a HasPtr
    and must not delete that object; the HasPtr class will delete it
    */
    class HasPtr {
    public:
    // HasPtr owns the pointer; p must have been dynamically allocated
    HasPtr(int *p, int i)
    : ptr(new U_Ptr(p)), val(i) { }
    // copy members and increment the use count
    HasPtr(const HasPtr &orig)
    : ptr(orig.ptr), val(orig.val) { ++ptr->use; }
    HasPtr& operator=(const HasPtr&);
    // if use count goes to zero, delete the U_Ptr object
    ~HasPtr() { if (--ptr->use == 0) delete ptr; } 
    friend ostream& operator<<(ostream&, const HasPtr&);
    // copy control and constructors as before
    // accessors must change to fetch value from U_Ptr object
    int *get_ptr() const { return ptr->ip; } 
    int get_int() const { return val; }
    // change the appropriate data member
    void set_ptr(int *p) { ptr->ip = p; }
    void set_int(int i) { val = i; }
    // return or change the value pointed to, so ok for const objects
    // Note: *ptr->ip is equivalent to *(ptr->ip)
    int get_ptr_val() const { return *ptr->ip; } 
    void set_ptr_val(int i) { *ptr->ip = i; }
    private:
    U_Ptr *ptr;        // points to use-counted U_Ptr class
    int val;
    };
    

Wonder:我很好奇为什么不简单地使用int *来充当Use-Count Class,就像以下新Smart Pointer Class:中使用的int* countPtr;一样

class T
{
private:
int* countPtr; //
int* p;
int val;
public:
T(){
p = new int();
countPtr = new int();
*countPtr = 1;
val = 0;
}
T(T& t){
p = t.p;
countPtr = t.countPtr;
val = t.val;
*countPtr += 1;
}
T& operator = ( const T& rT){
if(*countPtr>1){
*countPtr -= 1;
}
else{
delete p;
delete countPtr;
}
p = rT.p;
countPtr = rT.countPtr;
val = rT.val;
*countPtr += 1;
return *this;
}
~T(){
if(*countPtr>1){
*countPtr -= 1;
}
else{
delete p;
delete countPtr;
}
}
int *get_ptr() const { return p; } 
int get_int() const { return val; }
// change the appropriate data member
void set_ptr(int *ptr) { p = ptr; }
void set_int(int i) { val = i; }
};

测试:我使用如下代码测试了上述Smart Pointer Class,它似乎运行良好。

int main()
{
T t1;
T t2(t1);
T t3(t1);
T t4;
t4 = t1;
return 0;
}

真正的问题:这个新的Smart Pointer Class和一个简单的int *countPtr足够吗?如果是,为什么要像书中那样使用额外的Use-Count Class?如果没有,我会错过什么?

原始实现的一个属性是在控制块对象中执行delete,其中为原始指针类型。这是部分类型的擦除。无论智能指针对象被复制了多少,使用不同的类型,原始控制块都保持不变,delete通过原始指针类型。

但是,由于您显示的原始代码不是模板化的,因此必须假设它是早期的示例,后面是类似的模板化代码。

在基类层次结构中向上转换指针,就像复制智能指针一样,意味着只有当静态已知的新类型具有虚拟析构函数时,新指针类型上的delete才有效。

例如,std::shared_ptr还通过原始指针类型删除(保证),除非显式提供了一个执行其他操作的deleter函子。

我的猜测是,作者——无论是有意识还是无意识地——都意识到拥有一个单独的类在现实世界的智能指针中是有用的,例如:

  • 一个使共享指针线程安全的互斥(尽管原子操作在可用时可能更好),

  • 调试信息(例如boost::shared_ptr有一个#ifdef,包含一个共享计数器id)

  • 虚拟调度表,例如由boost共享指针用于调度到操作系统适当的代码(请参阅boost/smart_ptr/detail/sp_counted_base_*.hpp头)

  • 我不知道这本书,但也许他们会继续解释U_Ptr中可能包含的其他内容。。。。

    您的代码相当于本书中报告的标准代码。然而,在某些方面它是最糟糕的:

    1. 您需要两个分配/解除分配,而不是一个(两个int,而不是单个对象)。这可能会更慢,也更难管理。

    2. 在每个对象中都有一个重复的指针副本。所以:重复的信息,你应该保证保持有效。

    3. 你的对象更大(两个指针而不是一个)

    你只有一个积极的音符:

    1. 对指针的访问是直接的,而不是间接的。这可能意味着,通过您的实现,对引用对象的访问稍微快一些

    (2017年4月14日更新)

    我自己尝试了unique_ptr和shared_ptr,发现这些类并没有让你的生活变得更轻松,我有点惊讶。我在一个API中有一个函数,该函数将获取Object*&-填充它(指针),然后您需要删除该对象。使用c++11是可能的,但您需要为此添加额外的临时指针。(所以使用*_ptr类不会让我的生活更轻松)

    实现智能指针有多复杂?

    通过快速浏览auto_ptr类的实现,我已经快速编码了简单的智能点容器类,但后来我注意到我需要支持引用同一对象指针的多个智能指针。

    好吧,那么代码引用计数有多复杂呢?我浏览了一下,然后去了谷歌,发现了一篇有趣的文章:

    http://www.codingwisdom.com/codingwisdom/2012/09/reference-counted-smart-pointers-are-for-retards.html

    不知怎的,我倾向于同意这篇文章的作者和文章中的评论,即引用计数使生活变得更加复杂,但仍然试图坚持纯C听起来也有点愚蠢。

    我现在将在这里添加我自己类的代码片段,如果你想获得最新版本,你可以在这个svn存储库中进行检查:https://sourceforge.net/p/testcppreflect/code/HEAD/tree/SmartPtr.h

    以下是旧版本。

    #pragma once
    //
    // If you're using multithreading, please make sure that two threads are not accessing 
    // SmartPtr<> pointers which are cross linked.
    //
    template <class T>
    class SmartPtr
    {
    public:
    SmartPtr() : ptr( nullptr ), next( nullptr )
    {
    }
    SmartPtr( T* pt ) : ptr( pt ), next( nullptr )
    {
    }
    SmartPtr( SmartPtr<T>& sp ) : ptr( nullptr ), next( nullptr )
    {
    operator=(sp);
    }
    ~SmartPtr()
    {
    release();
    }
    // Reference to pointer - assumed to be filled out by user.
    T*& refptr()
    {
    release();
    return ptr;
    }
    // Pointer itself, assumed to be used.
    T* get()
    {
    return ptr;
    }
    T* operator->() const
    {
    return ptr;
    }
    T* operator=( T* _ptr )
    {
    release();
    ptr = _ptr;
    return ptr;
    }
    SmartPtr<T>& operator=( SmartPtr<T>& sp )
    {
    release();
    ptr = sp.ptr;
    if ( ptr )      // If we have valid pointer, share ownership.
    {
    if( sp.next == nullptr )
    {
    next = &sp;
    sp.next = this;
    } else {
    SmartPtr<T>* it = &sp;
    while( it->next != &sp )
    it = it->next;
    next = &sp;
    it->next = this;
    }
    }
    return *this;
    }
    void release()
    {
    if ( !ptr )
    return;
    // Shared ownership.
    if( next != nullptr )
    {
    // Remove myself from shared pointer list.
    SmartPtr<T>* it = next;
    while( it->next != this )
    it = it->next;
    if( it == it->next->next )
    it->next = nullptr;
    else
    it->next = next;
    next = nullptr;
    ptr = nullptr;
    return;
    }
    // Single user.
    delete ptr;
    ptr = nullptr;
    }
    T* ptr;                 // pointer to object
    SmartPtr<T>* next;      // nullptr if pointer is not shared with anyone, 
    // otherwise cyclic linked list of all SmartPtr referencing that pointer.
    };
    

    我用简单的链表取代了引用计数——链表保持所有类实例被引用,每个析构函数删除一个引用。

    我决定将运算符*重命名为refptr()函数,只是为了避免开发人员编写额外的花哨代码。("C++珠宝")

    所以总的来说,我同意上面的文章——请不要把聪明的指针做得太聪明

    我可以自由地接受关于这个类的任何改进建议和潜在的错误修复。

    我也想回答原作者的问题:

    真正的问题是:这个新的智能指针类只是一个int*countPtr吗足够吗?如果是,为什么要使用额外的use Count类就像书中那样?如果没有,我会错过什么?

    您正在使用单独的机制进行计数管理,就像上面提到的文章链接一样——跟踪和调试引用计数将变得非常重要。在我的代码片段中,我使用了智能指针实例的链表,它不执行任何分配(因此上面的实现比任何其他现有的智能指针实现都快),也更容易调试智能指针本身-你可以通过链接(下一个)来检查谁锁定了你的内存不被收集。

    但总的来说,如果你正在经历内存泄漏,我想说,如果你不是最初编写的代码,那么找到它们的位置是非常重要的。从这个意义上说,智能类指针并不能帮助计算出谁以及泄漏了多少。最好是一次正确的编码,以便稍后与自己的野兽战斗。

    对于内存泄漏,我建议找到现有的工具并使用它们——例如这个:

    https://sourceforge.net/projects/diagnostic/

    (它们有很多,但没有一个工作可靠/足够好)。

    我知道你很想讨厌这个实现,但实际上,请告诉我你在这个实现中看到了什么障碍?!

    相关内容

    • 没有找到相关文章

    最新更新