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

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

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

286

Решение

Вступление

Для технического обзора — перейти к этому ответу.

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

Копирование 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();
}

В зависимости от компилятора & настройки, следующие выводы все действительны:

Привет, мир!
Копия была сделана.
Копия была сделана.


Привет, мир!
Копия была сделана.


Привет, мир!

Это также означает, что можно создавать меньше объектов, поэтому вы также не можете полагаться на определенное количество вызываемых деструкторов. У вас не должно быть критической логики внутри конструкторов копирования / перемещения или деструкторов, так как вы не можете полагаться на их вызов.

Если вызов конструктора копирования или перемещения исключен, этот конструктор все еще должен существовать и быть доступным. Это гарантирует, что исключение копирования не позволяет копировать объекты, которые обычно не копируются, например потому что у них есть приватный или удаленный конструктор копирования / перемещения.

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
}
186

Другие решения

Стандартная ссылка

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

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

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

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

как

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

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

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

81

Распространенные формы исключения

Для технического обзора — перейти к этому ответу.

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

(Named) Оптимизация возвращаемого значения является распространенной формой исключения из копирования. Это относится к ситуации, когда объект, возвращаемый значением из метода, получает свою копию. Пример, изложенный в стандарте, иллюстрирует оптимизация именованных возвращаемых значений, так как объект назван.

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

Общие ограничения права на копирование:

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

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

75

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

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

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

Даже если исключение копии имеет место, а конструктор копирования / перемещения не вызывается, он должен присутствовать и быть доступным (как если бы оптимизация вообще не происходила), в противном случае программа некорректна.

Вы должны разрешить такое копирование только в тех местах, где это не повлияет на наблюдаемое поведение вашего программного обеспечения. Исключение копирования является единственной формой оптимизации, которой разрешено иметь (то есть 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.
Если вы хотите избежать возможного копирования, используйте -fno-elide-constructors,

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

При каждом исключении копии одна конструкция и одно соответствующее уничтожение копии опускаются, что экономит время ЦП, а один объект не создается, тем самым экономя место на фрейме стека.

39
По вопросам рекламы [email protected]