Создание копий при возвращении из функции

135
01 мая 2021, 00:50

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

class A {
public:
    A() {
        cout << "construct\n";
    }
    ~A() {
        cout << "destruct\n";
    }
    A(const A &obj) {
        cout << "copy\n";
    }
};
A f() {
    A a;
    cout << &a << "\n";
    return a;
}
int main() {
    A a(f());
    cout << &a << "\n";
}

Результат его выполнения

construct
0x7ffdca371107
0x7ffdca371107
destruct

Честно говоря я без понятия почему результат такой. По идее при возвращении из функции должна создаваться копия объекта, затем при инициализации переменной в main'e должен вызываться конструктор копии этой копии объекта, в результате должны получить новый объект с новым адресом, но бит в бит такой же как его копия. Но в результате всё не так, при этом абсолютно не так. А теперь ещё для кого-то сюрприз, для кого-то нет: закомментируем обязательно и деструктор, и конструктор копии, если что-то из них останется, то это не прокатит. Теперь результат такой

construct
0x7fff090c99f7
0x7fff090c9a27

А вот это уже больше походит на то, что я написал ранее. Но всё же я не уверен, что полностью, т.к. я не могу использовать конструктор копии и деструктор, чтобы убедиться, не изменяя поведение кода. Конкретно меня интересует 2 раза ли выполнится код деструктора при инициализации переменной a в main'e. По идее 1 раз должен вызваться при завершении функции f(), а 2 после того, как инициализируется переменная a. Меня интересуют ответы на поставленные вопросы, а также логика, почему всё-таки всё работает так, а не иначе.

Answer 1

В данном случае имеют место две оптимизации:

  1. Named Returned Value Optimization - при возврате локальной переменной a, чья область видимости тут же оканчивается, компилятор ограничивается созданием только одной переменной - возвращаемым значением. Эта оптимизация не гарантирована, хотя почти всегда выполняется.
  2. Temporary materialization - rvalue, возвращаемое функцией f, материализуется сразу в переменную а без создания временного объекта. В С++17 компилятор обязан откладывать материализацию временных переменных как можно дальше, устраняя все избыточные промежуточные объекты. Так что цепочки вида A a{A{A{A{A{}}}}}; приводят к появлению только одного объекта, а не целой пачки, и не содержат вызовов копирующих или перемещающих конструкторов (которых может вообще не быть).

Таким образом, в функции main выделяется место только под один объект, который инициализируется в функции f минуя все промежуточные временные объекты. Причем компилятор пропускает вызовы конструкторов и деструкторов с побочными эффектами.

READ ALSO
Glfw С++ Ввод русского текста с клавиатуры

Glfw С++ Ввод русского текста с клавиатуры

Питаюсь вводить текст с клавиатуры и сразу отправляю в консоль, с английским без проблем но вместо русского вводить какие то цифры и английские...

152
Несколько вопросов по указателям в С++

Несколько вопросов по указателям в С++

Возникло несколько вопросов связанных с raw pointers в С++Рассмотрим следующий код

117
Чем открыть проект .cbproj?

Чем открыть проект .cbproj?

Достался проект с расширениемcbproj

119