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

232

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

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

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

Я могу это сделать?

Мартейн Курто
источник
13
Основная проблема заключается в том, что если вы delete thisсоздали тесную связь между классом и методом выделения, используемым для создания объектов этого класса. Это очень плохой дизайн ОО, так как самое основное в ООП - создавать автономные классы, которые не знают или не заботятся о том, что делает их вызывающая программа. Таким образом, правильно разработанный класс не должен знать или заботиться о том, как он был распределен. Если вам по какой-то причине нужен такой своеобразный механизм, я думаю, что лучше было бы использовать класс-оболочку вокруг фактического класса и позволить обертке иметь дело с распределением.
Лундин
Вы не можете удалить в setWorkingModule?
Джимми Т.

Ответы:

238

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

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

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

JaredPar
источник
15
Соответствующий FQA также имеет несколько полезных комментариев: yosefk.com/c++fqa/heap.html#fqa-16.15
Александр С.
1
В целях безопасности вы можете использовать закрытый деструктор для исходного объекта, чтобы убедиться, что он не создан в стеке или как часть массива или вектора.
Джем Калионку,
Определите «осторожный»
CinCout
3
«Тщательный» определяется в связанной статье FAQ. (Хотя ссылка FQA в основном работает - как и почти все в ней - насколько плох C ++)
CharonX
88

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

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

Джерри Гроб
источник
2
Спасибо, я положу это где-нибудь в моей памяти. Я предполагаю, что вы определяете конструкторы и деструкторы как частные и используете какой-то статический фабричный метод для создания таких объектов.
Александр С.
@Alexandre: Вы, вероятно, сделали бы это в большинстве случаев в любом случае - я не знаю где-нибудь близко ко всем деталям системы, над которой он работал, поэтому я не могу точно сказать об этом.
Джерри Гроб
Я часто решаю проблему распределения памяти, включая bool selfDeleteв конструктор параметр, который присваивается переменной-члену. Конечно, это означает передачу программисту достаточного количества веревки, чтобы завязать в ней петлю, но я считаю, что это предпочтительнее утечек памяти.
MBraedley
1
@MBraedley: я сделал то же самое, но предпочитаю избегать того, что мне кажется клуджем.
Джерри Гроб
Для всех, кого это волнует ... вполне вероятно, что он обрабатывается (по крайней мере частично) кодом, который делает это точно this. Да, код обрабатывается точно this. ;)
Galaxy
46

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

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, он сделает то же самое.

Марк Рэнсом
источник
Я не могу получить это для компиляции с использованием C ++ 11, есть ли какие-то специальные параметры компилятора для этого? Также не требует перемещения указателя этого?
Сова
@ Сова не знаю, что вы имеете в виду, у меня это работает: ideone.com/aavQUK . Создание unique_ptrиз другого unique_ptr требует перемещения, но не из необработанного указателя. Разве вещи не изменились в C ++ 17?
Марк Рэнсом
Ааа C ++ 14, вот почему. Мне нужно обновить мой C ++ на моем Dev Box. Сегодня вечером я попробую еще раз на моей недавно появившейся системе gentoo!
Сова
25

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

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

Боб Брайан
источник
7
+1. Я не могу понять, почему вы были отклонены. «C ++ должен быть написан так, чтобы он работал независимо от того, создан ли класс в куче, в массиве или в стеке» - очень хороший совет.
Джо
1
Вы можете просто обернуть объект, который хотите удалить, в специальный класс, который удаляет объект, а затем и самого себя, и использовать этот метод для предотвращения выделения стека: stackoverflow.com/questions/124880/… Бывают моменты, когда действительно нет жизнеспособная альтернатива. Я просто использовал эту технику для самостоятельного удаления потока, который запускается функцией DLL, но функция DLL должна возвращаться до окончания потока.
Феликс Домбек
Вы не можете программировать таким образом, чтобы кто-то просто копировал и вставлял ваш код в конечном итоге неправильно его использовал
Джимми Т.
22

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

Кирилл Васильевич Лядвинский
источник
Который точно один раз в каждом моем проекте ... :-)
cmaster - восстановить монику
15

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

void IMyInterface::Release()
{
    --instanceCount;
    if(instanceCount == 0)
        delete this;
}
UnknownGosu
источник
8

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

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

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

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

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

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

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

Эдвард Стрендж
источник
4

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

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

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

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

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

Увеличение и уменьшение счетчика ссылок, как манипулируют явным

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

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

Но потом мне пришло в голову: если объект, на который указывает указатель, знает, что на него подсчитывается ссылка, например, если счетчик находится внутри объекта (или в какой-то другой таблице), тогда процедура 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

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

Свифт - пятничный пирог
источник