当右值不实现move时,在具有move语义的容器上实现insert()

Implementing insert() on a container with move semantics when rvalue does not implement move

本文关键字:move 实现 insert 语义      更新时间:2023-10-16

我正在尝试实现一个容器,该容器提供具有复制和移动语义的插入方法。

实现是这样的:

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));,我会在读取*head = std::move(item);的行上得到push(T&& item)-方法的编译器错误。错误是operator=没有接受右值的可行重载——这是正确的,没有。只有一个赋值运算符接受lvalues。

但是,由于我不能确保存储在容器中的每个对象都有一个正确实现的移动分配运算符,我需要确保正确处理这种情况。更重要的是,std::vector可以毫无问题地处理这个问题。当一个对象实现移动赋值时,push_back会移动它,如果不是,它会复制它。不管它是否是右值。事实上,如果我把之前导致错误的有问题的Foo右值放入std::向量中,它的工作原理应该是这样的。

那么我错过了什么?我的容器如何实现移动语义,并且仍然支持未实现移动赋值的对象的右值引用?

您正在做/假设错误的是复制分配运算符的错误签名:

Foo& operator=(Foo& other);

其采用对CCD_ 7实例的非常量左值引用。如果没有用户提供的赋值运算符获取右值引用(也就是说,右值可以由常量左值引用绑定),这可以防止移动返回到常规副本,因此它应该是:

Foo& operator=(const Foo& other);
//             ~~~~^

那么为什么它能与std::vector<Foo>一起工作呢

buffer.push_back(Foo(42));语句使用复制构造函数,而不是赋值运算符。这是有效的,因为Foo有一个隐式生成的复制构造函数,用于以下签名:

Foo(const Foo&);

它同时适用于左值和右值(DEMO)。

你想做的事:

*head = std::move(item);

是使用赋值运算符。由于您已经自己声明了一个,编译器不能隐式生成采用常量左值引用的,也不能使用用户声明的采用非常量左值参考的,这会导致您看到的错误。

考虑在push操作中使用分配器或placement-new运算符,使用item参数作为Foo构造函数的参数,而不是使用复制赋值和移动赋值运算符。