25 августа 2007

`this' is just an ordinary pointer

Как вы думаете, насколько надежна такая конструкция:
int Bar();
class Foo
{
int x;
public:
void y()
{
x = Bar();
}
};
На первый взгляд все отлично. До тех пор, пока не увидим всю картину:
Foo* foo;
int Bar()
{
if (...) delete foo;
return ...;
}
void Main
{
foo = new Foo();
foo->y();
}
Ситауция представлена здесь довольно утрированно, но суть понятна: мембер-функция Foo::y() вызывает другую функцию Bar(), которая удаляет объект. Тот самый объект, который Foo::y() знает как this. Далее Foo::y() пытается записать что-то в this->x, который уже не существует.

Реальна ли такая ситуация, или это ошибка архитектуры? Думаю, такая ситуация вполне может иметь место. Ведь мы давно свыклись с тем, что после vector::push_back() может привести в негодность все наши итераторы на этот вектор, и к другим подобным ситуациям. Нужно просто при вызове функции (в нашем случае Bar()) знать, что this может стать недействительным.

Такая ситуация может возникнуть, например, если элементы GUI (controls) реализованы как объекты (привет WTL):
void Button::OnClick()
{
Dialog.Close();
// this больше недействителен!
}
Вобщем, this - это такой же обычный указатель, и логично сделать его умным указателем. Пусть сам о себе заботится.
class Foo : public boost::enable_shared_from_this<Foo>
{
int x;
public:
void y()
{
boost::weak_ptr<Foo> This(shared_from_this());
int bar = Bar();
if (!This->expired()) x = bar;
}
};

boost::shared_ptr<Foo> foo;
int Bar()
{
if (...) foo.reset();
return ...;
}

1 комментарий:

Qbit комментирует...

Мне такая ситуация попадалась при работе с Boost.Statechart (библиотека для работы с машинами состояний).

Там в классах состояний приходится писать методы react():

namespace sc = boost::statechart;
...
sc::result react(SomeEvent const& ev)
{
return transit<OtherState>(
&Machine::SomeAction, ev);
}

Функция transit() удаляет объект текущего состояния и создаёт объект нового состояния. Т. е., делает, по сути, «delete this;». В доках так прямо и говорится: использование состояния после вызова в нём функции transit() приведёт к неопределённому поведению.
--
Qbit

P. S. Блин, Блоггер запрещает тэг <PRE>!