数据成员和右值生命周期

时间:2013-08-07 11:42:43

标签: c++ c++11 rvalue expression-templates

以某种方式受到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

因此,显然存储在表达式模板中的引用不是悬空的。这是怎么回事?

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));
}

如果用户通过左值引用,则她有责任确保引用对象超过表达式树对象。

相关问题