24

Я провел последние 4 года в C#, поэтому меня интересуют современные лучшие практики и общие шаблоны проектирования на 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; 
     } 
    } 
} 

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

Это разумная вещь, чтобы сделать или есть лучший дизайн, который поможет очистить эти объекты?

ответ

20

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

Если я попытаюсь вызвать Update() вне класса World, что произойдет? Я могу в конечном итоге удалить объект, и я не знаю, почему. Похоже, что ответственность плохо перепутана. Это вызовет проблемы в тот момент, когда вы используете класс Fire в новой ситуации, о которой вы не думали, когда написали этот код. Что произойдет, если объект должен быть удален из нескольких мест? Возможно, его следует удалить как из мира, так и из текущей карты и инвентаря игрока? Функция «Обновить» удалит его из мира, а затем удалит объект, а в следующий раз, когда карта или инвентарь попытаются получить доступ к объекту, «Плохие вещи».

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

Кому принадлежит данный объект? Мир? Это означает, что только мир может решить, когда объект умрет. Это прекрасно, если мировая ссылка на объект собирается пережить другие ссылки на него. Считаете ли вы, что объект сам по себе? Что это вообще значит? Объект следует удалить, если объект больше не существует? Не имеет смысла.

Но если нет четко определен единственным владельца, осуществить совместное владение, например, с помощью смарт-указателя реализации подсчета ссылок, таких как boost::shared_ptr

Но имеющей функцию-члена на самом объекте, который HARDCODED для удаления объект от один конкретный список, независимо от того, существует он или нет, и существует ли он в любом другом списке, а также удалять сам объект, независимо от того, какие ссылки на него существуют, является плохой идеей.

+1

Мне было трудно выбрать приемлемый ответ здесь. Я выбираю это, потому что он описывает проблемы, с которыми я столкнулся (вещи, отличные от мировых ссылок), а также предлагает shared_ptr, который оказался частью моего решения. Благодарю. –

+1

Другая причина, заключающаяся в том, что я ошибаюсь, заключается в том, что она не позволяет создавать объект в стеке вместо кучи. Кроме того, связь с World делает независимое тестирование невозможным. – walrii

0

Я не вижу в этом ничего неправильного с удалением объекта, но другой возможный метод заключается в том, чтобы мир нести ответственность за удаление объектов как часть :: Remove (при условии, что все удаленные объекты также были удалены.)

6

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

1

Как правило, не рекомендуется «удалять это», если это не необходимо или не используется очень простым способом. В вашем случае похоже, что World может просто удалить объект при его удалении, если нет других зависимостей, которые мы не видим (в этом случае ваш вызов на удаление вызовет ошибки, так как они не будут уведомлены). Сохранение собственности за пределами вашего объекта позволяет вашему объекту быть лучше инкапсулированным и более стабильным: объект не станет некорректным в какой-то момент просто потому, что он решил удалить себя.

+0

Если вы имеете в виду World :: Remove (Object * o) {delete o}, то это плохо, так как был вызван Remove из объекта, который он удаляет. Это означает, что он вернется к методу abject, который больше не существует. – KeithB

5

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

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

2

Это довольно распространенная реализация подсчета ссылок, и я успешно использовал это раньше.

Однако, я также видел, как он разбился. Хотел бы я вспомнить обстоятельства катастрофы. @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; 
}; 
+0

Возможно, после удаления вызова вы попытались получить доступ к переменной-члену или вызвали виртуальную функцию –

+0

отличную иллюстрацию необходимости виртуальных деструкторов. Модифицируйте вас. – abelenky

6

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

+0

также - он может оказаться с помощью навесных указателей загрузки – JohnIdol

30

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

1.) Перевернутая реализация может запомниться World.Remove, но может забыть delete this. Произошла утечка памяти.

2.) Переопределенная реализация вызывает базовый класс Update, который выполняет команду delete this, но затем продолжает работу, но с недопустимым указателем.

Рассмотрим следующий пример:

class WizardsFire: public Fire 
{ 
public: 
    virtual void Update() 
    { 
     Fire::Update(); // May delete the this-object! 
     RegenerateMana(this.ManaCost); // this is now invaild! GPF! 
    } 
} 
+0

Я видел это! Это не забавно пытаться отследить. –

+0

Эйк, это не забава. Спасибо, что указали на это, 2 проблемы, которые мы не имеем (напрямую) в C#. –

5

Это, конечно, работает для объектов, созданных new и когда вызывающий Update правильно осведомленных о том, что поведение. Но я бы избегал этого. В вашем случае владение явно находится в мире, поэтому я бы уничтожил мир. Объект не создает себя, я думаю, он также не должен удалять себя. Может быть очень удивительно, если вы вызываете функцию «Обновить» на свой объект, но вдруг этот объект уже не существует, и мир не делает ничего из себя (кроме удаления его из своего списка, но в другом фрейме! Обновление объекта не заметит этого).

Некоторые идеи по этому

  1. Добавить список ссылок на объекты в мире. Каждый объект в этом списке ожидает удаления. Это обычная техника и используется в wxWidgets для закрытых окон верхнего уровня, но все равно может получать сообщения. В режиме простоя, когда все сообщения обрабатываются, обрабатываемый список обрабатывается и объекты удаляются. Я считаю, что Qt следует аналогичной технике.
  2. Сообщите миру, что объект хочет быть удаленным. Мир будет правильно проинформирован и позаботится о том, что нужно сделать. Возможно, что-то вроде deleteObject(Object&).
  3. Добавить функцию shouldBeDeleted для каждого объекта, который возвращает true, если объект желает удалить его владельцем.

Я бы предпочел вариант 3. Мир назвал бы Update. И после этого он смотрит, должен ли объект быть удален, и может сделать это - или, если он пожелает, он помнит этот факт, добавив этот объект в список ожидающего удаления вручную.

Это боль в заднице, когда вы не можете быть уверены, когда и когда не сможете получить доступ к функциям и данным объекта. Например, в wxWidgets существует класс wxThread, который может работать в двух режимах. Один из этих режимов (называемый «съемным») заключается в том, что если его основная функция возвращается (и ресурсы потока должны быть освобождены), он удаляет себя (чтобы освободить память, занятую объектом wxThread), вместо того, чтобы ждать владельца потока объект для вызова функции ожидания или соединения. Однако это вызывает сильную головную боль. Вы никогда не можете называть какие-либо функции, потому что это могло быть прекращено при любых обстоятельствах, и вы не можете создать его не с новым. Некоторые люди говорили мне, что они очень не любят этого поведения.

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

// 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(); 

}; 

Сравните с самостоятельным удалением refcounted объектов:

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» становится, возможно, висящим указателем в деструкторе ~ Bitmap. По-видимому, не делать этого «удалить это» гораздо приятнее здесь.

Ответ на подобный вопрос "What is the use of delete this"

+0

Мне нравится этот ответ и в конечном итоге использовал его элементы, но, к сожалению, я могу только принять его. Благодаря! –

+0

Я рад, что мы вам помогли :) jalf имеет некоторые реальные хорошие идеи в его ответе, я думаю. получайте удовольствие :) –

0

удалить это очень рискованно. Нет ничего «неправильного» с ним. Это законно. Но есть так много вещей, которые могут пойти не так, когда вы используете его, чтобы использовать его экономно.

Рассмотрите случай, когда у кого есть открытые указатели или ссылки на объект, а затем он идет и удаляется.

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

2

Другие проблемы, связанные с «удалить это.«Короче говоря, поскольку« Мир »управляет созданием Огня, он также должен управлять его удалением.

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

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

-.

Еще одно замечание заключается в том, что в вашем написанном коде есть Fire, который наследует от Object, поскольку он является дефолтом, хотя он гораздо менее распространен, чем публичное нарушение. Вероятно, вы должны сделать вид наследования явным, и я подозреваю, что вы действительно хотите публичное наследование.

+0

Мы не начинали огнь Он всегда горит Поскольку в мире поворачивал Мы не начинали огнь Нет, мы не зажигали его Но мы пытались бороться с ней – abelenky

+0

Это отсутствует публика, потому что мой мозг все еще в режиме C# ... не подумал, как удалить огонь, если мир заканчивается первым ... это звучит как серьезный! –

Смежные вопросы