(Ответ на мой вопрос касается конструкторов копирования, но копирование происходит по возвращении из функции, а не в вызове метода другого класса. Я действительно видел возможный дубликат, на который ссылаются, но не делал вывод из копии, сделанной 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?
Вы возвращаете копию. Поэтому переменная в getPhantom()
уничтожается в конце области видимости, и у вас остается его копия с идентификатором 2. Это происходит потому, что при возврате он вызывает конструктор копирования (также по умолчанию), который не увеличивает идентификатор.
Вы забыли правильно учесть:
Копировать конструкторы.
Операторы присваивания.
В обоих случаях вы получите более одного объекта с одинаковыми id
с обоими объектами, заканчивающимися печатью того же самого id
в их деструкторе. В случае конструктора копирования никакое сообщение не будет напечатано в конструкторе, так как вы не определяете свой собственный конструктор копирования. В случае оператора присваивания id
назначенный в конструкторе перезаписывается дубликатом id
с другого объекта. Вот что здесь происходит:
phantom = getPhantom();
Таким образом, ваш бухгалтерский учет получает это неправильно.
Я прокомментирую вашу обеспокоенность тем, что возвращать объект с автоматическим хранением небезопасно:
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;
}
Фантом автофантом;
возврат автофантом; // ЭТО НЕ МОЖЕТ БЕЗОПАСНО
Это совершенно безопасно. Функция возвращает объект по значению, то есть копия будет сделана и возвращена (возможно, с помощью «оптимизации возвращаемого значения» (RVO)).
Если бы функция вернула ссылку или указатель на локальную переменную, то вы были бы правы, и это было бы небезопасно.
Причиной «лишнего» вызова деструктора является просто то, что локальная переменная уничтожается, а затем возвращаемая копия уничтожается.
Вместо того чтобы задавать вопрос, приводит ли такой простой код к разрушению объекта, который никогда не создавался, или к уничтожению чего-либо дважды, подумайте, что это гораздо более вероятно, что объект было построен, и каждый объект уничтожается только один раз, но вы точно не отслеживали конструкции и разрушения.
Теперь подумайте о других способах создания объектов в C ++ и подумайте, что произойдет, если в любой момент используется конструктор копирования. Затем подумайте, как вы возвращаете локальный объект из функции и используется ли конструктор копирования.
Если вы хотите улучшить свой тестовый код, распечатайте значение this
указатель в деструкторе, и вы увидите, что ваша попытка присвоить каждому объекту идентификатор ошибочна. У вас есть несколько объектов с разными идентификаторами (то есть адресами в памяти), но с одним и тем же идентификатором.
Добавьте такой код в ваш класс:
Phantom& operator=(const Phantom& inPhantom)
{
printf("Assigning.\n");
}
и вы увидите, что 2-й объект не уничтожается дважды. Расшифровка проще. При операции присваивания первый объект меняет все значения своих полей на значения второго объекта, но он не уничтожается. И это все еще объект номер один.
Ваш обновленный пример: http://cpp.sh/6b4lo