Std::Shared_Ptr Initialization: Make_Shared<Foo>() VS Shared_Ptr<T>(New Foo)

std::shared_ptr initialization: make_sharedFoo() vs shared_ptrT(new Foo)

Both examples are rather more verbose than necessary:

std::shared_ptr<int> p(new int);  // or '=shared_ptr<int>(new int)' if you insist
auto p = std::make_shared<int>(); // or 'std::shared_ptr<int> p' if you insist

What's the difference?

The main difference is that the first requires two memory allocations: one for the managed object (new int), and one for the reference count. make_shared should allocate a single block of memory, and create both in that.

Which one should I prefer and why?

You should usually use make_shared as it's more efficient. As noted in another answer, it also avoids any possibility of a memory leak, since you never have a raw pointer to the managed object.

However, as noted in the comments, it has a potential disadvantage that the memory won't be released when the object is destroyed, if there are still weak pointers preventing the shared count from being deleted.


EDIT 2020/03/06:

Further recommendations come also from the official Microsoft documentation with associated examples. Keep the focus on the Example 1 snippet:

Whenever possible, use the make_shared function to create a shared_ptr
when the memory resource is created for the first time. make_shared is
exception-safe. It uses the same call to allocate the memory for the
control block and the resource, which reduces the construction
overhead. If you don't use make_shared, then you have to use an
explicit new expression to create the object before you pass it to the
shared_ptr constructor. The following example shows various ways to
declare and initialize a shared_ptr together with a new object.

Difference in make_shared and normal shared_ptr in C++

The difference is that std::make_shared performs one heap-allocation, whereas calling the std::shared_ptr constructor performs two.

Where do the heap-allocations happen?

std::shared_ptr manages two entities:

  • the control block (stores meta data such as ref-counts, type-erased deleter, etc)
  • the object being managed

std::make_shared performs a single heap-allocation accounting for the space necessary for both the control block and the data. In the other case, new Obj("foo") invokes a heap-allocation for the managed data and the std::shared_ptr constructor performs another one for the control block.

For further information, check out the implementation notes at cppreference.

Update I: Exception-Safety

NOTE (2019/08/30): This is not a problem since C++17, due to the changes in the evaluation order of function arguments. Specifically, each argument to a function is required to fully execute before evaluation of other arguments.

Since the OP seem to be wondering about the exception-safety side of things, I've updated my answer.

Consider this example,

void F(const std::shared_ptr<Lhs> &lhs, const std::shared_ptr<Rhs> &rhs) { /* ... */ }

F(std::shared_ptr<Lhs>(new Lhs("foo")),
std::shared_ptr<Rhs>(new Rhs("bar")));

Because C++ allows arbitrary order of evaluation of subexpressions, one possible ordering is:

  1. new Lhs("foo"))
  2. new Rhs("bar"))
  3. std::shared_ptr<Lhs>
  4. std::shared_ptr<Rhs>

Now, suppose we get an exception thrown at step 2 (e.g., out of memory exception, Rhs constructor threw some exception). We then lose memory allocated at step 1, since nothing will have had a chance to clean it up. The core of the problem here is that the raw pointer didn't get passed to the std::shared_ptr constructor immediately.

One way to fix this is to do them on separate lines so that this arbitary ordering cannot occur.

auto lhs = std::shared_ptr<Lhs>(new Lhs("foo"));
auto rhs = std::shared_ptr<Rhs>(new Rhs("bar"));
F(lhs, rhs);

The preferred way to solve this of course is to use std::make_shared instead.

F(std::make_shared<Lhs>("foo"), std::make_shared<Rhs>("bar"));

Update II: Disadvantage of std::make_shared

Quoting Casey's comments:

Since there there's only one allocation, the pointee's memory cannot be deallocated until the control block is no longer in use. A weak_ptr can keep the control block alive indefinitely.

Why do instances of weak_ptrs keep the control block alive?

There must be a way for weak_ptrs to determine if the managed object is still valid (eg. for lock). They do this by checking the number of shared_ptrs that own the managed object, which is stored in the control block. The result is that the control blocks are alive until the shared_ptr count and the weak_ptr count both hit 0.

Back to std::make_shared

Since std::make_shared makes a single heap-allocation for both the control block and the managed object, there is no way to free the memory for control block and the managed object independently. We must wait until we can free both the control block and the managed object, which happens to be until there are no shared_ptrs or weak_ptrs alive.

Suppose we instead performed two heap-allocations for the control block and the managed object via new and shared_ptr constructor. Then we free the memory for the managed object (maybe earlier) when there are no shared_ptrs alive, and free the memory for the control block (maybe later) when there are no weak_ptrs alive.

Does shared_ptr a = make_shared() create a copy of the shared_ptr before constructor is run?

There are two things to avoid the copy:

  • 1 is the compiler's RVO (return value optimization);
  • 2 is the move constructor/assignment.

for code auto foo = std::make_shared<Foo>();
RVO will create the object directly on the stack. and even we disable the RVO by -fno-elide-constructors, the move constructor will try used as the returned object from make_shared is a temporary one.

Below is a simple test code. (this code only show the concept but not for a real-world shared_ptr implementation)

#include <iostream>

template <typename T>
struct my_shared_ptr
{
T *t_{nullptr};

my_shared_ptr(T *t): t_(t) {
std::cout << "constructor" << std::endl;
};

my_shared_ptr(const my_shared_ptr<T>&) {
std::cout << "copy" << std::endl;
}

my_shared_ptr<T>& operator=(const my_shared_ptr<T>&) {
std::cout << "copy" << std::endl;
return *this;
}

#ifndef NO_MOVE
my_shared_ptr(my_shared_ptr<T>&&) {
std::cout << "move" << std::endl;
}

my_shared_ptr<T>& operator=(my_shared_ptr<T>&&) {
std::cout << "move" << std::endl;
return *this;
}
#endif
};

template <typename T>
my_shared_ptr<T>
my_make_shared() {
return my_shared_ptr<T>(new T);
}

struct Foo {};

int main()
{
auto foo = my_make_shared<Foo>();
return 0;
}

Condition 1, compile with c++11 shows:

$ g++ a.cc -std=c++11 ; ./a.out
constructor

Condition 2, compile with c++11/disable RVO shows:

$ g++ a.cc -std=c++11 -fno-elide-constructors ; ./a.out
constructor
move
move

Condition 3, compile with c++11/disable RVO/no move shows:

$ g++ a.cc -std=c++11 -fno-elide-constructors -DNO_MOVE ; ./a.out
constructor
copy
copy

Initializing shared_ptr member variable, new vs make_shared?

The only times when make_shared is not allowed are:

  1. If you're getting a naked pointer allocated by someone else and storing it in shared_ptr. This is often the case when interfacing with C APIs.
  2. If the constructor you want to call is not public (make_shared can only call public constructors). This can happen with factory functions, where you want to force users to create the object from the factory.

    However, there are ways to get around this. Instead of having a private constructor, have a public constructor. But make the constructor take a type with can only be constructed by those with private access to the class. That way, the only people who can call make_shared with that object type are those with private access to the class.

So yes, you can do this.

std::make_shared leads to undefined behavior, but new works

Your call to make_shared is using a copy constructor for your Foo class, which you haven't defined. Thus, the default (compiler-generated) copy will be used, and the destructor will be called to delete the temporary. As your class doesn't properly implement the Rule of Three, this (potentially) causes undefined behaviour.

The copy constructor is being used because the argument list in your call to std::make_shared is Foo(3, false) – a Foo object, so the call fits only the first overload listed on this cppreference page. (Note that there is nothing resembling a std::make_shared<T>(const T& src) overload.) From that page, we see:

  1. Constructs an object of type T and wraps it in a std::shared_ptr
    using args as the parameter list for the constructor of T.

So, with your "args" of Foo(3, false), that make_shared actually calls the following constructor for the object to wrap:

Foo(Foo(3, false))

For correct behaviour, just pass the 3 and false as arguments to make_shared:

std::shared_ptr<Foo> sp_foo = std::make_shared<Foo>(3, false);

You can demonstrate the error in your original code by adding a destructor to the Foo class that logs some output, like: ~Foo() { std::cout << "Destroying...\n"; }. You will see that output after execution of the call to make_shared.

You can prevent this accidental error/oversight by deleting the Foo copy constructor: Foo(const Foo& f) = delete;. This will generate a compiler error along the following lines:

error : call to deleted constructor of 'Foo'


However, in your second case, you use a std::shared_ptr constructor (the third form shown on the linked page). This has no problem, because that constructor 'just' wraps the given pointer into the managed object, and no copying or destruction is needed.

How to initialize shared_ptr not knowing its type?

Assuming that sometypePtr is a non-array std::shared_ptr, then you can use sometypePtr::element_type.

template<typename sometypePtr>
void foo()
{
sometypePtr ptr = std::make_shared<typename sometypePtr::element_type>();
}

If sometypePtr is an array std::shared_ptr, you will have to supply the extent as well as the type.

no matching function for call to 'make_shared'

The error message is complaining you using an prvalue 10. Try to use

int avar = 10;
auto foo = std::make_shared<int> (avar);

It is interesting to see what happens when using a lvalue.

Did you build the std library locally? If you are, maybe you could try to rebuild again or grab a prebuilt library from somewhere.

I tested the code on https://godbolt.org/ with configuration x86-64 clang 7.0.0 and -std=c++11. it works fine. Even you using iOS, it should be good on that os I guess.

I also see you using -Wc++11-extensions when you building. Try use -std=c++11 instead maybe?


Edit by DG: As noted in my comment below, this last suggestion, "Try use -std=c++11," worked! With -std=c++11 then all values (lvalues, rvalues, prvalues, etc.) work fine. See comments below.

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.

How do I call ::std::make_shared on a class with only protected or private constructors?

This answer is probably better, and the one I'll likely accept. But I also came up with a method that's uglier, but does still let everything still be inline and doesn't require a derived class:

#include <memory>
#include <string>

class A {
protected:
struct this_is_private;

public:
explicit A(const this_is_private &) {}
A(const this_is_private &, ::std::string, int) {}

template <typename... T>
static ::std::shared_ptr<A> create(T &&...args) {
return ::std::make_shared<A>(this_is_private{0},
::std::forward<T>(args)...);
}

protected:
struct this_is_private {
explicit this_is_private(int) {}
};

A(const A &) = delete;
const A &operator =(const A &) = delete;
};

::std::shared_ptr<A> foo()
{
return A::create();
}

::std::shared_ptr<A> bar()
{
return A::create("George", 5);
}

::std::shared_ptr<A> errors()
{
::std::shared_ptr<A> retval;

// Each of these assignments to retval properly generates errors.
retval = A::create("George");
retval = new A(A::this_is_private{0});
return ::std::move(retval);
}

Edit 2017-01-06: I changed this to make it clear that this idea is clearly and simply extensible to constructors that take arguments because other people were providing answers along those lines and seemed confused about this.



Related Topics



Leave a reply



Submit