参考成员总数和临时寿命 [英] Aggregate reference member and temporary lifetime

查看:109
本文介绍了参考成员总数和临时寿命的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

鉴于此代码示例,关于将临时字符串的生存期传递给 S 的规则是什么。

Given this code sample, what are the rules regarding the lifetime of the temporary string being passed to S.

struct S
{
    // [1] S(const std::string& str) : str_{str} {}
    // [2] S(S&& other) : str_{std::move(other).str} {}

    const std::string& str_;
};

S a{"foo"}; // direct-initialization

auto b = S{"bar"}; // copy-initialization with rvalue

std::string foobar{"foobar"};
auto c = S{foobar}; // copy-initialization with lvalue

const std::string& baz = "baz";
auto d = S{baz}; // copy-initialization with lvalue-ref to temporary

根据标准:

N4140 12.2 p5.1(已在N4296中删除)

N4140 12.2 p5.1 (removed in N4296)


临时绑定到参考成员构造函数的ctor初始化程序(12.6.2)一直持续到
构造函数退出。

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

N4296 12.6.2 p8

N4296 12.6.2 p8


绑定到mem初始化程序中引用成员的临时表达式格式错误。

A temporary expression bound to a reference member in a mem-initializer is ill-formed.

因此,拥有用户定义的构造函数,例如 [1] 绝对不是我们想要的。它甚至应该在最新的C ++ 14中格式错误(或者是?),gcc和clang都没有警告它。

直接聚合初始化会改变吗?我看起来在那种情况下,临时生存期得到延长。

So having a user defined constructor like [1] is definitively not what we want. It's even supposed to be ill-formed in the latest C++14 (or is it?) neither gcc nor clang warned about it.
Does it change with direct aggregate initialization? I looks like in that case, the temporary lifetime is extended.

现在有关复制初始化,默认移动构造函数和引用成员指出 [2] 是隐式生成的。鉴于可能会忽略移动,事实是否同样适用于隐式生成的移动构造函数?

Now regarding copy-initialization, Default move constructor and reference members states that [2] is implicitly generated. Given the fact that the move might be elided, does the same rule apply to the implicitly generated move constructor?

其中 a,b,c中的哪个,d 具有有效的引用?

推荐答案

绑定到引用的临时对象的生存期得到延长,除非有特殊的例外。也就是说,如果没有这样的例外,则会延长生存期。

The lifetime of temporary objects bound to references is extended, unless there's a specific exception. That is, if there is no such exception, then the lifetime will be extended.

从最近的草案N4567:

From a fairly recent draft, N4567:


第二个上下文(其中延长了生存期)是将
引用绑定到临时对象时。
引用绑定到的临时对象或该引用绑定到的
子对象的完整对象的临时对象在
的生存期内一直存在,除了:

The second context [where the lifetime is extended] is when a reference is bound to a 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:


  • (5.1)绑定到函数调用(5.2.2)中的引用参数的临时对象一直存在,直到
    full-

  • (5.2)在函数return语句(6.6.3)中,临时绑定到返回值的临时生存期不会延长;

  • (5.3)绑定到new-initializer中的引用的临时对象(5.3.4)。 )一直持续到包含新初始化程序的完整表达式
    完成。

正如OP所述,对C ++ 11的唯一重大更改是在C ++ 11中,引用类型的数据成员(来自N3337)还有一个例外:

The only significant change to C++11 is, as the OP mentioned, that in C++11 there was an additional exception for data members of reference types (from N3337):



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

此内容已在 CWG 1696 (后C ++ 14),并且通过mem-initializer将临时对象绑定到引用数据成员的格式现在不正确。

This was removed in CWG 1696 (post-C++14), and binding temporary objects to reference data members via the mem-initializer is now ill-formed.

关于OP中的示例:


struct S
{
    const std::string& str_;
};

S a{"foo"}; // direct-initialization


这将创建一个临时 std :: string 并使用它初始化 str _ 数据成员。 S a { foo} 使用聚合初始化,因此不涉及内存初始化。没有任何适用于生存期扩展的例外,因此该临时生存期将扩展为参考数据成员 str _ 的生存期。

This creates a temporary std::string and initializes the str_ data member with it. The S a{"foo"} uses aggregate-initialization, so no mem-initializer is involved. None of the exceptions for lifetime extensions apply, therefore the lifetime of that temporary is extended to the lifetime of the reference data member str_.


auto b = S{"bar"}; // copy-initialization with rvalue


强制初始化之前的复制初始化用C ++ 17复制省略项:
形式上,我们创建一个临时 std :: string ,初始化一个临时 S ,将临时 std :: string 绑定到 str _ 参考成员。然后,我们将该临时 S 移到 b 中。这将复制引用,不会延长 std :: string 临时文件的寿命。
但是,实现将避免从临时 S 迁移到 b 。但是,这绝对不能影响临时 std :: string 的生存期。您可以在以下程序中观察到这一点:

Prior to mandatory copy elision with C++17: Formally, we create a temporary std::string, initialize a temporary S by binding the temporary std::string to the str_ reference member. Then, we move that temporary S into b. This will "copy" the reference, which will not extend the lifetime of the std::string temporary. However, implementations will elide the move from the temporary S to b. This must not affect the lifetime of the temporary std::string though. You can observe this in the following program:

#include <iostream>

#define PRINT_FUNC() { std::cout << __PRETTY_FUNCTION__ << "\n"; }

struct loud
{
    loud() PRINT_FUNC()
    loud(loud const&) PRINT_FUNC()
    loud(loud&&) PRINT_FUNC()
    ~loud() PRINT_FUNC()
};

struct aggr
{
    loud const& l;
    ~aggr() PRINT_FUNC()
};

int main() {
    auto x = aggr{loud{}};
    std::cout << "end of main\n";
    (void)x;
}

实时演示

请注意,大声的析构函数在 main of end之前被调用,而 x 生存到该跟踪之后。正式地,临时 loud 在创建它的完整表达式的末尾被销毁。

Note that the destructor of loud is called before the "end of main", whereas x lives until after that trace. Formally, the temporary loud is destroyed at the end of the full-expression which created it.

该行为确实如果用户定义了 aggr 的move构造函数,则不会更改。

The behaviour does not change if the move constructor of aggr is user-defined.

在C ++ 17:我们将rhs S { bar} 上的对象与lhs b上的对象标识。这导致临时对象的生存期延长到 b 的生存期。参见 CWG 1697

With mandatory copy-elision in C++17: We identify the object on the rhs S{"bar"} with the object on the lhs b. This causes the lifetime of the temporary to be extended to the lifetime of b. See CWG 1697.

对于其余两个示例,move构造函数(如果调用)仅复制引用。当然,可以省略( S 的move构造函数),但这不是可观察到的,因为它仅复制引用。

For the remaining two examples, the move constructor - if called - simply copies the reference. The move constructor (of S) can be elided, of course, but this is not observable since it only copies the reference.

这篇关于参考成员总数和临时寿命的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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