什么是复制省略和返回值优化?

什么是复制elision? 什么是(命名)返回值优化? 他们意味着什么?

在什么情况下可以发生? 什么是限制?

  • 如果您参考了这个问题,您可能正在寻找介绍
  • 有关技术概述,请参阅标准参考
  • 在这里查看常见案例

  • 介绍

    技术概述 - 跳到这个答案。

    对于出现复制瑕疵的常见情况 - 跳到此答案。

    复制elision是由大多数编译器实施的优化措施,以防止在某些情况下额外(可能是昂贵的)副本。 它使价值回归或价值传递在实践中可行(限制适用)。

    这是唯一的优化形式, 即使复制/移动对象具有副作用 ,elides(ha!)as-if规则 - 复制elision也可以应用

    下面的例子来自维基百科:

    struct C {
      C() {}
      C(const C&) { std::cout << "A copy was made.n"; }
    };
    
    C f() {
      return C();
    }
    
    int main() {
      std::cout << "Hello World!n";
      C obj = f();
    }
    

    根据编译器和设置,以下输出全部有效

    你好,世界!
    复制了。
    复制了。


    你好,世界!
    复制了。


    你好,世界!

    这也意味着可以创建更少的对象,所以你也不能依赖被调用的特定数量的析构函数。 您不应该在复制/移动构造函数或析构函数中使用关键逻辑,因为您不能依赖它们被调用。

    如果消除对副本或移动构造函数的调用,则该构造函数必须仍然存在并且必须可访问。 这可以确保copy elision不允许复制通常不可复制的对象,例如因为它们具有私有或已删除的复制/移动构造函数。


    标准参考

    对于较少技术的观点和介绍 - 跳到这个答案。

    对于出现复制瑕疵的常见情况 - 跳到此答案。

    复制elision在标准中定义如下:

    12.8复制和移动类对象[class.copy]

    31)当满足某些标准时,即使对象的复制/移动构造函数和/或析构函数具有副作用,也允许实现省略类对象的复制/移动构造。 在这种情况下,实现将被忽略的复制/移动操作的来源和目标视为简单地引用同一对象的两种不同方式,并且对象的销毁发生在两个对象将在在没有优化的情况下被销毁.123在下列情况下(这可以合并以消除多个副本),允许复制/移动操作的缩写(称为复制删除):

    - 在具有类返回类型的函数的返回语句中,当表达式是具有与函数返回类型相同的cvun限定类型的非易失性自动对象(函数或catch-clause参数除外)的名称时,通过将自动对象直接构造到函数的返回值中,可以省略复制/移动操作

    - 在操作数是一个非易失性自动对象(函数或catch-clause参数除外)的名称时,它的作用域未超出最内层的try-block的末尾(如果存在一个),通过将自动对象直接构造到异常对象中,可以省略从操作数到异常对象(15.1)的复制/移动操作

    - 当未绑定到引用(12.2)的临时类对象将被复制/移动到具有相同cv-unqualified类型的类对象时,可以通过将临时对象直接构造到省略复制/移动的目标

    - 当异常处理程序(第15章)的异常声明声明与异常对象(15.1)相同类型的对象(cv-qualification除外)时,可以通过处理异常声明来省略复制/移动操作作为异常对象的别名,如果除异常声明声明的对象的构造函数和析构函数执行外,程序的含义将保持不变。

    123)因为只有一个对象被破坏而不是两个,并且一个复制/移动构造函数没有被执行,所以每个构造对象仍然有一个对象被销毁。

    给出的例子是:

    class Thing {
    public:
      Thing();
      ~Thing();
      Thing(const Thing&);
    };
    Thing f() {
      Thing t;
      return t;
    }
    Thing t2 = f();
    

    并解释说:

    这里elision的标准可以合并,以消除对Thing类的拷贝构造函数的两次调用:将本地自动对象t复制到函数f()的返回值的临时对象中,并将该临时对象复制到对象中t2 。 实际上,本地对象t的构造可以被看作是直接初始化全局对象t2 ,并且该对象的销毁将在程序出口处发生。 向Thing添加一个移动构造函数具有相同的效果,但它是从临时对象到t2的移动构造被消除。


    常见的复制形式

    技术概述 - 跳到这个答案。

    对于较少技术的观点和介绍 - 跳到这个答案。

    (命名)返回值优化是复制elision的常见形式。 它指的是根据方法的值返回的对象将其副本消除的情况。 标准中列出的示例说明了命名的返回值优化 ,因为该对象已被命名。

    class Thing {
    public:
      Thing();
      ~Thing();
      Thing(const Thing&);
    };
    Thing f() {
      Thing t;
      return t;
    }
    Thing t2 = f();
    

    定期返回值优化发生在临时返回时:

    class Thing {
    public:
      Thing();
      ~Thing();
      Thing(const Thing&);
    };
    Thing f() {
      return Thing();
    }
    Thing t2 = f();
    

    其他发生复制冲突的常见场所是临时性按价值传递的情况

    class Thing {
    public:
      Thing();
      ~Thing();
      Thing(const Thing&);
    };
    void foo(Thing t);
    
    foo(Thing());
    

    或者当一个异常被抛出并被值捕获时

    struct Thing{
      Thing();
      Thing(const Thing&);
    };
    
    void foo() {
      Thing c;
      throw c;
    }
    
    int main() {
      try {
        foo();
      }
      catch(Thing c) {  
      }             
    }
    

    复制省略的常见限制是:

  • 多个返回点
  • 条件初始化
  • 大多数商业级编译器都支持copy elision和(N)RVO(取决于优化设置)。

    链接地址: http://www.djcxy.com/p/72909.html

    上一篇: What are copy elision and return value optimization?

    下一篇: >, .* (C++)