Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

std::make_shared makes two constructor calls in VS2012

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.

like image 771
Mike Avatar asked Oct 22 '25 18:10

Mike


2 Answers

When you call:

std::shared_ptr<MyClass> x = std::make_shared<MyClass>(MyClass());

this is what happens:

  1. The inner MyClass() instance is created (the first constructor call - a default one).
  2. The inner MyClass() is a temporary.
  3. Now, 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:

  1. The inner MyClass() instance is created (the first constructor call - a default one).
  2. The inner MyClass() is a temporary.
  3. Now, 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.

like image 92
Piotr Skotnicki Avatar answered Oct 24 '25 09:10

Piotr Skotnicki


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.

like image 42
Vaughn Cato Avatar answered Oct 24 '25 08:10

Vaughn Cato



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!