Копировать элизию
В на C++ программировании компьютерном исключение копирования относится к методу оптимизации компилятора , который устраняет ненужное копирование объектов .
Стандарт языка C++ обычно позволяет реализациям выполнять любую оптимизацию при условии, что наблюдаемое поведение результирующей программы такое же, как если бы , т. е. притворялось, что программа выполнялась точно так, как того требует стандарт. Помимо этого, стандарт также описывает несколько ситуаций, когда копирование можно исключить, даже если это изменит поведение программы, наиболее распространенной из которых является оптимизация возвращаемого значения (см. ниже ). Другая широко реализованная оптимизация, описанная в стандарте C++ , — это копирование временного объекта типа класса в объект того же типа. [1] [2] В результате инициализация копирования обычно эквивалентна прямой инициализации с точки зрения производительности, но не семантики; для инициализации копирования по-прежнему требуется доступный конструктор копирования . [3] Оптимизацию нельзя применить к временному объекту, привязанному к ссылке.
Пример
[ редактировать ]#include <iostream>int n = 0;struct C { explicit C(int) {} C(const C&) { ++n; } // the copy constructor has a visible side effect}; // it modifies an object with static storage durationint main() { C c1(42); // direct-initialization, calls C::C(int) C c2 = C(42); // copy-initialization, calls C::C(const C&) std::cout << n << std::endl; // prints 0 if the copy was elided, 1 otherwise}
Согласно стандарту аналогичная оптимизация может быть применена к брошенным и пойманным объектам . [4] [5] но неясно, применяется ли оптимизация как к копии из выброшенного объекта в объект исключения , так и к копии из объекта исключения в объект, объявленный в объявлении исключения предложения catch . Также неясно, применяется ли эта оптимизация только к временным объектам или к именованным объектам. [6] Учитывая следующий исходный код:
#include <iostream>struct C { C() = default; C(const C&) { std::cout << "Hello World!\n"; }};void f() { C c; throw c; // copying the named object c into the exception object.} // It is unclear whether this copy may be elided (omitted).int main() { try { f(); } catch (C c) { // copying the exception object into the temporary in the // exception declaration. } // It is also unclear whether this copy may be elided (omitted).}
Поэтому соответствующий компилятор должен создать программу , которая выводит «Hello World!» дважды. В версии C++11 стандарта C++ эти проблемы были решены, по существу позволяя исключить как копирование из именованного объекта в объект исключения, так и копирование в объект, объявленный в обработчике исключений. [6]
GCC предоставляет -fno-elide-constructors
возможность отключить копирование. Эта опция полезна для наблюдения (или не наблюдения) за эффектами оптимизации возвращаемого значения или других оптимизаций, при которых копии опускаются. Обычно не рекомендуется отключать эту важную оптимизацию.
C++17 обеспечивает «гарантированное исключение копирования», значение prvalue не материализуется до тех пор, пока оно не понадобится, а затем оно создается непосредственно в хранилище конечного пункта назначения. [7]
Оптимизация возвращаемой стоимости
[ редактировать ]В контексте C++ языка программирования , оптимизация возвращаемого значения ( RVO ) — это оптимизация компилятора , которая включает в себя удаление временного объекта созданного для хранения возвращаемого значения функции . [8] RVO разрешено изменять наблюдаемое поведение результирующей программы по стандарту C++ . [9]
Краткое содержание
[ редактировать ]В общем, стандарт C++ позволяет компилятору выполнять любую оптимизацию при условии, что результирующий исполняемый файл демонстрирует такое же наблюдаемое поведение , как если бы (то есть делал вид, что) все требования стандарта были выполнены. Это обычно называют « правилом как если бы ». [10] [2] Термин «оптимизация возвращаемого значения» относится к специальному предложению стандарта C++ , которое идет даже дальше, чем правило «как если бы»: реализация может опускать операцию копирования, следующую из оператора return , даже если конструктор копирования имеет побочные эффекты . [1] [2]
В следующем примере показан сценарий, в котором реализация может исключить одну или обе копии, даже если конструктор копирования имеет видимый побочный эффект (печать текста). [1] [2] Первой копией, которую можно удалить, является та, в которой находится безымянный временный объект. C
можно скопировать в функцию f
значение возвращаемое . Вторая копия, которую можно удалить, — это копия временного объекта, возвращаемого f
к obj
.
#include <iostream>struct C { C() = default; C(const C&) { std::cout << "A copy was made.\n"; }};C f() { return C();}int main() { std::cout << "Hello World!\n"; C obj = f();}
В зависимости от компилятора и его настроек результирующая программа может отображать любой из следующих выходных данных:
Hello World!A copy was made.A copy was made.
Hello World!A copy was made.
Hello World!
Фон
[ редактировать ]Возврат объекта встроенного типа из функции обычно практически не требует накладных расходов, поскольку объект обычно помещается в регистр ЦП . Возврат более крупного объекта типа класса может потребовать более дорогостоящего копирования из одной области памяти в другую. Чтобы избежать этого, реализация может создать скрытый объект во фрейме стека вызывающей стороны и передать адрес этого объекта функции. Возвращаемое значение функции затем копируется в скрытый объект. [11] Таким образом, такой код:
struct Data { char bytes[16]; };Data F() { Data result = {}; // generate result return result;}int main() { Data d = F();}
может сгенерировать код, эквивалентный этому:
struct Data { char bytes[16];};Data* F(Data* _hiddenAddress) { Data result = {}; // copy result into hidden object *_hiddenAddress = result; return _hiddenAddress;}int main() { Data _hidden; // create hidden object Data d = *F(&_hidden); // copy the result into d}
что вызывает Data
объект, который нужно скопировать дважды.
На ранних этапах развития C++ неспособность языка эффективно возвращать объект типа класса из функции считалась его слабостью. [12] Примерно в 1991 году Уолтер Брайт реализовал метод минимизации копирования, эффективно заменяя скрытый объект и именованный объект внутри функции объектом, используемым для хранения результата: [13]
struct Data { char bytes[16];};void F(Data* p) { // generate result directly in *p}int main() { Data 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"); // the function may return one of two named objects // depending on its argument. RVO might not be applied return cond ? first : second;}int main() { std::string result = F();}
Внешние ссылки
[ редактировать ]Ссылки
[ редактировать ]- ^ Jump up to: а б с ИСО / МЭК (2003). ISO/IEC 14882:2003(E): Языки программирования – C++ §12.8 Копирование объектов класса [class.copy], параграф. 15
- ^ Jump up to: а б с д ИСО / МЭК (2003). «§ 12.8 Копирование объектов класса [class.copy]». ISO/IEC 14882:2003(E): Языки программирования — C++ (PDF) . пункт. 15. Архивировано из оригинала (PDF) 10 апреля 2023 г. Проверено 26 февраля 2024 г.
- ^ Саттер, Херб (2001). Еще более исключительный C++ . Аддисон-Уэсли.
- ^ ИСО / МЭК (2003). ISO/IEC 14882:2003(E): Языки программирования – C++ §15.1 Выдача исключения [Exception.throw], параграф. 5
- ^ ИСО / МЭК (2003). ISO/IEC 14882:2003(E): Языки программирования – C++ §15.3 Обработка исключения [Exception.handle], параграф. 17
- ^ Jump up to: а б «Отчеты о дефектах стандартного базового языка C++» . РГ21 . Проверено 27 марта 2009 г.
- ^ https://en.cppreference.com/w/cpp/language/copy_elision
- ^ Jump up to: а б Мейерс, Скотт (1995). Более эффективный C++ . Аддисон-Уэсли. ISBN 9780201633719 .
- ^ Александреску, Андрей (01 февраля 2003 г.). «Перемещение конструкторов» . Журнал доктора Добба . Проверено 25 марта 2009 г.
- ^ ИСО / МЭК (2003). ISO/IEC 14882:2003(E): Языки программирования – C++ §1.9 Выполнение программы [intro.execution], параграф. 1
- ^ Jump up to: а б Булька, Дов; Дэвид Мэйхью (2000). Эффективный С++ . Аддисон-Уэсли. ISBN 0-201-37950-3 .
- ^ Jump up to: а б Липпман, Стэн (3 февраля 2004 г.). «Оптимизация возвращаемого значения имени» . Майкрософт . Проверено 23 марта 2009 г.
- ^ Jump up to: а б «Глоссарий языка программирования D 2.0» . Цифровой Марс . Проверено 23 марта 2009 г.
- ^ Jump up to: а б Шукри, Айман Б. (октябрь 2005 г.). «Оптимизация именованного возвращаемого значения в Visual C++ 2005» . Майкрософт . Проверено 20 марта 2009 г.
- ^ «Параметры, управляющие диалектом C++» . ССЗ . 17 марта 2001 г. Проверено 20 января 2018 г.
- ^ Хиннант, Ховард; и др. (10 сентября 2002 г.). «N1377: Предложение добавить поддержку семантики перемещения в язык C++» . РГ21 . Проверено 25 марта 2009 г.