Можно ли удалить это?

183

Разрешено ли delete this;, если оператор delete является последним оператором, который будет выполняться в этом экземпляре класса? Конечно, я уверен, что объект, представленный this -pointer, new ly-created.

Я думаю о чем-то вроде этого:

void SomeModule::doStuff()
{
    // in the controller, "this" object of SomeModule is the "current module"
    // now, if I want to switch over to a new Module, eg:

    controller->setWorkingModule(new OtherModule());

    // since the new "OtherModule" object will take the lead, 
    // I want to get rid of this "SomeModule" object:

    delete this;
}

Могу ли я это сделать?

  • 10
    Основная проблема заключается в том, что если вы delete this вы создадите тесную связь между классом и методом выделения, используемым для создания объектов этого класса. Это очень плохой дизайн ОО, так как самое основное в ООП - создавать автономные классы, которые не знают или не заботятся о том, что делает их вызывающая программа. Таким образом, правильно разработанный класс не должен знать или заботиться о том, как он был распределен. Если вам по какой-то причине нужен такой специфический механизм, я думаю, что лучше было бы использовать класс-оболочку вокруг фактического класса, и позволить оболочке иметь дело с распределением.
Теги:
memory-management
delete-operator
new-operator
self-destruction

10 ответов

198
Лучший ответ

В С++ FAQ Lite есть запись специально для этого

Я думаю, что эта цитата суммирует ее красиво

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

  • 13
    Соответствующий FQA также имеет несколько полезных комментариев: yosefk.com/c++fqa/heap.html#fqa-16.15
  • 1
    В целях безопасности вы можете использовать закрытый деструктор для исходного объекта, чтобы убедиться, что он не создан в стеке или как часть массива или вектора.
Показать ещё 2 комментария
66

Да, delete this; определил результаты, пока (как вы отметили) вы гарантируете, что объект был распределен динамически, и (конечно) никогда не пытайтесь использовать объект после его уничтожения.

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

Изменить: [в основном в ответ на комментарий @Alexandre C]: основное время, которое вы делаете, это объект, у которого есть жизнь, которая почти полностью принадлежит ей. В одном примере Джеймс Канзе цитирует систему биллинга/отслеживания, которую он работал для телефонной компании. В принципе, когда вы подбираете телефон, что-то это замечает и создает объект phone_call. С этого момента объект phone_call обрабатывает детали телефонного звонка (соединение с вами, когда вы набираете номер, добавляете запись в базу данных, чтобы сказать при запуске вызова, возможно, подключить больше людей, если вы выполняете конференц-связь, и т.д..) Когда вы вешаете трубку, объект phone_call выполняет свою окончательную учетную запись (например, добавляет запись в базу данных, чтобы сказать, когда вы повесили трубку, чтобы они могли вычислить, как долго ваш вызов был), а затем уничтожает себя. Время жизни объекта phone_call основывается на том, что когда вы поднимаете/вешаете трубку - с точки зрения остальной системы, она в основном совершенно произвольная, поэтому вы не можете привязать ее к какой-либо лексической области в код или что-либо в этом порядке.

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

  • 2
    Спасибо, я положу это где-нибудь в моей памяти. Я предполагаю, что вы определяете конструкторы и деструкторы как частные и используете какой-то статический фабричный метод для создания таких объектов.
  • 0
    @Alexandre: Вы, вероятно, в любом случае сделали бы это в большинстве случаев - я не знаю где-либо близко ко всем деталям системы, над которой он работал, поэтому я не могу точно сказать об этом.
Показать ещё 3 комментария
37

Если это вас пугает, есть совершенно законный хак:

void myclass::delete_me()
{
    std::unique_ptr<myclass> bye_bye(this);
}

Я думаю, что delete this - это идиоматический С++, хотя я представляю это как любопытство.

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

void myclass::throw_error()
{
    std::unique_ptr<myclass> bye_bye(this);
    throw std::runtime_exception(this->error_msg);
}

Примечание. Если вы используете компилятор старше С++ 11, вы можете использовать std::auto_ptr вместо std::unique_ptr, он будет делать то же самое.

  • 7
    +1 потому что я просто сильно рассмеялся: D
  • 0
    Я не могу получить это для компиляции с использованием C ++ 11, есть ли какие-то специальные параметры компилятора для этого? Также не требует перемещения указателя этого?
Показать ещё 2 комментария
19

Одна из причин, по которой С++ была разработана, заключалась в том, чтобы упростить повторное использование кода. В общем, С++ должен быть написан так, чтобы он работал, если экземпляр класса создается в куче, в массиве или в стеке. "Удалить это" - очень плохая практика кодирования, потому что она будет работать только в том случае, если в куче определен один экземпляр; и лучше не будет другого оператора удаления, который обычно используется большинством разработчиков для очистки кучи. Это также предполагает, что ни один программист по обслуживанию в будущем не вылечит ложно воспринятую утечку памяти, добавив инструкцию удаления.

Даже если вы заранее знаете, что ваш текущий план состоит только в том, чтобы выделить один экземпляр в кучу, что, если в будущем появится какой-нибудь счастливый разработчик, и решит создать экземпляр в стеке? Или, что, если он режет и вставляет определенные части класса в новый класс, который он намеревается использовать в стеке? Когда код достигнет "delete this", он погаснет и удалит его, но затем, когда объект выходит из области видимости, он вызовет деструктор. Затем деструктор попытается удалить его, а затем вы будете закрыты. В прошлом делать что-то подобное было бы испортить не только программу, но и операционную систему, и компьютер нужно было бы перезагрузить. В любом случае это настоятельно НЕ рекомендуется, и его почти всегда следует избегать. Мне пришлось бы отчаянно, серьезно оштукатурить или действительно ненавидеть компанию, в которой я работал, чтобы написать код, который сделал это.

  • 6
    +1. Я не могу понять, почему вы были отклонены. «C ++ должен быть написан так, чтобы он работал независимо от того, создается ли класс в куче, в массиве или в стеке» - очень хороший совет.
  • 0
    Джо - спасибо за отзыв! Я тоже не понимаю ...
Показать ещё 1 комментарий
19

Это разрешено (просто не используйте объект после этого), но я не буду писать такой код на практике. Я думаю, что delete this должен отображаться только в функциях, называемых release или release, и выглядит так: void release() { ref--; if (ref<1) delete this; }.

  • 0
    Который точно один раз в каждом моем проекте ... :-)
12

Ну, в Component Object Model (COM) delete this конструкция может быть частью метода Release, который вызывается всякий раз, когда вы хотите выпустить объект aquisited:

void IMyInterface::Release()
{
    --instanceCount;
    if(instanceCount == 0)
        delete this;
}
6

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

Конечно, вы используете RAII-объекты, поэтому на самом деле вам вообще не нужно называть delete...

  • 1
    +1 за упоминание RAII.
5

Это основная идиома для объектов с подсчетом ссылок.

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

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

Ключом к обеспечению безопасности является не предоставление пользователям доступа к CONSTRUCTOR объекта (его защита), а вместо этого обращение к некоторым статическим членам - FACTORY - как "static Reference CreateT (...)". Таким образом, вы ЗНАЕТЕ, что они всегда построены с обычным "новым" и что никакой необработанный указатель не доступен, поэтому "удалить это" никогда не будет взорваться.

  • 0
    Почему вы не можете просто иметь (одноэлементный) класс «распределитель / сборщик мусора», интерфейс, через который выполняется все выделение, и позволяющий этому классу обрабатывать весь подсчет ссылок выделенных объектов? Вместо того, чтобы заставлять сами объекты заниматься задачами по сбору мусора, что совершенно не связано с их назначением.
  • 1
    Вы также можете просто сделать деструктор защищенным, чтобы запретить статические и стековые выделения вашего объекта.
3

Это старый, ответивший вопрос, но @Alexandre спросил: "Зачем кому-то это делать?", и я подумал, что могу представить пример использования, которое я рассматриваю сегодня днем.

Устаревший код. Использует голые указатели Obj * obj с удалением obj в конце.

К сожалению, мне иногда требуется не часто, чтобы объект был дольше.

Я рассматриваю возможность сделать его посчитанным интеллектуальным указателем. Но было бы много кода для изменения, если бы я использовал ref_cnt_ptr<Obj> всюду. И если вы смешиваете обнаженные Obj * и ref_cnt_ptr, вы можете получить объект, неявно удаленный, когда последний ref_cnt_ptr уйдет, хотя Obj * все еще жив.

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

Прибавьте и уменьшите счетчик ссылок как explicit_delete_ref_cnt_ptr.

Но НЕ освобождение, когда счетчик ссылок считается равным нулю в destructor explicit_delete_ref_cnt_ptr.

Только освобождение, когда счетчик ссылок считается равным нулю в явной операции удаления. Например. в чем-то вроде:

template<typename T> class explicit_delete_ref_cnt_ptr { 
 private: 
   T* ptr;
   int rc;
   ...
 public: 
   void delete_if_rc0() {
      if( this->ptr ) {
        this->rc--;
        if( this->rc == 0 ) {
           delete this->ptr;
        }
        this->ptr = 0;
      }
    }
 };

Хорошо, что-то в этом роде. Немного необычно иметь ссылочный подсчитанный тип указателя, который не удаляет автоматически объект, на который указывает rc'ed ptr destructor. Но похоже, что это могло бы сделать смешение голых указателей и rc'ed указателей немного безопаснее.

Но до сих пор не нужно удалять это.

Но потом мне пришло в голову: если объект, на который указывает объект, указывает, что он подсчитывается ссылкой, например. если счетчик находится внутри объекта (или в какой-либо другой таблице), то процедура delete_if_rc0 может быть методом объекта pointee, а не (умным) указателем.

class Pointee { 
 private: 
   int rc;
   ...
 public: 
   void delete_if_rc0() {
        this->rc--;
        if( this->rc == 0 ) {
           delete this;
        }
      }
    }
 };

Фактически, он не должен быть элементом-членом вообще, но может быть свободной функцией:

map<void*,int> keepalive_map;
template<typename T>
void delete_if_rc0(T*ptr) {
        void* tptr = (void*)ptr;
        if( keepalive_map[tptr] == 1 ) {
           delete ptr;
        }
};

(Кстати, я знаю, что код не совсем прав - он становится менее читаемым, если я добавлю все детали, поэтому я оставляю его так.)

0

Удалить это законно, пока объект находится в куче. Вам нужно будет требовать, чтобы объект был только кучей. Единственный способ сделать это - защитить защищаемый деструктор - таким образом, удаление может быть вызвано ТОЛЬКО из класса, поэтому вам понадобится метод, обеспечивающий удаление

Ещё вопросы

Сообщество Overcoder
Наверх
Меню