Деструкторы в классах, возвращаемых функциями - PullRequest
3 голосов
/ 14 июня 2019

У меня есть следующий код:

#include <stdio.h>

class Foo {
    public:
    int a;
    ~Foo() { printf("Goodbye %d\n", a); }
};

Foo newObj() {
    Foo obj;
    return obj;
}

int main() {
    Foo bar = newObj();
    bar.a = 5;
    bar = newObj();
}

Когда я компилирую с g++ и запускаю его, я получаю:

Goodbye 32765
Goodbye 32765

Напечатанное число кажется случайным.

У меня два вопроса:

  1. Почему деструктор вызывается дважды?
  2. Почему 5 не печатается в первый раз?

Я пришел из C-фона, отсюда и printf, и у меня возникают проблемы с пониманием деструкторов, когда они вызываются и как класс должен быть возвращен из функции.

Ответы [ 3 ]

11 голосов
/ 14 июня 2019

Давайте посмотрим, что происходит в вашей основной функции:

int main() {
    Foo bar = newObj();

Здесь мы просто создаем экземпляр Foo и инициализируем его возвращаемым значением newObj().Здесь не вызывается деструктор из-за copy elision : чтобы суммировать очень быстро, вместо копирования / перемещения obj в bar и последующей деструкции obj, obj напрямую создается в bar Хранилище.

    bar.a = 5;

Здесь нечего сказать.Мы просто меняем значение bar.a на 5.

    bar = newObj();

Здесь bar присваивается копия 1 , возвращаемое значение newObj(), затем временный объект, созданныйэтот вызов функции уничтожен 2 , это первый Goodbye.На данный момент bar.a больше не 5, но что бы ни было в a.

}

Конце main(), локальные переменные уничтожаются, включая bar, этовторой Goodbye, который не печатает 5 из-за предыдущего назначения.


1 Здесь не происходит перемещение из-за пользовательского деструктора, нет назначения перемещенияоператор неявно объявлен.
2 Как упоминалось в комментариях YSC, обратите внимание, что этот вызов деструктора имеет неопределенное поведение, потому что он обращается к a, который не инициализирован в этой точке.Присвоение bar временному объекту и, в частности, присваивание a как его части, также имеет неопределенное поведение по тем же причинам.

2 голосов
/ 14 июня 2019

1) Все просто, в вашем коде два объекта Foomain и в newObj), поэтому два вызова деструктора. На самом деле это минимальное количество вызовов деструкторов, которые вы видите, компилятор может создать неназванный временный объект для возвращаемого значения, и если это будет сделано, вы увидите три вызова деструкторов. Правила оптимизации возвращаемого значения изменились в истории C ++, поэтому вы можете видеть или не видеть это поведение.

2) Поскольку значение Foo::a никогда не равно 5, когда вызывается деструктор, его никогда не бывает 5 в newObj, и хотя оно было 5 в main, это не к тому времени, когда вы дойдете до конца main (когда вызывается деструктор).

Полагаю, ваше недоразумение заключается в том, что вы думаете, что оператор присваивания bar = newObj(); должен вызывать деструктор, но это не так. Во время назначения объект перезаписывается, он не уничтожается.

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

Я думаю, что одно из главных заблуждений здесь - это идентичность объекта.

bar - это всегда один и тот же объект. Когда вы назначаете другой объект для bar, вы не уничтожаете первый - вы вызываете operator=(const& Foo) (оператор назначения копирования). Это одна из пяти специальных функций-членов , которые могут быть автоматически сгенерированы компилятором (что в данном случае и происходит) и просто перезаписывает bar.a на все, что находится в newObj().a. Предоставьте свой operator=, чтобы увидеть, что / когда это произойдет (и подтвердить, что a действительно 5 до того, как это произойдет).

Деструктор

bar вызывается только один раз - когда bar выходит из области видимости в конце функции. Есть только один другой вызов деструктора - для временного, возвращаемого вторым newObj(). Первое временное значение из newObj() исключается (язык позволяет это в этом конкретном случае, потому что на самом деле никогда не существует смысла его создавать и немедленно уничтожать) и инициализирует bar непосредственно возвращаемым значением newObj().

...