【问题标题】:Copy elision possible if returning parameter?如果返回参数可以复制省略?
【发布时间】:2014-03-21 09:32:51
【问题描述】:

考虑一个按值获取对象的函数,对其执行一些操作并返回该对象,例如:

std::string MyToUpper (std::string s)
{
      std::transform(s.begin(), s.end(), s.begin(), std::toupper);
      return s;
}

现在你用一个临时函数调用这个函数:

std::string Myupperstring = MyToUpper("text");

从概念上讲,不需要副本。在这种情况下,现代编译器是否能够删除所有副本?如果没有,是否只有动作?这个案例怎么样:

std::string Mylowerstring("text");
std::string Myupperstring = MyToUpper(std::move(Mylowerstring));

【问题讨论】:

  • 在这种情况下,编译器不允许在返回中省略副本,AFAIK 这里也没有隐式的move
  • @Simple:如果类型是可移动的,则返回值被移动(因为这里是`string)。
  • 与您的问题无关,但您的代码具有未定义的行为(假设它可以编译,但它可能不会)。 std::transform 将使用 char 调用 std::toupper,如果签署了普通的 char(通常是这种情况),则会导致未定义的行为。
  • 我检查了标准和函数参数在用作return 中的表达式时被隐式移动(以为只是自动变量),所以参数将被省略,参数将被移动,然后返回的值将被忽略。

标签: c++ gcc c++11 compiler-construction clang


【解决方案1】:

最多可以省略两个概念副本中的一个。如果您将临时值传递给函数,则可以省略该副本,根据 C++11 12.8/31 的第三个项目符号:

当一个临时类对象...将被复制/移动...时,复制/移动操作可以省略

返回不能省略;这只能针对临时变量(根据上面引用的规则)或局部变量,根据第一个项目符号:

在 return 语句中 ... 当表达式是 非易失性自动对象(函数或 catch 子句参数除外)...复制/移动操作可以省略

在没有省略的情况下,返回值被视为 rvalues 并在可能的情况下移动(并且这里是可能的);如果函数参数是右值,则它们会被移动,就像在您的两个示例中一样。

【讨论】:

    【解决方案2】:

    我不这么认为。一些副本可以,并且可能会 被淘汰,但 NVRO 不能申请,因为它依赖于 在与返回相同的位置构造变量 价值。除了带有值参数外,参数是 由调用者构造,他(通常)看不到 参数将被返回,因此不能在 正确的地方。

    【讨论】:

      【解决方案3】:

      从概念上讲,不需要副本。在这种情况下,现代编译器是否能够删除所有副本?

      是的,如果函数是内联的,这是可能的。但是,我想考虑下面的代码示例而不是你的代码示例,因为std::string 是一头野蛮的野兽,充满了晦涩的优化。

      因此,让我们考虑一个使用ints 的示例。调用者拥有{1, 2, 3},并希望从中就地创建一个包含{2, 4, 6}std::vector<int>。 (这大致类似于在调用者处拥有 literal "text" 并希望就地构造包含 "TEXT"std::string。)

      代码:

      #include <cstdio>
      #include <vector>
      using namespace std;
      
      vector<int> mult(vector<int> v) {
        for (int& e : v)
          e *= 2;
        return v;
      }
      
      int main() {
        vector<int> v( mult({1, 2, 3}) );
        for (int i : v)
          printf("%d\n", i);
      }
      

      如果我使用 gcc 4.7.2 将其编译为:g++ -O3 -fwhole-program -Wall -Wextra -std=c++11 -S file.cpp,我会在程序集中得到一个 std::vector&lt;int&gt; 析构函数调用。 向量是在原地创建的。生成的程序集尽可能好。

      如果我编译完全相同的代码但省略了-fwhole-program 标志,mult() 函数不会被内联,我会收到两次对std::vector&lt;int&gt; 析构函数的调用。生成的程序集也比前一种情况差很多。

      Clang 不知道-fwhole-program 标志,所以我将static 关键字添加到mult()

      static vector&lt;int&gt; mult(vector&lt;int&gt; v) { ...

      然后它还就地创建向量。


      From James Kanze's answer:

      使用值参数,参数由调用者构造,调用者 (通常)看不到该参数将被返回,因此 无法在正确的位置构建它。

      我在上面所做的(内联mult())使得调用者可以看到参数将被返回,实际上,结果是就地构造的。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-11-12
        • 2018-10-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-12-09
        • 2016-02-09
        • 1970-01-01
        相关资源
        最近更新 更多