Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C++11

Smart Pointers Gotchas

4.83/5 (22 votes)
27 Aug 2014CPOL7 min read 38.8K  
Several issues related to smart pointers that are worth knowing.

Introduction

There are several questions about using smart pointers in modern C++:

  • Why is auto_ptr deprecated?
  • Why unique_ptr finally works good?
  • How to use arrays with unique_ptr?
  • Why create shared_ptr with make_shared?
  • How to use arrays with shared_ptr?
  • How to pass smart pointers to functions?
  • How to cast smart pointers?

While learning how to use new C++ standard, I came across several issues with smart pointers. In general, you can mess a lot less using those helper objects. It is advised to use them instead of raw pointers as often as possible. Unfortunately, there are some topics you have to understand to take full advantage of them. As in most cases you get a new tool to solve your problems, but on the other hand this tool introduces other problems as well.

Some Predefines

Let us take a simple Test class with one member to present further concepts:

C++
class Test
{
public:
    Test():m_value(0) { std::cout << "Test::Test" << std::endl; }
    ~Test() { std::cout << "Test::~Test destructor" << std::endl; }
    int m_value;
};
typedef std::auto_ptr<Test>   TestAutoPtr;
typedef std::unique_ptr<Test> TestUniquePtr;
typedef std::shared_ptr<Test> TestSharedPtr;

Why auto_ptr is Deprecated?

auto_ptr was one of the first types of smart pointers introduced in C++ (in C++98 to be more precise). It was designed to serve as a simple unique pointer (only one owner, without any reference counter), but people tried to use it also in a form of shared pointer. None of those functionalities were satisfied by auto_ptr's implementation! Quick example below:

C++
void doSomethig(TestAutoPtr myPtr) {
    myPtr->m_value = 11;
}
void AutoPtrTest() {
    TestAutoPtr myTest(new Test());
    doSomethig(myTest);
    myTest->m_value = 10;
}

Try to compile and run this... what happens? It crashes just after we leave doSomething procedure! We would assume than in doSomething some reference counter for our pointer is incremented, but auto_ptr has no such thing.

The object is destroyed because when we leave doSomething procedure our pointer gets out of scope and is deleted. To make it work, we could pass a reference to auto pointer.
Another thing is that we have limited way of deleting more complicated objects, there is no control over it at all, only delete can be used here.

Why unique_ptr Finally Works Good?

Fortunately with the new standard, we got a brand new set of smart pointers! When we change auto_ptr to std::unique_ptr<Test> in our previous example, we will get compile (not runtime) error saying that we cannot pass pointer to other function. This is the proper behaviour.

unique_ptr is correctly implemented because of move semantics basically. We can move (but not copy) ownership from pointer to another. We also need to be aware when and where we pass the ownership.
In our example, we can use:

C++
doSomethig(std::move(myTest));

to move the pointer's ownership. That way after the function returns out pointer is also not valid, but we did it on purpose after all. Another nice advantage of this type of pointer is that we can use custom deleters. It is useful when we have some complicated resources (files, textures, etc.).

How to Use Arrays with unique_ptr?

First thing to know:

C++
std::unique_ptr<int> p(new int[10]);  // will not work!

The above code will compile, but when resources are about to be deleted, only single delete will be called. So how do we ensure that delete[] is called? Fortunately, unique pointers have proper partial specialization for arrays and we can have:

C++
std::unique_ptr<int[]> p(new int[10]);  
p[0] = 10;

For our particular example, we can write:

C++
std::unique_ptr<Test[]> tests(new Test[3]);

And we will get the desired output:

Test::Test
Test::Test
Test::Test
Test::~Test destructor
Test::~Test destructor
Test::~Test destructor

As expected. Smile | <img src=

Note that if you want to pass address of the first element, you have to use &(pointerToArray[0]). Writing pointerToArray will not work.

Why Create shared_ptr with make_shared?

Unique pointers provides their features only via wise usage of C++ syntax (using private copy constructor, assignment, etc.), they do not need any additional memory. With shared_ptr we need to associate some reference counter with our object. When we do:

C++
std::shared_ptr<Test> sp(new Test());
std::shared_ptr<Test> sp2 = std::make_shared<Test>();

We will get the output as expected:

Test::Test
Test::Test
Test::~Test destructor
Test::~Test destructor

So what is the difference? Why not use syntax similar to creation of unique_ptr? Answer lies in the allocation process. With the first construct, we need to allocate a space for the object and then for the reference counter. With the second construct, there is only one allocation (using placement new) and ref counter shares the same memory block as the pointed object.

VS2012 locals view

Above, you can see a picture with local's view in the VS 2012. Compare the addresses of object data and reference counter block. For the sp2, we can see that they are very close to each other.

To be sure, I got proper results I've even asked question on stackoverflow: http://stackoverflow.com/questions/14665935/make-shared-evidence-vs-default-construct.

BTW: in C++14 there is a nice improvement: make_unique function ! That way creating smart pointers is a bit more 'unified'. We have make_shared and make_unique.

How to Use Arrays with shared_ptr?

Arrays with shared_ptr are a bit trickier that when using unique_ptr, but we can use our own deleter and have full control over them.

C++
std::shared_ptr<Test> sp(new Test[2], [](Test *p) { delete [] p; });

We need to use custom deleter (here as a lambda expression). Additionally, we cannot use make_shared construction. Unfortunately using shared pointers for arrays is not so nice. I suggest taking boost instead. For instance: http://www.boost.org/doc/libs/1520/libs/smartptr/sharedarray.htm.

How to Pass Smart Pointers to Functions?

We should use smart pointers as a first class objects in C++, so in general we should pass them by value to functions. That way reference counter will increase/decrease correctly. But we can use some other constructions which seems to be a bit misleading. Here is some code:

C++
void testSharedFunc(std::shared_ptr<Test> sp) {
    sp->m_value = 10;
}
void testSharedFuncRef(const std::shared_ptr<Test> &sp) {
    sp->m_value = 10;
}
void SharedPtrParamTest() {
    std::shared_ptr<Test> sp = std::make_shared<Test>();
    testSharedFunc(sp);
    testSharedFuncRef(sp);
}

The above code will work as assumed, but in testSharedFuncRef we get no benefit of using shared pointers at all! Only testSharedFunc will increase reference counter. For some performance critical code we, additionally, need to notice that passing by value will need to copy whole pointer block, so maybe it is better to use even raw pointer there.

But, perhaps the second option (with reference) is better? It depends. The main question is if you want to have full ownership of the object. If not (for instance, you have some generic function that calls methods of the object) then we do not need ownership... simple passing by reference is good and fast method.

It is not only me who gets confused. Even Herb Sutter paid some attention to this problem and here is his post on that matter: http://herbsutter.com/2012/06/05/gotw-105-smart-pointers-part-3-difficulty-710/.

How to cast smart pointers?

Let's take a common example with a simple inheritance:

C++
class BaseA
{
protected:
    int a{ 0 };
public:
    virtual ~BaseA() { }

    void A(int p) { a = p; }
};

class ChildB : public BaseA
{
private:
    int b{ 0 };
public:
    void B(int p) { b = p; }
};

Without a problem, you can create a smart pointer to BaseA and initialize it with ChildB:

C++
std::shared_ptr<BaseA> ptrBase = std::make_shared<ChildB>();
ptrBase->A(10);

But how to get a pointer to a ChildB class from ptrBase? Although, it is not a good practice, sometimes we know it is needed.

You can try this:

C++
ChildB *ptrMan = dynamic_cast<ChildB *>(ptrBase.get());
ptrMan->B(10);

It should work. But, that way you get a 'normal' pointer only! The use_count for the original ptrBase is not incremented. You can now observe the object, but you are not the owner.

It is better to use casts designed for smart pointers:

C++
std::shared_ptr<ChildB> ptrChild = std::dynamic_pointer_cast<ChildB>(ptrBase);
if (ptrChild)
{
    ptrChild->B(20);
    std::cout << "use count A: " << ptrBase.use_count() << std::endl;
    std::cout << "use count B: " << ptrChild.use_count() << std::endl;
}

by using std::dynamic_pointer_cast you get a shared pointer. Now you are also the owner. Use count for ptrBase and ptrChild is '2' in this case.

Take a look at std::static_pointer_cast and std::const_pointer_cast for more information.

What about unique_ptr?

In the previous example you got a copy of the original pointer. But unique_ptr cannot have copies... so it is no sense to provide a casting functions. If you need a casted pointer for observation then you need to do it the old way.

Some Additional Comments

Smart pointers are very useful, but we, as users, also need to be smart. I am not as experienced with smart pointers as I would like to be. For instance, sometimes I am tempted to use raw pointers: I know what will happen, and at a time I can guarantee that it will not mess with the memory. Unfortunately, this can be a potential problem in the future. When code changes, my assumptions can be not valid any more and new bugs may occur. Another thing is when new developer starts changing my code. With smart pointers, it is not so easy to break things.

All this topic is a bit complicated, but as usual in C++, we get something at a price. We need to know what we are doing to fully utilize a particular feature.

The code for the article can be found here.

Links

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)