请考虑以下代码:
class Foo
{
private:
const string& _bar;
public:
Foo(const string& bar)
: _bar(bar) { }
const string& GetBar() { return _bar; }
};
int main()
{
Foo foo1("Hey");
cout << foo1.GetBar() << endl;
string barString = "You";
Foo foo2(barString);
cout << foo2.GetBar() << endl;
}
当我执行此代码时(在VS 2013中),foo1
实例在其_bar
成员变量中有一个空字符串,而foo2
的相应成员变量包含对值的引用“您”。 为什么?
更新:我当然在本例中使用std :: string类。
答案 0 :(得分:11)
对于Foo foo1("Hey")
,编译器必须执行从const char[4]
到std::string
的转换。它创建了std::string
类型的prvalue。这一行相当于:
Foo foo1(std::string("Hey"));
从prvalue到bar
发生引用绑定,然后从bar
到Foo::_bar
发生另一个引用绑定。这里的问题是std::string("Hey")
是一个临时的,当它出现的完整表达式结束时会被销毁。也就是说,在分号后,std::string("Hey")
将不存在。
这会导致悬空引用,因为您现在有Foo::_bar
引用已被销毁的实例。当您打印字符串时,您会因使用悬空参考而产生未定义的行为。
第Foo foo2(barString)
行很好,因为在barString
初始化后foo2
存在Foo::_bar
,因此std::string
仍然引用{{1}}的有效实例。未创建临时,因为初始化程序的类型与引用的类型匹配。
答案 1 :(得分:7)
您正在引用一个在foo1
行末尾被销毁的对象。在foo2
中,barString对象仍然存在,因此引用仍然有效。
答案 2 :(得分:2)
是的,这是C ++和理解的奇迹:
在任何情况下,string都是一个类,“Hey”实际上只是一个字符数组。因此,当您使用“Hey”构造Foo时需要引用字符串,它会执行所谓的隐式转换。发生这种情况是因为string
具有来自字符数组的隐式构造函数。
现在为对象问题的生命周期。为你构造了这个字符串,它存在于哪里以及它的生命周期。实际上对于该调用的值,这里是Foo的构造函数,以及它调用的任何东西。所以它可以调用所有类型的函数,并且该字符串是有效的。
但是,一旦调用结束,对象就会过期。不幸的是,你已经在你的类中存储了一个const引用,你被允许。编译器不会抱怨,因为你可以将const引用存储到一个寿命更长的对象。
不幸的是,这是一个令人讨厌的陷阱。我记得曾经故意给我的构造函数,它真的想要一个const引用,一个非const的引用,以确保没有发生这种情况(也不会得到临时的)。可能不是最好的解决方法,但它当时有用。
你最好的选择大部分时间只是复制字符串。它比你想象的要便宜,除非你真的处理了很多这些。在你的情况下,它可能实际上不会复制任何东西,编译器会秘密移动它所做的副本。
您还可以对字符串进行非const引用,并在
中“交换”它使用C ++ 11还有一个使用移动语义的选项,这意味着传入的字符串将变为“获取”,本身无效。当你想要接收临时数据时,这是特别有用的,你可以将其作为一个例子(尽管大多数临时数据是通过显式构造函数或返回值构造的)。
答案 3 :(得分:2)
问题在于此代码:
Foo foo1("Hey");
从字符串文字"Hey"
(原始char
数组,更准确地说const char [4]
,考虑嘿中的三个字符和终止\0
)创建一个临时 std::string
实例,并将其传递给Foo(const string&)
构造函数。
此构造函数将引用保存到此临时字符串中const string& _bar
数据成员:
Foo(const string& bar) : _bar(bar) { }
现在,问题是您要将引用保存到临时字符串。因此,当临时字符串&#34;蒸发&#34; (在构造函数调用语句之后)时,引用变为悬空,即它引用(&#34 ;指向...&#34; )一些垃圾 因此,您会遇到未定义的行为(例如,使用带有g ++的Windows上的MinGW编译代码,我会得到不同的结果)。
相反,在第二种情况下:
string barString = "You"; Foo foo2(barString);
您的foo2::_bar
引用与(&#34;指向&#34; )barString
相关联,{em>不是临时,但是main()
中的局部变量。因此,在构造函数调用之后,当您使用barString
打印字符串时,cout << foo2.GetBar()
仍然存在。
当然,要解决这个问题,您应该考虑使用std::string
数据成员,而不是参考
通过这种方式,字符串将被深度复制到数据成员中,即使构造函数中使用的输入源字符串是临时的,它也会持久存在(并且&#34;蒸发&构造函数调用后#34; 。