std :: make_shared在VS2012中进行两次构造函数调用

Mik*_*ike 3 c++ constructor c++11 visual-studio-2012

我写了一段简单的代码来试用C++ 11的make_shared.当我打电话时,我不明白为什么

std::shared_ptr<MyClass> x = std::make_shared<MyClass>(MyClass());
Run Code Online (Sandbox Code Playgroud)

调用默认构造函数并调用移动构造函数.这看起来很好,因为移动构造函数不会创建副本.但是如果我注释掉MyClass的move构造函数的实现,它会调用默认的构造函数,然后是复制构造函数,这似乎打败了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;
}
Run Code Online (Sandbox Code Playgroud)

上面代码的输出是:

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
Run Code Online (Sandbox Code Playgroud)

如果我注释掉移动构造函数,则输出为:

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
Run Code Online (Sandbox Code Playgroud)

也许我对make_shared的作用有一个缺陷.任何人都可以向我解释为什么会这样吗?

谢谢.

Pio*_*cki 7

你打电话的时候:

std::shared_ptr<MyClass> x = std::make_shared<MyClass>(MyClass());
Run Code Online (Sandbox Code Playgroud)

这是发生的事情:

  1. MyClass()创建内部实例(第一个构造函数调用 - 默认调用).
  2. 内心MyClass()是暂时的.
  3. 现在,make_shared完美,转发你的临时MyClass移动构造函数MyClass,因为MyClass声明一个和临时可以通过右值引用的约束MyClass&&.(第二个构造函数调用 - 移动构造函数).

现在,当您删除move-constructor时,会发生以下情况:

  1. MyClass()创建内部实例(第一个构造函数调用 - 默认调用).
  2. 内心MyClass()是暂时的.
  3. 现在,make_shared完美转发你的临时MyClass,但因为MyClass没有移动构造函数,而是临时由const MyClass&复制构造函数的引用绑定,因此复制构造函数被调用(第二个构造函数调用 - 复制构造函数).

也就是说,传递给的参数std::make_shared实际上是构造函数的参数而不是实例本身.因此你应该写:

std::shared_ptr<MyClass> x = std::make_shared<MyClass>();
Run Code Online (Sandbox Code Playgroud)

例如,如果您有一个MyClass以下签名的构造函数:

MyClass(int x, float f, std::unique_ptr<int> p);
Run Code Online (Sandbox Code Playgroud)

然后你会说:

std::shared_ptr<MyClass> x
        = std::make_shared<MyClass>(123, 3.14f, std::make_unique<int>(5));
Run Code Online (Sandbox Code Playgroud)

make_shared保证这些参数将完美地转发MyClass构造函数.

您可以将std::make_shared辅助函数视为如下所示:

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)...));
}
Run Code Online (Sandbox Code Playgroud)

注意:实际上make_shared还为连续内存块中的引用计数器分配空间,绑定析构函数,并执行其他魔术.上面的代码片段只是一个例子来说明参数本身会发生什么.