为什么我的字符串引用成员变量在 C++ 中设置为空字符串?

Why is my string reference member variable set to an empty string in C++?

考虑以下代码:

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 的相应成员变量包含引用到值 "You"。 这是为什么?

更新:我当然在这个例子中使用 std::string class。

您正在引用一个在 foo1 行末尾被销毁的对象。在 foo2 中,barString 对象仍然存在,因此引用仍然有效。

对于 Foo foo1("Hey"),编译器必须执行从 const char[4]std::string 的转换。它创建一个 std::string 类型的纯右值。这一行相当于:

Foo foo1(std::string("Hey"));

从纯右值到 bar 发生引用绑定,然后从 barFoo::_bar 发生另一个引用绑定。这里的问题是 std::string("Hey") 是一个临时变量,当它出现的完整表达式结束时就会被销毁。即分号后的std::string("Hey")将不存在

这会导致 悬空引用 因为您现在 Foo::_bar 引用了一个已经被销毁的实例。当您打印字符串时,您会因使用悬空引用而导致 未定义的行为

Foo foo2(barString)很好,因为barString存在于foo2的初始化之后,所以Foo::_bar仍然引用std::string的有效实例。未创建临时文件,因为初始化程序的类型与引用的类型匹配。

是啊,这就是C++和理解的妙处:

  1. 对象的生命周期
  2. 该字符串是 class 而文字字符数组不是 "strings"。
  3. 隐式构造函数会发生什么。

无论如何,string是一个class,"Hey"其实只是一个字符数组。因此,当您使用 "Hey" 构造 Foo 时,它需要对字符串的引用,它会执行所谓的隐式转换。发生这种情况是因为 string 有一个来自字符数组的隐式构造函数。

现在是对象问题的生命周期。为您构建了这个字符串后,它位于何处以及它的生命周期是多少。好吧,实际上对于那个调用的值,这里是 Foo 的构造函数,以及它调用的任何东西。所以它可以调用各种函数并且该字符串是有效的。

但是,一旦该调用结束,该对象就会过期。不幸的是,您在 class 中存储了对它的 const 引用,并且您可以这样做。编译器不会抱怨,因为您可能会存储一个指向寿命更长的对象的 const 引用。

不幸的是,这是一个令人讨厌的陷阱。我记得有一次我故意给了我的构造函数,它真的想要一个 const 引用,一个非 const 引用,目的是确保这种情况不会发生(也不会收到临时引用)。可能不是最好的解决方法,但当时有效。

大多数时候你最好的选择就是复制字符串。它比你想象的要便宜,除非你真的处理了很多很多这样的东西。在你的情况下,它可能不会实际复制任何东西,编译器会秘密地移动它制作的副本。

您还可以对字符串进行非常量引用,并"swap"它在

在 C++11 中,还有一个使用移动语义的选项,这意味着传入的字符串将变为 "acquired",它本身会失效。这在您确实想要使用临时变量时特别有用,您的例子就是这样(尽管大多数临时变量是通过显式构造函数或 return 值构造的)。

问题是在这段代码中:

Foo foo1("Hey");

来自字符串文字 "Hey"(原始 char 数组,更准确地说 const char [4],考虑 Hey 中的三个字符和终止符[=16=]) 创建了一个 temporary std::string 实例,并将其传递给 Foo(const string&) 构造函数。

此构造函数将此 临时 字符串的 引用 保存到 const string& _bar 数据成员中:

Foo(const string& bar)
        : _bar(bar) { }

现在,问题是您正在将 reference 保存到 temporary 字符串。所以当临时字符串"evaporates"时(在构造函数调用语句之后),引用变成了dangling,即它引用了("points to...") 一些垃圾。
因此,您会导致 未定义的行为 (例如,在 Windows 上使用 MinGW 和 g++ 编译您的代码,我得到不同的结果)。

相反,在第二种情况下:

string barString = "You";
Foo foo2(barString);

您的 foo2::_bar 引用与 ("points to") barString 关联,即 而不是 临时的,但是在 main() 中是一个局部变量。因此,在构造函数调用之后,当您使用 cout << foo2.GetBar().

打印字符串时, barString 仍然存在

当然,要解决这个问题,您应该考虑使用 std::string 数据成员,而不是引用。
这样,字符串将被深度复制到数据成员中,即使构造函数中使用的输入源字符串是临时的(并且 "evaporates" 在构造函数调用之后)。