【问题标题】:How to properly return std::string (or how to properly use that returned value)如何正确返回 std::string (或如何正确使用该返回值)
【发布时间】:2011-01-06 22:08:33
【问题描述】:

假设您有一个全局类(例如,可用于应用程序的运行时)

class MyClass {
  protected:
    std::string m_Value;
  public:
    MyClass () : m_Value("hello") {}
    std::string value() { return m_Value; }      
};

MyClass v1;

当我这样做时,使用第一种形式会给我带来奇怪的行为

printf("value: %s\n", v1.value().c_str());

看起来好像字符串在 printf 可以使用之前从内存中消失了。 有时它会打印 value: hello 其他时候它会崩溃或什么也不打印。

如果我首先像这样复制字符串

   std::string copiedString = v1.value();
   printf("value: %s\n", copiedString.c_str());

事情确实有效。

肯定有办法避免使用临时字符串进行此操作。

编辑:所以共识是使用 const std::string 和返回值。

我知道每个人都说原始代码应该没问题,但我可以告诉你,我已经看到 Windows CE 上的 MSVC 2005 遇到了问题,但只是在 CE 盒子上。不是 Win32 交叉编译。

【问题讨论】:

  • 你能发布一个可编译的例子吗?对,我不明白你所经历的事情是如何发生的。
  • 临时的v1.value() 应该一直存在到完整表达式的末尾,即它不应该在printf 返回之前被销毁。
  • 12.2 [class.temporary] / 5 表示“临时对象在评估完整表达式 (1.9) 的最后一步时被销毁,该完整表达式 (1.9) 包含它们的创建点。”跨度>
  • @user566129: 如果你的代码是 char const* s = v1.value().c_str(); printf("%s", s);,那么你可以看到你描述的行为。发帖的时候有没有不小心改了意思? (这就是为什么完整的、可编译的示例如此重要的原因之一。)
  • 我注意到 WinCE 的编译器在其他方面不符合标准。不幸的是,如果这是一个编译器错误,我不会完全感到惊讶。

标签: c++ return-value stdstring


【解决方案1】:

您的代码应该可以正常工作。还有其他问题,我们无法从这个测试用例中检测到。也许通过 valgrind 运行您的可执行文件以搜索内存错误。

【讨论】:

    【解决方案2】:

    好吧,代码没有任何问题(正如我所解释的那样)。它不是最佳的,当然也不是The Right Way (R),您应该像 villentehaspam 建议的那样修改您的代码。就像现在一样,您的代码复制了字符串 m_value,因为您按值返回,这还不如只返回一个 const 引用。

    如果您提供一个完整的代码示例来显示问题,我可以更好地帮助您。

    【讨论】:

    • 使用已经实现 C++1x 移动语义的编译器和利用这一点的标准库(最新的 VC 和 GCC 就足够了)进行编译,并且该副本应该变成一个非常便宜的移动。跨度>
    • 由于 Windows CE 开发而坚持使用 MSVC2005(你能看到我的头发变白了吗?)
    • 编译器在移动字符串之前首先尝试省略复制构造函数。 AFAIK,在这种情况下,即使是旧的 VC2005 也能轻松做到这一点。
    • @Tomalak:我认为下一个 C++ 标准不可能在 2009 年完成,所以除非你说的是 210x,否则我宁愿坚持使用可能性更大的“C++1x” .
    • @sbi 您假设委员会对该标准的非官方名称仅基于提议的批准年份,但事实并非如此。他们仍然称其为 C++0x,原因有几个,包括避免混淆以及大部分工作是在 2000 年代完成的。 Bjarne 还继续将其称为 C++0x (www2.research.att.com/~bs/C++0xFAQ.html)。
    【解决方案3】:

    这并不重要,但该类中的 std::string 返回可以使用 const 否则明智的是,您只是在创建成员值的副本,这是一种浪费。

    std::string value() const { return m_value; }
    

    【讨论】:

      【解决方案4】:

      以下是您写作时通常会发生的情况:

      printf("value: %s\n", v1.value().c_str());
      
      1. 编译器会创建一个临时的std::string 来保存从v1.value() 返回的值。
      2. 它调用v1.value() 并将其返回值放入临时字符串中(具体操作方式可能会有所不同:通常它会将临时引用作为隐藏参数传递给方法。参见http://en.wikipedia.org/wiki/Return_value_optimization 进行讨论) .
      3. 它在临时的std::string 上调用.c_str(),将const char * 结果隐藏在某个地方(例如寄存器)。
      4. 它现在用临时的std::string 完成,所以销毁它(即调用它的析构函数,可能会释放一些堆栈空间)。
      5. 它将在步骤 (3) 中获得的 const char * 指针作为参数传递给 printf()

      问题在于步骤 3 中的指针指向由临时 std::string 分配的内存,当调用临时的析构函数时,该内存被释放。这段内存在被printf() 使用时可能早已不复存在。

      基本上,您所展示的任何用法都是危险的,应该避免。使用以下是正确的:

      std::string copiedString = v1.value();
      printf("value: %s\n", copiedString.c_str());
      

      因为copiedString 的析构函数在copiedString 超出范围之前不会被调用,在printf() 返回之后的一段时间。事实上,这并不比v1.value().c_str() 效率低,因为在任何一种情况下都会创建一个临时的std::string

      返回对字符串的引用是一个不错的选择,只要引用在调用者需要时保持有效。因此,对长寿命对象中的成员变量的引用是可以的;对最终证明是临时的东西的引用不是。

      【讨论】:

      • 第 4 步发生在第 5 步之后,临时变量一直存在到 FULL 表达式的末尾。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-01-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多