常量引用类成员是否会延长临时类的生命周期?

为什么会这样:

#include <string>
#include <iostream>
using namespace std;


class Sandbox
{
public:
Sandbox(const string& n) : member(n) {}
const string& member;
};


int main()
{
Sandbox sandbox(string("four"));
cout << "The answer is: " << sandbox.member << endl;
return 0;
}

提供以下产出:

答案是:

而不是:

答案是: 四个

53454 次浏览

只有 本地 const参考延长寿命。

该标准在8.5.3/5[ dcl.init.ref ]中指定了这种行为,这一章节讨论了引用声明的初始化器。示例中的引用绑定到构造函数的参数 n,当对象 n绑定到超出作用域时,该引用将变为无效。

生存期扩展不是通过函数参数传递的。12.2/5[ class.Clinical ] :

第二个上下文是将引用绑定到临时。引用绑定到的临时对象或作为绑定到临时对象的子对象的完整对象的临时对象将在引用的生命周期内持续存在,但下面指定的情况除外。临时绑定到构造函数的 ctor-initializer (12.6.2[ class.base.init ])中的引用成员,直到构造函数退出为止。临时绑定到函数调用(5.2.2[ expr.call ])中的引用参数,直到包含调用的完整表达式完成为止。

因为一旦 Sandbox 构造函数返回,您的临时字符串就超出了作用域,并且为了其他目的回收了它所占用的堆栈。

一般来说,你永远不应该长期保留引用。引用对参数或局部变量有好处,而对类成员则没有好处。

这里有一个最简单的方法来解释发生了什么:

在 main ()中,您创建了一个字符串并将其传递给构造函数。此字符串实例只存在于构造函数中。在构造函数内部,您分配了成员直接指向此实例。当范围离开构造函数时,字符串实例被销毁,成员随后指向不再存在的字符串对象。让 Sandbox.member 指向其作用域之外的引用不会将这些外部实例保留在作用域中。

如果希望修改程序以显示所需的行为,请进行以下更改:

int main()
{
string temp = string("four");
Sandbox sandbox(temp);
cout << sandbox.member << endl;
return 0;
}

现在 temp 将在 main ()的末尾而不是构造函数的末尾传递出作用域。然而,这是不好的做法。您的成员变量永远不应该是对存在于实例之外的变量的引用。实际上,您永远不知道该变量何时会超出作用域。

我建议将 Sandbox.member 定义为 const string member;。这将把临时参数的数据复制到成员变量中,而不是将成员变量指定为临时参数本身。

从技术上讲,这个程序不需要实际输出任何东西到标准输出(这是一个缓冲流开始)。

  • cout << "The answer is: "位将向 stdout 的 < em > buffer 发出 "The answer is: "

  • 然后,<< sandbox.member位将提供到 operator << (ostream &, const std::string &)的悬空引用,该引用调用 < strong > 未定义行为 。

正因为如此,什么都不能保证发生。程序可能看起来运行良好,甚至可能在不刷新 stdout 的情况下崩溃——这意味着文本“答案是:”不会出现在屏幕上。

你指的是已经消失的东西,下面这些可以

#include <string>
#include <iostream>


class Sandbox
{


public:
const string member = " "; //default to whatever is the requirement
Sandbox(const string& n) : member(n) {}//a copy is made


};


int main()
{
Sandbox sandbox(string("four"));
std::cout << "The answer is: " << sandbox.member << std::endl;
return 0;
}

从其他答案中可以清楚地看出,类成员不会延长构造函数调用之外的临时调用的生命。但是,在一些情况下,你的 API 可以“安全地”假设传递给类的所有 const&对象都不是临时对象,而是对作用域良好的对象的引用。

如果您不想创建副本,那么可以做些什么来确保 UB 不会潜入您的代码?您所拥有的最好的工具是保护传递给构造函数的 std::string const&不是临时的假设,通过 将接受此类临时项的重载声明为已删除:

#include <string>
#include <iostream>
using namespace std;


class Sandbox
{
public:
Sandbox(const string& n) : member(n) {}
    

Sandbox(string&&) = delete;
// ^^^ This guy ;)


const string& member;
};


int main()
{
Sandbox sandbox(string("four"));
// Detect you're trying ^^^ to bind a
// reference to a temporary and refuse to compile


return 0;
}

译自: 美国《科学》杂志网站(http://coliru.stack-crooked.com/a/c063b4043a0a7138)原文链接: http://coliru.stack-crooked.com/a/c063b4043a0a7138