【问题标题】:Why is my string reference member variable set to an empty string in C++?为什么我的字符串引用成员变量在 C++ 中设置为空字符串?
【发布时间】:2015-03-11 16:43:35
【问题描述】:

考虑以下代码:

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 类。

【问题讨论】:

  • 因为第一个指向一个临时字符串,该字符串在调用 Foo 构造函数后立即被销毁。 (从 (c-)string 文字 "Hey" 创建的临时 std::string)
  • 只是指出,如果你使用const char *,第一种情况就可以了,因为字符串文字永远存在。
  • 顺便说一句,如果您的构造函数采用非常量参数,第一个版本将无法编译,因为您无法将临时对象绑定到非常量引用。
  • @Borgleader 是的,我知道,这就是我将其设为 const 的原因 :-)

标签: c++ string reference pass-by-reference


【解决方案1】:

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

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

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

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

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

【讨论】:

  • 感谢您的详细解释 - 显然有很多事情我还没有完全理解,尤其是prvalue 和引用绑定。我会调查的。
【解决方案2】:

您正在使用foo1 引用在行尾被销毁的对象。在foo2 中,barString 对象仍然存在,所以引用仍然有效。

【讨论】:

    【解决方案3】:

    是的,这就是C++和理解的奇迹:

    1. 对象的生命周期
    2. 该字符串是一个类,而文字字符数组不是“字符串”。
    3. 隐式构造函数会发生什么。

    无论如何,字符串是一个类,“嘿”实际上只是一个字符数组。因此,当您使用想要引用字符串的“Hey”构造 Foo 时,它会执行所谓的隐式转换。这是因为string 有一个来自字符数组的隐式构造函数。

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

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

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

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

    您还可以对字符串进行非常量引用并“交换”它

    在 C++11 中,还有一个使用移动语义的选项,这意味着传入的字符串将变为“已获取”,本身无效。当您确实想要使用临时对象时,这尤其有用,您的示例就是一个例子(尽管大多数临时对象是通过显式构造函数或返回值构造的)。

    【讨论】:

      【解决方案4】:

      问题是在这段代码中:

      Foo foo1("Hey");
      

      从字符串文字"Hey"(原始char 数组,更准确地说是const char [4],考虑到Hey 中的三个字符和终止\0)一个临时 em> std::string 实例被创建,并被传递给Foo(const string&amp;) 构造函数。

      这个构造函数将一个reference保存到这个temporary字符串到const string&amp; _bar数据成员中:

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

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

      相反,在第二种情况下:

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

      您的foo2::_bar 引用关联到(“指向”barString,这不是临时的,而是main() 中的一个局部变量.因此,在构造函数调用之后,当您使用 cout &lt;&lt; foo2.GetBar() 打印字符串时,barString 仍然存在。

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

      【讨论】:

        猜你喜欢
        • 2020-01-14
        • 1970-01-01
        • 2019-08-16
        • 2012-02-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多