2

Herb Sutter 的第 4 周 Guru “Class Mechanics”教导说,重载运算符的“a op b”形式应该按照“a op= b”形式来实现(参见解决方案中的第 4 点) .

作为一个例子,他向操作员展示了如何做到这一点+

T& T::operator+=( const T& other ) {
    //...
    return *this;
}

T operator+( T a, const T& b ) {
    a += b;
    return a;
}

他指出,第一个参数 inoperator+是有意按值传递的,因此如果调用者传递一个临时参数,它可以被移动。

请注意,这要求operator+是非成员函数。

我的问题是,如何将此技术应用于CRTP基类中的重载运算符?

所以说这是我的 CRTP 基类,它有operator+=

template <typename Derived>
struct Base
{
    //...

    Derived operator+=(const Derived& other)
    {
        //...
        return static_cast<Derived&>(*this);
    }
};

如果我放弃“按值传递第一个参数”优化,我可以看到如何以成员函数的形式实现operator+operator+=

template <typename Derived>
struct Base
{
    //...

    Derived operator+(const Derived& other) const
    {
        Derived result(static_cast<const Derived&>(*this);
        result += other;
        return result;
    }
};

但是有没有办法在使用该优化时做到这一点(因此使operator+非成员)?

4

1 回答 1

4

执行 Herb 建议的正常方式如下:

struct A {
      A& operator+=(cosnt A& rhs)
      {
          ...
          return *this;
      }
      friend A operator+(A lhs, cosnt A& rhs)
      {
          return lhs += rhs;
      }
};

将此扩展到 CRTP:

template <typename Derived>
struct Base
{
    Derived& operator+=(const Derived& other)
    {
        //....
        return *self();
    }
    friend Derived operator+(Derived left, const Derived& other)
    {
        return left += other;
    }
private:
    Derived* self() {return static_cast<Derived*>(this);}
};

如果你试图避免使用friend这里,你会发现它几乎是这样的:

 template<class T>
 T operator+(T left, const T& right) 
 {return left += right;}

但仅对派生自 的事物有效Base<T>,这样做既棘手又丑陋。

template<class T, class valid=typename std::enable_if<std::is_base_of<Base<T>,T>::value,T>::type>
T operator+(T left, const T& right) 
{return left+=right;}

此外,如果它是friend类的内部,那么它在技术上不在全局命名空间中。a+b因此,如果有人在两者都不是 a 的地方写了一个 invalid Base,那么您的重载将不会导致 1000 行错误消息。免费的类型特征版本可以。


至于为什么要签名:可变的值,不可变的 const&。&& 仅适用于移动构造函数和其他一些特殊情况。

 T operator+(T&&, T) //left side can't bind to lvalues, unnecessary copy of right hand side ALWAYS
 T operator+(T&&, T&&) //neither left nor right can bind to lvalues
 T operator+(T&&, const T&) //left side can't bind to lvalues
 T operator+(const T&, T) //unnecessary copies of left sometimes and right ALWAYS
 T operator+(const T&, T&&) //unnecessary copy of left sometimes and right cant bind to rvalues
 T operator+(const T&, const T&) //unnecessary copy of left sometimes
 T operator+(T, T) //unnecessary copy of right hand side ALWAYS
 T operator+(T, T&&) //right side cant bind to lvalues
 T operator+(T, const T&) //good
 //when implemented as a member, it acts as if the lhs is of type `T`.

如果移动比副本快得多,并且您正在处理交换运算符,那么您可能有理由重载这四个。但是,它适用于交换运算符(其中 A?B==B?A,所以 + 和 *,但不适用 -、/ 或 %)。对于非交换运算符,没有理由不使用上面的单个重载。

T operator+(T&& lhs , const T& rhs) {return lhs+=rhs;}
T operator+(T&& lhs , T&& rhs) {return lhs+=rhs;} //no purpose except resolving ambiguity
T operator+(const T& lhs , const T& rhs) {return T(lhs)+=rhs;} //no purpose except resolving ambiguity
T operator+(const T& lhs, T&& rhs) {return rhs+=lhs;} //THIS ONE GIVES THE PERFORMANCE BOOST
于 2014-04-30T00:54:09.673 回答