Почему деструктор автоматического объекта вызывается дважды?

(Ответ на мой вопрос касается конструкторов копирования, но копирование происходит по возвращении из функции, а не в вызове метода другого класса. Я действительно видел возможный дубликат, на который ссылаются, но не делал вывод из копии, сделанной vector: push_back, что моя функция здесь также сделал копию. Возможно, я должен был.)

Я пытаюсь понять строительство / уничтожение автоматических объектов. Я натолкнулся на какой-то код, который показался мне сомнительным, поэтому я написал свою собственную версию, чтобы понять ее. Короче говоря, оригинальный код включал функцию, которая возвращала объект, который был локальным для функции (автоматически). Мне это показалось небезопасным, поэтому я написал эту программу, чтобы изучить ее:

#include <stdio.h>

class Phantom
{
private:
static int counter;
int id;

public:
Phantom()
{
++counter;
id = counter;
printf("Phantom %d constructed.\n", id);
};

virtual ~Phantom()
{
printf("Phantom %d destructed.\n", id);
};

void speak()
{
printf("Phantom %d speaks.\n", id);
};
};

int Phantom::counter = 0;

Phantom getPhantom()
{
Phantom autoPhantom;

return autoPhantom; // THIS CAN'T BE SAFE
}

int main()
{
Phantom phantom;

phantom = getPhantom();

phantom.speak();

return 0;
}

Я получаю этот вывод:

Призрак 1 построен.
Призрак 2 построен.
Призрак 2 разрушен.
Призрак 2 разрушен.
Призрак 2 говорит.

Это четвертая строка в выводе, которая смущает меня.

Фантом 1 создается автоматически, когда main введен

Призрак 2 создается автоматически, когда getPhantom введен

Призрак 2 уничтожается автоматически, когда getPhantom выход (поэтому я считаю, что возвращение его из getPhantom небезопасно).

Но после этого я запутался. По словам отладчика, getPhantom вернулся до появляется четвертая строка вывода. когда Phantomдеструктор вызывается второй раз, стек вызовов такой:

главный
~ Фантом

На управляемом языке я мог видеть, как эта строка:

phantom = getPhantom();

уничтожит Phantom 1, но не затронет Phantom 2. И это C ++, а не Java.

Что вызывает второй вызов деструктора Phantom 2?

3

Решение

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

7

Другие решения

Вы забыли правильно учесть:

  1. Копировать конструкторы.

  2. Операторы присваивания.

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

phantom = getPhantom();

Таким образом, ваш бухгалтерский учет получает это неправильно.

5

Я прокомментирую вашу обеспокоенность тем, что возвращать объект с автоматическим хранением небезопасно:

Phantom getPhantom()
{
Phantom autoPhantom;

return autoPhantom; // THIS CAN'T BE SAFE
}

Если это не будет безопасно, то C ++ будет довольно бесполезен, не так ли? Чтобы увидеть, о чем я говорю, просто замените тип на … сказать int:

int getPhantom()
{
int autoPhantom = 0;

return autoPhantom; // How does this look to you now?
}

Чтобы было понятно: это совершенно безопасно, потому что вы возвращаете значение (то есть копию объекта).

Небезопасно возвращать указатель или ссылку на такой объект:

int* getInt()
{
int a = 0;
return &a;
}
3

Фантом автофантом;

возврат автофантом; // ЭТО НЕ МОЖЕТ БЕЗОПАСНО

Это совершенно безопасно. Функция возвращает объект по значению, то есть копия будет сделана и возвращена (возможно, с помощью «оптимизации возвращаемого значения» (RVO)).

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

Причиной «лишнего» вызова деструктора является просто то, что локальная переменная уничтожается, а затем возвращаемая копия уничтожается.

2

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

Теперь подумайте о других способах создания объектов в C ++ и подумайте, что произойдет, если в любой момент используется конструктор копирования. Затем подумайте, как вы возвращаете локальный объект из функции и используется ли конструктор копирования.

Если вы хотите улучшить свой тестовый код, распечатайте значение this указатель в деструкторе, и вы увидите, что ваша попытка присвоить каждому объекту идентификатор ошибочна. У вас есть несколько объектов с разными идентификаторами (то есть адресами в памяти), но с одним и тем же идентификатором.

2

Добавьте такой код в ваш класс:

Phantom& operator=(const Phantom& inPhantom)
{
printf("Assigning.\n");
}

и вы увидите, что 2-й объект не уничтожается дважды. Расшифровка проще. При операции присваивания первый объект меняет все значения своих полей на значения второго объекта, но он не уничтожается. И это все еще объект номер один.
Ваш обновленный пример: http://cpp.sh/6b4lo

1