[英]Reference member binds to a temporary object whose life-time would be shorter than the lifetime of the constructed object
[英]Data member and rvalue life-time
由Paul Preney编写的Expression template和C ++ 11中的expression template代码启发了我,决定测试以下内容:
template<typename T>
struct X
{
X(T t) : t(std::forward<T>(t)) {}
T t;
};
template<typename T>
auto CreateX(T&& t) -> X<decltype(std::forward<T>(t))>
{
return X<decltype(std::forward<T>(t))>(std::forward<T>(t));
}
然后,我用它来生成X<const vector<int>&>
和X<vector<int>&&>
的实例,如下所示:
int main()
{
int vec = {1,2,3,4};
auto x1 = CreateX(vec);
auto x2 = CreateX(vector<int>{5,6,7,8});
cout << "x1: "; for(auto x : x1.t) cout << x << " "; cout << endl;
cout << "x2: "; for(auto x : x2.t) cout << x << " "; cout << endl;
}
输出为:
x1: 1 2 3 4
x2: 0 0 33 0 0 0 7 8
这表明临时vector<int>{5,6,7,8}
的寿命没有延长,并且右值引用成员X::t
绑定到其他对象。
好的,从这个答案中,const引用一个右值的类数据成员的生存期是多少? ,我知道这是预期的行为。
但是,这里的问题是: 表达式模板和C ++ 11中的保尔·普雷尼的代码有什么不同,只要存在右值引用成员,它们就允许存在临时矢量? 请参阅他的案例2,其中创建了临时对象。
显然 ,这里使用了相同的构造,但是我可能缺少了一些东西。
编辑:根据下面的R. Martinho Fernandes的回答,我尝试了以下操作:
int main()
{
using namespace std;
auto expr = math_vector<3>{1.0, 1.1, 1.2} + math_vector<3>{2.0, 2.1, 2.2};
cout << "vec1: "; for(int i = 0; i < 3; ++i) cout << expr.le()[i] << " "; cout << endl;
cout << "vec2: "; for(int i = 0; i < 3; ++i) cout << expr.re()[i] << " "; cout << endl;
}
原来这是一个有效的代码,输出:
vec1: 1.0 1.1 1.2
vec2: 2.0 2.1 2.2
因此,显然存储在表达式模板中的引用不是悬挂的。 这里发生了什么?
Paul Preney的Expression模板和C ++ 11中的代码有什么不同,只要存在rvalue-references成员,它们就允许存在临时矢量?
没有什么允许这样的事情。
那里的临时向量一直存在到完整表达式的结尾,就像其他任何未绑定到局部引用变量的临时向量一样。 这在Paul的代码中就足够了,因为代码会立即将表达式树具体化为实际的math_vector
,之后便不再需要临时对象了。
Paul的代码不在任何地方存储任何表达式模板节点( math_vector_expr
),而您的代码将一个( X
)存储为x2
。 这是auto
的一个已知问题:使用表达式模板时,它做错了事,因为它会导致存储表达式树,该树可能包含立即悬挂的引用。
为了清楚起见,以下内容是可以的。
math_vector<3> result =
math_vector<3>{1.0, 1.1, 1.2} +
math_vector<3>{2.0, 2.1, 2.2} +
math_vector<3>{3.0, 3.1, 3.2} +
math_vector<3>{4.0, 4.1, 4.2}
; // no references are held to any temporaries past this point
以下是不正确的。
math_vector_expr<3> result = // or auto
math_vector<3>{1.0, 1.1, 1.2} +
math_vector<3>{2.0, 2.1, 2.2} +
math_vector<3>{3.0, 3.1, 3.2} +
math_vector<3>{4.0, 4.1, 4.2}
; // result now holds references to those temporaries
正如R. Martinho Fernandes在他的回答中指出的那样,问题是您在“表达式树”中捕获了对rvalue的引用,这些引用超出了其引用对象的寿命。 解决方案是仅存储对左值的引用,并直接捕获由右值引用传递的对象。 换句话说,将它们的值存储在表达式树中,而不是通过引用存储( Coliru的实时代码 ):
template<typename T>
struct X
{
X(T t) : t(std::move(t)) {}
T t;
};
// Capture lvalue references
template<typename T>
X<const T&> CreateX(const T& t)
{
return X<const T&>(t);
}
// Copy rvalue references
template<typename T>
typename std::enable_if<std::is_rvalue_reference<T&&>::value,X<T>>::type
CreateX(T&& t)
{
return X<T>(std::move(t));
}
如果用户向您传递了一个左值引用,则她有责任确保引用对象的寿命超过了表达式树对象。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.