使用unique_ptr实现移动语义

13

我正在使用Visual Studio 2012 Update 2,但是我无法理解为什么std::vector尝试使用unique_ptr的拷贝构造函数。我查看了类似的问题,发现大多数与没有显式移动构造函数和/或运算符有关。

如果我将成员变量更改为字符串,我可以验证移动构造函数被调用;然而,尝试使用unique_ptr会导致编译错误:

error C2248: 'std::unique_ptr<_Ty>::unique_ptr' : cannot access private member declared in class 'std::unique_ptr<_Ty>'

我希望有人能指出我错过了什么,谢谢!

#include <vector>
#include <string>
#include <memory>

class MyObject
{
public:
    MyObject() : ptr(std::unique_ptr<int>(new int))
    {
    }

    MyObject(MyObject&& other) : ptr(std::move(other.ptr))
    {
    }

    MyObject& operator=(MyObject&& other)
    {
        ptr = std::move(other.ptr);
        return *this;
    }

private:
    std::unique_ptr<int> ptr;
};

int main(int argc, char* argv[])
{
    std::vector<MyObject> s;
    for (int i = 0; i < 5; ++i)
    {
        MyObject o;
        s.push_back(o);
    }

    return 0;
}

1
如果你想直接在向量中构建对象,你也可以使用 emplacefor (int i = 0; i < 5; ++i) s.emplace_back(); 这也适用于 VC11。 - user2218982
2个回答

15

push_back()函数以值作为参数传递。因此,如果您传递了一个左值参数,则尝试复制构造push_back()的参数;如果您传递的是右值参数,则尝试移动构造它。

在这种情况下,o是一个左值——因为命名对象是左值——而右值引用无法绑定到左值。因此,编译器无法调用移动构造函数。

为了让您的对象被移动,您需要写成:

s.push_back(std::move(o));
//          ^^^^^^^^^
这个案例令我感到惊讶的是,从你发布的错误信息来看,似乎VC11隐式地为MyObject生成了一个复制构造函数,而没有将其定义为删除。根据C++11标准的第12.8/7段,这不应该是这种情况,因为您的类声明了一个移动构造函数。事实上:

  

如果类定义没有明确声明复制构造函数,则会隐式声明一个。如果类定义声明了移动构造函数或移动分配操作符,那么隐式声明的复制构造函数将被定义为已删除;否则,它将被定义为默认值(8.4)

我必须得出结论,尽管您收到的错误信息是正确的——因为您没有将rvalue传递给push_back() ——但VC11在这里并不完全符合规范。


太好了!我之前不知道这个。但我还是有些困惑,为什么只是将 unique_ptr 更改为 string 就会调用移动构造函数呢?如果 VC11 生成隐式复制构造函数,我认为应该使用它,因为我没有使用 std::move。 - zYzil
1
@zYzil:正确的行为是不调用std::string的复制构造函数或移动构造函数(请参见此实时示例),因为MyObject是不可复制的,而且您没有从中移动。如果您的代码与我的示例完全相同(包括缺少的std::move()),那么VC11存在一个错误。 - Andy Prowl
我肯定认为VC11存在一个错误。这个例子在GCC 4.8.0上无法编译;然而,在VC11上,它可以编译并输出“Move ctor”,因为移动构造函数被调用了。感谢您的所有帮助! - zYzil
@zYzil:听起来像是个bug。祝你的项目好运! - Andy Prowl
1
VC11目前还没有实现有关自动创建移动构造函数和禁止自动创建复制构造函数的规则。我认为STL将这些部分称为右值引用3.0,而VC11仅实现了2.1。 - Sebastian Redl
@SebastianRedl:好的,那就解释通了。谢谢您提供的信息。 - Andy Prowl

4

MyObject o; 定义了 o 为一个对象,这意味着它是一个左值。执行 s.push_back(o); 将调用 push_back() 的左值重载(因为没有其他选择),它尝试创建一个副本。

由于您的类是不可复制的,因此必须将对象移动到向量中:

for (int i = 0; i < 5; ++i)
{
    MyObject o;
    s.push_back(std::move(o));
}

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