Поліпшення повернення значення

Поліпшення повернення значення (англ. return value optimization, RVO) чи просто ППЗ — це техніка оптимізації, яку застосовує компілятор, що серед іншого виключає створення тимчасового об'єкта для збереження значення, що вертається з функції.[1] В C++, вона особливо примітна тим, що дозволяє змінити поведінку отриманої програми.[2]

Підсумок

[ред. | ред. код]

Загалом, стандарт С++ дозволяє компіляторам застосовувати будь-які оптимізації, допоки отриманий виконуваний файл поводиться так, наче всі вимоги стандарту було виконано. Цей підхід зазвичай згадується як правило "так наче" (англ. as-if rule).[3] Термін поліпшення повернення значення звертається до пункту в ISO/IEC 14882, який дозволяє реалізації опустити копіювання, що відбувається через інструкцію повернення, навіть якщо конструктор копіювання має побічні ефекти,[4], хоча це й не дозволяється самим правилом так наче.[3]

Наступний приклад показує хід дій, за якого реалізація може позбутись одного чи двох копіювань, навіть якщо конструктор копіювання має видимі побічні ефекти, приміром, роздрук тексту.[4] Перше копіювання якого можна уникнути - копіювання створеного екземпляру класу C() у значення, що поверне функція f. Друге - копіювання тимчасового об'єкта повернутого функцією f у obj.

#include <iostream>  struct C {   C() {}   C(const C&) { std::cout << "Відбулось копіювання.\n"; } };  C f() {   return C(); }  int main() {   std::cout << "Здоровенькі були!\n";   C obj = f(); } 

Залежно від компілятора та його налаштувань, програма може вивести такий текст:

Здоровенькі були! Відбулось копіювання. Відбулось копіювання.
Здоровенькі були! Відбулось копіювання.
Здоровенькі були!

Пояснення

[ред. | ред. код]

При повернені вбудованого типу з функції додаткових операцій зазвичай не виконується, оскільки об'єкт передається через регістр. Вертання більших об'єктів (екземпляру деякого класу) може вимагати копіювання з однієї місцини в пам'яті до іншої. Для цього у стековому кадрі створюється прихований об'єкт, адреса якого передається у функцію. Значення що повертається з функції копіюється в цей об'єкт за наданою адресою.[5] Отже, для коду на кшталт:

struct Data {    char bytes[16];  };  Data f() {   Data result = {};   // утворення результату   return result; }  int main() {   Data 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++, нездатність мови ефективно повернути об'єкт класу з функції вважалась одним з її недоліків.[6] Близько 1991, Волтер Брайт винайшов спосіб зменшення кількості копіювань, використовуючи замість двох об'єктів (прихованого та локальної змінної функції f) один - у який зберігається результат:[7]

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

Брайт реалізував своє поліпшення в компіляторі Zortech C++.[6] Цей особливий спосіб був з часом названий «Поліпшення повернення іменованого значення» (англ. named return value optimization), вказуючи на той факт, що копіювання іменованого об'єкта скасоване.[7]

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

[ред. | ред. код]

Поліпшення повернення значення підтримується більшістю компіляторів[1][8][9]. Однак можливі умови, коли компілятор не може виконати поліпшення. Найпоширенішим є приклад, коли функція повертає різні значення в залежності від шляху виконання програми: [8][10][5]

#include <string> std::string f(bool cond = false) {   std::string first("first");   std::string second("second");   // функція може повернути один із двох іменованих об'єктів   // залежно від параметра. ППЗ не може бути застосоване   return cond ? first : second; }  int main() {   std::string result = f(); } 

Примітки

[ред. | ред. код]
  1. а б Meyers, Scott (1996). More Effective C++. Addison Wesley.
  2. Alexandrescu, Andrei (1 лютого 2003). Move Constructors. Dr. Dobbs Journal. Архів оригіналу за 14 липня 2013. Процитовано 25 березня 2009.
  3. а б ISO/IEC (2003). ISO/IEC 14882:2003(E): Programming Languages - C++ §1.9 Program execution [intro.execution] para. 1
  4. а б ISO/IEC (2003). ISO/IEC 14882:2003(E): Programming Languages - C++ §12.8 Copying class objects [class.copy] para. 15
  5. а б Bulka, Dov; David Mayhew (2000). Efficient C++. Addison-Wesley. ISBN 0-201-37950-3.
  6. а б Lippman, Stan. The Name Return Value Optimization. Stan Lippman. Архів оригіналу за 14 липня 2013. Процитовано 23 березня 2009.
  7. а б Glossary D Programming Language 2.0. Digital Mars. Архів оригіналу за 14 липня 2013. Процитовано 23 березня 2009.
  8. а б Shoukry, Ayman B. Named Return Value Optimization in Visual C++ 2005. Microsoft. Архів оригіналу за 14 липня 2013. Процитовано 20 березня 2009.
  9. Options Controlling C++ Dialect. GCC. 17 березня 2001. Архів оригіналу за 14 липня 2013. Процитовано 20 березня 2009.
  10. Hinnant, Howard; et al. (10 вересня 2002). N1377: A Proposal to Add Move Semantics Support to the C++ Language. WG21. Архів оригіналу за 8 лютого 2007. Процитовано 25 березня 2009.