stringtranslate.com

Копировать элизию

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

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

Пример

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

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

#include <iostream> struct C { C () = default ; C ( const C & ) { std :: cout << "Привет, мир! \n " ; } };            void f () { C c ; throw c ; // копирование именованного объекта c в объект исключения. } // Неясно, можно ли опустить (пропустить) эту копию.        int main () { try { f (); } catch ( C c ) { // копирование объекта исключения во временный объект в // объявлении исключения. } // Также неясно, можно ли опустить (пропустить) эту копию. }              

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

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

C++17 обеспечивает «гарантированное исключение копирования», prvalue не материализуется до тех пор, пока не понадобится, а затем оно создается непосредственно в хранилище его конечного назначения. [7]

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

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

Краткое содержание

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

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

#include <iostream> struct C { C () = default ; C ( const C & ) { std :: cout << "Была сделана копия. \n " ; } };            С ф () { вернуть С (); }    int main () { std :: cout << "Привет, мир! \n " ; C obj = f (); }         

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

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

Фон

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

struct Data { char bytes [ 16 ]; };      Данные F () { Данные результат = {}; // сгенерировать результат вернуть результат ; }         int main () { Данные d = F (); }      

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

struct Data { char bytes [ 16 ]; };    Data * F ( Data * _hiddenAddress ) { Data result = {}; // копируем результат в скрытый объект * _hiddenAddress = result ; return _hiddenAddress ; }             int main () { Data _hidden ; // создать скрытый объект Data d = * F ( & _hidden ); // скопировать результат в d }          

что приводит к тому, Dataчто объект копируется дважды.

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

struct Data { char bytes [ 16 ]; };    void F ( Data * p ) { // сгенерировать результат непосредственно в *p }    int main () { Данные d ; F ( & d ); }     

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

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

Оптимизация возвращаемого значения поддерживается большинством компиляторов. [8] [14] [15] Однако могут быть обстоятельства, при которых компилятор не может выполнить оптимизацию. Один из распространенных случаев — когда функция может возвращать различные именованные объекты в зависимости от пути выполнения: [11] [14] [16]

#include <string> std :: string F ( bool cond = false ) { std :: string first ( "first" ); std :: string second ( "second" ); // функция может возвращать один из двух именованных объектов // в зависимости от ее аргумента. RVO может не применяться return cond ? first : second ; }                  int main () { std :: string result = F (); }      

Внешние ссылки

Ссылки

  1. ^ abc ISO / IEC (2003). ISO/IEC 14882:2003(E): Языки программирования - C++ §12.8 Копирование объектов класса [class.copy] параграф 15
  2. ^ abcd ISO / IEC (2003). "§ 12.8 Копирование объектов класса [class.copy]". ISO/IEC 14882:2003(E): Языки программирования - C++ (PDF) . параграф 15. Архивировано из оригинала (PDF) 2023-04-10 . Получено 2024-02-26 .
  3. ^ Саттер, Херб (2001). Еще более исключительный C++ . Эддисон-Уэсли.
  4. ^ ISO / IEC (2003). ISO/IEC 14882:2003(E): Языки программирования - C++ §15.1 Выброс исключения [except.throw] параграф 5
  5. ^ ISO / IEC (2003). ISO/IEC 14882:2003(E): Языки программирования - C++ §15.3 Обработка исключения [except.handle] параграф 17
  6. ^ ab "Отчеты о дефектах стандартного основного языка C++". WG21 . Получено 27.03.2009 .
  7. ^ https://en.cppreference.com/w/cpp/language/copy_elision [ пустой URL-адрес ]
  8. ^ ab Meyers, Scott (1995). Более эффективный C++ . Addison-Wesley. ISBN 9780201633719.
  9. ^ Александреску, Андрей (1 февраля 2003 г.). «Перемещение конструкторов». Журнал доктора Добба . Проверено 25 марта 2009 г.
  10. ^ ISO / IEC (2003). ISO/IEC 14882:2003(E): Языки программирования - C++ §1.9 Выполнение программы [введение.выполнение] параграф 1
  11. ^ ab Bulka, Dov; David Mayhew (2000). Эффективный C++ . Addison-Wesley. ISBN 0-201-37950-3.
  12. ^ ab Липпман, Стэн (2004-02-03). "Оптимизация возвращаемого значения имени". Microsoft . Получено 2009-03-23 ​​.
  13. ^ ab "Глоссарий языка программирования D 2.0". Digital Mars . Получено 23.03.2009 .
  14. ^ ab Shoukry, Ayman B. (октябрь 2005 г.). "Оптимизация именованных возвращаемых значений в Visual C++ 2005". Microsoft . Получено 2009-03-20 .
  15. ^ "Параметры управления диалектом C++". GCC . 2001-03-17 . Получено 2018-01-20 .
  16. ^ Хиннант, Говард и др. (10.09.2002). "N1377: Предложение добавить поддержку семантики перемещения в язык C++". WG21 . Получено 25.03.2009 .