Страницы

Поиск по вопросам

среда, 5 февраля 2020 г.

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

#cpp


Всё глубже и глубже погружаясь в 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. Меня интересуют ответы
на поставленные вопросы, а также логика, почему всё-таки всё работает так, а не иначе.
    


Ответы

Ответ 1



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

Комментариев нет:

Отправить комментарий