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

4

我写了一段简单的代码来尝试使用C++11中的make_shared。当我调用以下代码时,我不明白为什么:

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

默认构造函数被调用并调用了移动构造函数。这一开始看起来很好,因为移动构造函数不会创建副本。但是,如果我注释掉MyClass的移动构造函数实现,它将调用默认构造函数,然后调用复制构造函数,这似乎违反了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;
}

上述代码的输出结果为:
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

如果我注释掉移动构造函数,输出结果将是这样的:
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

也许我对make_shared的理解有缺陷。有没有人能够解释一下为什么会发生这种情况?谢谢。

看起来很简单——make_shared需要将您明确创建的未命名MyClass()临时对象复制/移动到它分配的共享对象空间中。如果可以使用移动构造函数,则使用移动构造函数,否则使用复制构造函数。您期望会发生什么? - Chris Dodd
我实际上想要做(在我的脑海中)Piotr S.建议的事情,这样它只会进行一次构造函数调用。我没有完全理解make_shared的语法。谢谢你的评论。 - Mike
2个回答

10

当您进行调用时:

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

这里是发生的情况:
  1. 创建内部的 MyClass() 实例(第一个构造函数调用 - 默认构造函数)。
  2. 内部的 MyClass() 是一个临时对象。
  3. 现在,make_shared 对你的临时 MyClass 进行了完美转发到 MyClass 的移动构造函数中, 因为 MyClass 声明了一个移动构造函数,而且临时对象可以通过rvalue引用 MyClass&& 绑定。(第二个构造函数调用 - 移动构造函数)。

当你删除移动构造函数时,这里是发生的情况:

  1. 创建内部的 MyClass() 实例(第一个构造函数���用 - 默认构造函数)。
  2. 内部的 MyClass() 是一个临时对象。
  3. 现在,make_shared 对你的临时 MyClass 进行了完美转发,但因为 MyClass 没有移动构造函数,所以临时对象被绑定到一个拷贝构造函数的 const MyClass& 引用上,因此拷贝构造函数被调用(第二个构造函数调用 - 拷贝构造函数)。
这意味着你传递给 std::make_shared 的参数实际上是构造函数的参数,而不是实例本身。因此你应该写成:
std::shared_ptr<MyClass> x = std::make_shared<MyClass>();

如果您有以下签名的MyClass构造函数:

例如:

MyClass(int x, float f, std::unique_ptr<int> p);

那么你会说:
std::shared_ptr<MyClass> x
        = std::make_shared<MyClass>(123, 3.14f, std::make_unique<int>(5));

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)...));
}

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


您的建议正是我需要做的。非常感谢您详细的回复! - Mike

3

只需使用以下代码:

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

那么就不会创建临时对象。 std::make_shared<X>(args...)将args...传递给要创建的对象的X构造函数。在您的情况下,您不希望将任何参数传递给构造函数。 如果您传递MyClass(),那么您正在创建一个默认构造的对象,然后将其作为参数传递给正在创建的对象。这就好像您这样做:
std::shared_ptr<MyClass> x(new MyClass(MyClass()));

这是多余的。


谢谢您的建议。我现在明白了,这就是我需要做的。 - Mike

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接