При передаче класса по значению вызывающая или вызывающая сторона вызывает деструктор? - PullRequest
10 голосов
/ 13 июня 2019

Предположим, у меня есть следующий (урезанный) код:

class P { P(); P(const P&); ~P(); }

void foo(P x) {
  ...
}

void bar() {
  P p{};
  foo(p); // compiler uses P::(const P&) to construct the value for x
  ...
  // compiler calls P::~P() on p
}

Компилятор должен создать копию p, чтобы вызвать foo, поэтому вызывающая сторона вызывает конструктор копирования перед вызовом. У меня вопрос, кто отвечает за уничтожение этого созданного объекта? Кажется, есть два правильных варианта:

  1. Вызываемый (то есть foo) вызывает деструктор для всех своих аргументов по значению перед его возвратом, а затем вызывающий освобождает память (выталкивая ее из стека).
  2. Вызываемый не делает ничего, и вызывающий (т.е. bar) вызывает деструктор на всех временных объектах перед точкой последовательности в конце вызова foo(p).

Ответы [ 4 ]

12 голосов
/ 13 июня 2019

Стандарт отвечает на этот вопрос в [expr.call] / 4, с удивительной тщательностью:

... Инициализация и уничтожение каждого параметра происходит в контексте вызывающая функция. [ Пример: Проверяется доступ конструктора, функций преобразования или деструктора в точке вызова в вызывающей функции. Если конструктор или деструктор для параметра функции выдает исключение, поиск обработчика начинается в области вызывающей функции; в частности, если функция Вызванный имеет function-try-block (раздел 18) с обработчиком, который может обработать исключение, этот обработчик не является считается. - конец примера ]

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

1 голос
/ 13 июня 2019

Звонящий уничтожает его. См. https://en.cppreference.com/w/cpp/language/lifetime. Цитирование:

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

Также соблюдайте это как общее правило - тот, кто создает, уничтожает. Обычно в обратном порядке.

0 голосов
/ 13 июня 2019

Идея звонящего и вызываемого абонента выглядит неправильно для меня. Вы должны думать о scopes здесь.

В тот момент, когда создается стек для функции там, где оживает объект P x в foo, объект будет «создан». Таким образом, объект будет удален, наконец, оставив область действия, в вашем случае - функцию.

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

Компилятор может «видеть», как ваш объект используется, особенно модифицируется, и может путем встраивания функции также пропустить создание «временного» объекта, пока код ведет себя «как будто» написано.

0 голосов
/ 13 июня 2019

Деструктор вызывается всякий раз, когда заканчивается срок жизни объекта, который включает

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

Итак, bar, который является владельцем скопированного объекта, вызовет dtor для скопированного объекта. Cppreference

...