为什么使用std::vector::push_back的移动变量会调用移动项的复制构造函数?

3

这里有一段代码,因为push_back尝试调用已删除的MoveOnlyClass中的复制构造函数而无法编译:

class MoveOnlyClass
{
public:
    MoveOnlyClass() {};
    MoveOnlyClass& operator=(const MoveOnlyClass& other) = delete;
    MoveOnlyClass(const MoveOnlyClass& other) = delete;
};

int main()
{
    std::vector<MoveOnlyClass> vec;
    vec.push_back(std::move(MoveOnlyClass()));
}

为什么会出现这种情况?向量应该只调用移动构造函数。正确的方式是将对象移动到向量中。


7
该类没有移动构造函数。 - tkausl
@user463035818 是的。写例子时我没有考虑到这一点。 - Tharwen
@Tharwen SE存在毒性问题。你的问题涉及相对琐碎(但可以理解)的错误,因此更加精英主义的用户有时会对这类问题做出有趣的反应。不幸的是,你无法做太多事情来改变这种情况。 - Xirema
2
我的投票反对是因为没有进行研究。你可以通过=default添加移动构造函数,而不是假设它已经存在,或者你可以花2分钟查看编译器在什么情况下生成移动构造函数。没有个人恩怨,一个反对票不会伤害任何人,我相信其他人会觉得这篇文章值得点赞,我坚持我的决定 ;) - 463035818_is_not_a_number
1
值得注意的是:如果一个类既有拷贝构造函数又有移动构造函数,但移动构造函数没有声明为 noexcept,那么 std::vector 将使用拷贝构造函数。(但默认的移动构造函数在适当的情况下会自动成为 noexcept。) - Daniel Schepler
显示剩余4条评论
2个回答

6

删除复制构造函数/复制赋值函数也会隐式删除移动构造函数/移动赋值函数。 如果您打算使对象可移动但不可复制,则还需要default移动构造函数。

class MoveOnlyClass
{
public:
    MoveOnlyClass() {};
    MoveOnlyClass& operator=(const MoveOnlyClass& other) = delete;
    MoveOnlyClass(const MoveOnlyClass& other) = delete;
    MoveOnlyClass& operator=(MoveOnlyClass&& other) = default;
    MoveOnlyClass(MoveOnlyClass&& other) = default;
};

//Will now compile as you expect
int main()
{
    std::vector<MoveOnlyClass> vec;
    vec.push_back(std::move(MoveOnlyClass()));
}

此外,std::move(T())是多余的;像那样就地构造对象已经使它成为R值,当你不需要使用std::move时,使用它可能会阻止某些种类的编译器优化(如拷贝省略)。

@JeJo 如果 std::vector 的对象是不可复制的,它仍然可以调整大小。只有非可移动对象才会出现问题。 - Xirema
4
建议使用emplace_back而不是push_back - tkausl
2
<type_traits> 中的 std::is_move_assignablestd::is_move_constructible 可能会引起兴趣,以编程方式验证目标类对象是否具有所需的特性。 - Eljay
如果移动构造函数和移动赋值运算符是noexcept, 将它们标记为noexcept是一个好习惯。 - Justin

2
@Xirema的答案解决了代码中的问题,并解释了为什么会出现这种情况。我只想引用语言规范中的适当摘录,以正式确认这一点。因此,根据[class.copy.ctor¶8]

(8)如果类X的定义没有显式声明移动构造函数,则只有在以下条件下才会隐式声明非显式移动构造函数:

  • (8.1)X没有用户声明的复制构造函数
因此,我们应该补充说明声明为删除也是声明。因此,根据这个规定,在您的情况下,我们不会得到隐式声明的移动构造函数,因为我们已经有了用户声明的复制构造函数

此外,在[dcl.fct.def.delete¶3]中:
可以通过删除复制构造函数和复制赋值运算符的定义,然后提供移动构造函数和移动赋值运算符的默认定义,使一个类成为不可复制的(即只能移动)。

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