为什么我的字符串引用成员变量在 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
发生引用绑定,然后从 bar
到 Foo::_bar
发生另一个引用绑定。这里的问题是 std::string("Hey")
是一个临时变量,当它出现的完整表达式结束时就会被销毁。即分号后的std::string("Hey")
将不存在
这会导致 悬空引用 因为您现在 Foo::_bar
引用了一个已经被销毁的实例。当您打印字符串时,您会因使用悬空引用而导致 未定义的行为。
行Foo foo2(barString)
很好,因为barString
存在于foo2
的初始化之后,所以Foo::_bar
仍然引用std::string
的有效实例。未创建临时文件,因为初始化程序的类型与引用的类型匹配。
是啊,这就是C++和理解的妙处:
- 对象的生命周期
- 该字符串是 class 而文字字符数组不是 "strings"。
- 隐式构造函数会发生什么。
无论如何,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" 在构造函数调用之后)。
考虑以下代码:
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
发生引用绑定,然后从 bar
到 Foo::_bar
发生另一个引用绑定。这里的问题是 std::string("Hey")
是一个临时变量,当它出现的完整表达式结束时就会被销毁。即分号后的std::string("Hey")
将不存在
这会导致 悬空引用 因为您现在 Foo::_bar
引用了一个已经被销毁的实例。当您打印字符串时,您会因使用悬空引用而导致 未定义的行为。
行Foo foo2(barString)
很好,因为barString
存在于foo2
的初始化之后,所以Foo::_bar
仍然引用std::string
的有效实例。未创建临时文件,因为初始化程序的类型与引用的类型匹配。
是啊,这就是C++和理解的妙处:
- 对象的生命周期
- 该字符串是 class 而文字字符数组不是 "strings"。
- 隐式构造函数会发生什么。
无论如何,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" 在构造函数调用之后)。