1

我在尝试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);
}

令人费解?

4

1 回答 1

2

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

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

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

当您为移动构造函数提供定义时,由您来处理Base. 如果你只是写

Test(Test&&) { }

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


Base不是可移动构造的,因为它有一个用户声明的复制构造函数,它阻止了移动构造函数的隐式声明——它根本没有移动构造函数——并且它的复制构造函数被删除(它无论如何都无法处理右值,因为它需要非常量引用)。

如果您使Base移动可构造,例如通过添加,

Base(Base&&) = default;

然后Test也变得可移动构造,并且您的示例将编译。


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

有关std::vector在重新分配期间选择使用哪个构造函数来复制/移动元素的逻辑的解释,请参阅此答案。查看std::move_if_noexcept用于选择返回哪种引用的逻辑,在我们的例子中它将是一个右值引用(当T不可复制时,条件始终为假)。所以,我们仍然希望编译器尝试调用移动构造函数。

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

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

于 2017-02-05T00:38:58.183 回答