Подтвердить что ты не робот

Должны ли объекты удаляться на С++?

Я провел последние 4 года на С#, поэтому меня интересуют современные передовые методы и общие шаблоны проектирования на С++. Рассмотрим следующий частичный пример:

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;
        }
    }
}

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

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

4b9b3361

Ответ 1

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

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

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

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

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

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

Ответ 2

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

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

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

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

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

Ответ 3

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

Ответ 4

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

Ответ 5

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

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

Ответ 6

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

Некоторые идеи на этом

  • Добавить список ссылок на объекты в World. Каждый объект в этом списке ожидает удаления. Это обычная техника и используется в wxWidgets для закрытых окон верхнего уровня, но все равно может получать сообщения. В режиме простоя, когда все сообщения обрабатываются, обрабатываемый список обрабатывается и объекты удаляются. Я считаю, что Qt следует аналогичной технике.
  • Сообщите миру, что объект хочет быть удаленным. Мир будет правильно проинформирован и позаботится о том, что нужно сделать. Возможно, что-то вроде deleteObject(Object&).
  • Добавьте функцию 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 objects:

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. По-видимому, не делать этого "удалить это" гораздо приятнее здесь.

Ответ на аналогичный вопрос "Что такое удаление этого"

Ответ 7

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

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

Ответ 8

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

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

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

-

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

Ответ 9

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

Ответ 10

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

Ответ 11

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

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

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