我在一个不可复制但可移动的对象向量中尝试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);
}
令人费解?
答案 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
左值引用的复制构造函数(如果可用)。请注意,当移动构造函数显式声明为已删除时,该规则不适用。