以某种方式受到Paul Preney编写的Expression templates and C++11中的表达式模板代码的启发,我决定测试以下内容:
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
与其他成员绑定。
好的,从这个答案What is the lifetime of the class data member which const reference to a rvalue?,我知道这是预期的行为。
然而,这里的问题是:只要rvalue-references成员存在,允许临时向量存在的Expression templates and C++11中的Paul Preney代码有什么不同?见他的案例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
因此,显然存储在表达式模板中的引用不是悬空的。这是怎么回事?
答案 0 :(得分:3)
表达式模板和C ++ 11中的Paul Preney代码有什么不同,只要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
答案 1 :(得分:2)
这个问题 - 正如R. Martinho Fernandes在他的回答中所说的那样 - 是你在你的“表达树”中捕获了对rvalues的引用,这种引用比他们的指称更长。解决方案是仅存储对左值的引用,并直接捕获由右值引用传递的对象。换句话说,将值存储在表达式树中而不是通过引用(Live code at 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));
}
如果用户通过左值引用,则她有责任确保引用对象超过表达式树对象。