HiveBrain v1.2.0
Get Started
← Back to all entries
patterncppMajor

Efficient smart pointer implementation in C++

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
smartimplementationefficientpointer

Problem

The idea behind this is mainly educational but I might even consider using it in reality if turns out to be good. Here's my first try at implementing smart pointers:

template
class smart_pointer{
    T* pointer;
    std::size_t *refs;

    void clear(){
        if (!--*refs){
            delete pointer;
            delete refs;
        }
    }

public:
    smart_pointer(T* p = NULL)
        : pointer(p), refs(new std::size_t(1))
    {}
    smart_pointer(const smart_pointer& other)
        : pointer(other.pointer), refs(other.refs)
    {
        ++*refs;
    }
    ~smart_pointer(){
        clear();
    }

    smart_pointer& operator=(const smart_pointer& other){
        if (this != &other){
            clear();

            pointer = other.pointer;
            refs    = other.refs;
            ++*refs;
        }
        return *this;
    }

    smart_pointer& operator=(T* p){
        if (pointer != p){
            pointer = p;
            *refs = 1;
        }
        return *this;
    }

    T& operator*(){
        return *pointer;
    }

    const T& operator*() const{
        return *pointer;
    }

    T* operator->(){
        return pointer;
    }

    const T* operator->() const{
        return pointer;
    }

    std::size_t getCounts(){
        return *refs;
    }
};


I have tested this under valrind and its clean. Also, to see how much the "smartness" makes things slower, I did the following test:

struct foo{
    int a;
};

template
class bar{
    pointer_t f_;

public:
    bar(foo *f)
        :f_(f)
    {}

    void set(int a){
        f_->a = a;
    }
};

int main()
{
    foo *f = new foo;

    typedef smart_pointer ptr_t;
//    typedef boost::shared_ptr ptr_t;
//    typedef foo* ptr_t;

    bar b(f);
    for (unsigned int i = 0; i<300000000; ++i)
        b.set(i);

//    delete f;
    return 0;
}


Here is some timing between my implementation, boost, and raw pointers: (code compiled with clang++ -O3)

```
typedef smart_pointer ptr_t;

r

Solution

So the idea behind this is mainly educational...

Cool. Always good to try and understand how things work under the covers.


...but I might even consider using it in reality if turns out to be good.

Please rethink this. Smart pointer implementations are incredibly difficult to get right. Scott Myers, of Effective C++ fame, famously tried to implement a shared_ptr. After feedback from something like 10 iterations, it was still wrong.

Let's go through a few things here.

smart_pointer(T* p = NULL)
  : pointer(p), refs(new std::size_t(1))


This constructor should be explicit to avoid implicit type conversions through construction.

Your operator=(T* p) leaks. Here's a small example:

int main()
{
    int *x = new int(5);
    {
        smart_pointer sp(x);
        sp = new int(6);
    } // sp destroyed here 

    std::cout << *x << "\n"; // This should have been deleted but wasn't
}


To convince yourself that this is the case, modify your clear() method:

void clear(){
    if (!--*refs){
        std::cout << "deleteting " << *pointer << " at " << pointer << "\n";
        delete pointer;
        delete refs;
    }
}


This will print out "deleting 6 at `".

Here's just a small rundown of what
boost::shared_ptr (or std::shared_ptr) provides that is missing here:

  • Constructors allowing a Deleter, which is used instead of a raw delete.



  • Constructor allowing construction from shared_ptr(Tp1 * ...) where Tp1 is convertible to type T.



  • Copy constructor allowing construction from a shared_ptr with convertible template type .



  • Copy constructor taking weak_ptr, unique_ptr.



  • Move constructor and assignment operator.



  • Usage of nothrow where it is required to be.



  • reset, which will replace the currently managed object.



  • All the non-member operators like operator



  • A specialization of std::swap.



  • A specialization of std::hash. Your pointer class won't work correctly with std::unordered_set or std::unordered_map.



  • explicit operator bool conversion for nullptr tests, so that one can write if () { ... }.



  • Utility functions like std::make_shared which you should pretty much always use when creating a shared_ptr.



The big pain point, and the reason why boost::shared_ptr or std::shared_ptr will be slower is the fact that they are thread-safe. Your smart_ptr when accessed by multiple threads could very easily delete the pointer it holds before another class is done with it, leading to dereferencing of a deleted object, and thus undefined behaviour. Furthermore, all of the atomic operations are specialized for shared_ptr.

The above is not an exhaustive list.

I don't write this to discourage you, but merely to try and reinforce the fact that writing something like this is a lot of work. It is very technical, and really, really easy to get wrong. In terms of the standard library, shared_ptr may be one of the most difficult things to write correctly.

Code Snippets

smart_pointer(T* p = NULL)
  : pointer(p), refs(new std::size_t(1))
int main()
{
    int *x = new int(5);
    {
        smart_pointer<int> sp(x);
        sp = new int(6);
    } // sp destroyed here 

    std::cout << *x << "\n"; // This should have been deleted but wasn't
}
void clear(){
    if (!--*refs){
        std::cout << "deleteting " << *pointer << " at " << pointer << "\n";
        delete pointer;
        delete refs;
    }
}

Context

StackExchange Code Review Q#26353, answer score: 33

Revisions (0)

No revisions yet.