Конструкция 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<<"CBase"<<endl; }
};
int main()
{
CBase* b = NULL;
b->printBase();
return 0;
}
Экземпляр объекта CBase никогда даже и не создавался, но код будет шикарно работать годами до тех пор, пока вы не обратитесь к данным класса как-нибудь так:
void printBase() { m_i++; cout<<"CBase"<<endl; }
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 занимаемая им память должна специально портиться.<skip>
Если эти 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).
Это приём такого же сорта, как, например, и "деструктор на месте":
this->Object::~Object();
В DLL такая конструкция применяется повсемесно для изоляции рантайма. Экспортируется фукнция, которая создает экземпляр чисто виртуального класса со скрытыми конструкотором и деструктором. Обычно присутствует функция release, которая в классе реализации содержит как раз delete this.
Вкупе с delete this можно использовать private деструктор - он некоторые проблемы убирает.
А для коллекции могу подкинуть более интересную семантику
new (this) MyClass()
размещающий оператор new у Саттера вроде был описан неплохо
2Анонимный:(возможно не к месту)
Конструкция не всегда применима. Что если объект класса размещен в статической области памяти. Как проверить доступен ли this в этом случае?
Угу, не всегда применима. Забота о том, чтобы объект был создан с помощью new, лежит на плечах программиста.
На мой взгляд - это вполне естественная конструкция для С++. Об этом во многих книгах написано.
Конечно С++ сложен и требует тщательного подхода к проектированию и программированию, но в этом его гибкость и универсальность.
Я не вижу причин пугаться такой конструкции. Она опасна, но может быть применима :) .
/* ill-formed program */
class CBase
{
public;
void release()
{
delete this;
}
};
CBase * items= new CBase[10];
items[5].release();
delete [] items;
// Sm0ke \\
Такая конструкция очень часто встречается в интерфейсных библиотеках в Symbian для запуска всяких диалогов и т.п. Потому как после завершения диалога он больше не нужен, и нужно скорее освобождать память, которой немного в мобилах. А ограничения на такие конструкции соблюдаются правилами, по которым построены все классы в Symbian.
delete this;
Используется в объектах с подсчетом ссылок. Каждый объект хранит значение, сколько его экземпляров используется. При создании/копировании счетчик увеличивается, а при удалении (вызов деструктора) уменьшается. При достижении 0, объект удаляет сам себя и сразу же покидает деструктор.
Об этом хорошо написано у Скота Мейерса.
Я знаю применение: когда нужно "подменить" один объект другим, и при этом сделать это нужно методом этого класса. Например, есть класс А и его метод "А* А::В();", который должен удалить текущий объект (delete this) и создать новый, вернув указатель на него. Но лучше использовать класс-контейнер для такого класса.
А как быть в случае когда скажем есть обьект который управляет окном.По закрытию окна оконной процедуре приходит сообщение destroy.
Как самоудалить обьект если не через delete this
Анонимный
А как быть в случае когда скажем есть обьект который управляет окном.По закрытию окна оконной процедуре приходит сообщение destroy.
Как самоудалить обьект если не через delete this
Можно не самоудалять, а сделать некий следящий класс, который будет смотреть на, скажем, флаг, выставленный в объекте и удалять такие объекты раз в N секунд.
Да вообще можно и delete this использовать, только осторожно :-)
абсолютно нормальная конструкция. Как вы думаете сделан Release в каком-нить Com объекте? Ну а про то что ногу можно отстрелить, если неправильно воспользоваться это известный факт. Взять тот же пресловутый shallow copy c указателями на память внутри объектов.
delete this; вполне нормальная инструкция. Просто нужно понимать зачем она и использовать соотв. Пример реального применения для подсчета ссылок на инстансы объектов в одной кросплатформенной библиотеке http://irrlicht.svn.sourceforge.net/viewvc/irrlicht/trunk/include/IReferenceCounted.h?revision=4267&view=markup
Конструкция не нужна в "полностью готовой" работе.
А в качестве заглушки, пока не написан более правильный внешний кусочек кода (который удалит объект и занулит указатель) - вполне нормальная штука. Например, когда пишется новый класс и хочется его потестить в неспецифичном участке программы.
Я использовал данную конструкцию в функции класса, выделяющей память. В случае её отказа необходимо удалить целиком объект и произвести инициализацию заново, иначе последствия могут стать очень интересными.
Отправить комментарий