Копировать elision - Copy elision

В C ++ компьютерное программирование, копировать элизию относится к оптимизация компилятора техника, устраняющая ненужные копирование объектов. Стандарт языка C ++ обычно позволяет реализациям выполнять любую оптимизацию при условии, что наблюдаемое поведение результирующей программы такое же. будто, то есть притворяясь, что программа была выполнена точно так, как предписано стандартом.

Стандарт также описывает несколько ситуаций, в которых можно исключить копирование, даже если это изменит поведение программы, наиболее распространенной из которых является оптимизация возвращаемого значения. Еще одна широко применяемая оптимизация, описанная в Стандарт C ++, это когда временный объект из тип класса копируется в объект того же типа.[1] Как результат, копирование-инициализация обычно эквивалентно прямая инициализация по производительности, но не по семантике; копирование-инициализация по-прежнему требует доступный конструктор копирования.[2] Оптимизация не может быть применена к временному объекту, привязанному к ссылке.

Пример

#включают <iostream>int п = 0;структура C {  явный C(int) {}  C(const C&) { ++п; }  // конструктор копирования имеет видимый побочный эффект};                      // модифицирует объект со статической продолжительностью храненияint главный() {  C c1(42);      // прямая инициализация, вызывает C :: C (42)  C c2 = C(42);  // инициализация копирования, вызывает C :: C (C (42))  стандартное::cout << п << стандартное::конец;  // выводит 0, если копия была удалена, 1 в противном случае}

Согласно стандарту аналогичная оптимизация может применяться к объектам, находящимся в брошенный и пойманный,[3][4] но неясно, применяется ли оптимизация как к копии из брошенного объекта в объект исключения, и копия из объект исключения к объекту, объявленному в исключение-объявление из оговорка. Также неясно, применяется ли эта оптимизация только к временные объекты, или именованные объекты.[5] Учитывая следующий исходный код:

#включают <iostream>структура C {  C() = дефолт;  C(const C&) { стандартное::cout << "Привет, мир! п"; }};пустота ж() {  C c;  бросать c;  // копирование именованного объекта c в объект исключения.}  // Неясно, можно ли исключить эту копию (опустить).int главный() {  пытаться {    ж();  } ловить (C c) {  // копируем объект исключения во временное хранилище                   // объявление исключения.  }  // Также неясно, можно ли исключить эту копию (опустить).}

Соответствующий компилятор поэтому должен произвести программа который печатает "Hello World!" дважды. В текущей версии стандарта C ++ (C ++ 11 ), проблемы были решены, что позволило исключить как копию именованного объекта в объект исключения, так и копию в объект, объявленный в обработчике исключения.[5]

GCC предоставляет ‑Fno ‑ elide ‑ constructors возможность отключить копирование-элизию. Эта опция полезна, чтобы наблюдать (или не наблюдать) эффекты оптимизации возвращаемого значения или других оптимизаций, при которых копии опускаются. Обычно не рекомендуется отключать эту важную оптимизацию.

Оптимизация возвращаемого значения

В контексте C ++ язык программирования, оптимизация возвращаемого значения (RVO) это оптимизация компилятора это предполагает устранение временный объект создан для проведения функция возвращаемое значение.[6] RVO особенно примечателен тем, что ему разрешено изменять наблюдаемое поведение результирующего программа посредством Стандарт C ++.[7]

Резюме

В целом стандарт C ++ позволяет компилятор проводить любую оптимизацию, если в результате исполняемый файл демонстрирует такое же наблюдаемое поведение будто (т. е. притворяться), что выполнены все требования стандарта. Это обычно называется "как если бы правило ".[8] Период, термин оптимизация возвращаемого значения ссылается на специальный пункт в Стандарт C ++ это идет даже дальше, чем правило «как если бы»: реализация может опустить операцию копирования, являющуюся результатом заявление о возврате, даже если конструктор копирования имеет побочные эффекты.[1]

В следующем примере демонстрируется сценарий, в котором реализация может исключить одну или обе создаваемые копии, даже если конструктор копирования имеет видимый побочный эффект (печать текста).[1] Первая копия, которую можно удалить, - это та, где безымянный временный C можно скопировать в функцию жс возвращаемое значение. Вторая копия, которую можно удалить, - это копия временного объекта, возвращенная ж к объект.

#включают <iostream>структура C {  C() = дефолт;  C(const C&) { стандартное::cout << "Копия была сделана. п"; }};C ж() {  возвращаться C();}int главный() {  стандартное::cout << "Привет, мир! п";  C объект = ж();}

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

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

Фон

Возврат объекта встроенный тип из функция обычно не несет никаких накладных расходов, так как объект обычно помещается в Регистр процессора. Возврат более крупного объекта тип класса может потребоваться более дорогое копирование из одной области памяти в другую. Чтобы избежать этого, реализация может создать скрытый объект в вызывающей кадр стека, и передать адрес этого объекта функции. Затем возвращаемое значение функции копируется в скрытый объект.[9] Таким образом, такой код:

структура Данные {   char байты[16]; };Данные F() {  Данные результат = {};  // генерируем результат  возвращаться результат;}int главный() {  Данные d = F();}

может сгенерировать код, эквивалентный этому:

структура Данные {  char байты[16];};Данные* F(Данные* _hiddenAddress) {  Данные результат = {};  // копируем результат в скрытый объект  *_hiddenAddress = результат;  возвращаться _hiddenAddress;}int главный() {  Данные _скрытый;           // создаем скрытый объект  Данные d = *F(&_скрытый);  // копируем результат в d}

что вызывает Данные объект, который нужно скопировать дважды.

На ранних этапах эволюции C ++, неспособность языка эффективно вернуть объект тип класса от функции считалось слабым местом.[10] Примерно в 1991 г. Уолтер Брайт реализовал метод минимизации копирования, эффективно заменяя скрытый объект и именованный объект внутри функции на объект, используемый для хранения результата:[11]

структура Данные {  char байты[16];};пустота F(Данные* п) {  // генерируем результат прямо в * p}int главный() {  Данные d;  F(&d);}

Брайт реализовал эту оптимизацию в своем Zortech C ++ компилятор.[10] Этот конкретный метод позже был придуман как «оптимизация именованного возвращаемого значения», имея в виду тот факт, что копирование именованного объекта исключается.[11]

Поддержка компилятора

Оптимизация возвращаемого значения поддерживается большинством компиляторов.[6][12][13] Однако могут быть обстоятельства, при которых компилятор не может выполнить оптимизацию. Один из распространенных случаев - когда функция может возвращать разные именованные объекты в зависимости от пути выполнения:[9][12][14]

#включают <string>стандартное::нить F(bool cond = ложный) {  стандартное::нить первый("первый");  стандартное::нить второй("второй");  // функция может возвращать один из двух именованных объектов  // в зависимости от аргумента. RVO может не применяться  возвращаться cond ? первый : второй;}int главный() {  стандартное::нить результат = F();}

внешняя ссылка

Рекомендации

  1. ^ а б c ISO /IEC (2003). ISO / IEC 14882: 2003 (E): Языки программирования - C ++ §12.8 Копирование объектов класса [class.copy] пункт 15
  2. ^ Саттер, Херб (2001). Более исключительный C ++. Эддисон-Уэсли.
  3. ^ ISO /IEC (2003). ISO / IEC 14882: 2003 (E): Языки программирования - C ++ §15.1 Создание исключения [except.throw] пункт 5
  4. ^ ISO /IEC (2003). ISO / IEC 14882: 2003 (E): Языки программирования - C ++ §15.3 Обработка исключения [except.handle] пункт 17
  5. ^ а б «Отчеты о дефектах стандартного основного языка C ++». WG21. Получено 2009-03-27.
  6. ^ а б Мейерс, Скотт (1995). Более эффективный C ++. Эддисон-Уэсли.
  7. ^ Александреску, Андрей (01.02.2003). «Конструкторы перемещения». Журнал доктора Добба. Получено 2009-03-25.
  8. ^ ISO /IEC (2003). ISO / IEC 14882: 2003 (E): Языки программирования - C ++ §1.9 Выполнение программы [intro.execution] пункт 1
  9. ^ а б Булка, Дов; Дэвид Мэйхью (2000). Эффективный C ++. Эддисон-Уэсли. ISBN  0-201-37950-3.
  10. ^ а б Липпман, Стэн (2004-02-03). "Оптимизация возвращаемого значения имени". Microsoft. Получено 2009-03-23.
  11. ^ а б «Глоссарий языка программирования D 2.0». Цифровой Марс. Получено 2009-03-23.
  12. ^ а б Шукри, Айман Б. (октябрь 2005 г.). «Оптимизация именованных возвращаемых значений в Visual C ++ 2005». Microsoft. Получено 2009-03-20.
  13. ^ «Параметры, управляющие диалектом C ++». GCC. 2001-03-17. Получено 2018-01-20.
  14. ^ Хиннант, Ховард; и другие. (2002-09-10). «N1377: Предложение о добавлении поддержки семантики перемещения в язык C ++». WG21. Получено 2009-03-25.