使用 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 也是如此。
但是,如果你想那样做,你可以减少创建的临时文件的数量:
- 创建右值限定的
operator+
实现
- 使用移动赋值运算符将结果向量移动到
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'
我有一个结构,例如:
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 也是如此。
但是,如果你想那样做,你可以减少创建的临时文件的数量:
- 创建右值限定的
operator+
实现 - 使用移动赋值运算符将结果向量移动到
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'