The dynamic creation and destruction of objects was always one of the bugbears of C. It required the programmer to (manually) control the allocation of memory for the object, handle the object’s initialisation then ensure that the object was safely cleaned-up after use and its memory returned to the heap. Because many C programmers weren’t educated in the potential problems (or were just plain lazy or delinquent in their programming) C got a reputation in some quarters for being an unsafe, memory-leaking language.
Things didn’t significantly improve in C++. We replaced malloc and free with new and delete; but the memory management issue remained.
I concede – the code above is trivial and stupid but I suspect if I looked around I could find similar (or even worse!) examples in actual production code.
Languages such as Java and C# solved this problem by taking memory management out of the hands of the programmer and using a garbage collector mechanism to ensure memory is cleaned up when not in use.
In Modern C++ they have chosen not to go down this route but instead make use of C++’s Resource Acquisition Is Initialisation (RAII) mechanism to encapsulate dynamic object creation / destruction within smart pointers.
A smart pointer is basically a class that has the API of a ‘raw’ pointer. In Modern C++ we have four classes for dynamic object management:
std::auto_ptr : Single-owner managed pointer, from C++98; now deprecated
std::shared_ptr : A reference-counted pointer, introduced in C++98 TR1
std::unique_ptr : Single-owner managed pointer which replaces (the now deprecated) auto_ptr
std::weak_ptr : Works with shared_ptr in situations where circular references could be a problem
Avoid using std::auto_ptr
std::auto_ptr was introduced in C++98 as a single-owner resource-managed smart pointer. That is, only one auto_ptr can ever be pointing at the resource.
auto_ptr objects have the peculiarity of taking ownership of the pointers assigned (or copied) to them: An auto_ptr object that has ownership over one element is in charge of destroying the element it points to and to deallocate the memory allocated to it when itself is destroyed. The destructor does this by calling delete automatically.
When an assignment operation takes place between two auto_ptr objects, ownership is transferred, which means that the object losing ownership is set to no longer point to the element (it is set to nullptr). This also happens if you copy from one auto_ptr to another – either explicitly, or by passing an auto_ptr to a function by value.
This could lead to unexpected null pointer dereferences – an unacceptable consequence for most (if not all) systems. Therefore, we recommend avoiding the use of auto_ptr. It has now been deprecated in C++11 (and replaced with the much more consistent std::unique_ptr)
Use std::unique_ptr for single ownership
std::unique_ptr allows single ownership of a resource. A std::unique_ptr is an RAII wrapper around a ‘raw’ pointer, therefore occupies no more memory (and is generally as fast) as using a raw pointer. Unless you need more complex semantics, unique_ptr is your go-to smart pointer.
unique_ptr does not allow copying (by definition); but it does support move semantics, so you can explicitly transfer ownership of the resource to another unique_ptr.
The utility function make_unique<T>() hides away the memory allocation and is the preferred mechanism for dynamically creating objects. make_unique<T>() is not officially supported in C++11; but it is part of C++14 and is supported by many C++11-compliant compilers. (A quick search will turn up an implementation if your compiler doesn’t currently support it)
For sharing a resource, use std::shared_ptr
std::shared_ptr is a reference-counted smart pointer.
Creating a new dynamic object also creates a new associated management structure that holds (amongst other things) a reference count of the number of shared_ptrs currently ‘pointing’ at the object.
Each time a shared_ptr is copied the reference count is incremented. Each time one of the pointers goes out of scope the reference count on the resource is decremented. When the reference count is zero (that is, the last shared_ptr referencing the resource goes out of scope) the resource is deleted.
std::shared_ptrs have a higher overhead (in memory and code) than std::unique_ptr but they come with more sophisticated behaviours (like the ability to be copied at relatively low cost).
Once again, the standard library provides a utility function make_shared<T>() for creating shared dynamic objects; and, once again, this is the preferred mechanism.
Use std::weak_ptr for tracking std::shared_ptrs
A std::weak_ptr is related to a std::shared_ptr. Think of a weak_ptr as a ‘placeholder’ for a shared_ptr. std::weak_ptrs are useful if you want to track the existence of a resource without the overhead of a shared_ptr; or you need to break cyclic dependencies between shared_ptrs (A topic that is outside the scope of this article; but have a look here if you’re interested)
When you create a weak_ptr it must be constructed with an extant shared_ptr. It then becomes a ‘placeholder’ for that shared_ptr. You can store weak_ptrs, copy and move them, but doing so has no effect the reference count on the resource.
Note you cannot directly use a weak_ptr. You must convert it back to a shared_ptr first. weak_ptrs have a method, lock(), that creates (in effect) a copy of the original shared_ptr, which can then be accessed.
Since weak_ptrs can have a different lifetime to their associated shared_ptr there is a chance the original shared_ptr could go out of scope (and conceptually delete its resource) before the weak_ptr is destroyed. (Strictly speaking, the resource is deleted when the last referencing shared_ptr and/or weak_ptr have gone out of scope)
A weak_ptr can therefore be invalid – that is, referencing a resource that is no longer viable. You should use the expired() method on the weak_ptr to see if it is still valid, before attempting to access it (alternatively, calling lock() on an expired weak_ptr will return nullptr).
That’s all for now.
We’ve got to the end of the Bitesize series for Modern C++. You should now be in a much stronger position to explore the new features of C++ in more detail.
If you missed an article, or just want the complete set in a single document, you can download the full set of articles as a PDF, here.
To learn more about Feabhas’ Modern C++ training courses, click here.
- Practice makes perfect, part 3 – Idiomatic kata - February 27, 2020
- Practice makes perfect, part 2– foundation kata - February 13, 2020
- Practice makes perfect, part 1 – Code kata - January 30, 2020
Glennan is an embedded systems and software engineer with over 20 years experience, mostly in high-integrity systems for the defence and aerospace industry.
He specialises in C++, UML, software modelling, Systems Engineering and process development.
This blog is a goldmine for C++ programmers. I'm trying to read the blog posts in order. Would it be possible to enable the Next-Prev links that are typically shown at the bottom of individual blog posts?
std::shared_ptr by value is not free, it should be avoided except when it make sense to use a copy
also
auto p = temp.lock(); // might return an empty std::shared_ptr
*p = 200;
could be undefined behavior if the smart pointer is owned by another thread and if it's being freed between the expired and the lock method
Good points, Stephane.
This sort of detail was deliberately glossed-over in these 'Bitesize' articles, in order to get people going with the new C++ features.