Table of Contents

While learning how to use the new C++ Standard, I encountered several intriguing cases with smart pointers. Casting? Array handling? Passing to functions?

Let’s review some common concerns so that we don’t shoot yourself in the foot :)

I created this post back in 2013, and I updated it in 2014 and recently in 2021.

Some predefines  

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

struct Test {
   Test() { std::cout << "Test::Test\n"; }
   ~Test() { std::cout << "Test::~Test destructor\n"; }

   int val_ { 0 };
};

With the above declaration, we’ll be able to see when it was constructed and destructed.

If we need some more advanced reporting, including move semantics, we could also implement move and copy constructors and assignment operators, but it will be good for now. See more in: Moved or Not Moved - That Is the Question! - C++ Stories

How not to use smart pointers  

The critical thing about smart pointers is that they bind the pointer to an object allocated on the heap and then provide precise semantics of who’s the owner of that pointer. That makes it clear who should delete the resource.

In that context, the following use cases are dangerous and wrong:

Test test;

// 1. ptr to a resource on stack!
std::unique_ptr<Text> ptr(&test);  // !!!

// 2. ptr of some other ptr
std::unique_ptr<Test> ptr(new Test());
std::unique_ptr<Test> otherPtr(ptr.get()); // !!

Do you see all errors?

  1. In the first case, test lives on the stack, so the compiler knows where it should be deleted. If you pass the pointer into a smart pointer, then at the end of the lifetime of that smart pointer, it will attempt to call delete on that pointer! This is undefined behavior!
  2. The second case shows two smart pointers that “shares” a single pointer. When the scope for otherPtr ends, it will delete the resource, and then ptr will try to delete it the second time. This is also undefined behavior. If you want to share a resource, then use shared_ptr.

Why is auto_ptr removed in C++17?  

I hope it’s an ancient story for you, and you might even not come across auto_ptr these days (in 2021)… but to have a complete picture, it’s best to mention it here.

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 this also in the form of a shared pointer. None of those functionalities were satisfied by auto_ptr’s implementation!

A quick example below:

void dangerous(std::auto_ptr<Test> myPtr) {
    myPtr->m_value = 11;
}

void AutoPtrTest() {
    std::auto_ptr<Test> myTest(new Test());
    dangerous(myTest);
    myTest->m_value = 10;
}

See the complete example @Compiler Explorer

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

In my case, I got:

Program returned: 139 // segmentation fault

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

The C++ Committee deprecated auto_ptr in C++11 - the compiler should emit a warning in that mode. And in C++17, the type is removed. If you try to compile the above example under the std=c++17 flag (or higher), then you’ll get the following warnings or errors:

warning: 'auto_ptr<Test>' is deprecated: use 'std::unique_ptr' instead

What’s more, due to misleading copy semantics, auto_ptr couldn’t be used in standard containers. So you couldn’t create std::vector<std::auto_ptr<Test>>. This works fine with new smart pointers, including unique_ptr.

See more in: 5 ways how unique_ptr enhances resource safety in your code.

Here’s the link to clang tool: clang-tidy - modernize-replace-auto-ptr - it allows you to automatically replace auto_ptr with unique_ptr.

Why unique_ptr does work well?  

Fortunately, in C++11, 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 a pointer to another function. And this is the proper behavior.

unique_ptr is correctly implemented because of move semantics. We can move (but not copy) ownership from one pointer to another. We also need to be aware of when and where we pass the ownership.

In our example we can use:

dangerous(std::move(myTest));

To move the pointer’s ownership.

That way, dangerous has the ownership now and will destroy the pointer at the end of its scope.

See the full example:

#include <memory>
#include <iostream>

struct Test {
   Test() { std::cout << "Test::Test\n"; }
   ~Test() { std::cout << "Test::~Test destructor\n"; }

   int val_ { 0 };
};

void dangerous(std::unique_ptr<Test> myPtr) {
    myPtr->val_ = 11;
    std::cout << "dangerous() ends...\n";
}

void uniquePtrTest() {
    std::unique_ptr<Test> myTest(new Test());
    dangerous(std::move(myTest));
    // myTest->val_ = 10; // not valid
    std::cout << "after dangerous()\n";
}

int main() {
    uniquePtrTest();
}

And play with the code @Compiler Explorer.

How to use arrays with unique_ptr?  

First thing to know:

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

The above code will compile, but only delete (and not delete[]!) will be called when resources are about to be deleted.

How do we ensure that delete[] is called?

Fortunately unique pointers have a proper partial specialization for arrays and we can write:

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

For our particular example:

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

// or better:
auto ptr = std::make_unique<Test[]>(3);

And we will get the desired output:

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

See the code @Compiler Explorer

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

How to use arrays with shared_ptr?  

The array support for shared_ptr came after unique_ptr and it’s finally enabled since C++17. It works similarly to the other smart pointer:

#include <memory>
#include <iostream>

struct Test {
   Test() { std::cout << "Test::Test\n"; }
   ~Test() { std::cout << "Test::~Test destructor\n"; }

   int val_ { 0 };
};

int main() {
    std::shared_ptr<Test[]> ptr(new Test[3]);
    std::cout << "finishing main...\n";
}

See the code @Compiler Explorer

Since C++20 make_shared is also updated to handle array types:

auto ptr = std::make_shared<Test[]>(3);

(Note, as of October 2021 make_shared for arrays is only supported by the MSVC compiler).

Before C++17 shared_ptr didn’t work with arrays. You can use a custom deleter. For example:

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

Why create shared_ptr with make_shared?  

Unique pointers provide their features only via wise usage of C++ syntax (using private copy constructor, assignment, etc.); they do not need any additional memory. But with shared_ptr, we need to associate some reference counter with our object. How to do that efficiently?

When we do:

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 the creation of unique_ptr? The answer lies in the allocation process. With the first construct, we need to allocate space for the object and the reference counter. There is only one allocation (using placement new), and the ref counter shares the same memory block as the pointed object.

VS 2012 local&rsquo;s view

Above, you can see a picture with local’s view in Visual Studio. 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: c++ - make_shared “evidence” vs default construct - Stack Overflow.

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.

Why don’t create shared_ptr with make_shared?  

While make_shared is the first choice and should work in 99% of use cases, there’s one thing you should be aware of.

It’s connected with weak_ptr and shared_ptr interaction.

Basically, weak_ptr stores a weak counter in the control’s block of the shared pointer. There might be a case where the reference counter for a shared pointer is zero, but the block cannot be deallocated because there might still be weak references.

If you use make_shared, the object is also allocated in the same memory block as the control block, and thus the memory for your object won’t be deallocated.

This is a scarce situation and might not even harm you in any way (destructors are still called), but it’s good to be aware of this fact.

See the full explanation with examples here: How a weak_ptr might prevent full memory cleanup of managed object - C++ Stories.

How to pass smart pointers to functions?  

You can pass a smart pointer to a function; it’s no big deal… but you need to ask one question:

Do I need to give the ownership or just the pointer to the object?

If you need to operate on the object itself, and don’t change the pointer, change the ownership, etc… then pass the pointer for “observation”:

Like

void importantFunction(Test* ptr) { // or a reference
    ptr->val_ = 10;
}

auto ptr = make_unique<Test>();
importantFunction(ptr.get());

It’s also consistent with the following recommendation from C++ Core Guidelines:

F.7: For general use, take T* or T& arguments rather than smart pointers

How about cases where you want to change the pointer itself?

In that case you pass a reference:

void importantFunction(std::unique_ptr<Test>& ptr) {
    ptr.reset(nullptr);
}

auto ptr = make_unique<Test>();
importantFunction(ptr);
// ptr might be null

See the following C++ Core Guideline:

R.33: Take a unique_ptr<widget>& parameter to express that a function reseats the widget.

There are also other options:

  • Pass unique_ptr by value - this is called sink function, and it transfers the ownership of the pointer. See my other post on that: Modernize: Sink Functions.
  • Pass shared_ptr by value - this shares the pointer, so its reference counter is updated. Such operation is relatively heavy, so use it only if you need ownership inside the function.
  • Pass shared_ptr by reference - similar to the unique_ptr case, this tells the caller that the function may reset the pointer.

How to cast smart pointers?  

Let’s take a typical example with a simple inheritance:

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:

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:

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 casting functions designed for smart pointers:

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 correct shared pointer. Now you are also the owner. Use count for ptrBase and ptrChild is ‘2’ in this case.

What about unique_ptr casting?  

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 casting functions. If you need a casted pointer for observation, then you need to do it the old way.

If you're interested in smart pointers - have a look at my handy reference card. It covers everything you need to know about unique_ptr, shared_ptr and weak_ptr, wrapped in a beautiful PDF:

Summary  

Smart pointers are handy, but we, as users, also need to be smart :)

We covered a lot in this article! From basic scenarios to managing arrays or even pointer casting. I hope this will give you enough start to explore smart pointers and refactor code.

Back to you

  • What are the everyday use cases for smart pointers in your projects?
  • Do you know some other tricks?

Share your experience in the comments below the article.