Using std::unique_ptr as a class member (C++11)

Abstract

For this post I’m going to go through some of the gotchas most people might face when using unique_ptr as a class member (most examples out there only show unique_ptr as a local variable), all accompanied by examples. I will also compare it with shared_ptr.

Assumptions

I’m assuming you know your way round classic/naked pointers, basic memory management and object construction and destruction.

Intro

Unique pointers is type of smart pointer in the C++11 standard that uses the concept of ownership, which meant that only 1 pointer can point to 1 resource (ergo ‘owns’ the resource). In other words only a single pointer is responsible for the destruction of its resource, hence the term ‘unique’.

Theories aside, let’s see some code! To see the source code of the files in this post, just click the link – it will point to a github gist (For example, click Point.hpp below).

Getting Started

In this scenario we’ll be managing a class Point (Point.hpp) from a manager class PointMan. Then add shared_and_unique.cpp to the same directory get started.

So we start with our unique pointer q:

Point::UPtr q;

UPtr here refers to a typedef std::unique_ptr<Point>, you can actually use these interchangeably. Why do Point::UPtr? Because other people do this. And it looks more concise. In fact, if you ever intend to use smart pointers to manage a class, you should add a typedef there.

Now, notice that the unique_ptr p is initialized at the constructor:

PointMan() : q(new Point("q")) {
    q->set(6, 12);
    // ...

well, what if you don’t want to initialize at the constructor? You do this:

r = Point::UPtr(new Point("r"));
r->set(r_x, r_y);

Here is the output of unique_and_shared.cpp:

Image

Ownership Transfering

Moving on to the next example: unique_container.cpp

How does one transfers a unique pointer to another unique pointer? An example is when you have created a point:

Point::UPtr temp(new Point("element-" + ss.str()));
temp->set(i, i*2);

And now you want to transfer this temp Point to a container of unique pointers. Why? Currently the unique pointer is being initialized in a for loop. Exiting or restarting the loop will destroy the resource. That is why the life management of the object needs to be passed to a variable in the class scope. However, doing this:

container[i] = temp;

will result in a compile time error:

unique_container.cpp:16:17: error: use of deleted function ‘std::unique_ptr<_Tp, _Dp>& std::unique_ptr<_Tp, _Dp>::operator=(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = Point; _Dp = std::default_delete<Point>]’

what does it mean? You can’t use assignment operator from one unique pointer to another unique pointer. Why is this so? Because unique pointers are designed in such a way such that only one pointer can own one resource. However, ownership can be transfered. The following is valid:

container[i] = std::move(temp);

What happens now is that the job of managing the life time of the object is now passed down to the container. So now temp pointer doesn’t point to anything:

assert(temp == nullptr);

Of course, if you don’t want to create temporary variables and move them, you could simply initialize the array element directly:

container[i] = Point::UPtr(new Point("element-" + ss.str()));
container[i]->set(i, i*2);

Non-owning Pointers

Now comes a tricky problem: say there is an element my container of points that I want to designate as the location of the current player on a game board. I need a convenient means to access this particular point in the container. I can’t store this Point as a unique pointer as only one unique pointer can point to one resource. How can I go about doing so?

Well, what you can do is save the index of that container and use it accordingly:

container[player_idx]->set(45, 12);

but it’s rather tedious, messy, and not very expressive. And what about data structures that are not indexed? This creates an overhead simply to get to the resource. What we can do is use a non-owning pointer. Non-owning pointers are classic pointers that do what you would expect normal pointers would do, except they should not manage the life time of the object.

Point *mainPoint;

You assign a raw pointer the resource by using the get() method:

mainPoint = container[1].get();

Now here’s where you need to be careful: do not delete this pointer!

delete mainPoint; // don't do this! no no no!

Your compiler won’t stop you from doing so, but you can cause some really insidious bugs to your program. mainPoint should only serve as a pointer where you can pass around to access the object in the heap. By no reason should you try to manipulate the life time of the resource via this pointer.

But anyhow, I was curious what would happen:

Image

Data corruption! Wohoo! 😀

VS Shared Pointers

So to conclude, here’s the same Pointer manager class, but now using shared pointers: shared_container.cpp

So it’s really a lot easier to read, and more consistent. In fact, Objective C only uses reference counting to handle heap memory. SFGUI, a GUI toolkit built for SFML, forces you to use shared pointers to create GUI components. Seems like shared pointers is one size fits all right? I would think so.

But of course, people would debate that unique pointers perform better than shared pointers, and that shared pointers should be only used as a last resort.

Personally? Shared pointers for the win, man.

So leave a comment if you feel this article could elaborate on certain things or whatever. Bye for now! 😀

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s