Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is make_shared really more efficient than new?

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?

like image 696
user1212354 Avatar asked Feb 15 '12 22:02

user1212354


People also ask

Why should I use Make_shared?

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.

What does Make_shared return?

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.

What is boost :: Make_shared?

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.

Does Make_shared throw?

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.


1 Answers

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.

like image 147
Nicol Bolas Avatar answered Sep 19 '22 19:09

Nicol Bolas