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

C++ 什么是复制省略和返回值优化?,c++,optimization,c++-faq,return-value-optimization,copy-elision,C++,Optimization,C++ Faq,Return Value Optimization,Copy Elision,什么是复制省略?什么是(命名)返回值优化?它们意味着什么 在什么情况下会发生这种情况?限制是什么 如果你提到这个问题,你可能在寻找 有关技术概述,请参阅 看 导言 有关技术概述- 对于发生复制省略的常见情况- 拷贝省略是大多数编译器实现的一种优化,用于在某些情况下防止额外的(可能昂贵的)拷贝。它使按值返回或按值传递在实践中可行(适用限制) 这是唯一一种省略的优化形式(哈!)即使复制/移动对象有副作用,也可以应用“仿佛”规则-复制省略 以下示例取自: struct C{ C(){} C(常数C&

什么是复制省略?什么是(命名)返回值优化?它们意味着什么

在什么情况下会发生这种情况?限制是什么

  • 如果你提到这个问题,你可能在寻找
  • 有关技术概述,请参阅
导言 有关技术概述-

对于发生复制省略的常见情况-

拷贝省略是大多数编译器实现的一种优化,用于在某些情况下防止额外的(可能昂贵的)拷贝。它使按值返回或按值传递在实践中可行(适用限制)

这是唯一一种省略的优化形式(哈!)即使复制/移动对象有副作用,也可以应用“仿佛”规则-复制省略

以下示例取自:

struct C{
C(){}
C(常数C&){std::cout标准参考
对于技术性较低的视图和介绍-

对于发生复制省略的常见情况-

复制省略在标准中定义为:

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

31)当满足某些条件时,允许实现省略类的复制/移动构造 对象,即使该对象的复制/移动构造函数和/或析构函数有副作用。在这种情况下, 该实现将省略的复制/移动操作的源和目标视为两个不同的操作 指代同一对象的方式,并且该对象的销毁发生在时间较晚的时候 如果没有优化,这两个对象将被销毁。123这省略了复制/移动 在以下情况下允许进行称为复制省略的操作 消除多个副本):

-在具有类返回类型的函数中的return语句中,当表达式是 具有相同参数的非易失性自动对象(函数或catch子句参数除外) 类型作为函数返回类型,可以通过构造 自动对象直接输入函数的返回值

-在抛出表达式中,当操作数是非易失性自动对象(而不是 函数或catch子句参数),其范围不超出最内层 封闭try块(如果有),从操作数到异常的复制/移动操作 通过将自动对象直接构造到异常对象中,可以省略对象(15.1)

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

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

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

举例如下:

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

在这里,省略的条件可以组合起来,以消除对类
Thing
的复制构造函数的两个调用: 将本地自动对象
t
复制到函数
f()返回值的临时对象中
将临时对象复制到对象
t2
。有效地,本地对象
t
可以视为直接初始化全局对象
t2
,并且该对象的销毁将在程序中发生 将move构造函数添加到Thing具有相同的效果,但它是来自 被省略的
t2
的临时对象

抄袭省略的常见形式 有关技术概述-

对于技术性较低的视图和介绍-

(命名)返回值优化是一种常见的复制省略形式。它指的是从方法中通过值返回的对象的副本被省略的情况。标准中给出的示例说明了命名返回值优化,因为该对象是命名的

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) {  
  }             
}
或者当值引发并捕获异常时

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) {  
  }             
}

  • 多个返回点
  • 条件初始化

大多数商用级编译器支持复制省略和(N)RVO(取决于优化设置)。

复制省略是一种编译器优化技术,可消除不必要的对象复制/移动

在以下情况下,允许编译器忽略复制/移动操作,因此不调用关联的构造函数:

  • NRVO(命名返回值优化):如果函数按值返回类类型,并且返回语句的表达式是具有自动存储持续时间的非易失性对象的名称(不是函数参数),则可以省略将由非优化编译器执行的复制/移动。如果是这样,则返回值将直接在存储器中构造,否则函数的返回值将被移动或复制到存储器中。#include <iostream> using namespace std; class ABC { public: const char *a; ABC() { cout<<"Constructor"<<endl; } ABC(const char *ptr) { cout<<"Constructor"<<endl; } ABC(ABC &obj) { cout<<"copy constructor"<<endl;} ABC(ABC&& obj) { cout<<"Move constructor"<<endl; } ~ABC() { cout<<"Destructor"<<endl; } }; ABC fun123() { ABC obj; return obj; } ABC xyz123() { return ABC(); } int main() { ABC abc; ABC obj1(fun123());//NRVO ABC obj2(xyz123());//NRVO ABC xyz = "Stack Overflow";//RVO return 0; } **Output without -fno-elide-constructors** root@ajay-PC:/home/ajay/c++# ./a.out Constructor Constructor Constructor Constructor Destructor Destructor Destructor Destructor **Output with -fno-elide-constructors** root@ajay-PC:/home/ajay/c++# g++ -std=c++11 copy_elision.cpp -fno-elide-constructors root@ajay-PC:/home/ajay/c++# ./a.out Constructor Constructor Move constructor Destructor Move constructor Destructor Constructor Move constructor Destructor Move constructor Destructor Constructor Move constructor Destructor Destructor Destructor Destructor Destructor
  • #include <iostream>     
    int n = 0;    
    class ABC     
    {  public:  
     ABC(int) {}    
     ABC(const ABC& a) { ++n; } // the copy constructor has a visible side effect    
    };                     // it modifies an object with static storage duration    
    
    int main()   
    {  
      ABC c1(21); // direct-initialization, calls C::C(42)  
      ABC c2 = ABC(21); // copy-initialization, calls C::C( C(42) )  
    
      std::cout << n << std::endl; // prints 0 if the copy was elided, 1 otherwise
      return 0;  
    }
    
    Output without -fno-elide-constructors  
    root@ajay-PC:/home/ayadav# g++ -std=c++11 copy_elision.cpp  
    root@ajay-PC:/home/ayadav# ./a.out   
    0
    
    Output with -fno-elide-constructors  
    root@ajay-PC:/home/ayadav# g++ -std=c++11 copy_elision.cpp -fno-elide-constructors  
    root@ajay-PC:/home/ayadav# ./a.out   
    1
    
    # include <iostream>
    
    
    class Obj {
    public:
      int var1;
      Obj(){
        std::cout<<"In   Obj()"<<"\n";
        var1 =2;
      };
      Obj(const Obj & org){
        std::cout<<"In   Obj(const Obj & org)"<<"\n";
        var1=org.var1+1;
      };
    };
    
    int  main(){
    
      {
        /*const*/ Obj Obj_instance1;  //const doesn't change anything
        Obj Obj_instance2;
        std::cout<<"assignment:"<<"\n";
        Obj_instance2=Obj(Obj(Obj(Obj(Obj_instance1))))   ;
        // in fact expected: 6, but got 3, because of 'copy elision'
        std::cout<<"Obj_instance2.var1:"<<Obj_instance2.var1<<"\n";
      }
    
    }
    
    
    In   Obj()
    In   Obj()
    assignment:
    In   Obj(const Obj & org)
    Obj_instance2.var1:3