Функциональные параметры не разрушаются при выходе этой функции

Я был уверен, что деструкторы для параметров функции должны вызываться при выходе из соответствующей функции. Рассмотрим 5.2.2p4 стандарта С++ 11:

[...] Время жизни параметра заканчивается, когда возвращается функция, в которой она определена. [...]

Однако попробуйте этот код:

#include <iostream>
using namespace std;

struct Logger {
    Logger(int) { cout << "Construct " << this << '\n'; }
    Logger(const Logger&) { cout << "Copy construct " << this << '\n'; }
    ~Logger() { cout << "Destruct " << this << '\n'; }
};

int f(Logger)
{
    cout << "Inside f\n";
    return 0;
}

int main()
{
    f(f(f(10)));
}

После компиляции с помощью gcc или clang, выход будет выглядеть следующим образом:

Construct 0x7fffa42d97ff
Inside f
Construct 0x7fffa42d97fe
Inside f
Construct 0x7fffa42d97fd
Inside f
Destruct 0x7fffa42d97fd
Destruct 0x7fffa42d97fe
Destruct 0x7fffa42d97ff

Как мы видим, все три параметра были уничтожены только после завершения последнего вызова функции. Это правильное поведение?

Ответы

Ответ 1

Выяснить, что уже можно найти в комментариях:

Учитывая int f(Logger);, когда вы пишете:

f(10);

это (концептуально) создает временный объект Logger, строит параметр функции из этого временного объекта, вызывает функцию, разрушает параметр функции и, наконец, уничтожает временный объект.

Когда вы пишете:

f(f(10));

это (концептуально) создает временный объект Logger, строит параметр функции из этого временного объекта, вызывает функцию, разрушает параметр функции, создает новый временный объект Logger, используя первый результат вызова функции, строит функции из этого временного объекта, вызывает функцию, разрушает параметр функции и, наконец, уничтожает два временных объекта.

Я не буду писать его для случая f(f(f(10)));.

Теперь эти два временных объекта можно опустить:

При выполнении определенных критериев реализация допускает опустить конструкцию копирования/перемещения объекта класса, даже если конструктор copy/move и/или деструктор для объекта имеют побочные эффекты. В таких случаях реализация рассматривает источник и цель пропущенной операции копирования/перемещения как просто два разных способа обращения к одному и тому же объекту, а уничтожение этого объекта происходит в более поздние времена, когда эти два объекта были бы разрушен без оптимизации. Это разрешение копирования/перемещения операции, называемые копией, разрешены в следующих случаях (которые могут быть объединены для устранения нескольких копий):

  • ...

  • когда объект временного класса, который не был привязан к ссылке (12.2), был бы скопирован/перенесен в объект класса с тем же самым неавтоматизированным типом, операция копирования/перемещения может быть опущена путем построения временный объект непосредственно в цель пропущенной копии/перемещения

  • ...

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

Однако, когда исключение копирования не выполняется, например, потому что вы не настроили компилятор, или потому, что в первую очередь нет копии, чтобы увидеть ее (см. ниже), тогда параметры функции действительно должны быть уничтожены, когда вы скажем, они должны быть, и вы должны увидеть "Destruct (...)" до того, как второй вызов функции начнется во всех соответствующих реализациях С++ 11.

Параметр может быть создан без временного использования с помощью фигурных скобок: вы можете повторно выполнить вызов как

f({f({f({10})})});

Здесь каждый параметр инициализируется списком, который в этом случае не включает временные объекты, и нет экземпляров для исключения. Это должно разрушить параметры функции, как только функция f вернется, прежде чем f будет вызван снова, во всех реализациях, совместимых с С++ 11, независимо от параметров командной строки -felide-constructors и того факта, что компиляторы не делайте этого, это область, в которой они не соответствуют С++ 11.

Это не так просто, как это: CWG-выпуск 1880 гласит:

WG решила не указывать, уничтожены ли объекты параметров сразу после вызова или в конце полного выражения, к которому принадлежит вызов.

Это позволит точно, что теперь делают компиляторы: параметры могут быть уничтожены после окончания полного выражения после того, как последний f вернулся. Точный буквальный текст С++ 11 не соответствует текущим компиляторам.

Ответ 2

См. стандарт С++ 11, §12.2/3, говоря

Временные объекты уничтожаются как последний шаг при оценке full-expression (1.9), который (лексически) содержит точку, в которой они были созданы.

Ответ 3

Это правильное поведение. Он следует методу First in, Last out для разрушения ресурсов. Если вы вызывали функцию последовательно, вы получили бы другой результат.

f(10)
f(10)
f(10)

Будет уничтожен следующим образом:

Construct 0x7fffa42d97ff
Inside f
Destruct 0x7fffa42d97ff
Construct 0x7fffa42d97fe
Inside f
Destruct 0x7fffa42d97fe
Construct 0x7fffa42d97fd
Inside f
Destruct 0x7fffa42d97fd

Ответ 4

Что вы ожидаете увидеть:

Construct 0x7fffa42d97ff         //  Creation of temporary object
Copy construct 0xAAAAAAAAAA      //  copy constuction of parameter
Inside f
Destruct 0xAAAAAAAAAA            // destruction of parameter.
Construct 0x7fffa42d97fe
Copy construct 0xBBBBBBBBB
Inside f
Destruct 0xBBBBBBBBB
Construct 0x7fffa42d97fd
Copy construct 0xCCCCCCCCC
Inside f
Destruct 0xCCCCCCCCC
Destruct 0x7fffa42d97fd
Destruct 0x7fffa42d97fe
Destruct 0x7fffa42d97ff          // destruction of temporary

Но компилятору разрешено удалять (удалять) структуру копирования параметров (и их деструкторов) и встроить функцию. Если вы это сделаете, единственными оставшимися объектами, которые были созданы, являются временные файлы, которые передаются в функции.

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

Если вы хотите увидеть результат выше. Затем запретите компилятору встраивать функции: См. fooobar.com/info/63764/...

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