188

为什么会这样:

#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;
}

给出以下输出:

答案是:

代替:

答案是:四

4

6 回答 6

180

只有本地 const参考才能延长使用寿命。

该标准在第 8.5.3/5 节 [dcl.init.ref] 中指定了此类行为,即引用声明的初始化程序部分。您示例中的引用绑定到构造函数的参数,并且当对象绑定超出范围n时变得无效。n

生命周期扩展不能通过函数参数传递。§12.2/5 [class.temporary]:

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

于 2010-05-06T20:36:24.850 回答
30

这是解释发生了什么的最简单的方法:

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

如果您想修复您的程序以显示您想要的行为,请进行以下更改:

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

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

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

于 2010-05-06T21:02:38.150 回答
5

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

  • cout << "The answer is: "位将发送"The answer is: "到标准输出的缓冲区中。

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

因此,没有任何事情可以保证发生。该程序可能看起来很好,或者甚至可能在没有刷新标准输出的情况下崩溃——这意味着文本“答案是:”不会出现在您的屏幕上。

于 2015-05-09T15:28:31.883 回答
2

从其他答案中可以清楚地看出,类成员不会延长构造函数调用之后的临时生命周期。在某些情况下,您的 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;
}

演示

于 2021-05-31T15:43:01.593 回答
0

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

一般来说,您永远不应该长期保留参考资料。引用适用于参数或局部变量,而不是类成员。

于 2010-05-06T20:36:39.060 回答
0

你指的是已经消失的东西。以下将起作用

#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;
}
于 2019-09-21T14:23:22.230 回答