数值向量运算符重载 + 右值引用参数

Numeric vector operator overload+ rvalue reference parameter

本文关键字:引用 参数 重载 向量 运算符      更新时间:2023-10-16

我在下面有数字向量模板类(用于数值计算的向量(。我正在尝试编写所有变量都Vector对象的D=A+B+CABC不应修改。我的想法是使用Vector operator+(Vector&& B),以便在(希望(从B+C返回 Rvalue Vector后,所有后续添加都存储在该对象中,即窃取 Rvalue 的存储以进行所有后续添加。这是为了消除创建新对象和所需存储。

我的问题是,我可以从每个调用函数的输出语句中看到Vector operator+(Vector&& B)从未被调用过。我不明白为什么,因为如果我有一个重载的虚拟函数foo(Vector& B)foo(Vector&& B)并尝试foo(A+B+C),那么第二个函数的调用完全符合我的希望。

很抱歉这个问题冗长,但这是我在这里的第一个问题,我想尽量说清楚。

任何关于我明显做错了什么或为什么我不应该尝试这个的建议,将不胜感激。

template <typename T>
class Vector
{
        int n;
        T* v;
        Vector();
        ~Vector();
        Vector(const Vector& B);
        Vector(Vector&& B);
        inline Vector operator+(const Vector& B) const;
        inline Vector operator+(Vector&& B) const;
};
template <typename T>
Vector<T>::Vector(const Vector<T>& B)
{
        ...
}
template <typename T>
Vector<T>::Vector(Vector<T>&& B)
{
        ...
}
template <typename T>
Vector<T> Vector<T>::operator+(const Vector<T>& B) const
{
        Vector<T> C;
        ...
        return C;
}
template <typename T>
Vector<T> Vector<T>::operator+(Vector<T>&& B) const
{
        ...do stuff to B
        return B;
}

在表达式中:

D=A+B+C

AB 是左值,因此调用A+B调用Vector::operator(const Vector&)

这返回一个右值,我们称之为 tmp ,所以下一个子表达式是 tmp+C

C也是一个左值,所以它再次调用Vector::operator(const Vector&)。这返回另一个 rvalue,我们称之为 tmp2

最后一个子表达式是 D=tmp2 ,但您的类型没有移动赋值运算符,因此使用隐式定义的复制赋值运算符。

也就是说,您永远不会在右侧使用右值调用operator+,并且唯一具有右值参数的表达式是您尚未为右值定义的赋值。

最好定义重载的非成员运算符:

Vector operator+(const Vector&, const Vector&);
Vector operator+(Vector&&, const Vector&);
Vector operator+(const Vector&, Vector&&);
Vector operator+(Vector&&, Vector&&);

这将适用于右值和左值的任意组合。(一般来说,operator+通常应该是非会员。

编辑:下面的替代建议不起作用,在某些情况下会导致歧义。

另一种选择,如果你的编译器支持它(我认为只有 clang 支持(,是保留你现有的Vector::operator+(Vector&&),但用两个由 ref-qualifier 区分的重载替换你的Vector::operator+(const Vector&)

Vector Vector::operator+(const Vector& v) const&
{
  Vector tmp(*this);
  tmp += v;
  return tmp;
}
Vector Vector::operator+(const Vector& v)&&
{
  *this += v;
  return std::move(*this);
}

当已知它是右值时,这会重用*this,即当添加的左侧是右值时,它使用移动语义,而原始代码只能在右侧是右值时使用移动语义。 (注意:上面的代码假设你已经按照David Rodriguez的回答中的建议定义了成员operator+=(

我建议您提供operator+=作为成员方法,然后从定义为operator+的自由函数中重用它:

template <typename T>
Vector<T> operator+( Vector<T> lhs,              // by value
                     Vector<T> const & rhs ) {
    lhs += rhs;
    return lhs;
}

给定一个a + b + c,它分组为(a+b) + c,编译器将为第一次调用operator+创建一个a的副本,就地修改它(lhs += rhs(,然后将其移动到返回值中。然后它会将该返回值(除非它省略移动(移动到第二个operator+的参数中,在那里它将再次就地修改,然后移动到返回值。

总的来说,将创建一个新对象,保存a+b+c的结果,提供等效的语义:

Vector<T> tmp = a;
tmp += b;
tmp += c;

但是有了更好、更紧凑的语法a + b + c.


请注意,这不会优雅地处理a + (b+c),并且会创建两个对象,如果要支持它,则需要生成多个重载。