I was experimenting with shared_ptr
and make_shared
from C++11 and programmed a little toy example to see what is actually happening when calling make_shared
. As infrastructure I was using llvm/clang 3.0 along with the llvm std c++ library within XCode4.
class Object { public: Object(const string& str) { cout << "Constructor " << str << endl; } Object() { cout << "Default constructor" << endl; } ~Object() { cout << "Destructor" << endl; } Object(const Object& rhs) { cout << "Copy constructor..." << endl; } }; void make_shared_example() { cout << "Create smart_ptr using make_shared..." << endl; auto ptr_res1 = make_shared<Object>("make_shared"); cout << "Create smart_ptr using make_shared: done." << endl; cout << "Create smart_ptr using new..." << endl; shared_ptr<Object> ptr_res2(new Object("new")); cout << "Create smart_ptr using new: done." << endl; }
Now have a look at the output, please:
Create smart_ptr using make_shared...
Constructor make_shared
Copy constructor...
Copy constructor...
Destructor
Destructor
Create smart_ptr using make_shared: done.
Create smart_ptr using new...
Constructor new
Create smart_ptr using new: done.
Destructor
Destructor
It appears that make_shared
is calling the copy constructor two times. If I allocate memory for an Object
using a regular new
this does not happen, only one Object
is constructed.
What I am wondering about is the following. I heard that make_shared
is supposed to be more efficient than using new
(1, 2). One reason is because make_shared
allocates the reference count together with the object to be managed in the same block of memory. OK, I got the point. This is of course more efficient than two separate allocation operations.
On the contrary I don't understand why this has to come with the cost of two calls to the copy constructor of Object
. Because of this I am not convinced that make_shared
is more efficient than allocation using new
in every case. Am I wrong here? Well OK, One could implement a move constructor for Object
but still I am not sure whether this this is more efficient than just allocating Object
through new
. At least not in every case. It would be true if copying Object
is less expensive than allocating memory for a reference counter. But the shared_ptr
-internal reference counter could be implemented using a couple of primitive data types, right?
Can you help and explain why make_shared
is the way to go in terms of efficiency, despite the outlined copy overhead?
As well as this efficiency, using make_shared means that you don't need to deal with new and raw pointers at all, giving better exception safety - there is no possibility of throwing an exception after allocating the object but before assigning it to the smart pointer.
std::make_shared Allocates and constructs an object of type T passing args to its constructor, and returns an object of type shared_ptr<T> that owns and stores a pointer to it (with a use count of 1). This function uses ::new to allocate storage for the object.
The header file <boost/make_shared. hpp> provides a family of overloaded function templates, make_shared and allocate_shared , to address this need. make_shared uses the global operator new to allocate memory, whereas allocate_shared uses an user-supplied allocator, allowing finer control.
So, if you throw exception from your class' constructor, then std::make_shared will throw it too. Besides exceptions thrown from constructor, std::make_shared could throw std::bad_alloc exception on its own.
As infrastructure I was using llvm/clang 3.0 along with the llvm std c++ library within XCode4.
Well that appears to be your problem. The C++11 standard states the following requirements for make_shared<T>
(and allocate_shared<T>
), in section 20.7.2.2.6:
Requires: The expression ::new (pv) T(std::forward(args)...), where pv has type void* and points to storage suitable to hold an object of type T, shall be well formed. A shall be an allocator (17.6.3.5). The copy constructor and destructor of A shall not throw exceptions.
T
is not required to be copy-constructable. Indeed, T
isn't even required to be non-placement-new constructable. It is only required to be constructable in-place. This means that the only thing that make_shared<T>
can do with T
is new
it in-place.
So the results you get are not consistent with the standard. LLVM's libc++ is broken in this regard. File a bug report.
For reference, here's what happened when I took your code into VC2010:
Create smart_ptr using make_shared... Constructor make_shared Create smart_ptr using make_shared: done. Create smart_ptr using new... Constructor new Create smart_ptr using new: done. Destructor Destructor
I also ported it to Boost's original shared_ptr
and make_shared
, and I got the same thing as VC2010.
I'd suggest filing a bug report, as libc++'s behavior is broken.
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