В данной небольшой статье я предлагаю рассмотреть как работает принцип RVO (return value optimization) в компиляторе gcc. Автор статьи не претендует на уникальность и какую-то новизну. Ориентировано на начинающих и представляет собой больше некую заметку.
Итак, рассмотрим класс и код, его использующий:
#include <iostream> // Для предотвращения закрытия окна консоли сразу после всех вычислений void WaitForAnyKey() { static std::string q; std::cout << "Press any key to continue...\n"; std::cin >> q; } namespace my { using std::cout; using std::cin; using std::endl; // Класс для эксперимента struct Dummy { Dummy() { cout << "[" << ((unsigned)this) << "]: " << "c'tor" << endl; } ~Dummy() { cout << "[" << ((unsigned)this) << "]: " << "d'tor" << endl; } Dummy(const Dummy& oth) { cout << "[" << ((unsigned)this) << "]: " << "copy c'tor from [" << ((unsigned)&oth) << "]" << endl; } Dummy(Dummy&& oth) { cout << "[" << ((unsigned)this) << "]: " << "move c'tor from [" << ((unsigned)&oth) << "]" << endl; } Dummy& operator=(const Dummy& oth) { cout << "[" << ((unsigned)this) << "]: " << "copy assignment from [" << ((unsigned)&oth) << "]" << endl; return *this; } Dummy& operator=(Dummy&& oth) { cout << "[" << ((unsigned)this) << "]: " << "move assignment from [" << ((unsigned)&oth) << "]" << endl; return *this; } }; Dummy ExampleRVO() { return Dummy(); } void test() { cout << "my::test()\n"; cout << "==========\n"; { ExampleRVO(); } cout << "my::test() -- end\n"; cout << "=================\n"; } } // end of my int main() { my::test(); WaitForAnyKey(); return 0; }
Я использовал вывод this и адреса oth для того, чтобы было понятно, конструктор какого именно объекта вызывается и ссылка на какой объект передается. Компилируется это дело в gcc так. Сохраняется файл с именем, скажем sample01.cpp и вызывается в командной строке следующая команда:
g++ sample01.cpp -osample01 -std=c++11 -fno-elide-constructors -fpermissive
где опция -fno-elide-constructors подавляет RVO, а -fpermissive позволяет забить на ошибки приведения this к unsigned.
Вывод после запуска собранного приложения такой:
my::test() ========== [2453666255]: c'tor [2453666335]: move c'tor from [2453666255] [2453666255]: d'tor [2453666335]: d'tor my::test() -- end ================= Press any key to continue...
Видно, что return Dummy() вызывает конструктор по умолчанию при создании объекта с адресом 2453666255, который затем передается в перемещающий конструктор для создания объекта с адресом 2453666335. Вызов функции ExampleRVO() помещен в блок, чтобы временные возвращенные объекты был уничтожены при выходе из блока в тело функции test() и мы смогли увидеть работу деструкторов. Объект с адресом 2453666255 существует от момента вызова return после конструирования этого объекта и до конца работы перемещающего конструктора, вызываемого при создании объекта с адресом 2453666335. При выходе из тела перемещающего конструктора объекта с адресом 2453666335, вызывается деструктор объекта с адресом 2453666255. Далее при выходе из блока, в котором происходит вызов функции ExampleRVO(), происходит вызов деструктора и для объекта с адресом 2453666335.
То, что функция ExampleRVO() возвращает объект в никуда вовсе не означает, что его нет. Он есть - на стеке функции test(), но без имени.

Теперь добавим локальный объект в функции test() с явным именем:
Dummy dummy = ExampleRVO(); cout << "[" << ((unsigned)&dummy) << "]: in test()" << endl;
my::test() ========== [2164258975]: c'tor [2164259055]: move c'tor from [2164258975] [2164258975]: d'tor [2164259054]: move c'tor from [2164259055] [2164259055]: d'tor [2164259054]: in test() [2164259054]: d'tor my::test() -- end ================= Press any key to continue...
Поменялись адреса, так как был перезапуск перестроенного приложения. Но видно, что добавился некий третий объект, который и есть та самая локальная переменная dummy с адресом 2164259054. Получается, что функция ExampleRVO() конструирует на стеке test() сначала невидимый безымянный объект (в данном случае объект с адресом 2164259055) из объекта с адресом 2164258975, а уже потом использует объект с адресом 2164259055 для конструирования именованного локального объекта dummy с адресом 2164259054. Весьма странное в плане оптимизации поведение. "Вери стрейндж ситуэйшн!".
Именно, вследствие таких вещей и было придумано и введено понятие RVO. Ясно, что если у нас есть выражение конструирования локального объекта тем объектом, что возвращает функция ExampleRVO() (которая сама, в свою очередь, возвращает то, что возвращает конструктор), нет необходимости в создании невидимых безымянных объектов. Итак, собираем теперь проект такой строкой:
g++ sample01.cpp -osample01 -std=c++11 -fpermissive
my::test() ========== [4223663343]: c'tor [4223663343]: in test() [4223663343]: d'tor my::test() -- end ================= Press any key to continue...
Красота! Мы теперь имеем дело только с именованным объектом, что и ожидается нами.
Спасибо за чтение! Приятного дня!
