GotW #102: Exception-Safe Function Calls (Difficulty: 7/10)

[This is a C++11-updated version of the original GotW #56.]

JG Questions

1. In each of the following statements, what can you say about the order of evaluation of the functions f, g, and h and the expressions expr1 and expr2? Assume that expr1 and expr2 do not contain more function calls.

// Example 1(a)
//
f( expr1, expr2 );

// Example 1(b)
//
f( g( expr1 ), h( expr2 ) );

Guru Questions

2. In your travels through the dusty corners of your company’s code archives, you find the following code fragment:

// Example 2

// In some header file:
void f( T1*, T2* );

// At some call site:
f( new T1, new T2 );

Does this code have any potential exception safety or other problems? Explain.

3. As you continue to root through the archives, you see that someone must not have liked Example 2 because later versions of the files in question were changed as follows:

// Example 3

// In some header file:
void f( std::unique_ptr<T1>, std::unique_ptr<T2> );

// At some call site:
f( std::unique_ptr<T1>{ new T1 }, std::unique_ptr<T2>{ new T2 } );

What improvements does this version offer over Example 2, if any? Do any exception safety problems remain? Explain.

4. Demonstrate how to write a make_unique facility that solves the safety problems in Example 3 and can be invoked as follows:

// Example 4

// In some header file:
void f( std::unique_ptr<T1>, std::unique_ptr<T2> );

// At some call site:
f( make_unique<T1>(), make_unique<T2>() );

Solution

1. In each of the following statements, what can you say about the order of evaluation of the functions f, g, and h and the expressions expr1 and expr2? Assume that expr1 and expr2 do not contain more function calls.

The answer hinges on the following basic rules:

  • All of a function’s arguments must be fully evaluated before the function is called. This includes the completion of any side effects of expressions used as function arguments.
  • Once the execution of a function begins, no expressions from the calling function may begin or continue to be evaluated until execution of the called function has completed. Function executions never interleave with each other.
  • Expressions used as function arguments may generally be evaluated in any order, including interleaved, except as otherwise restricted by the other rules.

In ISO C++11, these intra-thread ordering constraints are specified by the “sequenced before” relation, which constrains the transformations that compilers and hardware can perform within a single thread of execution. This replaces, but is intended to be identical to, the older C/C++ formulation of sequence points.

Given those rules, let’s see what happens in our opening examples:

// Example 1(a)
//
f( expr1, expr2 );

In Example 1(a), all we can say is that both expr1 and expr2 must be evaluated before f is called.

That’s it. The compiler may choose to perform the evaluation of expr1 before, after, or interleaved with the evaluation of expr2. There are enough people who find this surprising that it comes up as a regular question in C++ forums, but it’s just a direct result of the C and C++ rules about intra-thread sequencing.

// Example 1(b)
//
f( g( expr1 ), h( expr2 ) );

In Example 1(b), the functions and expressions may be evaluated in any order that respects the following rules:

  • expr1 must be evaluated before g is called.
  • expr2 must be evaluated before h is called.
  • Both g and h must complete before f is called.

The evaluations of expr1 and expr2 may be interleaved with each other, but nothing may be interleaved with any of the function calls. For example, no part of the evaluation of expr2 or any of the execution of h may occur from the time g begins until it ends; however, it’s possible for h to be called either before or after g.

Some Function Call Exception Safety Problems

2. In your travels through the dusty corners of your company’s code archives, you find the following code fragment:

// Example 2

// In some header file:
void f( T1*, T2* );

// At some call site:
f( new T1, new T2 );

Does this code have any potential exception safety or other problems? Explain.

Yes, there are several potential exception safety problems.

Brief recap: An expression such as new T1 is called, simply enough, a new-expression. Recall what a new-expression really does (ignoring in-place and array forms for simplicity, because they’re not very relevant here):

  • it allocates memory;
  • it constructs a new object in that memory; and
  • if the construction fails because of an exception the allocated memory is freed.

So each new-expression is essentially a series of two function calls: one call to operator new (either the global one, or one provided by the type of the object being created), and then a call to the constructor.

For Example 2, consider what happens if the compiler decides to generate code that performs the following steps in order:

  1. allocate memory for the T1
  2. construct the T1
  3. allocate memory for the T2
  4. construct the T2
  5. call f

The problem is this: If either step 3 or step 4 fails because of an exception, the C++ standard does not require that the T1 object be destroyed and its memory deallocated. This is a classic memory leak, and clearly Not a Good Thing.

Another possible sequence of events is the following:

  1. allocate memory for the T1
  2. allocate memory for the T2
  3. construct the T1
  4. construct the T2
  5. call f

This sequence has, not one, but two exception safety problems with different effects:

  • If step 3 fails because of an exception, then the memory allocated for the T1 object is automatically deallocated (step 1 is undone), but the standard does not require that the memory allocated for the T2 object be deallocated. The memory is leaked.
  • If step 4 fails because of an exception, then the T1 object has been allocated and fully constructed, but the standard does not require that it be destroyed and its memory deallocated. The T1 object is leaked.

“Hmm,” you might wonder, “then why does this exception safety loophole exist at all? Why doesn’t the standard just prevent the problem by requiring compilers to Do the Right Thing when it comes to cleanup?”

Following the spirit of C in the matter of efficiency, the C++ standard allows the compiler some latitude with the order of evaluation of expressions, because this allows the compiler to perform optimizations that might not otherwise be possible. To permit this, the expression evaluation rules are specified in a way that is not exception-safe, so if you want to write exception-safe code you need to know about, and avoid, these cases.

Fortunately, you can do just that and prevent this problem. Perhaps a smart pointer like unique_ptr could help?

3. As you continue to root through the archives, you see that someone must not have liked Example 2 because later versions of the files in question were changed as follows:

// Example 3

// In some header file:
void f( std::unique_ptr<T1>, std::unique_ptr<T2> );

// At some call site:
f( std::unique_ptr<T1>{ new T1 }, std::unique_ptr<T2>{ new T2 } );

What improvements does this version offer over Example 2, if any? Do any exception safety problems remain? Explain.

This code attempts to “throw unique_ptr at the problem.” Many people believe that a smart pointer is an exception-safety panacea, a touchstone or amulet that by its mere presence somewhere nearby can help ward off compiler indigestion.

It is not. Nothing has changed. Example 3 is still not exception-safe, for exactly the same reasons as before.

Specifically, the problem is that the resources are safe only if they really make it into a managing unique_ptr, but the same problems already noted can still occur before either unique_ptr constructor is ever reached. This is because both of the two problematic execution orders mentioned earlier are still possible, but now with the unique_ptr constructors tacked onto the end before invoking f. For one example:

  1. allocate memory for the T1
  2. construct the T1
  3. allocate memory for the T2
  4. construct the T2
  5. construct the unique_ptr<T1>
  6. construct the unique_ptr<T2>
  7. call f

In the above case, the same problems are still present if either of steps 3 or 4 throws. Similarly with:

  1. allocate memory for the T1
  2. allocate memory for the T2
  3. construct the T1
  4. construct the T2
  5. construct the unique_ptr<T1>
  6. construct the unique_ptr<T2>
  7. call f

Again, the same problems are present if either of steps 3 or 4 throws.

Fortunately, though, this is not a problem with unique_ptr; it’s just being used the wrong way, that’s all. Let’s see how to use it better.

Enter make_unique

4. Demonstrate how to write a make_unique facility that solves the safety problems in Example 3 and can be invoked as follows:

// Example 4

// In some header file:
void f( std::unique_ptr<T1>, std::unique_ptr<T2> );

// At some call site:
f( make_unique<T1>(), make_unique<T2>() );

The basic idea is:

  • We want to leverage the fact that functions called from the same thread won’t interleave, so we want to provide a function that does the work of allocation and construction of the object and construction of the unique_ptr.
  • Because the function should be able to work with any type, we want to express it as a function template.
  • Because the caller will want to pass constructor parameters from outside make_unique, we’ll use the C++11 perfect forwarding style to pass those along to the new-expression inside make_unique.
  • Because shared_ptr already has an analogous std::make_shared, for consistency we’ll call this one make_unique. (That C++11 doesn’t include make_unique is partly an oversight, and it will almost certainly be added in the future. In the meantime, use the one provided below.)

Putting the ingredients together, we get:

template<typename T, typename ...Args>
std::unique_ptr<T> make_unique( Args&& ...args )
{
    return std::unique_ptr<T>( new T( std::forward<Args>(args)... ) );
}

This solves the exception safety problems. No sequence of generated code can cause resources to be leaked, because now all we have is two functions, and we know that one must be executed entirely before the other. Consider the following evaluation order:

  1. call make_unique<T1>
  2. call make_unique<T2>
  3. call f

If step 1 throws, there are no leaks because make_unique is itself strongly exception-safe.

If step 2 throws, then is the temporary unique_ptr<T1> created by step 1 guaranteed to be cleaned up? Yes, it is. One might wonder: Isn’t this pretty much the same as the new T1 object created in the corresponding case in Example 2, which isn’t correctly cleaned up? No, this time it’s not quite the same, because here the unique_ptr<T1> is actually a temporary object, and cleanup of temporary objects is correctly specified in the standard. From the standard, in 12.2/3 (unchanged from C++98):

Temporary objects are destroyed as the last step in evaluating the full-expression that (lexically) contains the point where they were created. This is true even if that evaluation ends in throwing an exception.

Guidelines:

  • Prefer to allocate objects to be managed by a shared_ptr using make_shared, and objects to be managed by a unique_ptr with make_unique.
  • Although Standard C++ does not yet have make_unique, this is mostly an oversight and it will almost certainly eventually be added. In the meantime, use the version shown above, and your code will be forward-compatible with the likely direction of the C++ standard.
  • Avoid using plain new or other raw unmanaged allocation directly. Instead, use a factory like make_unique that wraps the raw allocation and immediately passes it to another object that will own the resource. Often that owning object will be a smart pointer, but it can be a scope guard or any other kind of owning object whose destructor will deallocate the resource safely.

Acknowledgments

This Item was prompted by a discussion thread on comp.lang.c++.moderated. This solution draws on observations presented by James Kanze, Steve Clamage, and Dave Abrahams in that and other threads, and in private correspondence. Thanks also to Clark Nelson, who drafted C++11’s “sequenced before” wording, for clarifying the C++11 status quo.

33 thoughts on “GotW #102: Exception-Safe Function Calls (Difficulty: 7/10)

  1. Why is there any difference between 1(a) and 1(b)? Is wrapping an expression into a function call makes it a non-expression? I.e. is g( expr1 ) not an expression in its own right?

  2. I might be wrong, but there is an unfortunate side effect of ‘make_unique’ trick:
    – normally this construct utilizes RVO and avoids calling copy constructor:
    MyType foo() { return MyType(…); }
    MyType* p = new MyType( foo() );

    logic suggest that it should not work (foo() call should complete before we allocate any memory and therefore we are doomed to have a copy), but it seems that major compilers (GCC/MSVC) recognize this situation and (unless you overload operator new) go extra mile and allow RVO to work here (or may be there is an obscure clause in C++ standard that allows that).

    – unfortunately ‘make_unique’ facility will break it and this will leave you with unnecessary copy constructor call:

    auto p = make_unique(foo());

    Disclaimer: I reserve my right to be wrong. :-)

  3. Correction: you should end up with unnecessary move constructor call (assuming your class has it). It is better, but still an unnecessary overhead.

  4. As I discussed in my proposed solution, while the call to f is now safe enough, f is still as unsafe as ever.

    It is still not just possible, but most natural, to call f in an unsafe way.

    And one solution is to replace the make_unique function with a Unique class whose constructor does the same as make_unique (namely forwarding arguments and doing the new‘ing), and use this class for the formal arguments of f.

    Cheers & hth., & thanks for keeping up the GOTW series!, :-)

    – Alf

  5. As I discussed in my proposed solution, while the call to f is now safe enough, f is still as unsafe as ever.

    It is still not just possible, but most natural, to call f in an unsafe way.

    And one solution is to replace the make_unique function with a Unique class whose constructor does the same as make_unique (namely forwarding arguments and doing the new‘ing), and use this class for the formal arguments of f.

    Cheers & hth., & thanks for keeping up the GOTW series!, :-)

    – Alf

  6. Just checked both GCC and MSVC2010 — in both cases adding make_unique causes magic of RVO to disappear. I suspect this has something to do with the fact that to allocate an object we use ‘statement new’ (as opposed to ‘operator new’) and certain rules about sequence points do not apply to it (thus allowing RVO to work).
    I.e. perfect forwarding actually is not perfect :-) — in order for it to become perfect we need a way to declare that make_unique is not a function, but a statement.
    I hope that make_shared/make_unique in future C++ will get special status similar to statement new.

    Here is test code, if anyone wants to trcheck:

    #include
    #include
    #include
    #include

    using namespace std;

    struct A
    {
    int m;
    A(int i) : m(i) { printf(“ctor(%d)\n”, i); }
    A(A const& o) : m(o.m) { printf(“cctor(%d)\n”, o.m); }
    A const& operator=(A const& o){ m = o.m; printf(“op=(%d)\n”, o.m); }
    ~A() { printf(“dtor\n”); }
    };

    A foo() { return A( rand() ); }
    auto_ptr make_unique(A&& a) { return auto_ptr(new A(forward(a))); }
    //unique_ptr
    make_unique(A&& a) { return unique_ptr(new A(forward(a))); }

    int main()
    {
    srand(time(NULL));
    auto_ptr
    p( new A(foo()) );
    // auto_ptr
    p( make_unique(foo()) );
    // unique_ptr
    p( make_unique(foo()) );
    return 1;
    }

  7. @Alf – I can’t see your original solution you refer to; could you repost or clarify here please? I cannot see why f() would be unsafe if it is accepting 2 unique_ptr arguments…unless you mean that the most natural way to call it is without using make_unique?

    Thanks Herb :-)

  8. @Michael – Yes, g( expr1 ) is still an expression in its own right. However, the key here is that before each function call all parameters must be fully evaluated.
    f( g( expr1 ), h( expr2 ) );
    g and h must be evaluated before f is called
    expr1 must be evaluated before g is called
    expr2 must be evaluated before h is called

    That means expr1 and expr2 can still be executed in whatever order.
    Case 1: expr1, expr2, g, h, f
    Case 2: expr2, expr1, g, h, f
    Either way still meets the guarantee that the expressions will have been fully evaluated before the functions are called. (This is opposed to, say, assuming arguments will be evaluated left-to-right.)

    In case 1 for example, you could have: new T2, new T1 (throws)(cleans up this T1 instance), T2 was not cleaned. Plus what Herb mentioned about the new actually breaking into allocate and constructor pieces.

    Thanks for the great insight, Herb!

  9. @Chris – The point that confuses me about 1(b) is this: “For example, no part of the evaluation of expr2 or any of the execution of h may occur from the time g begins until it ends”.

    Consider an expression

    f( !expr1, -expr2 );

    If I read Herb correctly, the execution of operator!() and operator-() is unsequenced and allowed to overlap if the operators are built-in (e.g. if expr1 and exp2 are int), but is indeterminately sequenced and not allowed to overlap if the operators are user-defined. This looks counter-intuitive so I want to make sure my understanding is correct.

  10. @Herb: For f to be unsafe it is enough that f can be called without using make_unique.

    Relying on the user realzing and remembering that make_unique should be used, is to rely on convention.

    That’s always risky, and to be avoided when the convention can very easily be expressed and enforced within the language (in this case, by replacing the free function with a constructor, andly changing the function signature accordingly).

    Cheers & hth.,

    – Alf

    PS: Again, thanks for keeping up this eris,

  11. @herb: by “free function” to be replaced with a constructor, I meant make_unique. Sorry but WordPress is not perfect wrt. offering editing of user comments.

    Cheers,

    – Alf

  12. @herb: Could you please provide information how to implement make_unique in a correct way in VS2010 and VS2011?

    Thanks

    Mat

  13. template
    std::unique_ptr make_unique( Args&& …args )
    {
    return std::unique_ptr( new T( std::forward(args)… ) );
    }

    Herb, I have this etched into my future template trinkets, but sadly VC as of yet does not support variadiac templates. I would like to express gratitude for all the hard work you and the standards committee have done preparing this wonderful new language to use. Thank You! I’m sure Mr Caves is working diligently on the variadiac template issue, but if you get the chance give him and STL a pat on the back for the ranged for loop added to the beta.

  14. I thought that inlining make_unique in f( make_unique(), make_unique() ); could lead to a code equivalent to f( std::unique_ptr{ new T1 }, std::unique_ptr{ new T2 } );. Does it mean that the latitude of code optimization differ when we refactor a part of code inside a function we could expect to be inline by the compiler (The compiler is not forced to inline, but let’s suppose it does …) ?

  15. One drawback I see with make_unique is that you can’t use it to construct a unique_ptr with a deleter other than std::default_deleter. Is there any way around that?

  16. Is there a way to write make_unique() in VS2012?

    Yes, but it’s not pretty, as you have to fake variadic templates. Fortunately, the library provides these (for relatively small numbers of parameters – Boost metaprogramming provides larger numbers, but is correspondingly harder to use).

    The following code works for zero to four parameters; it will probably get mangled during the post.

    #include <memory> // brings in TEMPLATE macros.

    #define _MAKE_UNIQUE(TEMPLATE_LIST, PADDING_LIST, LIST, COMMA, X1, X2, X3, X4) \
    \
    template<class T COMMA LIST(_CLASS_TYPE)> \
    inline std::unique_ptr<T> make_unique(LIST(_TYPE_REFREF_ARG)) \
    { \
    return std::unique_ptr<T>(new T(LIST(_FORWARD_ARG))); \
    }

    _VARIADIC_EXPAND_0X(_MAKE_UNIQUE, , , , )
    #undef _MAKE_UNIQUE

  17. @David: Well, it’s not that difficult to support a makeUnique function with Visual C++ 11.0 (Visual Studio 2012), with standard-conforming C++03 code. I have already linked to my long-ago blog article about it. But just to be concrete, I now sat down and created the following proof of concept code, in Visual Studio 2012. It’s proof-of-concept because it doesn’t handle a deleter, and it doesn’t handle arrays. But in that sense also the implementation given by Herb is proof-of-concept.

    Important detail: here makeUnique produces an instance of a *derived class*, so that functions like this GOTW’s foo can be made type safe.

    #include <memory>           // std::unique_ptr
    #include <utility>          // std::move
    #include "progrock/cppx/ConstructorArgForwarder_.h"
    
    namespace progrock { namespace cppx {
        using std::unique_ptr;
    
        template< class Pointee >
        class UniquePtr_
            : public unique_ptr< Pointee >
        {
        public:
            typedef unique_ptr< Pointee > Base;
    
        protected:
            UniquePtr_( Pointee* p ): Base( p ) {}
    
            UniquePtr_( unique_ptr< Pointee >&& other )
                : Base( move( other ) )
            {}
    
        public:
            template< class ArgPack >
            static UniquePtr_ from( ArgPack const& args )
            {
                return UniquePtr_( new ConstructorArgForwarder_< Pointee >( args ) );
            }
    
            static UniquePtr_ fromExisting( unique_ptr< Pointee > existingPtr )
            {
                return UniquePtr_( move( existingPtr ) );
            }
    
            UniquePtr_( UniquePtr_&& other )
                : Base( move( other ) )
            {}
        };
    
        template< class Pointee, class ArgPack >
        UniquePtr_< Pointee > makeUnique_( ArgPack const& args )
        {
            return UniquePtr_< Pointee >::from( args );
        }
    
    } }  // namespace progrock::cppx
    
    #include <iostream>
    #include <string>
    using namespace std;
    using namespace progrock;
    using cppx::UniquePtr_;
    using cppx::makeUnique_;
    using cppx::args;
    
    void foo( UniquePtr_< wstring > a, UniquePtr_< wstring > b )
    {
        wcout << *a << endl;
        wcout << *b << endl;
    }
    
    void bar( unique_ptr< wstring > a, unique_ptr< wstring > b )
    {
        wcout << *a << endl;
        wcout << *b << endl;
    }
    
    int main()
    {
        // OK
        foo(
            makeUnique_< wstring >(
                args( L"The Visual Studio 2012 text editor can't handle this string:" )
                ),
            makeUnique_ <wstring >( args( 72, L'-' ) )
            );
    
        // OK
        bar(
            makeUnique_< wstring >(
                args( L"The edit position just goes completely haywire..." )
                ),
            makeUnique_< wstring >( args( 72, L'-' ) )
            );
    
        // Uh oh -- the `bar` programmer didn't do his job properly, and the using code
        // programmer hasn't understood the importance of using `makeUnique_`, which 
        // after all isn't enforced by bar (if it was important it would be enforced?).
        bar(
            unique_ptr< wstring >(
                new wstring( L"Simply put, it gets the width of a hyphen, wrong. Oh my!" )
                ),
            unique_ptr< wstring >( new wstring( 72, L'-' ) )
            );
        // Well, nothing bad happened here, because no exception was thrown. Phew! :-)
    }
    
  18. @Alf,

    Your UniquePtr_<> is an interesting and clever example of using the type system to enforce a programming style – in this case, “never use new or delete in application level code”.

    My version of make_unique is VC++ specific (recent versions – works with 2012), and makes use of Microsoft’s macros for faking variadic templates by creating one template for each number of template parameters from 0 through 4 (VC++2010 supported up to ten!).

    Other than this kludge, it is a blatant copy of Herb’s original version, including the signature which matches std::make_shared. This may be clearer if I manage to post it with markup:

     
    // TEMPLATE FUNCTION make_unique
    #define _MAKE_UNIQUE(TEMPLATE_LIST, PADDING_LIST, LIST, COMMA, X1, X2, X3, X4)	\
    																				\
    template&lt;class T COMMA LIST(_CLASS_TYPE)>&gt;									\
    std::unique_ptr&lt;T&gt; make_unique(LIST(_TYPE_REFREF_ARG))							\
    {																				\
        return std::unique_ptr&lt;T&gt;(new T(LIST(_FORWARD_ARG)));						\
    }
    
    _VARIADIC_EXPAND_0X(_MAKE_UNIQUE, , , , )
    

    Barring the kludge, I believe that this, like Herb’s original, is complete:

    std::unique_ptrs do not have associated deleter objects, though they may templatized with a Deleter type, so the “obvious” solution of allocate_unique() (c.f. std::allocate_shared()) would require the provision of an extended unique pointer type.

    As for arrays, it may be significant that the standards committee didn’t decide to adopt/adapt Boost’s shared/scoped_array. The reason for this may be because there are very few situations where there is a significant benefit: for fixed size arrays, std::unique_array<std::array<T, N>> works fine (and fits in with make_unique()).

    For variable-sized arrays, std::vector<T> is almost always a better bet: it’s familiar and easy to use: std::vector<T> v(n) versus:

    std::unique_ptr&lt;T[], std::default_delete&lt;T[]&gt;&gt; v(new T[n], std::default_delete&lt;T[]&gt;());

    even if that was simplified by a make_unique overload into:

    std::unique_ptr&lt;T[], std::default_delete&lt;T[]&gt;&gt; v = make_unique&lt;T[]&gt;(42));

    Cheers!

    David.

  19. (resent with markup fixed)
    @Alf,

    My version of make_unique is VC++ specific (recent versions – works with 2012), and makes use of Microsoft’s macros for faking variadic templates by creating one template for each number of template parameters from 0 through 4 (VC++2010 supported up to ten!).

    Other than this kludge, it is a blatant copy of Herb’s original version, including the signature which matches std::make_shared. This may be clearer if I manage to post it with markup:

     
    // TEMPLATE FUNCTION make_unique
    #define _MAKE_UNIQUE(TEMPLATE_LIST, PADDING_LIST, LIST, COMMA, X1, X2, X3, X4)	\
    																				\
    template<class T COMMA LIST(_CLASS_TYPE)>>									\
    std::unique_ptr<T> make_unique(LIST(_TYPE_REFREF_ARG))							\
    {																				\
        return std::unique_ptr<T>(new T(LIST(_FORWARD_ARG)));						\
    }
    
    _VARIADIC_EXPAND_0X(_MAKE_UNIQUE, , , , )
    

    Barring the kludge, I believe that this, like Herb’s original, is complete:

    std::unique_ptrs do not have associated deleter objects, though they may templatized with a Deleter type, so the “obvious” solution of allocate_unique() (c.f. std::allocate_shared()) would require the provision of an extended unique pointer type.

    As for arrays, it may be significant that the standards committee didn’t decide to adopt/adapt Boost’s shared/scoped_array. The reason for this may be because there are very few situations where there is a significant benefit: for fixed size arrays, std::unique_array<std::array<T, N>> works fine (and fits in with make_unique()).

    For variable-sized arrays, std::vector<T> is almost always a better bet: it’s familiar and easy to use: std::vector<T> v(n) versus:

    std::unique_ptr<char[], std::default_delete<char[]>> v(new char[42], std::default_delete<char[]>());

    even if that was simplified by a make_unique overload into:

    std::unique_ptr<T[], std::default_delete<T[]>> v = make_unique<T[], std::default_delete>(42));

    Cheers!

    David.

  20. @David: well I hope Herb can fix up the postings (by deleting first tries).

    Anyway, just a few factoids:

    std::unique_ptr does have an associated (stored) deleter object. But it’s designed to allow Empty Base Class optimization, which you in practice can rely on for the default deleter. In other direction, ungoodness, for performance reasons it does not support general conversion up a class inheritance chain wrt. getting the deleter argument correct, i.e. it’s not quite type safe, whereas `std::shared_ptr` is.
    std::unique_ptr is specialized for arrays, and there is no such thing as std::unique_array

    Therefore, in order for make_unique to be able to create all kinds of std::unique_ptr, it needs to be (effectively) specialized for array pointee so that in that case, it allocates an array pointee.

    The deleter problem is that a deleter argument comes in addition to the pointee constructor arguments.

    True, it’s not particularly difficult, or doesn’t seem to be, but in light of the two bullet points above (hopefully the markup worked) the implementations we’ve all put forward, are merely proofs of concept, nothing more.

    Cheers & hth.,

    – Alf

  21. @Alf,

    Thanks for your comments.

    Some of the slips were typos as I fiddled with the markup: I should have said to use std::unique_ptr<std::array<T, N>> for fixed size arrays. Unlike vector, this results in a true pointer-to-array type requiring an extra level of indirection:

        typedef std::array<char, 42> Array;
        std::unique_ptr<Array> v = make_unique<Array>();
        (*v)[2] = 'x';
        Assert::AreEqual('x', (*v)[2]);
        Assert::AreEqual(size_t(42), v->size());
    

    Thanks for the correction about deleters being – possibly elided – objects, rather than just types: when all else fails, read the standard.

    In this case, it would seem that providing a function modelled on allocate_shared() is appropriate, something like:

    template <typename T, template <typename > typename Alloc, typename... Args>
    unique_ptr<T, Deleter<Alloc<T>>> allocate_unique(const Alloc& alloc, Args&&... args)
    {
        Alloc<T> allocator(alloc);      // allocator is copied, c.f. allocate_shared<>()
        typedef Deleter<Alloc<T>>	Del;
        T* tp = allocator.allocate(1);
        try {
            allocator.construct(tp, args...);
            return unique_ptr<T, Del>(tp, Del());
        }
        catch (...) {   // allocation succeeded, but construction failed.
            allocator.deallocate(tp, 1);
            throw;
        }
    }
    // For example 
    auto uptr = allocate_unique<MyClass>(my_alloc, "hello", "world"));
    

    The Deleter wraps the allocator looks like:

    template <class Alloc>
    class Deleter
        : private Alloc     // use EBCO to avoid growing unique_ptr.
    {
    private:
        using Alloc::destroy;       // needed for VC++12
        using Alloc::deallocate;
    
    public:
    	typedef typename Alloc::value_type value_type;
    
    	Deleter() : Alloc() {}
    	Deleter(const Alloc& alloc) : Alloc(alloc) {}
    	void operator()(value_type* p) {
    		destroy(p);
    		deallocate(p, 1);
    	}
    };
    

    Caveat: I have tested this with VC++12, using “manually expanded variable arguments” and without confirming correct exception handling.

    If only local, scoped heap objects were needed, using auto in declarations would be fine, and Deleter could be buried out of the user’s sight. Where a full type is needed, it becomes ugly:

    unique_ptr<MyClass, Deleter<MyAllocator<MyClass>>> uptr =
        allocate_unique<MyClass>(my_alloc, "hello", "world"));
    

    Ideally, a alias declaration would simplify this. In the meantime, a poor-man’s implementation “works” well enough for testing:

    template <class T, template <class S> class Alloc>
    class UniqueAllocPtr
        : public unique_ptr<T, Deleter<Alloc<T>>>
    {
    public:
        typedef unique_ptr<T, Deleter<Alloc<T>>> Ptr;
        class UniqueAllocPtr(Ptr&& ptr)
            : Ptr(std::move(ptr))
        {}
    };
    
    // For example:
    UniqueAllocPtr<MyClass, MyAllocator> uptr =
        allocate_unique<MyClass>(my_alloc, "hello", "world"));
    

    I’ll leave C-style arrays for another time: they seem to require the Deleter hanging onto the array size for the Allocator‘s deallocate() and repeated calls to destroy(). Some thought will also be needed to perform initialization cleanup correctly in the presence of exceptions, which I’ll leave for another day.

    Hoping my markup is better this time,

    Cheers!

    David.

Comments are closed.