1

不知何故,受到 Paul Preney 编写的Expression templates 和 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));
}

然后,我用它来生成 and 的一个实例,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 引用右值的类数据成员的生命周期是多少?,我知道这是预期的行为。

但是,这里的问题是:只要右值引用成员存在,Paul Preney 的表达式模板和 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

因此,显然存储在表达式模板中的引用不是悬空的。这里发生了什么?

4

2 回答 2

3

Paul Preney 在表达式模板和 C++11 中的代码有什么不同,只要右值引用成员存在就允许临时向量存在?

没有什么允许这样的事情。

那里的临时向量一直存在到完整表达式的末尾,就像任何其他未绑定到局部引用变量的临时向量一样。这在 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
于 2013-08-07T11:51:59.503 回答
2

问题 - 正如 R. Martinho Fernandes 在他的回答中所说 - 是您在“表达式树”中捕获对右值的引用,这些引用比它们的所指对象寿命长。解决方案是只存储对左值的引用,并直接捕获右值引用传递的对象。换句话说,将它们的存储在表达式树中,而不是通过引用(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));
}

如果用户将左值引用传递给您,则她有责任确保所指对象的寿命比表达式树对象的寿命长。

于 2013-08-07T13:23:36.270 回答