Показать полную графическую версию : *Теория* | C++ | Самоубийство класса
Есть два класса (С++) A и B (производные от одного и того же, например), которые нужно использовать по-очереди (они отражают внутреннее состояние другого большого объекта). По некоторому сигналу void A::foo() нужно, чтобы класс A уничтожился, а вместо него создался класс B. Какие будут предложения? Можно немного видоизменять задачу, но чтобы суть оставалась.
pva
Сделать враппер? По-моему, самое логичное решение. Т.е. для пользователя это один объект нкжного интерфейса, а внутри он переадресовывает все вызовы требуемому объекту. Просто и надёжно.
Второй вариант, если на объектр хранится всего один указатель (например как поле какого-то объекта), то можно, например, при создании этого обхекта дать ему указатель на этот указатель, и когда он пожелавет заменить себя на B, пусть просто удалиться, а по указателю запишет адрес ново-созданного B. Просто и очень ненадёжно.
Вариант третий, написать свой аллокатор (или перегрузить operator new/delete), что бы он выделял памяти достаточно под объекты как типа A, так и типа B. Тогда при необходимости удалиться можно вызвать на себя деструктор, и на своём же старом месте с помощью placement new создать объект класса B. По идее, если объекты наследуют одинаковые интерфейсы (лучше если в одинаковом порядке), то будет работать. Сложно, да и нафиг нужно.
Вопрос не в этом. Дело в том, что операция 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
}
Дело в том, что операция delete this вообще говоря противоречит смыслу.Ни в коей мере. Стандартный трюк в COM, например - когда на нас исчерпались внешние ссылки, то убиваем себя (в функции Release).
Другое дело, что после самоубийства в методе мы уже не можем обращаться к полям объекта.
В общем, не вижу противоречий.
Так что делать? Прослеживать, чтобы A::foo() запускалась только там, где можно?
Решил через очередь заданий
pva
Пардон, потерял тему, поэтому не отвечал. Как я уже сказал, я бы обернул вызовы методов A и B во враппер AB, который внутри вызвает методы соответствующего объекта. Опять же так и внешние ссылки отследить проще.
Решил через очередь заданийНе пояснишь, что такое очередь заданий?
Можно код? Я не понял.
Очередь заданий:
// класс задание или действие
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
vBulletin v3.6.4, Copyright ©2000-2025, Jelsoft Enterprises Ltd.