【问题标题】:Initializing a std::string with function return value, is there a copy?用函数返回值初始化 std::string,有副本吗?
【发布时间】:2016-09-28 14:38:53
【问题描述】:

我有以下代码,我使用的是 GCC 和 C++11:

std::string system_call(const char *cmd){
   std::string a;
   ...
   return a;
}

std::string st = system_call("whatever code");

那里有隐式副本吗? 我多次调用这个函数,我猜它是从system_call的返回值复制到变量st,然后释放临时r值。

有什么办法可以避免复制吗?在编译器中使用st.swap(system_call()) 抛出和错误:

错误:没有匹配的调用函数 'std::basic_string::swap(std::string)'

我的问题是:

  1. 是否有问题
  2. 如何避免,如果有的话

谢谢

编辑: 找到了一种使显式交换工作的方法。但是正确的答案没有任何好处,因为编译器已经将 st 替换为返回值,没有任何副本。

system_call("whatever").swap(st);

【问题讨论】:

    标签: c++ c++11 swap


    【解决方案1】:

    一般来说,如果 std::string 在调用中构造,然后返回大多数现代编译器并应用返回值优化(copy elision 的特殊情况)。如果您想查找精确的规则,您的案例尤其是 Named RVO(感谢@NathanOliver 指出这一点)。从 C++17 开始,此优化通过标准得到保证。

    是否应用了优化很难说,但如果没有,那么在 C++11 及更高版本中,函数范围内的 std::string 对象很可能会从和返回值将被移动到。理论上,您可以在返回值上调用 std::move,但因此您也可以防止任何 RVO 发生。虽然移动可能很有效,但 RVO 通常会生成更快的代码,因为根本不会移动或复制任何内容,但对象是在原地构造的,所以我建议不要这样做,而是支持依赖您的编译器。

    【讨论】:

    • 很高兴知道编译器可能已经在这样做了。但是有什么办法可以强迫它去做吗?交换似乎不起作用,因为它需要一个非临时引用。
    • 您可以在返回值上调用std::move,但这会根据标准规则防止复制省略,这仍然比移动更有效。如果应用了小字符串优化,则从字符串移动可能例如如果有的话,效率不会那么高。我建议不要调用移动。另外你想在哪里调用交换?个人估计,按价值返回并依赖 RVO 将在这里产生最高效的解决方案。
    • @DarkZeros 没有办法强制编译器不将a += b 实现为a = 0; for (int i = 0; i < b;++i) ++a; 在某些时候你必须假设一个非恶意编译器。删除返回值是当今通用的 C++ 编译器功能。只要您直接return named_variable;,或直接return temporary;,并且类型匹配,就会发生省略(如果可能)或移动(如果不是)。在system_callst 之间,保证在实践中除非有病态的编译器标志。
    • 具体来说这是 NRVO,它有点难以优化。
    • @DarkZeros 当我们担心编译器会做一些奇怪或愚蠢的事情时,我们会指示编译器保留中间文件并读取它们。如果我们发现不需要的行为,我们会返回并调整代码以避免它。或者有时您只是忍受它,因为修复的成本超过了收益。
    【解决方案2】:

    省略是标准授予编译器的权限,允许在多个值在代码中看起来是不同的值时共享存在。

    std::string system_call(const char *cmd){
      std::string a;
      ...
      return a; // all return paths return `a` directly
    }
    
    std::string st = system_call("whatever code");
    

    在上述情况下,省略表示asystem_callst的返回值都是同一个对象

    现代编译器会忽略,除非你给他们一个病态的标志,说“不要忽略”,只要标准和代码允许。 “如果它在可能的情况下不省略怎么办”就像在问编译器是否将整数加法实现为循环增量。

    两者都是标准允许的,都不是合理的预期。

    当省略失败时(因为您的代码使其不可能),它会退回到 C++11 中的移动语义。对于std::string,这意味着在移动中不会发生内存分配;在小字符串优化的情况下,可能会复制少量字符。

    如果您的代码可以沿一条路径返回给定的命名变量,而沿另一条路径返回不同的命名变量或临时变量,则省略可能会失败。或者如果你返回一个函数参数。

    如果您的 return 语句是 return named_variable;return some_temporary_object;,则允许省略,其中返回的对象与函数返回的类型匹配。当您执行some_type bob = some_temporary; 时也允许这样做,其中some_temporarysome_type 类型的临时对象。同样,您可以省略到函数参数中(但不能退出)。

    没有办法“保证”省略。

    C++17 使上述几乎所有的 elide-from-temporary 情况都成为强制性的。

    要让省略在 C++14 及之前的版本中工作,必须有一个复制或移动构造函数。当发生省略时,它不会被调用,但它必须存在。在 C++17 中,当临时对象被“强制”省略时,不需要存在复制或移动构造函数:临时对象不是一个独特的对象,而是一个表示“如何构造对象”的子句,仅在稍后完成。

    【讨论】:

      【解决方案3】:

      由于问题被标记为c++11,我假设您正在使用c++11 编译器编译代码。

      在这种情况下,system_call 返回的值不会被复制。编译器可能会使用返回值移动构造 st,或者使用返回值优化来擦除副本。无论哪种方式,都不会有副本。

      【讨论】:

      • 具体来说这是 NRVO,它有点难以优化。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-02-04
      • 2020-05-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多