const引用类成员是否可以延长临时对象的寿命?


171

为什么这样做:

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

给出以下输出:

答案是:

代替:

答案是:四


39
只是为了获得更多乐趣,如果您编写了cout << "The answer is: " << Sandbox(string("four")).member << endl;,那么它将保证能够工作。

7
@RogerPate你能解释为什么吗?
Paolo M

16
对于好奇的人,Roger Pate发布的示例之所以有用,是因为string(“ four”)是临时的,并且该临时在完全表达式结束时被销毁,因此在他的示例中,当SandBox::member读取时,临时字符串仍然有效
人民空军

1
问题是:由于编写此类很危险,是否有编译器警告不要将临时变量传递给此类,还是有设计指南(在Stroustroup中?)禁止编写存储引用的类?使用存储指针而不是引用的设计指南会更好。
Grim Fandango

@PcAF:您能解释一下为什么在string("four")完整表达式的末尾而不是在Sandbox构造函数退出后销毁临时文件吗?Potatoswatter的回答说,在构造函数的ctor-initializer(第12.6.2节[class.base.init])中,对引用成员的临时绑定将一直存在,直到构造函数退出为止。
泰勒·尼科尔斯

Answers:


166

本地 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])中,绑定到参考参数的临时绑定将一直持续到包含该调用的完整表达式完成为止。


49
您还应该看到GotW#88以获得更人性化的解释:herbutterutter.com/2008/01/01/…–
内森·恩斯特

1
我认为,如果标准说“第二个上下文是将引用绑定到prvalue时”,将会更清楚。在OP的代码中,您可以说它member绑定了一个临时对象,因为membern绑定member到同一对象的方法初始化n是绑定到了,在这种情况下,实际上是一个临时对象。
MM

2
@MM在某些情况下,包含prvalue的lvalue或xvalue初始化程序将扩展prvalue。我的建议书P0066审查了事态。
Potatoswatter '16

1
从C ++ 11开始,Rvalue引用还可以延长临时生存期,而无需使用const限定符。
GetFree

3
@KeNVinFavo是的,使用不活动的物体始终是UB
Potatoswatter

30

这是解释发生情况的最简单方法:

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

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

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

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

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


如果我这样做:const string & temp = string("four"); Sandbox sandbox(temp); cout << sandbox.member << endl;它将仍然有效吗?
伊夫

@Thomas const string &temp = string("four");的结果与相同const string temp("four"); ,除非您decltype(temp)专门使用
MM

@MM非常感谢,我完全理解了这个问题。
伊夫

However, this is bad practice.-为什么?如果临时对象和包含对象都在同一范围内使用自动存储,是不是100%安全?如果您不这样做,那么如果字符串太大而无法复制太昂贵,该怎么办?
最大

2
@max,因为该类不会将传入的临时实例强制具有正确的范围。这意味着有一天您可能会忘记此要求,传递无效的临时值,并且编译器不会警告您。
Alex Che

5

从技术上讲,该程序实际上不需要将任何输出输出到标准输出(首先是缓冲流)。

  • cout << "The answer is: "位将发射"The answer is: "到stdout 的缓冲区中。

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

因此,无法保证不会发生任何事情。该程序看似可以正常运行,甚至在没有刷新标准输出的情况下也可能崩溃-意味着“答案为:”文本不会出现在屏幕上。


2
当有UB时,整个程序的行为是不确定的-它不仅从执行的特定点开始。因此,我们不能肯定"The answer is: "会写在任何地方。
Toby Speight,

0

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

通常,您永远不应保留长期参考。引用适用于参数或局部变量,而不适用于类成员。


7
“从不”是一个非常强烈的词。
Fred Larson 2010年

17
从不对成员进行分类,除非您需要保留对对象的引用。在某些情况下,您需要保留对其他对象而不是副本的引用,因为在这种情况下,引用是比指针更清晰的解决方案。
大卫·罗德里格斯(DavidRodríguez)-dribeas,2010年

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;
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.