使用 C++ 复制省略和运算符重载

Copy elision and operator overloading with C++

我有一个结构,例如:

struct A { 
    double x,y; 
    vector<double> vec; 
};

我想重载运算符,例如加号运算符,以便我可以执行如下操作:

A a,b,c,d;
//do work to set up the four structs. Then:
d = a + b + c;

性能很重要,因为这些操作将在 'inner loops' 中执行 运行 很多次。我担心 (a + b + c) 行会创建临时对象,因此 运行 宁构造函数不必要地 'A'。 运行 C++17,编译器肯定会使用复制省略来避免创建临时对象吗? 我需要 运行 行“d=a+b+c " 从未 运行 将构造函数连接到 A.

我知道如果我可以通过写来绝对避免临时变量:

d = a;
d += b;
d += c;

然而,实际上,我将要写很多数学行的长代码,如果能够将所有内容写在一行 (a + b + c) 中会方便得多,而不是而不必将其分解成大量的“+=”行。

正如一位评论者所建议的那样,如果您的 operator+ 需要一个临时的,您仍然可以构建一个 vector 和 return 它,即使 NRVO 也是如此。

但是,如果你想那样做,你可以减少创建的临时文件的数量:

  1. 创建右值限定的 operator+ 实现
  2. 使用移动赋值运算符将结果向量移动到 d 中,或者使用移动构造函数将临时向量移动到 d 中,而不必

考虑一下:

A operator+(A& a, const A& b){
    A temp = /*...*/;
    return temp; // NRVO
}

A operator+(A&& a, const A& b){
    // We know a is temporary, so we can move its guts to a new one.
    a += b;
    return std::move(a); // In this case, we require the move, as it's not NRVO
}

// Then use it:
A d = a + b + c;
// What this does: 
// 1. Calls `a + b` and creates a temporary, as both are lvalues
// 2. Utilizes the same temporary to put the result of (a+b) + c
// 3. Still utilizes the same temporary to materialize the rvalue into d

感谢 Joel Filho for the suggestion to use expression templates, and the reference to the relevant Wikipedia article。 Wikipedia 文章中的这种方法很有效,尽管它必须针对我的特定情况稍作修改,因为我使用的是命名 class 成员,而不是实现向量。下面是一个实现。

template<typename E>
class AExpression {
public:
    AExpression() {};

    double Returna() const {
        return static_cast<E>(*this).Returna();
    };
    double Returnb() const {
        return static_cast<E>(*this).Returnb();
    };
};

struct A : public AExpression<A> {
    A() : a(kNaN), b(kNaN) { };
    double a,b;
    double Returna() const {
        return a;
    };
    double Returnb() const {
        return b;
    };
    
    template <typename E>
    A(AExpression<E> & expr) {
        a = expr.Returna();
        b = expr.Returnb();
    };
    
    //this method is needed because the return value of a line such as z = x + y is ASum, not A. This equality overload allows the code to convert back from type ASum to type A
    template <typename E>
    A & operator=(E const & expr) {
        a = expr.Returna();
        b = expr.Returnb();
        return *this;
    }
};

template <typename E1, typename E2>
class ASum : public AExpression<ASum<E1, E2>>  {
    E1 const& one;
    E2 const& two;

public:
    ASum(E1 const& onein, E2 const& twoin) : one(onein), two(twoin) { };

    double Returna() const {
        return one.Returna() + two.Returna();
    };
    double Returnb() const {
        return one.Returnb() + two.Returnb();
    };

};

template <typename E1, typename E2>
ASum<E1, E2>
operator+(AExpression<E1> const& u, AExpression<E2> const& v) {
   return ASum<E1, E2>(*static_cast<const E1*>(&u), *static_cast<const E2*>(&v));
}

使用该实现,如果我 运行 以下行,则它会执行“z = x + y”而不创建任何临时文件。

A x,y,z;
x.a = 1;
x.b = 2;
y.a = 3;
y.b = 4;

z = x + y;
//z is type 'A' and z.a = 4; z.b = 6; no temporaries are created in the line 'z = x + y'