Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Some std::unique_ptr uses and "gotchas"

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?

like image 973
Mr.C64 Avatar asked Jul 30 '13 10:07

Mr.C64


1 Answers

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 HANDLEs (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(....) );  
like image 80
Mr.C64 Avatar answered Sep 21 '22 02:09

Mr.C64