智能指针类的简单实现

时间:2014-01-03 04:34:16

标签: c++ copy-constructor smart-pointers

在书籍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;
    };
    

奇迹:我很好奇为什么不简单地使用int *就像Use-Count Class一样,就像下面新int* countPtr;中使用的Smart Pointer Class一样{1}}:

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?如果不是,我会错过什么?

4 个答案:

答案 0 :(得分:6)

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

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

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

例如,std::shared_ptr也会通过原始指针类型删除(保证),除非有人明确提供了执行其他操作的删除函数。

答案 1 :(得分:3)

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

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

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

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

  4. 你只有一个积极的信息:

    1. 对指针的访问是直接的,而不是一个间接。这可能意味着您的实施对访问对象的访问速度会稍快一些......

答案 2 :(得分:2)

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

  • 弱指针的数量(不确定你是否已经听说过它们 - 它们跟踪一个对象而不延长它的生命周期,这样你以后就可以尝试将一个转换成一个(普通的)共享指针,但它只有在至少有一个指向周围物体的共享指针才能保持活着的情况下才有效。

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

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

  • 虚拟调度表,由例如提升共享指针以调度适合OS的代码(请参阅boost / smart_ptr / detail / sp_counted_base _ * .hpp headers)

我不知道这本书,但也许他们会继续解释还有什么可能会进入U_Ptr ....

答案 3 :(得分:-2)

(已于14.4.2017更新)

我自己尝试了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.
};

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

我决定将operator *重命名为refptr()函数,以避免开发人员编写额外的花哨代码。 (&#34; C ++珠宝&#34;)

所以总的来说我同意上面的文章 - 请不要让智能指针过于聪明。 : - )

我可以自由地提供有关此课程和潜在错误修正的任何改进建议。

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

  

真正的问题:这个新的智能指针类只是一个int * countPtr   够了吗?如果是,为什么还要使用额外的Use-Count Class   喜欢在书中?如果不是,我会错过什么?

您正在使用单独的机制进行计数管理,例如上面提到的文章链接 - 遵循和调试引用计数将变得非常简单。在我的代码片段中,我使用智能指针实例的链接列表,它不执行任何分配(因此上面的实现比任何其他现有的智能指针实现更快),也更容易调试智能指针本身 - 您可以通过链接检查(下一步)谁锁定你的记忆被收集。

但总体而言 - 如果您遇到内存泄漏,我会说,如果您最初没有制作该代码,那么找到它们的位置非常重要。智能类指针在这个意义上无助于弄清楚泄漏了多少和多少。最好能够编码一次并正确地编码,以便稍后与你自己的野兽战斗。

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

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

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

我知道您非常希望不喜欢这种实现,但实际上 - 请告诉我您在此实施中遇到了哪些障碍?!