考虑一个X
带有重载右关联 C++ 运算符的二元运算:
a+=b+=c
-->Y{a, X{b,c}}
可以从某种语法树(X 和 Y 对象的组合)中的表达式“冻结”有关操作数的所有信息,并在以后访问它。(这不是问题)
struct X{Operand& l; Operand& r; /*...*/};
struct Y{Operand& l; X r; /*...*/};
Operand a, b, c;
auto x = Y{a, X{b,c}};
//access members of x...
如果我存储Y::r
为值(如上),则将涉及复制或至少移动。如果我存储Y::r
为右值引用(例如X&& r;
),那么它将引用一个临时的,当表达式结束时将被销毁,给我留下一个悬空的引用。
为了在多个地方多次使用已经构建的表达式,捕获它或防止这种自动破坏的最佳方法是什么?
- 通过捕捉我的意思是以某种方式延长它的寿命,而不是手动将它分配给局部变量(有效但不能很好地扩展!想想这个案例
a+=b+=…+=z
:) - 我知道移动比复制便宜......但什么都不做更好(对象在那里,已经构建)
- 您可以将表达式作为右值引用参数传递给函数或 lambda,并在该函数/lambda 内部访问其成员......但您不能重用它(外部)!您必须每次都重新创建它(有人称这种方法为“厄运 Lambda”,也许还有其他缺点)
这是一个测试程序(位于https://godbolt.org/z/7f78T4zn9):
#include <assert.h>
#include <cstdio>
#include <utility>
#ifndef __FUNCSIG__
# define __FUNCSIG__ __PRETTY_FUNCTION__
#endif
template<typename L,typename R> struct X{
L& l; R& r;
X(L& l, R& r): l{l}, r{r} {printf("X{this=%p &l=%p &r=%p} %s\n", this, &this->l, &this->r, __FUNCSIG__);};
~X(){printf("X{this=%p} %s\n", this, __FUNCSIG__);};
X(const X& other) noexcept = delete;
X(X&& other) noexcept = delete;
X& operator=(const X&) noexcept = delete;
X& operator=(X&&) noexcept = delete;
};
template<typename L,typename R> struct Y{
L& l; R&& r;
Y(L& l, R&& r): l{l}, r{std::forward<R>(r)} {
printf("Y{this=%p &l=%p r=%p} %s\n", this, &this->l, &this->r, __FUNCSIG__);
assert(&this->r == &r);
};
~Y(){printf("Y{this=%p} %s\n", this, __FUNCSIG__);};
void func(){printf("Y{this=%p} &r=%p ... ALREADY DELETED! %s\n", this, &r, __FUNCSIG__);};
};
struct Operand{
Operand(){printf("Operand{this=%p} %s\n", this, __FUNCSIG__);}
~Operand(){printf("Operand{this=%p} %s\n", this, __FUNCSIG__);}
};
//================================================================
int main(){
Operand a, b, c;
printf("---- 1 expression with temporaries\n");
auto y = Y{a, X{b,c}};//this will come from an overloaded right-associative C++ operator, like: a+=b+=c
printf("---- 2 immediately after expression... but already too late!\n");//at this point the temporary X obj is already deleted
y.func();//access members...
printf("---- 3\n");
return 0;
}
这是一个输出示例,您可以在其中看到 X 临时对象的地址进入 Y::r ... 并在有机会捕获它之前立即销毁:
---- 1 expression with temporaries
X{this=0x7ffea39e5860 &l=0x7ffea39e584e &r=0x7ffea39e584f} X::X(Operand&, Operand&)
Y{this=0x7ffea39e5850 &l=0x7ffea39e584d r=0x7ffea39e5860} Y::Y(Operand&, X&&)
X{this=0x7ffea39e5860} X::~X()
---- 2 immediately after expression... but already too late!