Std :: make_shared makes two constructor calls in VS2012
I wrote a simple piece of code to try make_shared for C ++ 11. I didn't understand why when I call:
std::shared_ptr<MyClass> x = std::make_shared<MyClass>(MyClass());
The default constructor is called and the move constructor is called. This seems fine at first, since the move constructor does not create a copy. But if I comment out the implementation of the move constructor for MyClass, it calls the default constructor followed by the copy constructor, which seems to outperform the purpose of make_shared.
#include <iostream>
#include <memory>
//-----------------------------------------------------------
class MyClass {
public:
// default constructor
MyClass() :
_data(0.0)
{
_data = (float)3.14;
std::cout << "MyClass::default constructor - data=" << _data << " ; class=" << this << std::endl;
};
// copy constructor
MyClass(const MyClass& input)
{
_data = input._data;
std::cout << "MyClass::copy constructor - data=" << _data << " ; class=" << this << std::endl;
};
// move constructor
MyClass(MyClass&& other)
{
std::cout << "MyClass::move constructor(before) - data=" << _data << " ; class=" << this << std::endl;
_swap(*this, other);
std::cout << "MyClass::move constructor(after) - data=" << _data << " ; class=" << this << std::endl;
};
// destructor
~MyClass()
{
std::cout << "MyClass::destructor - data=" << _data << " ; class=" << this << std::endl;
};
private:
// swap
void MyClass::_swap(MyClass& X, MyClass& Y)
{
std::swap(X._data, Y._data);
}
// members
float _data;
};
//-----------------------------------------------------------
int main()
{
std::shared_ptr<MyClass> x = std::make_shared<MyClass>(MyClass());
std::cout << std::endl << "Address for x: " << x << std::endl;
std::cout << std::endl << "Press Enter to exit." << std::endl;
std::cin.ignore();
return 0;
}
Output for the above code:
MyClass::default constructor - data=3.14 ; class=000000000019F860
MyClass::move constructor(before) - data=0 ; class=00000000003C3440
MyClass::move constructor(after) - data=3.14 ; class=00000000003C3440
MyClass::destructor - data=0 ; class=000000000019F860
Address for x: 00000000003C3440
Press Enter to exit.
MyClass::destructor - data=3.14 ; class=00000000003C3440
If I comment out the move constructor, the output is:
MyClass::default constructor - data=3.14 ; class=000000000016FA00
MyClass::copy constructor - data=3.14 ; class=00000000001B3440
MyClass::destructor - data=3.14 ; class=000000000016FA00
Address for x: 00000000001B3440
Press Enter to exit.
MyClass::destructor - data=3.14 ; class=00000000001B3440
Maybe there is a flaw in my understanding of what make_shared does. Can anyone explain to me why this is happening?
Thank.
source to share
When you call:
std::shared_ptr<MyClass> x = std::make_shared<MyClass>(MyClass());
here's what's going on:
- An internal instance is created
MyClass()
(the first call to the constructor is the default ). - Internal
MyClass()
is temporary. - Now
make_shared
perfect - translates your tempMyClass
into a move constructorMyClass
because itMyClass
declares one, and temp can be linked by rvalue referencesMyClass&&
. (call the second constructor - move the constructor ).
Now when you remove move-constructor this happens:
- An internal instance is created
MyClass()
(the first call to the constructor is the default ). - Internal
MyClass()
is temporary. - Now
make_shared
perfect - translates your temporaryMyClass
, but since itMyClass
doesn't have a move constructor, instead the temporary is referenced toconst MyClass&
the copy constructor, and therefore the copy constructor (calling the second constructor is the copy constructor ).
That is, the arguments you pass std::make_shared
to are actually constructor argumentsrather than the instance itself. Hence, you should write:
std::shared_ptr<MyClass> x = std::make_shared<MyClass>();
If, for example, you had a constructor for the MyClass
following signature:
MyClass(int x, float f, std::unique_ptr<int> p);
then you would say:
std::shared_ptr<MyClass> x
= std::make_shared<MyClass>(123, 3.14f, std::make_unique<int>(5));
And it make_shared
ensures that those arguments are routed to the constructor perfectly MyClass
.
You can think of a helper function std::make_shared
as something like this:
template <typename T, typename... Args>
auto make_shared(Args&&... args) -> std::shared_ptr<T>
{
return std::shared_ptr<T>(new T(std::forward<Args>(args)...));
}
NOTE. It actually make_shared
also allocates space for the reference count in a contiguous block of memory, binds the destructor, and does other magic. The above snippet is just an example to illustrate what happens with the arguments themselves.
source to share
Just use this:
std::shared_ptr<MyClass> x = std::make_shared<MyClass>();
Thus, no temporary creation is created.
std::make_shared<X>(args...)
passes args ... to the constructor X
for the object it creates. In your case, you don't want to pass arguments to the constructor.
If you pass MyClass()
then you create a default built object and then pass it as an argument to the created object. It's as if you did this:
std::shared_ptr<MyClass> x(new MyClass(MyClass()));
which is optional.
source to share