What are some of C++11 std::unique_ptr
uses and gotchas?
Can I use std::unique_ptr
also to store dynamically allocated arrays?
Can I use std::unique_ptr
also with resources using a custom deletion mechanism?
Let's organize some uses and gotchas using a Q&A format.
Q1: I'd like to store a pointer to a class Component
inside my class X
.
I don't want the "container" class X
to be copyable; X
is the only owner of Component
instances.
I know that owning raw pointers are a bad thing and potential sources of "leaktrocities" (instead observing raw pointers are fine).
What smart pointer could I use for this purpose?
A1: C++11's std::unique_ptr
is certainly a good option.
It's fine in cases of unique (non-shared) ownership, and doesn't have the overhead of std::shared_ptr
.
It's an excellent replacement for previous C++98/03 boost::scoped_ptr
.
In fact, in addition, std::unique_ptr
provides move semantics.
So, if class X
contains unique_ptr<Component>
data members (and other movable data members), the whole class X
will be automatically movable.
An example use is showed below:
#include <memory> // for std::unique_ptr
class X
{
std::unique_ptr<Component> m_pComponent;
....
public:
X()
: m_pComponent( new Component() )
{
....
}
}
(Of course, being a smart pointer, there's no need to explicitly delete it in the containing class destructor.)
Q2: That's great! No need for explicit destructor, no std::shared_ptr
overhead (typical C++ philosophy: "We don't pay for things we don't use"), move semantics machinery already implemented!
However, I have a problem: my class Component
has a constructor overload which takes some parameter that I need to calculate in constructor code before creating Component
instances. I tried using ordinary operator=
assigment in constructor to assign the newly created Component
to the unique_ptr
, but I got an error message:
X::X()
{
....
const int param = CalculateCoolParameter();
// This assignment fails:
m_pComponent = new Component(param); // <---- Error pointing to '=' here
^--- error
}
A2: OK, probably you had expected an operator=
overload releasing the previous owned
pointer (if any) and assigning to the newly created one.
Unfortunately, there isn't such an overload.
However, the std::unique_ptr::reset()
method will do!
m_pComponent.reset( new Component(param) );
Q3: Hey! This unique_ptr
is really cool!
I like the fact that it's smart, it's automatically movable, and doesn't bring overhead.
So, I'd like to use it to store a dynamically allocated array of some constant size (calculated at run-time) instead of using std::vector
(in this portion of code I'm highly constrained and I don't want to pay for the std:vector
overhead, since I don't want all the std::vector
dynamically resizing features, deep-copy, etc.).
I tried something like this:
const size_t count = GetComponentsCount();
unique_ptr<Component> components( new Component[count] );
It compiles fine, but I noted that ~Component
destructor is called only once, instead I expected count
destructor calls! What's going wrong here?
A3: The problem is that, with the above syntax, std::unique_ptr
uses delete
to release the allocated objects. But since those were allocated using new[]
, the proper cleanup call is delete[]
(not the simple delete
without brackets).
To fix that and to instruct unique_ptr
to properly use delete[]
for releasing resources, the following syntax must be used:
unique_ptr<Component[]> components( new Components[count] );
// ^^
//
// Note brackets "[]" after the first occurrence of "Component"
// in unique_ptr template argument.
//
Q4: That's great! But can I use unique_ptr
also in cases in which the resource release code is not performed using ordinary C++ delete
(or delete[]
), but instead using some custom cleanup function, like fclose()
for C <stdio.h>
files (opened with fopen()
), or CloseHandle()
for Win32 file HANDLE
s (created using CreateFile()
)?
A4: That's definitely possible: you can specify a custom deleter for std::unique_ptr
.
e.g.:
//
// Custom deleter function for FILE*: fclose().
//
std::unique_ptr<FILE, // <-- the wrapped raw pointer type: FILE*
int(*)(FILE*)> // <-- the custom deleter type: fclose() prototype
myFile( fopen("myfile", "rb"), // <-- resource (FILE*) is returned by fopen()
fclose ); // <-- the deleter function: fclose()
//
// Custom deleter functor for Win32 HANDLE: calls CloseHandle().
//
struct CloseHandleDeleter
{
// The following pointer typedef is required, since
// the raw resource is HANDLE (not HANDLE*).
typedef HANDLE pointer;
// Custom deleter: calls CloseHandle().
void operator()(HANDLE handle) const
{
CloseHandle(handle);
}
};
std::unique_ptr<HANDLE, CloseHandleDeleter> myFile( CreateFile(....) );
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With