我试图实现一个容器,它为insert方法提供复制和移动语义。
实现是这样的:
template <typename T> class Buffer {
// lots of stuff ommitted here
...
// copy semantics
Buffer<T>::Iterator push(const T& item) {
Buffer::Iterator head = push();
*head = item;
return head;
}
// move semantics
Buffer<T>::Iterator push(T&& item) {
Buffer::Iterator head = push();
*head = std::move(item);
return head;
}
}
如果类型T(要推入缓冲区的类型)实现移动赋值运算符,则此方法正常。但是,如果我尝试推送类似这样的实例,我会收到编译器错误:
struct Foo {
Foo(int a) : m_bar(a) {}
int m_bar;
Foo& operator=(Foo& other) {
this.m_bar = other.m_bar;
}
}
如果我尝试编译buffer.push(Foo(42));
,我会在push(T&& item)
的行上看到*head = std::move(item);
- 方法的编译器错误。错误是接受rvalue的operator=
没有可行的重载 - 这是正确的,没有一个。只有一个赋值操作符接受左值。
但是,由于我无法确保我的容器中存储的每个对象都有一个正确实现的移动赋值运算符,我需要确保正确处理这种情况。更重要的是,std :: vector可以毫无问题地处理这个问题。当一个对象实现移动赋值时,push_back
将移动它,如果没有,它将复制它。无论它是否是左值。事实上,如果我将我之前导致错误的有问题的Foo
rvalue放入std :: vector中,它的工作方式应该如此。
那我错过了什么?我的容器如何实现移动语义,并且仍然支持对不执行移动赋值的对象进行右值引用?
答案 0 :(得分:5)
您正在做/假设错误的是复制赋值运算符的不正确的签名:
Foo& operator=(Foo& other);
对<{1}}实例采用非const左值引用。如果没有用户提供的赋值运算符采用右值引用(也就是说,rvalue可以通过 const 左值引用绑定),这可以防止移动回退到常规副本中,因此它应该是:
other
那么为什么它适用于
Foo& operator=(const Foo& other); // ~~~~^
呢?
std::vector<Foo>
语句使用复制构造函数,而不是赋值运算符。这是有效的,因为buffer.push_back(Foo(42));
具有以下签名的隐式生成的副本构造函数:
Foo
适用于左值和右值(DEMO)。
您要做的是:
Foo(const Foo&);
是使用赋值运算符。由于您已经自己声明了一个,因此编译器不能隐式生成采用const左值引用的编译器,并且它也不能使用用户声明的一个采用非const左值引用,这会导致在你看到的错误中。
考虑在*head = std::move(item);
操作中使用分配器或 placement-new 运算符,使用push
参数作为item
构造函数的参数,使用复制赋值和移动赋值运算符。