Что такое оптимизация копирования и возврата стоимости?

Что такое копирование elision? Что такое (именованное) оптимизация возвращаемого значения? Что они означают?

в каких ситуациях они могут возникать? Что такое ограничения?

4 ответов


введение

техническое описание перейти к этому ответу.

для распространенных случаев, когда происходит копирование elision -перейти к этому ответу.

Copy elision-это оптимизация, реализованная большинством компиляторов для предотвращения дополнительных (потенциально дорогостоящих) копий в определенных ситуациях. Это делает возврат по стоимости или по стоимости возможным на практике (применяются ограничения).

Это единственная форма оптимизации элидес (ха!) The as-if rule -копировать 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 не позволяет копировать объекты, которые обычно не копируются, например, потому, что у них есть частный или удаленный конструктор копирования/перемещения.

C++17: начиная с C++17, Copy Elision гарантируется, когда объект возвращается напрямую:

struct C {
  C() {}
  C(const C&) { std::cout << "A copy was made.\n"; }
};

C f() {
  return C(); //Definitely performs copy elision
}
C g() {
    C c;
    return c; //Maybe performs copy elision
}

int main() {
  std::cout << "Hello World!\n";
  C obj = f(); //Copy constructor isn't called
}

стандартные

для менее технического просмотра и введения -перейти к этому ответу.

для распространенных случаев, когда происходит копирование elision -перейти к этому ответу.

копировать elision определяется в стандарте в:

12.8 копирование и перемещение объектов класса [class.копировать]

as

31) при соблюдении определенных критериев, реализация разрешено опустить конструкцию копирования / перемещения класса объект, даже если конструктор копирования/перемещения и/или деструктор объекта имеют побочные эффекты. В таких случаях, реализация рассматривает источник и цель опущенной операции копирования / перемещения как просто два разных способы обращения к одному и тому же объекту и уничтожение этого объекта происходит в более поздние времена когда два объекта были бы уничтожены без оптимизации.123 это elision копировать / переместить операции, называется копировать elision, допускается в следующих случаях (которые могут быть объединены в исключить несколько копий):

- в оператор return в функции класса Тип возврата, когда выражение является именем энергонезависимой автоматический объект (кроме функции или предложении catch параметр) с тем же cvunqualified типа, как функция возвращает тип, операции копирования/перемещения может быть опущен за счет строительства этот автоматических объекта непосредственно в возвращаемое значение функции

- в выражении throw, когда операнд является именем энергонезависимого автоматического объекта (кроме функции или предложении catch параметр) рамки которого не выходит за пределы внутреннего заключая try-block (Если он есть), операция копирования / перемещения из операнда в исключение объект (15.1) можно опустить, построив автоматический объект непосредственно в объект исключения

- когда временный объект класса, который не был привязан к ссылке (12.2) будут скопированы/перемещены для объекта класса с тем же типом cv-unqualified операция копирования / перемещения может быть опущена построение временного объекта непосредственно в целевой объект опущенной копии / перемещения

- когда объявление исключения обработчика исключений (пункт 15) объявляет объект того же типа (за исключением cv-квалификации) в качестве объекта исключения (15.1), копирование / перемещение операция может быть опущена рассматривая объявление исключения как псевдоним для объекта исключения, если значение программы будет неизменным, за исключением выполнения конструкторов и деструкторов для объекта, объявленного исключение-декларация.

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, и уничтожение этого объекта произойдет в программе выход. Добавление конструктора перемещения к вещи имеет тот же эффект, но это конструкция перемещения из временный объект для t2 что является упраздненным.


общие формы копирования elision

техническое описание перейти к этому ответу.

для менее технического просмотра и введения -перейти к этому ответу.

(Named) оптимизация возвращаемого значения является распространенной формой копирования 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();

другие общие места, где происходит копирование elision, когда temporary передается по значению:

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

Общие ограничения копирования elision являются:

  • несколько точек
  • условное инициализации

большинство коммерческих компиляторов поддерживают copy elision & (N)RVO (в зависимости от настроек оптимизации).


Copy elision-это метод оптимизации компилятора, который устраняет ненужное копирование / перемещение объектов.

в следующих случаях компилятору разрешено опустить операции копирования/перемещения и, следовательно, не вызывать связанный конструктор:

  1. NRVO (оптимизация возвращаемого значения): если функция возвращает тип класса по значению, а выражение оператора return - это имя энергонезависимого объекта с автоматическим хранением duration (который не является параметром функции), то копирование/перемещение, которое будет выполняться не оптимизирующим компилятором, может быть опущено. Если это так, возвращаемое значение создается непосредственно в хранилище, в которое в противном случае будет перемещено или скопировано возвращаемое значение функции.
  2. RVO (оптимизация возвращаемого значения): если функция возвращает безымянный временный объект, который будет перемещен или скопирован в место назначения наивным компилятором, копирование или перемещение можно опустить как за 1.
#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  

даже когда происходит копирование elision и copy- / move-конструктор не вызывается, он должен присутствовать и доступен (как будто никакой оптимизации не произошло вообще), в противном случае программа плохо сформирована.

вы должны разрешить такое копирование только в тех местах, где это не повлияет на наблюдаемое поведение вашего программного обеспечения. Копирование elision является единственной формой оптимизации разрешено иметь (т. е. elide) наблюдаемые побочные эффекты. Пример:

#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

GCC предоставляет -fno-elide-constructors возможность отключить копирование elision. Если вы хотите избежать возможного копирования elision, используйте -fno-elide-constructors.

теперь почти все компиляторы предоставляют copy elision, когда оптимизация включена (и если нет другой опции для ее отключения).

вывод

С каждым elision экземпляра, одна конструкция и одно сопрягая разрушение экземпляра опущены, таким образом сохраняющ CPU время, и один предмет не создан, таким образом экономия места на стеке.