Должны ли объекты удалять себя в C++?
Я провел последние 4 года на C#, поэтому меня интересуют современные лучшие практики и общие шаблоны дизайна на C++. Рассмотрим следующий частичный пример:
class World
{
public:
void Add(Object *object);
void Remove(Object *object);
void Update();
}
class Fire : Object
{
public:
virtual void Update()
{
if(age > burnTime)
{
world.Remove(this);
delete this;
}
}
}
здесь мы имеем мир, ответственный за управление набором объектов и их регулярное обновление. Огонь-это объект, который может быть добавлен в мир при различных обстоятельствах, но обычно другим объектом уже в мире. Огонь-единственный объект, который знает, когда он сгорел. в настоящее время я его удаляю. Объект, который создал огонь, скорее всего, больше не существует или не имеет значения.
это разумная вещь, чтобы сделать или есть лучший дизайн, который поможет очистить эти объекты?
11 ответов
проблема в том, что вы действительно создаете неявную связь между объектом и мировым классом.
Если я попытаюсь вызвать Update () вне мирового класса, что произойдет? Я могу в конечном итоге удалить объект, и я не знаю, почему. Похоже, ответственность сильно перепутана. Это вызовет проблемы в тот момент, когда вы используете класс Fire в новой ситуации, о которой вы не думали, когда писали этот код. Что произойдет, если объект должен быть удалены из нескольких мест? Возможно, его следует удалить как из мира, текущей карты, так и из инвентаря игрока? Ваша функция обновления удалит его из мира, а затем удалит объект, и в следующий раз, когда карта или инвентарь попытаются получить доступ к объекту, происходят плохие вещи.
В общем, я бы сказал, что для функции Update() очень неинтуитивно удалять объект, который он обновляет. Я бы также сказал, что это неинтуитивно для объекта, чтобы удалить себя. Объект скорее всего, должен быть какой-то способ поджечь событие, сказав, что оно закончило гореть, и любой заинтересованный теперь может действовать на этом. Например, удалив его из мира. Чтобы удалить его, подумайте в терминах владения.
кому принадлежит объект? Мир? Это означает, что только мир решает, когда объект умирает. Это нормально, пока ссылка мира на объект переживет другие ссылки на него. Как вы думаете, объект владеет собой? Что это даже злой? Объект должен быть удален, когда объект больше не существует? Не имеет смысла.
но если нет четко определенного одного владельца, реализуйте совместное владение, например, используя интеллектуальный указатель, реализующий подсчет ссылок, например boost::shared_ptr
но имея функцию-член на самом объекте, который жестко закодирован, чтобы удалить объект из один конкретный список, существует ли он там, и существует ли он также в любом другой список, а также удалить сам объект, независимо от ссылки на него существуют, это плохая идея.
вы сделали Update
виртуальная функция, предполагающая, что производные классы могут переопределять реализацию Update
. Это создает два больших риска.
1.) Переопределенная реализация может не забыть сделать World.Remove
, но может забыть delete this
. Память утекает.
2.) Переопределенная реализация вызывает базовый класс Update
, что значит delete this
, но затем переходит к дополнительной работе, но с недопустимым this-pointer.
рассмотреть этот пример:
class WizardsFire: public Fire
{
public:
virtual void Update()
{
Fire::Update(); // May delete the this-object!
RegenerateMana(this.ManaCost); // this is now invaild! GPF!
}
}
нет ничего плохого в том, что объекты удаляют себя в C++, большинство реализаций подсчета ссылок будут использовать что-то подобное. Однако это рассматривается как немного эзотерическая техника, и вам нужно будет очень внимательно следить за вопросами собственности.
удалить не удастся, и может привести к сбою программы, если объект был не выделено и построено с новым. Эта конструкция предотвратит создание экземпляра класса в стеке или статически. В вашем случае вы, похоже, используете какую-то схему распределения. Если вы придерживаетесь этого, это может быть безопасно. Я, конечно, не сделал этого,.
Я предпочитаю более строгую модель владения. Мир должен владеть объектами в нем и нести ответственность за их очистку. Либо у world remove это сделать, либо у update (или другой функции) вернуть значение, указывающее, что объект должен быть удален.
Я также предполагаю, что в этом типе шаблона вы захотите ссылаться на подсчет ваших объектов, чтобы избежать в конечном итоге болтающихся указателей.
это, безусловно, работает для объектов, созданных new
и когда вызывающий Update
должным образом информирован об этом поведении. Но я бы этого избежал. В вашем случае владение явно находится в мире, поэтому я бы заставил мир удалить его. Объект не создает себя, я думаю, что он также не должен удалять себя. Может быть очень удивительно, если вы вызываете функцию "Update" на своем объекте, но затем внезапно этот объект больше не существует без того, чтобы мир ничего не делал сам по себе (кроме от удаления его из списка - но в другом кадре! Код, вызывающий обновление объекта, этого не заметит).
некоторые идеи по этому поводу
- добавить список ссылок на объекты в мире. Каждый объект в этом списке ожидает удаления. Это распространенный метод и используется в wxWidgets для окон верхнего уровня, которые были закрыты, но все еще могут получать сообщения. Во время простоя, когда обрабатываются все сообщения, обрабатывается ожидающий список и объекты удаленный. Я считаю, что Qt следует аналогичной технике.
- сообщите миру, что объект хочет быть удален. Мир будет должным образом информирован и позаботится обо всем, что необходимо сделать. Что-то вроде
deleteObject(Object&)
может быть. - добавить
shouldBeDeleted
функция для каждого объекта, которая возвращает true, если объект хочет быть удален его владельцем.
Я бы предпочел вариант 3. Мир назовет это обновлением. И после этого, выглядит ли объект должен быть удален и может это сделать - или, если захочет, он помнит этот факт, добавив этот объект в список ожидающих удаления вручную.
это боль в заднице, когда вы не можете быть уверены, когда и когда не иметь доступа к функциям и данным объекта. Например, в wxWidgets существует класс wxThread, который может работать в двух режимах. Один из этих режимов (называемый "съемным") заключается в том, что если его основная функция возвращает (и ресурсы потока должны быть освобождены), он удаляет себя (чтобы освободить память, занятую объектом wxThread) вместо ожидания владельца объекта thread для вызова функции wait или join. Однако, это вызывает сильную головную боль. Вы никогда не можете вызвать на нем какие-либо функции, потому что он мог быть прекращен при любых обстоятельствах, и вы не можете его создать не с новым. Некоторые люди говорили мне, что им очень не нравится такое поведение.
само удаление объекта подсчета ссылок пахнет, имхо. Давайте сравнить:
// bitmap owns the data. Bitmap keeps a pointer to BitmapData, which
// is shared by multiple Bitmap instances.
class Bitmap {
~Bitmap() {
if(bmp->dec_refcount() == 0) {
// count drops to zero => remove
// ref-counted object.
delete bmp;
}
}
BitmapData *bmp;
};
class BitmapData {
int dec_refcount();
int inc_refcount();
};
сравните это с самоудалением пересчитанных объектов:
class Bitmap {
~Bitmap() {
bmp->dec_refcount();
}
BitmapData *bmp;
};
class BitmapData {
int dec_refcount() {
int newCount = --count;
if(newCount == 0) {
delete this;
}
return newCount;
}
int inc_refcount();
};
Я думаю, что первый намного приятнее, и я считаю, что хорошо разработанные ссылки подсчитали объектыне "удалить это", потому что это увеличивает сцепление: класс, использующий данные подсчета ссылок, должен знать и помнить о том, что данные удаляются как побочный эффект уменьшения количества ссылок. Обратите внимание, как "bmp" становится, возможно, висячим указателем в - Деструктор растрового изображения. Возможно, не делать этого "удалить это" здесь намного приятнее.
ответ на аналогичный вопрос "какой смысл удалять это"
Это довольно распространенная реализация подсчета ссылок, и я успешно использовал это раньше.
тем не менее, я также видел, как он разбился. Хотел бы я вспомнить обстоятельства аварии. @abelenky это одно место, где я видел его крах.
возможно, это было там, где вы далее подкласс Fire
, но не удается создать виртуальный деструктор (см. ниже). Когда у вас нет виртуального деструктора,Update()
функция вызовет ~Fire()
вместо соответствующий деструктор ~Flame()
.
class Fire : Object
{
public:
virtual void Update()
{
if(age > burnTime)
{
world.Remove(this);
delete this; //Make sure this is a virtual destructor.
}
}
};
class Flame: public Fire
{
private:
int somevariable;
};
другие упоминали проблемы с " удалить это. Короче говоря, поскольку "мир" управляет созданием огня, он должен также управлять его удалением.
еще одна проблема заключается в том, что мир заканчивается все еще горящим огнем. Если это единственный механизм, с помощью которого огонь может быть уничтожен, тогда вы можете оказаться сиротой или мусорным огнем.
для семантики, которую вы хотите, у меня будет флаг" активный "или" живой "в вашем классе" огонь " (или объект, если применимо). Затем мир время от времени проверяет свой инвентарь объектов и избавляется от тех, которые больше не активны.
--
еще одна заметка заключается в том, что ваш код, как написано, имеет огонь в частном порядке наследования от объекта, так как это значение по умолчанию, хотя это гораздо менее распространено, чем public inheiritance. Вы, вероятно, следует сделать вид наследования явные, и я подозреваю, что вы действительно хотите публичных inheritiance.
Как правило, не рекомендуется делать "удалить это", если это не необходимо или не используется очень простым способом. В вашем случае похоже, что мир может просто удалить объект при его удалении, если нет других зависимостей, которые мы не видим (в этом случае ваш вызов удаления вызовет ошибки, так как они не уведомлены). Сохранение владения вне объекта позволяет объекту быть лучше инкапсулированным и более стабильным: объект не станет недействительным в какой-то момент просто потому, что он решил удалить себя.
Я не думаю, что есть что-то изначально неправильное с удалением объекта, но другим возможным методом было бы, чтобы мир отвечал за удаление объектов как часть ::Remove (предполагая, что все удаленные объекты также были удалены.)
удалить это очень рискованно. В этом нет ничего "неправильного". Это законно. Но есть так много вещей, которые могут пойти не так, когда вы используете его, что его следует использовать экономно.
рассмотрим случай, когда у кого-то есть открытые указатели или ссылки на объект, а затем он идет и удаляет себя.
в большинстве случаев я думаю, что время жизни объекта должно управляться тем же объектом, который его создает. Это просто облегчает понимание того, где происходит разрушение.