Войти

Показать полную графическую версию : *Теория* | C++ | Самоубийство класса


pva
31-10-2005, 12:56
Есть два класса (С++) A и B (производные от одного и того же, например), которые нужно использовать по-очереди (они отражают внутреннее состояние другого большого объекта). По некоторому сигналу void A::foo() нужно, чтобы класс A уничтожился, а вместо него создался класс B. Какие будут предложения? Можно немного видоизменять задачу, но чтобы суть оставалась.

ivank
31-10-2005, 19:40
pva
Сделать враппер? По-моему, самое логичное решение. Т.е. для пользователя это один объект нкжного интерфейса, а внутри он переадресовывает все вызовы требуемому объекту. Просто и надёжно.

Второй вариант, если на объектр хранится всего один указатель (например как поле какого-то объекта), то можно, например, при создании этого обхекта дать ему указатель на этот указатель, и когда он пожелавет заменить себя на B, пусть просто удалиться, а по указателю запишет адрес ново-созданного B. Просто и очень ненадёжно.

Вариант третий, написать свой аллокатор (или перегрузить operator new/delete), что бы он выделял памяти достаточно под объекты как типа A, так и типа B. Тогда при необходимости удалиться можно вызвать на себя деструктор, и на своём же старом месте с помощью placement new создать объект класса B. По идее, если объекты наследуют одинаковые интерфейсы (лучше если в одинаковом порядке), то будет работать. Сложно, да и нафиг нужно.

pva
03-11-2005, 10:39
Вопрос не в этом. Дело в том, что операция delete this вообще говоря противоречит смыслу. Перед вызовом деструктора объект уже не живёт, а его указатель используется, да ещё и виртуальной функцией.
использовать код вроде switch(action) {...} не хочется, т.к. объекты хранят разную информацию.

class X {}
class A : X {int n;virtual void foo();}
class B : X {double f;}

std::auto_ptr<X> px(new A());

void A::foo()
{
// пожелал заменить себя
// стандартный аллокатор ::new выделит достаточно памяти
px.reset(new B());
// A уже не существует, но используется его указатель
// например: ++n; по идее здесь access violation
}

ivank
03-11-2005, 20:56
Дело в том, что операция delete this вообще говоря противоречит смыслу.Ни в коей мере. Стандартный трюк в COM, например - когда на нас исчерпались внешние ссылки, то убиваем себя (в функции Release).

Другое дело, что после самоубийства в методе мы уже не можем обращаться к полям объекта.

В общем, не вижу противоречий.

pva
07-11-2005, 13:21
Так что делать? Прослеживать, чтобы A::foo() запускалась только там, где можно?

pva
14-11-2005, 10:20
Решил через очередь заданий

ivank
14-11-2005, 18:45
pva
Пардон, потерял тему, поэтому не отвечал. Как я уже сказал, я бы обернул вызовы методов A и B во враппер AB, который внутри вызвает методы соответствующего объекта. Опять же так и внешние ссылки отследить проще.

Решил через очередь заданийНе пояснишь, что такое очередь заданий?

pva
16-11-2005, 13:11
Можно код? Я не понял.

Очередь заданий:

// класс задание или действие
class TAction
{
public:
virtual ~TAction() {}
virtual void fire() = 0;
};

// список заданий (exception-safe)
class TActionList
{
typedef std::vector<TAction*>::iterator iter;
std::vector<TAction*> fitems;

void _remove(iter a, iter b)
{
for(iter c=a; a!=b; ++c) delete *c;
fitems.erase(a,b);
}

public:
~TActionList() {clear();}
void add(TAction* a) {fitems.push_back(a);}
void remove(TAction* a) {_remove(std::remove(fitems.begin(), fitems.end(), a));}
void clear() {_remove(fitems.begin(), fitems.end());}
void fire() {for(iter a=fitems.begin(), b=fitems.end(); a!=b; ++b) (*a)->fire();}
};

// класс, управляющий жизнью объектов
// TObject::execute() можно вызывать только один раз при запуске программы
class TObject
{
static TActionList fcleanup;
static void processTasks();
static void cleanup() {fcleanup.fire(); fcleanup.clear();}
public:
static void execute();
static void registerCleanup(TAction* a) {fcleanup.add(a);}
}

TActionList TObject::fcleanup;

void TObject::execute()
{
while (waitTask())
{
processTasks(); // каким-нибудь образом вызывает A::foo();
cleanup(); // такой доступ к cleanup() гарантирует, что в стеке ничего от A:: не запущено
}
}

...
// где-нибудь в программе:
std::auto_ptr<A> behavior;

void A::foo()
{
struct cleanup_A
{
void fire() { clog << "cleanup_A" << end;}
cleanup_A(A* a) : fcleanup(a);
private:
std::auto_ptr<A> fcleanup;
}

std::auto_ptr<A> b(new B(...));
std::auto_ptr<cleanup_A> clean_a(new cleanup_A(behavior.get()));
TObject::registerCleanup(clean_a.get());

clean_a.release(); // non-throwing
behavior.release(); // non-throwing
behavior = b; // non-throwing
}

Эффект кода подобен чистильщику java, только чистит когда делать нечего, а не когда память кончилась
Для полной картины, хорошо бы добавить A::unlink(), которая убирает связи ещё живого A с другими объектами.




© OSzone.net 2001-2012