I wrote a simple piece of code to try out 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 a move constructor is called. This seems fine at first, as the move constructor would not create a copy. But if I comment out the implementation of the move constructor for MyClass, it would call the default constructor, followed by the copy constructor, which seems to defeat 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;
}
The output for the above code is:
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 this:
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's a flaw to my understanding of what make_shared does. Can anyone explain to me why this is happening?
Thank you.
When you call:
std::shared_ptr<MyClass> x = std::make_shared<MyClass>(MyClass());
this is what happens:
MyClass() instance is created (the first constructor call - a default one).MyClass() is a temporary.make_shared perfect-forwards your temporary MyClass to the move constructor of MyClass, because MyClass declares one and temporaries can be bound by rvalue references MyClass&&. (the second constructor call - move constructor).Now, when you remove the move-constructor, this is what happens:
MyClass() instance is created (the first constructor call - a default one).MyClass() is a temporary.make_shared perfect-forwards your temporary MyClass, but because MyClass does not have a move constructor, instead the temporary is bound by the const MyClass& reference of a copy constructor, and so the copy constructor is invoked (the second constructor call - the copy constructor).That is, arguments you pass to std::make_shared are actually the constructor's arguments rather than the instance itself. Hence you should write:
std::shared_ptr<MyClass> x = std::make_shared<MyClass>();
If for instance you had a MyClass constructor of the 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 make_shared guarantees those arguments will be perfectly-forwarded to MyClass constructor.
You can think of the std::make_shared helper function as the one that looks as follows:
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: In reality make_shared also allocates space for a reference counter in a continuous memory block, binds a destructor, and does other magic. The above snippet is just an example to illustrate what happens with the arguments themselves.
Just use this:
std::shared_ptr<MyClass> x = std::make_shared<MyClass>();
That way, no temporary is created.
std::make_shared<X>(args...) passes args... to the X constructor for the object it is creating. In your case, you want no arguments passed to the constructor.
If you pass MyClass(), then you are creating a default-constructed object, and then passing that as an argument to the object that is being created. It is as if you did this:
std::shared_ptr<MyClass> x(new MyClass(MyClass()));
which is unnecessary.
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