Advantages of Using Std::Make_Unique Over New Operator

Advantages of using std::make_unique over new operator

Advantages

  • make_unique teaches users "never say new/delete and
    new[]/delete[]" without disclaimers.

  • make_unique shares two advantages with make_shared (excluding the third advantage, increased efficiency). First, unique_ptr<LongTypeName> up(new LongTypeName(args)) must mention LongTypeName twice, while auto up = make_unique<LongTypeName>(args) mentions it once.

  • make_unique prevents the unspecified-evaluation-order
    leak triggered by expressions like foo(unique_ptr<X>(new X), unique_ptr<Y>(new Y)). (Following the advice "never say new" is simpler than
    "never say new, unless you immediately give it to a named unique_ptr".)

  • make_unique is carefully implemented for exception safety and is recommended over directly calling unique_ptr constructors.

When not to use make_unique

  • Don't use make_unique if you need a custom deleter or are adopting a raw pointer from elsewhere.

Sources

  1. Proposal of std::make_unique.
  2. Herb Sutter's GotW #89 Solution: Smart Pointers

Why use std::make_unique in C++17?

You're right that the main reason was removed. There are still the don't use new guidelines and that it is less typing reasons (don't have to repeat the type or use the word new). Admittedly those aren't strong arguments but I really like not seeing new in my code.

Also don't forget about consistency. You absolutely should be using make_shared so using make_unique is natural and fits the pattern. It's then trivial to change std::make_unique<MyClass>(param) to std::make_shared<MyClass>(param) (or the reverse) where the syntax A requires much more of a rewrite.

Differences between std::make_unique and std::unique_ptr with new

The motivation behind make_unique is primarily two-fold:

  • make_unique is safe for creating temporaries, whereas with explicit use of new you have to remember the rule about not using unnamed temporaries.

    foo(make_unique<T>(), make_unique<U>()); // exception safe

    foo(unique_ptr<T>(new T()), unique_ptr<U>(new U())); // unsafe*
  • The addition of make_unique finally means we can tell people to 'never' use new rather than the previous rule to "'never' use new except when you make a unique_ptr".

There's also a third reason:

  • make_unique does not require redundant type usage. unique_ptr<T>(new T()) -> make_unique<T>()

None of the reasons involve improving runtime efficiency the way using make_shared does (due to avoiding a second allocation, at the cost of potentially higher peak memory usage).

* It is expected that C++17 will include a rule change that means that this is no longer unsafe. See C++ committee papers P0400R0 and P0145R3.

std::make_unique with placement new

There is no standard way to do this, because standard C++ doesn't provide any real support for VLAs/flexible array members. make_unique was written solely for either:

  1. A single object of fixed size (it has no mechanism to provide information about the "real" size, it just uses new, which assumes sizeof T is correct and complete)
  2. An array of objects of fixed size (by definition an array must have objects of fixed size, or indexing doesn't work)

I suppose in theory you could make your own version that supported quasi-flexible array members, but there is zero support for this in the C++ standard library. std::make_unique doesn't even have an allocator-aware variant; it's not going out of its way to support unusual use cases. std::allocate_shared (the allocator aware version of std::make_shared) might be forced to support this in ridiculous ways (you'd essentially need to write custom allocators that also knew how much extra memory an allocation request should provide), and there's a proposal for an allocator-aware version of std::make_unique, but again, making this work would be an exercise in insanity.

You say "as good practice, I'd prefer to use std::make_unique, over a raw new", but flexible array members in C++ are already bad practice; there is essentially zero support for them because they break all sorts of assumptions that much of the C++ language and library rely on. If you must do it, write your own factory methods to perform the work; sure, using raw new is frowned on, but confining it to a single API in your code that guarantees the result is a managed pointer before passing it outside that API limits the "damage".

Using std::make_unique with the GetProfileBinary function call

pDateTime is supposed to be nullptr, and GetProfileBinary handles the allocation. Code Analysis mistakenly thinks you forgot the allocation.

It does need to check for success before calling delete[]. We can't use delete[]pDatTime because pDatTime is not an array. But GetProfileBinary allocates using new BYTE[size], so we need to cast back to BYTE.

You can also add a NULL check before reading pDatTime, that might make Code Analysis happy.

if (pDatTime && uSize == sizeof(DATE))
rBackupDate = *pDatTime;
else
rBackupDate = COleDateTime::GetCurrentTime();
if(pDatTime) delete[](BYTE*)pDatTime;

You can use std::unique_ptr<BYTE[]> cleanup((BYTE*)pDatTime) for deletion, but this has to be after GetProfileBinary is called.

Example:

DATE* pDatTime = nullptr;
GetProfileBinary(_T("Options"), _T("BackupLastBackupDate"), (LPBYTE*)(&pDatTime), &uSize);
std::unique_ptr<BYTE[]> cleanup((BYTE*)pDatTime); //automatic delete

if (pDatTime && uSize == sizeof(DATE))
rBackupDate = *pDatTime;
else
rBackupDate = COleDateTime::GetCurrentTime();

//pDatTime = NULL; <- Error when used with unique_ptr
...
//pDatTime is deleted later, when `cleanup` goes out of scope

Assignment of unique_ptr inside member initializer list

The only difference is style. Since use of operator new is now considered old fashioned std::make_unique is recommended.

For compiler there is no difference.

By the way is should be:

A::A() : m_b{std::make_unique<B>()} {}

Also see
CppCoreGuidelines/CppCoreGuidelines.md at master · isocpp/CppCoreGuidelines

C.150: Use make_unique() to construct objects owned by unique_ptrs


Reason

make_unique gives a more concise statement of the construction. It also ensures exception safety in complex expressions.

Example

unique_ptr<Foo> p {new Foo{7}};    // OK: but repetitive

auto q = make_unique<Foo>(7); // Better: no repetition of Foo

// Not exception-safe: the compiler may interleave the computations of arguments as follows:
//
// 1. allocate memory for Foo,
// 2. construct Foo,
// 3. call bar,
// 4. construct unique_ptr<Foo>.
//
// If bar throws, Foo will not be destroyed, and the memory-allocated for it will leak.
f(unique_ptr<Foo>(new Foo()), bar());

// Exception-safe: calls to functions are never interleaved.
f(make_unique<Foo>(), bar());

Is there a way to let make_unique T[ ] can forward arguments to constructor of T?

I think the idea propsed by @AchimGuetlein is better.

Define as following:

vector<unique_ptr<ClassA> > container;

for (i = 0; i<number of queues; ++i)
container.emplace_back(make_unique<ClassA>(args to constructor of ClassA));

instead of :

unique_ptr<ClassA [] > ques = make_unique<ClassA [] >(n, ...);

Although elements of vector might be moved, there is no moving occured to my queues. The objs moved just are unique_ptr self, but the addresses of the queues would never be changed.

Sorry for committing it as an answer, since the code sample can not be gracefully showed as codelet in a comment.



Related Topics



Leave a reply



Submit