Можно ли удалить это? - PullRequest
214 голосов
/ 30 июня 2010

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

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

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 ]

224 голосов
/ 30 июня 2010

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

Я думаю, что эта цитата хорошо подводит итог

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

80 голосов
/ 30 июня 2010

Да, delete this; имеет определенные результаты, если (как вы заметили) вы гарантируете, что объект был распределен динамически, и (конечно) никогда не будете пытаться использовать объект после его уничтожения.На протяжении многих лет задавалось много вопросов о том, что в стандарте конкретно говорится о delete this;, в отличие от удаления какого-либо другого указателя.Ответ на это довольно короткий и простой: он ничего не говорит о многом.Это просто говорит о том, что операнд delete должен быть выражением, обозначающим указатель на объект или массив объектов.В нем подробно рассказывается о том, как выяснить, какую (если есть) функцию освобождения вызывать для освобождения памяти, но во всем разделе delete (§ [expr.delete]) не упоминается delete this; конкретно на всех.Раздел о деструкторах упоминает delete this в одном месте (§ [class.dtor] / 13):

В точке определения виртуального деструктора (включая неявное определение (15.8))функция освобождения немассива определяется так, как если бы выражение удаляло это, появляющееся в не виртуальном деструкторе класса деструктора (см. 8.3.5).

Это поддерживает идею, чтостандарт считает delete this; действительным - если он был недействительным, его тип не был бы значимым.Насколько мне известно, это единственное место, в котором стандарт упоминает delete this;.

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

Основное время использования этой техники -объект, у которого есть жизнь, которая почти полностью его собственная.Одним из примеров, приведенных Джеймсом Канзе, была система биллинга / отслеживания, над которой он работал в телефонной компании.Когда вы начинаете делать телефонный звонок, что-то принимает это к сведению и создает объект phone_call.С этого момента, объект phone_call обрабатывает детали телефонного звонка (установление соединения при наборе номера, добавление записи в базу данных, чтобы сообщить, когда начался звонок, возможно, подключить больше людей, если вы делаете конференц-связь, и т. Д..) Когда последние люди, участвующие в вызове, кладут трубку, объект phone_call ведет окончательный учет (например, добавляет запись в базу данных, чтобы сообщить, когда вы положили трубку, чтобы они могли вычислить, как долго длился ваш звонок) изатем уничтожает себя.Время жизни объекта phone_call основано на том, когда первый человек начинает вызов и когда последние люди покидают вызов - с точки зрения остальной части системы, это в основном совершенно произвольно, поэтому вы можете 't привязать его к любой лексической области в коде или к чему-либо в этом порядке.

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

45 голосов
/ 30 июня 2010

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

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

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

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

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

22 голосов
/ 24 июня 2012

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

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

19 голосов
/ 30 июня 2010

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

13 голосов
/ 30 июня 2010

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

void IMyInterface::Release()
{
    --instanceCount;
    if(instanceCount == 0)
        delete this;
}
7 голосов
/ 20 декабря 2013

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

Подсчет ссылок является сильной формой детерминированной сборки мусора - он обеспечивает объектам управление своим собственным временем жизни вместо того, чтобы полагаться на «умные» указатели и т. Д., Чтобы сделать это для них. Доступ к базовому объекту возможен только через интеллектуальные указатели «Reference», разработанные так, чтобы указатели увеличивали и уменьшали целое число членов (счетчик ссылок) в реальном объекте.

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

Ключом к обеспечению этой безопасности является не предоставление пользователям доступа к КОНСТРУКТОРУ рассматриваемого объекта (сделать его защищенным), но вместо этого заставление их вызывать некоторый статический член - ФАБРИКА - как "статическая ссылка CreateT (...) ». Таким образом, вы ЗНАЕТЕ, что они всегда построены с обычным «новым» и что необработанный указатель недоступен, поэтому «удалить это» никогда не взорвется.

7 голосов
/ 30 июня 2010

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

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

3 голосов
/ 28 апреля 2012

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

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

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

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

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

Увеличивается и уменьшается счетчик ссылок как явный_delete_ref_cnt_ptr.*

Но НЕ освобождает, когда счетчик ссылок считается равным нулю в деструкторе clear_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.Но кажется, что это может сделать смешивание обнаженных и rc'-указателей более безопасным.

Но пока нет необходимости удалять это.

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

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 голосов
/ 04 октября 2016

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

...