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.

+3


source to share


2 answers


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 temp MyClass

    into a move constructor MyClass

    because it MyClass

    declares one, and temp can be linked by rvalue references MyClass&&

    . (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 temporary MyClass

    , but since it MyClass

    doesn't have a move constructor, instead the temporary is referenced to const 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.

+7


source


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.

+3


source







All Articles