通过右值数据成员扩展临时的生命周期通过聚合而不是构造函数,为什么? [英] Extending temporary's lifetime through rvalue data-member works with aggregate, but not with constructor, why?

查看:142
本文介绍了通过右值数据成员扩展临时的生命周期通过聚合而不是构造函数,为什么?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我发现以下方案可以扩展临时生命周期的工作,我不知道它是否应该,但它确实如此。

  struct S {
std :: vector< int>&& vec;
};

int main(){
S s1 {std :: vector< int>(5)}; // construct with temporary
std :: cout<< s1.vec [0]<< '\\\
'; // fine,temporary is alive
}






但是,当 S 被赋予显式值构造函数时,它不再是聚合,并且此方案失败,并在 s1.vec [0]

  struct S {
std :: vector< int& & vec;
S(std :: vector< int>& v)
:vec {std :: move(v)} //绑定到临时提供
{}
};

int main(){
S s1 {std :: vector< int>(5)}; // construct with temporary
std :: cout<< s1.vec [0]<< '\\\
'; // 不好。无效读取free'd内存
}






为什么这对聚合有效?我认为它必须做的构造函数是一个实际的函数调用,基于我使用const lvalue refs红色。此外,有没有办法使后一种情况下工作?



有很多问题处理类似的情况使用左值引用SO。我看到如果我使用一个常量lvalue ref它不会帮助延长临时的生命周期,对于右值引用的规则是一样的吗?

解决方案

TL; DR



汇总初始化



注意: T const& T&& initalization 并延长与他们绑定的临时工的生命。









什么是 Aggregate



  struct S {//(1) 
std :: vector< int>&&& vec;
};

要回答这个问题,我们必须深入了解一个并初始化类类型,但首先我们必须确定 是什么:


8.5.1p1 汇总 [dcl.init.aggr]

是一个数组或类(第9条),没有用户提供的构造函数(12.1)。 ,没有私有或受保护的非静态数据成员(第11条),没有基类(第10条)和没有虚函数(10.3)



注意:上述意味着(1)是汇总。









如何初始化汇总?



非聚集之间的初始化有很大不同,这里直接来自标准的另一部分:


8.5.1p2 聚合 dcl.init.aggr]


当聚集由初始化器列表初始化时,如8.5中所述。 4,初始化器列表的元素作为聚合成员的初始值,以增加下标或成员顺序。每个成员都是从相应的 initializer-clause 中进行复制初始化






上面的引用说明我们正在初始化我们的聚集 initializer-clause 中,之间没有步骤。

  struct A {std ::字符串a int b; }; 

  A x {std :: string {abc},2}; 




语义上面的代码相当于使用下面的代码初始化我们的成员,只是在这个例子中 A :: a A :: b 案例只能通过 xa xb 访问。

  std :: string A :: a {std :: string {abc}}; 
int A :: b {2};




A :: a 的类型更改为右值引用或 const lvalue-reference ,我们将直接 >绑定临时用于初始化 xa



rvalue- >和 const lvalue-references ,表示临时生命周期将扩展到宿主的生命周期,这正是发生的事情。









使用用户声明的构造函数的初始化如何不同?



  struct S {//(2)
std :: vector< int>&& vec;
S(std :: vector< int>& v)
:vec {std :: move(v)} //绑定到临时提供
{}
};

构造函数实际上只是一个花哨函数,用于初始化实例。适用于函数的相同规则适用于它们。



延长临时工作的生命周期没有区别。

  std :: string&& func(std :: string&& ref){
return std :: move(ref);
}




传递给 func 的临时变量不会有它的生命延长,因为我们有一个参数声明为rvalue / lvalue-reference。即使我们返回相同引用,以便它在 func 之外可用,也不会发生。



这是在(2)的构造函数中发生的事情,毕竟一个构造函数只是一个 em>


12.2p5 临时对象 [class.temporary]


引用绑定到的临时变量或临时变量,即引用绑定的子对象的完整对象在引用的生存期内仍然存在,除了: p>


  • 在构造函数的ctor-initializer(12.6.2)中临时绑定到引用成员,直到构造函数退出。


  • 在函数调用(5.2.2)中临时绑定到引用参数,直到完成包含调用的完整表达式。


  • 在函数返回语句(6.6.3)中绑定到返回值的临时的生命周期未扩展;




    • 临时绑定到中的引用。

  • 新的初始化器(5.3.4)


注意:请注意通过 new T {...} 的聚合初始化与上述规则不同。


I've found the following scheme to extend a temporaries lifetime works, I don't know if it should, but it does.

struct S {
    std::vector<int>&& vec;
};

int main() {
    S s1{std::vector<int>(5)};      // construct with temporary
    std::cout << s1.vec[0] << '\n'; // fine, temporary is alive
}


However, when S is given an explicit value constructor it is no longer an aggregate, and this scheme fails with an invalid read on s1.vec[0]

struct S {
    std::vector<int>&& vec;
    S(std::vector<int>&& v)
        : vec{std::move(v)}         // bind to the temporary provided
    { }
};

int main() {
    S s1{std::vector<int>(5)};      // construct with temporary
    std::cout << s1.vec[0] << '\n'; // not ok. invalid read on free'd memory
}


Why is this valid with an aggregate? I'm thinking it has to do with the constructor being an actual function call, based on what I've red with const lvalue refs. Additionally, is there any way to make the latter case work?

There are a great deal of questions dealing with a similar situation using lvalue references on SO. I see that if I had used a const lvalue ref it wouldn't help to extend the lifetime of the temporary, are the rules for rvalue refs the same?

解决方案

TL;DR

Aggregate initialization can be used to extend the life-time of a temporary, a user-defined constructor cannot do the same since it's effectively a function call.

Note: Both T const& and T&& apply in the case of aggregate-initalization and extending the life of temporaries bound to them.



What is an Aggregate?

struct S {                // (1)
  std::vector<int>&& vec;
};

To answer this question we will have to dive into the difference between initialization of an aggregate and initialization of a class type, but first we must establish what an aggregate is:

8.5.1p1 Aggregates [dcl.init.aggr]

An aggregate is an array or a class (Clause 9) with no user-provided constructors (12.1), no private or protected non-static data members (Clause 11), no base classes (Clause 10), and no virtual functions (10.3)

Note: The above means that (1) is an aggregate.



How are Aggregates initialized?

The initialization between an aggregate and a "non-aggregate" differs greatly, here comes another section straight from the Standard:

8.5.1p2 Aggregates [dcl.init.aggr]

When an aggregate is initialized by an initializer list, as specified in 8.5.4, the elements of the initializer list are taken as initializers for the members of the aggregate, in increasing subscript or member order. Each member is copy-initialized from the corresponding initializer-clause.


The above quotation states that we are initializing the members of our aggregate with the initializers in the initializer-clause, there is no step in between.

struct A { std::string a; int b; };

A x { std::string {"abc"}, 2 };


Semantically the above is equivalent to initializing our members using the below, just that A::a and A::b in this case is only accessible through x.a and x.b.

std::string A::a { std::string {"abc"} };
int         A::b { 2 };


If we change the type of A::a to an rvalue-reference, or a const lvalue-reference, we will directly bind the temporary use for initialization to x.a.

The rules of rvalue-references, and const lvalue-references, says that the temporaries lifetime will be extended to that of the host, which is exactly what is going to happen.



How does initialization using a user-declared constructor differ?

struct S {                    // (2)
    std::vector<int>&& vec;
    S(std::vector<int>&& v)
        : vec{std::move(v)}   // bind to the temporary provided
    { }
};

A constructor is really nothing more than a fancy function, used to initialize a class instance. The same rules that apply to functions, apply to them.

When it comes to extending the life-time of temporaries there is no difference.

std::string&& func (std::string&& ref) {
  return std::move (ref);
}


A temporary passed to func will not have its life-time extended just because we have an argument declared as being a rvalue/lvalue-reference. Even if we return the "same" reference so that it's available outside of func, it just won't happen.

This is what happens in the constructor of (2), after all a constructor is just a "fancy function" used to initialize an object.


12.2p5 Temporary objects [class.temporary]

The temporary to which the reference is bound or the temporary that is the complete object of a subobject to which the reference is bound persists for the lifetime of the reference except:

  • A temporary bound to a reference member in a constructor's ctor-initializer (12.6.2) persists until the constructor exits.

  • A temporary bound to a reference parameter in a function call (5.2.2) persists until the completion of the full-expression containing the call.

  • The lifetime of a temporary bound to the returned value in a function return statement (6.6.3) is not extended; the temporary is destroyed at the end of the full-expression in the return statement.

    • A temporary bound to a reference in a new-initializer (5.3.4) persists until the completion of the full-expression containing the new-initializer.

Note: Do note that aggregate initialization through a new T { ... } differ from the previously mentioned rules.

这篇关于通过右值数据成员扩展临时的生命周期通过聚合而不是构造函数,为什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆