Компьютерный форум OSzone.net  

Компьютерный форум OSzone.net (http://forum.oszone.net/index.php)
-   Программирование и базы данных (http://forum.oszone.net/forumdisplay.php?f=21)
-   -   *Теория* | C++ | Самоубийство класса (http://forum.oszone.net/showthread.php?t=55934)

pva 31-10-2005 12:56 369162

*Теория* | C++ | Самоубийство класса
 
Есть два класса (С++) A и B (производные от одного и того же, например), которые нужно использовать по-очереди (они отражают внутреннее состояние другого большого объекта). По некоторому сигналу void A::foo() нужно, чтобы класс A уничтожился, а вместо него создался класс B. Какие будут предложения? Можно немного видоизменять задачу, но чтобы суть оставалась.

ivank 31-10-2005 19:40 369298

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

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

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

pva 03-11-2005 10:39 370043

Вопрос не в этом. Дело в том, что операция 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 370191

Цитата:

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

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

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

pva 07-11-2005 13:21 371077

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

pva 14-11-2005 10:20 373677

Решил через очередь заданий

ivank 14-11-2005 18:45 373856

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

Цитата:

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

pva 16-11-2005 13:11 374481

Можно код? Я не понял.

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

// класс задание или действие
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 с другими объектами.


Время: 02:35.

Время: 02:35.
© OSzone.net 2001-