R值参考上的重载和代码重复

| 考虑以下:
struct vec
{
    int v[3];

    vec() : v() {};
    vec(int x, int y, int z) : v{x,y,z} {};
    vec(const vec& that) = default;
    vec& operator=(const vec& that) = default;
    ~vec() = default;

    vec& operator+=(const vec& that)
    {
        v[0] += that.v[0];
        v[1] += that.v[1];
        v[2] += that.v[2];
        return *this;
    }
};

vec operator+(const vec& lhs, const vec& rhs)
{
    return vec(lhs.v[0] + rhs.v[0], lhs.v[1] + rhs.v[1], lhs.v[2] + rhs.v[2]);
}
vec&& operator+(vec&& lhs, const vec& rhs)
{
    return move(lhs += rhs);
}
vec&& operator+(const vec& lhs, vec&& rhs)
{
    return move(rhs += lhs);
}
vec&& operator+(vec&& lhs, vec&& rhs)
{
    return move(lhs += rhs);
}
多亏了r值引用,借助operator +的这四个重载,我可以通过重用临时对象来最大程度地减少创建的对象数量。但是我不喜欢本文介绍的重复代码。我可以减少重复次数来达到相同目的吗?     
已邀请:
回收临时对象是一个有趣的想法,因此您不是唯一编写返回右值引用的函数的人。在较旧的C ++ 0x中,还声明了运算符+(string &&,string const&)返回右值引用。但这是有充分理由的。我看到这种重载和返回类型选择存在三个问题。其中两个与实际类型无关,第三个参数指的是
vec
是哪种类型。 安全问题。考虑如下代码:
vec a = ....;
vec b = ....;
vec c = ....;
auto&& x = a+b+c;
如果最后一个运算符返回右值引用,则
x
将是悬空引用。否则,不会。这不是人为的例子。例如,在内部for-range循环中使用
auto&&
技巧以避免不必要的复制。但是由于引用绑定期间临时对象的生命周期扩展规则不适用于仅返回引用的函数调用,因此您将获得悬挂的引用。
string source1();
string source2();
string source3();

....

int main() {
  for ( char x : source1()+source2()+source3() ) {}
}
如果最后一个运算符+返回对在第一次串联期间创建的临时目录的右值引用,则此代码将调用未定义的行为,因为字符串临时目录将不会存在足够长的时间。 在通用代码中,返回右值引用的函数迫使您编写
typename std::decay<decltype(a+b+c)>::type
代替
decltype(a+b+c)
仅仅因为最后一个op +可能返回右值引用。以我的拙见,这越来越难看。 由于类型
vec
既“扁平”又小,因此这些op +重载几乎没有用。请参阅FredOverflow的答案。 结论:应该避免使用带有右值引用返回类型的函数,特别是如果这些引用可能引用短暂的临时对象。
std::move
std::forward
是此经验法则的特殊用途例外。     
由于您的
vec
类型是\“ flat \”(没有外部数据),因此移动和复制的作用完全相同。因此,所有右值引用和
std::move
绝对不会给您任何性能上的好处。 我将摆脱所有其他重载,而只需编写经典的引用到const版本:
vec operator+(const vec& lhs, const vec& rhs)
{
    return vec(lhs.v[0] + rhs.v[0], lhs.v[1] + rhs.v[1], lhs.v[2] + rhs.v[2]);
}
如果您对移动语义还不太了解,我建议您研究此问题。   多亏了r值引用,借助operator +的这四个重载,我可以通过重用临时对象来最大程度地减少创建的对象数量。 除少数例外,返回右值引用是一个非常糟糕的主意,因为此类函数的调用是xvalues而不是prvalues,并且您可能会遇到讨厌的临时对象生存期问题。不要做。     
这在当前的C ++中已经很好地发挥了作用,它将在C ++ 0x中使用移动语义(如果可用)。它已经可以处理所有情况,但是依靠复制省略和内联来避免复制-因此它可能制作比期望更多的副本,尤其是第二个参数。这样做的好处是,它可以在没有任何其他重载的情况下工作,并且可以(正确地)做正确的事情:
vec operator+(vec a, vec const &b) {
  a += b;
  return a;  // \"a\" is local, so this is implicitly \"return std::move(a)\",
             // if move semantics are available for the type.
}
这就是您将在99%的时间停止的地方。 (我可能会低估这个数字。)该答案的其余部分仅在您知道(例如通过使用探查器)值得进一步优化op +的额外副本后才适用。 为了完全避免所有可能的复制/移动,您确实需要这些重载:
// lvalue + lvalue
vec operator+(vec const &a, vec const &b) {
  vec x (a);
  x += b;
  return x;
}

// rvalue + lvalue
vec&& operator+(vec &&a, vec const &b) {
  a += b;
  return std::move(a);
}

// lvalue + rvalue
vec&& operator+(vec const &a, vec &&b) {
  b += a;
  return std::move(b);
}

// rvalue + rvalue, needed to disambiguate above two
vec&& operator+(vec &&a, vec &&b) {
  a += b;
  return std::move(a);
}
尽管您经常需要使用多种类型的op +,但是宏或CRTP可能会为您生成它,因此您处于正确的轨道,不可能真正减少(AFAICT)。唯一的实际差异(我在上面对单独语句的偏爱是次要的)是您在operator +(const vec&lhs,vec && rhs)中添加两个左值时的副本:
return std::move(rhs + lhs);
通过CRTP减少重复
template<class T>
struct Addable {
  friend T operator+(T const &a, T const &b) {
    T x (a);
    x += b;
    return x;
  }

  friend T&& operator+(T &&a, T const &b) {
    a += b;
    return std::move(a);
  }

  friend T&& operator+(T const &a, T &&b) {
    b += a;
    return std::move(b);
  }

  friend T&& operator+(T &&a, T &&b) {
    a += b;
    return std::move(a);
  }
};

struct vec : Addable<vec> {
  //...
  vec& operator+=(vec const &x);
};
现在,不再需要为vec专门定义任何op +。 Addable可用于任何带有op + =的类型。     
我使用clang + libc ++编写了Fred Fred的答案。我不得不删除初始化器语法,因为clang尚未实现。我还在副本构造函数中添加了一条print语句,以便我们可以计算副本数。
#include <iostream>

template<class T>
struct AddPlus {
  friend T operator+(T a, T const &b) {
    a += b;
    return a;
  }

  friend T&& operator+(T &&a, T const &b) {
    a += b;
    return std::move(a);
  }

  friend T&& operator+(T const &a, T &&b) {
    b += a;
    return std::move(b);
  }

  friend T&& operator+(T &&a, T &&b) {
    a += b;
    return std::move(a);
  }

};

struct vec
    : public AddPlus<vec>
{
    int v[3];

    vec() : v() {};
    vec(int x, int y, int z)
    {
        v[0] = x;
        v[1] = y;
        v[2] = z;
    };
    vec(const vec& that)
    {
        std::cout << \"Copying\\n\";
        v[0] = that.v[0];
        v[1] = that.v[1];
        v[2] = that.v[2];
    }
    vec& operator=(const vec& that) = default;
    ~vec() = default;

    vec& operator+=(const vec& that)
    {
        v[0] += that.v[0];
        v[1] += that.v[1];
        v[2] += that.v[2];
        return *this;
    }
};

int main()
{
    vec v1(1, 2, 3), v2(1, 2, 3), v3(1, 2, 3), v4(1, 2, 3);
    vec v5 = v1 + v2 + v3 + v4;
}

test.cpp:66:22: error: use of overloaded operator \'+\' is ambiguous (with operand types \'vec\' and \'vec\')
    vec v5 = v1 + v2 + v3 + v4;
             ~~~~~~~ ^ ~~
test.cpp:5:12: note: candidate function
  friend T operator+(T a, T const &b) {
           ^
test.cpp:10:14: note: candidate function
  friend T&& operator+(T &&a, T const &b) {
             ^
1 error generated.
我像这样修复此错误:
template<class T>
struct AddPlus {
  friend T operator+(const T& a, T const &b) {
    T x(a);
    x += b;
    return x;
  }

  friend T&& operator+(T &&a, T const &b) {
    a += b;
    return std::move(a);
  }

  friend T&& operator+(T const &a, T &&b) {
    b += a;
    return std::move(b);
  }

  friend T&& operator+(T &&a, T &&b) {
    a += b;
    return std::move(a);
  }

};
运行示例输出:
Copying
Copying
接下来,我尝试了一种C ++ 03方法:
#include <iostream>

struct vec
{
    int v[3];

    vec() : v() {};
    vec(int x, int y, int z)
    {
        v[0] = x;
        v[1] = y;
        v[2] = z;
    };
    vec(const vec& that)
    {
        std::cout << \"Copying\\n\";
        v[0] = that.v[0];
        v[1] = that.v[1];
        v[2] = that.v[2];
    }
    vec& operator=(const vec& that) = default;
    ~vec() = default;

    vec& operator+=(const vec& that)
    {
        v[0] += that.v[0];
        v[1] += that.v[1];
        v[2] += that.v[2];
        return *this;
    }
};

vec operator+(const vec& lhs, const vec& rhs)
{
    return vec(lhs.v[0] + rhs.v[0], lhs.v[1] + rhs.v[1], lhs.v[2] + rhs.v[2]);
}

int main()
{
    vec v1(1, 2, 3), v2(1, 2, 3), v3(1, 2, 3), v4(1, 2, 3);
    vec v5 = v1 + v2 + v3 + v4;
}
运行该程序完全没有输出。 这些是我使用clang ++获得的结果。向他们解释您的情况。您的里程可能会有所不同。     

要回复问题请先登录注册