在向量中放置派生的可移动但不可复制的内容会产生编译错误

时间:2017-02-05 00:05:17

标签: c++ vector emplace noncopyable

我在一个不可复制但可移动的对象向量中尝试emplace_back时遇到编译器错误,这些对象具有微妙的继承扭曲,据我所知,这不应该改变问题。

这是合法的C ++,这是一个Visual Studio 2015的错误,还是我犯了一个明显的错误?

#include <vector>

class Base
{
public:
    Base() {}

    Base(Base&) = delete;
    Base& operator= (Base&) = delete;
};

class Test : public Base
{
public:
    Test(int i) : m_i(i) {}

    Test(Test&&) = default;
    Test& operator= (Test&&) = default;

protected:
    int m_i;
};

int main(int argc, char *argv[])
{
    std::vector<Test> vec;
    vec.emplace_back(1);
}

输出:

error C2280: 'Test::Test(Test &)': attempting to reference a deleted function

没有继承,即在Test中删除了复制构造函数而没有基类,它就可以正确编译 不知何故,移除构造函数中的默认值也使它也能正确编译,但是我必须定义移动构造函数,我不想去那里。

这意味着编译好了:

#include <vector>

class Test
{
public:
    Test(int i) : m_i(i) {}

    Test(Test&) = delete;
    Test& operator= (Test&) = delete;

    Test(Test&&) = default;
    Test& operator= (Test&&) = default;

protected:
    int m_i;
};

int main(int argc, char *argv[])
{
    std::vector<Test> vec;
    vec.emplace_back(1);
}

令人费解?

1 个答案:

答案 0 :(得分:1)

在您描述的所有情况下,编译器都是正确的。

Test派生自Base时,其默认移动构造函数被定义为已删除,因为它会尝试移动Base,而Test无法移动构造。在你的第一个例子中,Test实际上不是可移动构造的。

在你的第二个例子中,没有基类,没有什么可以阻止定义默认的移动构造函数,所以Base变得可移动构造。

当您为移动构造函数提供定义时,由您来处理Test(Test&&) { } 。如果你只是写

Base

这只是默认构造一个Base对象,所以移动构造函数会编译,但它可能不会做你想要的。

Base不是可移动构造的,因为它有一个用户声明的复制构造函数,它可以防止移动构造函数的隐式声明 - 它根本没有移动构造函数 - 并且它的复制构造函数被删除(它不能& #39; t无论如何都要处理rvalues,因为它需要非const引用。)

如果你使Base(Base&&) = default; 可移动构造,可以添加,例如,

Test

然后Test也变得可移动构造,你的例子将被编译。

最后一个难题:因为我们已经为std::vector声明了一个移动构造函数,为什么错误消息会引用已删除的复制构造函数,即使是在第一种情况下呢?

有关T逻辑的解释,用于选择在重新分配期间用于复制/移动元素的构造函数,请参阅this answer。查看std::move_if_noexcept用于选择返回哪种引用的逻辑,在我们的情况下它将是一个右值引用(当const不是可复制构造时,条件总是为假)。因此,我们仍然希望编译器尝试调用移动构造函数。

但是,还有一条规则发挥作用:定义为已删除的默认移动构造函数不参与重载解析。

这样做是为了使rvalue的构造可以回退到带有filter左值引用的复制构造函数(如果可用)。请注意,当移动构造函数显式声明为已删除时,该规则不适用。