成员右值引用和对象生存期

时间:2017-02-05 18:21:05

标签: c++ c++11 c++14 language-lawyer c++17

自从我上次查看临时终身规则以来已经有一段时间了,我不记得成员右值引用如何影响 寿命。

例如,请使用以下两段代码:

int main() 
{
    std::get<0>(std::forward_as_tuple(
        [](int v){ std::cout << v << std::endl; }
    ))(6);
    return 0;
}

int main() 
{
    auto t = std::forward_as_tuple(
        [](int v){ std::cout << v << std::endl; }
    );
    std::get<0>(t)(6);
    return 0;
}

如果成员右值引用不影响生命周期规则,我希望第一个例子表现良好,而第二个例子 未定义(因为包含lambda对象的完整表达式 在第一个分号结束。)

C ++ 11,C ++ 14和C ++ 17如何处理给定的示例?三者之间是否存在差异?

3 个答案:

答案 0 :(得分:4)

自98年以来,任何版本的C ++都没有改变临时终身延长的规则。我们可能有新方法来表现临时性,但一旦存在,它们的生命周期就会被很好地理解。

应该注意的是,您的示例并不真正适用于任何类型的成员引用。您正在使用临时调用函数,该参数是转发引用。因此,有问题的临时文件绑定到函数参数引用,而不是成员引用。该临时的生命周期将在调用该函数的表达式之后结束,就像传递给引用参数的任何临时表一样。

此函数(forward_as_tuple)最终将该引用存储在tuple中的事实无关紧要。你用引用做什么不能改变它的生命周期。

同样,那是C ++ 98,而后来的版本都没有改变它。

答案 1 :(得分:4)

直接将临时绑定到引用但在构造函数初始化列表中的任何位置都适用Lifetime扩展。 (注意:聚合初始化不是构造函数)

std::forward_as_tuple是一个功能。传递给它的任何临时数据都不能超出当前行的生命周期。

默认情况下,Temporaries一直持续到当前行结束,基本上。 (这个点到底是什么,实际上并不是当前行的结束)。在您的两种情况下,它是临时结束其生命周期的当前行(;)的结尾。这对于第一种情况来说足够长了;在第二种情况下,临时文件已经死亡,您的代码表现出未定义的行为。

同时:

struct foo {
  int&& x;
};

int main() {
  foo f{3};
  std::cout << f.x << "\n";
}

完全明确。没有构造函数,我们将临时绑定到(rvalue)引用,从而延长了生命周期。

添加:

struct foo {
  int&& x;
  foo(int&& y):x(y) {}
};

struct foo {
  int&& x;
  foo(int y):x((int)y) {}
};

现在是UB。

第一个因为我们在调用ctor时将临时值绑定到右值引用。构造函数的内部是无关紧要的,因为没有临时绑定。然后函数的参数和临时值都超出了main中的范围。

第二个,因为在构造函数初始化列表中绑定临时(int)y 0到int&&x的规则不会像在其他地方那样延长生命周期。

答案 2 :(得分:3)

因为这是一个问题。终身延长的规则在[class.temporary]中。从C ++ 11到C ++ 14到C ++ 17的措辞并没有以与这个特定问题相关的方式改变。规则是:

  

有[两个/三个]上下文,其中temporaries在与完整表达式结束不同的点被销毁。第一个上下文是调用默认构造函数来初始化数组元素[...]

     

[second / third ]上下文是指引用绑定到临时的。引用所在的临时值   绑定或临时,即绑定引用的子对象的完整对象仍然存在   在参考文件的生命周期中除外:
   - 绑定到函数调用(5.2.2)中的引用参数的临时对象将持续到完成   包含调用的完整表达式。

这个表达式:

std::forward_as_tuple([](int v){ std::cout << v << std::endl; })

涉及将引用(forward_as_tuple中的参数)绑定到prvalue(lambda表达式),它在C ++ 11/14中明确提到作为创建临时的上下文:

  

类类型的临时代码在各种上下文中创建:绑定对prvalue的引用,[...]

在C ++ 17中的措辞为:

  

创建临时对象
  (1.1) - 当物有化prvalue以便它可以用作glvalue(4.4)时,

无论哪种方式,我们都有一个临时的,它绑定到函数调用中的引用,因此临时对象会持续到包含该调用的完整epxression完成。

所以这没关系:

std::get<0>(std::forward_as_tuple(
    [](int v){ std::cout << v << std::endl; }
))(6);

但这可以通过一个悬挂的参考来调用:

auto t = std::forward_as_tuple(
    [](int v){ std::cout << v << std::endl; }
);
std::get<0>(t)(6);

因为临时函数对象的生命周期在语句初始化t时结束。

请注意,这与成员右值引用无关。如果我们有类似的东西:

struct Wrapper {
    X&& x;
};

Wrapper w{X()};

然后临时X的生命周期在w的生命周期内持续存在,而w.x不是悬挂引用。但那是因为没有函数调用。

C ++ 17引入了第3个上下文,涉及复制数组,这里不相关。