不使用基类成员变量继承移动操作

3

作为一名对rvalue引用和移动语义不太熟悉的人,我试图从std::vector中派生一个简单的模板类(我保证我会小心虚拟析构函数的缺失)。为了“继承”移动构造函数,我只需调用基类的构造函数和std::move即可。

template <typename T>
class Vec: public std::vector<T> {
  public:
    ...
    //move ctors
    Vec(Vec&& v): std::vector<T>(v) { //move extra members with std::move }
    explicit Vec(std::vector<T>&& v): std::vector<T>(v) { //same as above }
    ...
};

根据我对C ++的理解,这似乎是可靠的。但是,我对移动赋值运算符的方法信心要少得多。

    //assignment
    Vec& operator=(const Vec& v) {
        std::vector<T>::operator=(v);
        if (&v != this)
          //copy extra members
        return *this;
    }
    //move assignment
    Vec& operator=(Vec&& v) {
        std::vector<T>::operator=(v);
        //move extra members with std::move
        return *this;
    }

这是实现我想要的一种万无一失的方法吗?在良好实践方面,有更好的替代方案吗?


2
一个好的指导原则是零规则 - Jonathan Wakely
1
@JonathanWakely 尽管如此,我仍然更喜欢在任何地方编写=default=delete - Barry
@Barry,我也是这样认为的,但我认为这仍然遵循规则,只是更明确地表明您有意识地不定义成员。 - Jonathan Wakely
1个回答

4

它看起来很可靠......

看起来很可靠。但事实并非如此。你会看到,这个:

Vec(Vec&& v)

是移动构造函数。因此,它将被调用时使用rvalue。但是一旦我们进入这里,v就变成了lvalue!经验法则:如果它有一个名称,它就是lvalue。所以这部分内容:

: std::vector<T>(v)

不调用 std::vectormove 构造函数,而是调用 copy 构造函数。您需要显式地将 v 转换为右值以执行正确的操作:

Vec(Vec&& v) : std::vector<T>(std::move(v)) { }
explicit Vec(std::vector<T>&& v): std::vector<T>(std::move(v)) { }

或者,更好的方式是:
Vec(Vec&& ) = default;

同样地,编写赋值运算符的无误方式就是使用 default 语句:
Vec& operator=(Vec const& ) = default;
Vec& operator=(Vec&& ) = default;

但如果您确实有特殊逻辑,请确保在那里也将其转换为rvalue:

Vec& operator=(Vec&& v) {
    std::vector<T>::operator=(std::move(v));
    // stuff
    return *this;
}

甚至更好的是,将您的特殊逻辑移动到一个独立的单元中,这样默认值仍然是正确的。


1
@downhillFromHere default 明确地执行函数的默认版本。如果您有成员变量,则 default 赋值运算符将是成员逐个赋值。如果您需要进行资源管理,您应该封装它,以便您可以利用零规则。 - Barry

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接