简体   繁体   中英

A simple implementation of Smart Pointer Class

In book C++ Primer 13.5.1 , it implement a Smart Pointer Class using a Use-Count Class . Their implementation is as follows:

  • 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

     /* 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 : I am curious about why not simply using a int * to act like the Use-Count Class , just like the int* countPtr; used in the following new Smart Pointer Class :

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; }
};

Test : I tested the above Smart Pointer Class using code like the following and it seems working well.

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

    return 0;
}

Real question : Is this new Smart Pointer Class with simply a int *countPtr sufficient enough? If yes, why bother to use an extra Use-Count Class like in the book? If no, what do I miss?

One property of the original implementation is that the delete is performed, in the control block object, with the original pointer type . This is a partial type erasure . No matter how much the smart pointer objects are copied, with somewhat different types, the original control block remains the same, with delete via the original pointer type.

However, since the original code you show is not templated, one must assume that it is an early example, followed later by similar templated code.

Converting a pointer up in a base class hierarchy, as can happen with copying of a smart pointer, means that delete on the new pointer type is only valid if the statically known new type has a virtual destructor.

For example, std::shared_ptr also deletes (guaranteed) via the original pointer type, unless one explicitly supplies a deleter functor that does something else.

Your code is equivalent to the standard code reported by the book. However it is worst in some respects:

  1. you need two allocations/deallocations instead of one (two ints instead of a single object). This might be slower and a little bit more difficult to manage.

  2. you have a copy of the pointer duplicated in every object. So: duplicated information which you should guarantee to keep valid.

  3. your object is larger (two pointers instead of one)

You only have one positive note:

  1. the access to the pointer is direct instead of having one indirection. This could mean that the access to the referred object is slightly faster with your implementation...

My guess is that the author - whether consciously or subconsciously - is aware that having a separate class is useful in real-world smart pointers, eg:

  • a count of weak pointers (not sure if you'll have heard of them yet - they track an object without extending its lifetime, such that you can try to convert one into a (normal) shared pointer later, but it only works if there's at least one shared pointer to the object around to have kept it alive)

  • a mutex to make the shared pointer thread safe (though atomic operations may be better when available),

  • debugging information (eg boost::shared_ptr has a #ifdef to include an shared counter id)

  • virtual dispatch table, used by eg boost shared pointers to dispatch to OS-appropriate code (see boost/smart_ptr/detail/sp_counted_base_*.hpp headers)

I don't know the book, but perhaps they'll go on to explain what else might go into U_Ptr ....

(Updated 14.4.2017)

I by myself tried out unique_ptr and shared_ptr and was bit surprised to see that those classes does not make your life easier. I had function in one API where function would pick up Object*& - fill it out (pointer) and after that you need to delete that object. It's possible to use c++11, but you need to add extra temporary pointer for that purpose. (So use of *_ptr classes does not makes my life easier)

How complex would it be to implement smart pointer ?

Just by quickly glancing auto_ptr class implementation, I've quickly coded simple smart point container class, but then I've noticed that I need to support multiple smart pointers referencing the same object pointer.

Ok, so how complex could it be to code reference counting - I through, and went to google - and found one interesting article about it:

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

Somehow I tend to agree with author of that article and comments written in that article that reference counting makes life even more complex, but still trying to stick to plain C also sounds bit dumb.

I'll now add code snippet here of my own class, and if you want to get newest version, you can check in this svn repository: https://sourceforge.net/p/testcppreflect/code/HEAD/tree/SmartPtr.h

Below is older version.

#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.
};

I've replaced reference counting with simple linked list - linked list keeps all class instances referenced, each destructor removes one reference away.

I've decided to rename operator* to refptr() function just to avoid developers writing extra fancy code. ("C++ jewels")

So in general I agree with article above - please don't make smart pointers too smart. :-)

I'm free to any improvement suggestions concerning this class and potential bugfixes.

And I wanted also to answer to original author's questions:

Real question: Is this new Smart Pointer Class with simply a int *countPtr sufficient enough? If yes, why bother to use an extra Use-Count Class like in the book? If no, what do I miss?

You're using separate mechanics for count management, like article link above mentions - it will becomes non-trivial to follow and debug reference counting. In my code snippet I use linked list of smart pointer instances, which does not perform any allocation ( so implementation above is faster than any other existing smart pointer implementation ), also it easier debugging of smart pointer itself - you can check by link (next) who locks down your memory from being collected.

But in overall - if you are experiencing memory leaks, I would say that it's highly non-trivial to find where they are if you're not originally made that code. Smart class pointer does not help in that sense to figure out who and how much has leaked out. Better to code once and properly that to fight with your own beast later on.

For memory leaks I recommend to find existing tools and use them - for example this one:

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

(There are plenty of them, but none of them works reliably/good enough).

I know that you're eager to put dislike to this implementation, but really - please tell me what obstacles you see in this implementation ?!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM