Алёна C++
Конструкция delete this вполне себе легальна, однако может привести к удивительно неприятным последствиям. Вот здесь написано, что нужно проверить при ее применении: Is it legal (and moral) for a member function to say delete this?. Особенно интересен предпоследний пункт — надо быть уверенным, что после удаления с объектом никто работать не будет.
Вот так вы напишете вряд ли
class CBase
int m_i;
public:
.
void MyFunction();
>;
void CBase::MyFunction()
delete this;
m_i = 5;
>
Однако вот так, очень может быть
void CBase::MyFunction()
innocentLookingFunction();
m_i = 5;
>
Т.е. возможно, что строки delete this нет, но innocentLookingFunction() таки ведет к удалению объекта через лихо закрученные вызовы.
Что будет дальше? Ничего хорошего. Access violation, порча чужой памяти, как повезет.
Еще одна известная особенность языка C++ может усугубить проблемы, возникающие при вызове delete this . После удаления объекта можно продолжить с ним работать, вызывать те его функции, что не работают с данными и не подозревать о том, что он вообще удален.
class CBase
int m_i;
public:
CBase() : m_i (0)<>
void printBase() < cout
>;
int main()
CBase* b = NULL;
b->printBase();
return 0;
>
Экземпляр объекта CBase никогда даже и не создавался, но код будет шикарно работать годами до тех пор, пока вы не обратитесь к данным класса как-нибудь так:
void printBase() < m_i++; cout
30 комментариев:
ИМХО конструкция ущербна — слишком небезопасно, а выгода может не стоить того, всегда лучше сделать рефакторинг и переделать архитектуру. Пока за всю мою долгую практику не понадобилось, удавалось избегать и моделировать без этого 🙂
Даже стало интересно, в какой идее/модели может понадобится такое? Были ли у вас какие-то примеры из практики? Ответить Удалить

А где можно про последний пример почитать поподробней? Я так понимаю компилятор работает с функцией как если бы она была static? Ответить Удалить
Поскольку в данном случае механизм вызова виртуальных функций не задействован, компилятор просто генерирует статический вызов с неявным параметром — адресом объекта. Который, опять такиЮ в данном случае не используется — и все работает. До поры до времени. 😉 Ответить Удалить
Практическая ценность трюка с delete this в создании объектов со счётчиками экземпляров внутри. Других примеров что-то в голову не приходит. Да и этот можно реализовать без таких кулинарных изысков.
С последним примеров всё довольно просто. Статические функции имеют заявленный список аргументов и не могут обращаться к нестатическим данным класса. Все остальные методы в качестве ещё одного неявного аргумента используют указатель на данные класса, который имеет имя this. И через него и происходит обращение к данным. То есть обращение к m_i на самом деле формируется как this->m_i, поэтому когда b = NULL, то и получим обращение к нулевому указателю. А пока мы не обращаемся к данным класса, то просто вхолостую передаём нулевой указатель которым не пользуемся. А вот с виртуальными функциями такой фокус уже не пройдёт.
P.S. из «изгибов» программирования
int *a = NULL;
int &b = *a; // no error
int c = *a; // access violation
b = 123; // access violation
таким способом можно отложить ошибку, если в аргумент T &v передавать *p, где p — невалидный указатель. В момент получения адреса объекта к нему нет реального обращения, что отложит появление ошибки и затруднит отладку, если такой код использовать. Ответить Удалить
ИМХО нужно использовать SharedPointer’ы. И для STL хорошо- он и не перегружает копированием структур и классов- и сам следит за кол-вом ссылок. Надо будет- сам грохнется. А пока SharedPointer в скопе, то кол-во ссылок на объект ненулевое. Ответить Удалить
Опасно? Да. Но, например, для класса-потока это один из неплохих методов подчистить память, неприбегая к монструозным глобальным списочным структурам. Поток по сути своей независим и в него лазают потенциально реже, так что «delete this» по завершению функции-executor’а потока вполне применимо. Запустили поток и забыли про него. Она за собой уберет.
Это как goto — в целом ни-ни, но если голова на плечах, то можно. Ответить Удалить
Когда объект удаляется, в debug build занимаемая им память должна специально портиться.
У каждого С++ объекта должен быть ровно один владелец. Если это недостижимо (например сложный объект реализован в DLL и клиент произвольный код) то объект должен быть COM-объектом.
Если эти 2 условия выполнены, таких проблем не возникнет. Ответить Удалить
Александр, для потока есть способ проще: размещать объекты в потоке на стеке, для больших данных использовать размещённые на стеке коллекции, освобождающие память в деструкторе.
В мире win32 кстати, шоб поток за собой всё убирал, нужно сразу после успешного вызова CreateThread вызвать CloseHandle — довольно как бы неочевидная конструкция 🙂 Ответить Удалить

2reperio:ИМХО конструкция ущербна — слишком небезопасно, а выгода может не стоить того,
всегда лучше сделать рефакторинг и переделать архитектуру.
Переделывание базовой архитектуры, на которой основывается большое количество кода — дорогое удовольствие.
Даже стало интересно, в какой идее/модели может понадобится такое? Были ли у вас какие-то примеры из практики?
Были. Был объект со счетчиком экземпляров, который должен был быть удален, когда экземпляры заканчивались (тут уже упомянули такой пример).
Или, например, последствие многочисленных измненений в коде, класс удалял себя через цепочку вызовов.
Дважды на моей памяти delete this выливалось в очень неприятные баги, поэтому я его стараюсь избегать со страшной силой.
2Ilya Kulakov:А где можно про последний пример почитать поподробней? Я так понимаю компилятор работает с функцией как если бы она была static?
Тут уже успели развернуто ответить.
2soonts:Когда объект удаляется, в debug build занимаемая им память должна специально портиться.
Если эти 2 условия выполнены, таких проблем не возникнет.
Никак тебе не поможет затирание памяти. Вне зависимости от того, что там лежит, ты можешь туда что-нибудь записать и что-нибудь считать неожиданное. Ответить Удалить
На мой взгляд глупая конструкция. Хотя довольно интересно над этим поразмышлять . например если написать в каком-нибудь методе класса «delete this», то это означает, что объекты класса имеют право находится только в куче(если не перегрузить этот оператор), а это ведет к тому, что нужно закрывать все конструкторы и для генерации объектов сделать статический метод. Ответить Удалить
По версии VS 2008 Express последний пример будет выглядеть так:
CBase* b = NULL;
0113671E mov dword ptr [b],0
b->printBase();
01136725 mov ecx,dword ptr [b]
01136728 call CBase::printBase (1133D34h)
Т.е. загрузили нуль и вызвали функцию под названием CBase::printBase, которая безболезненно отработала.
С virtual да, работать не будет. Упадёт при попытке получить содержимое по нулевому указателю, откуда оно по смещению в таблице виртуальных функций пытается получить адрес printBase. (В данном случае у нас функция первая в списке, поэтому смещений нет. Если добавить что-то такое:
virtual void stub()
то в листинге появится такая строчка:
0116D70F mov eax,dword ptr [edx+4]
)
P.S. Если наговорил бред — поправьте. В ассемблере, как и в C++ не силён. Просто очень заинтриговало. Ответить Удалить
2Алёна Верно, но дело в том, что архитектура продумывается заранее и тут то и можно избавится от таких конструкций, на то и есть идиомы. Конечно, когда поддерживаем старый код, то тут деватся некуда.
Авто счетчики дело хорошее, но shared_ptr лучше. Вообще вру, сталкивался с таким, но только у коллеги, и код, надо сказать, приводил к весьма скрытым багам, так как он сам иногда забывал что есть объект который вдруг сам себя может удалить 🙂 Ответить Удалить
ну и очевидное — экземпляры класса должны быть созданы на куче 🙂
class CBase
int m_i;
public:
void MyFunction();
>;
void CBase::MyFunction()
delete this;
>
int main () CBase a, *b = new CBase();
b->MyFunction();
a.MyFunction();//run time error
> Ответить Удалить
(возможно не к месту)
Конструкция не всегда применима. Что если объект класса размещен в статической области памяти. Как проверить доступен ли this в этом случае? Ответить Удалить
В прикладном коде этот фокус чаще всего применять не надо. Он — для библиотек, причём таких, где автор очень чётко понимает, что и зачем он написал (либо это сам разработчик компилятора, либо люди «глубоко в теме», вроде boost).
Это приём такого же сорта, как, например, и «деструктор на месте»:

В DLL такая конструкция применяется повсемесно для изоляции рантайма. Экспортируется фукнция, которая создает экземпляр чисто виртуального класса со скрытыми конструкотором и деструктором. Обычно присутствует функция release, которая в классе реализации содержит как раз delete this. Ответить Удалить
Вкупе с delete this можно использовать private деструктор — он некоторые проблемы убирает.
А для коллекции могу подкинуть более интересную семантику
new (this) MyClass() Ответить Удалить

размещающий оператор new у Саттера вроде был описан неплохо Ответить Удалить

2Анонимный:(возможно не к месту)
Конструкция не всегда применима. Что если объект класса размещен в статической области памяти. Как проверить доступен ли this в этом случае?
Угу, не всегда применима. Забота о том, чтобы объект был создан с помощью new, лежит на плечах программиста. Ответить Удалить
На мой взгляд — это вполне естественная конструкция для С++. Об этом во многих книгах написано.
Конечно С++ сложен и требует тщательного подхода к проектированию и программированию, но в этом его гибкость и универсальность.
Я не вижу причин пугаться такой конструкции. Она опасна, но может быть применима 🙂 . Ответить Удалить
Как удалить объект класса?
Она работает и выводит в консоль «destruct». Но после этого объект никуда не исчезает. К нему по прежнему можно обратиться и вывести значения xy. При этом по завершению работы программы, в консоль выводится «destruct» второй раз. То есть объект действительно где-то висит и затем удаляется повторно. Почему так происходит и как правильно уничтожить объект?
- Вопрос задан более трёх лет назад
- 4425 просмотров
Комментировать
Решения вопроса 1
«I’m here to consult you» © Dogbert
Она работает и выводит в консоль «destruct». Но после этого объект никуда не исчезает.
Тебе предстоит узнать много нового. О том, что такое неопределённое поведение. О том, что удалять опреатором delete можно только объекты созданные оператором new. О том, что обращение к полям объекта после вызова деструктора — это неопределённое поведение, так же как и вызов деструктора больше чем один раз. О том, что это забота программиста — следовать правилам языка, а рантайм может закрывать глаза на ошибки, а может громко кричать и завершать программу при первой же возможности.
Она работает и выводит в консоль «destruct». Но после этого объект никуда не исчезает. К нему по прежнему можно обратиться и вывести значения xy. При этом по завершению работы программы, в консоль выводится «destruct» второй раз.
Ну а у меня твоя программа ожидаемо вылетает сразу же в месте вызова delete.
как правильно уничтожить объект?
Объект на стеке можно корректно уничтожить двумя способами: естественным — выйдя из блока в котором он определён, и насильственным — вызвав явно его деструктор и создав на его месте другой объект.