Статический ctor / dtor наблюдатель для arbКлассы C ++ - PullRequest
1 голос
/ 07 ноября 2010

У меня есть серия классов A, B, ..., которые имеют много производных классов, созданных внутри модуля, который я не хочу менять.

Кроме того, у меня есть по крайней мереодин класс Z, который должен сообщаться всякий раз, когда объект типа A (или производные классы) создан или уничтожен .В будущем может появиться больше классов Y, X, которые хотят наблюдать за различными объектами.

Я ищу удобный способ решить эту проблему.На первый взгляд, проблема казалась тривиальной, но я застрял прямо сейчас.

Я придумал два базовых класса SpawnObserver и SpawnObservable, которые должны выполнять эту работу,но я очень недоволен ими по нескольким причинам (см. прилагаемое упрощение этих классов).

  1. При получении уведомления Z фактический объект либо еще не или больше не существует не существует из-за порядка, в котором создаются / уничтожаются базовые классы.Хотя указатели можно сравнивать при уничтожении объекта (чтобы удалить их из некоторых структур данных в Z), он не работает при его создании и, безусловно, не работает при множественном наследовании.
  2. Если вы хотите наблюдать только один класс, скажем, A, вы всегда будете уведомлены обо всех (A, B, ...).
  3. Вы должны явно, если / иначечерез все классы, поэтому вы должны знать все классы, которые наследуются от SpawnObservable, что довольно плохо.

Вот классы, которые я пытался обрезать досамый базовый функционал, который вам нужно знать, чтобы понять мою проблему.В двух словах: вы просто наследуете от SpawnObservable, а ctor / dtor выполняет функцию уведомления наблюдателей (по крайней мере, это то, что я хочу иметь).

#include <list>
#include <iostream>

class SpawnObservable;

class SpawnObserver {
  public:
    virtual void ctord(SpawnObservable*) = 0;
    virtual void dtord(SpawnObservable*) = 0;
};

class SpawnObservable {
  public:
    static std::list<SpawnObserver*> obs;
    SpawnObservable() {
      for (std::list<SpawnObserver*>::iterator it = obs.begin(), end = obs.end(); it != end; ++it) {
        (*it)->ctord(this);
      }
    }
    ~SpawnObservable() {
      for (std::list<SpawnObserver*>::iterator it = obs.begin(), end = obs.end(); it != end; ++it) {
        (*it)->dtord(this);
      }
    }
    virtual void foo() {} // XXX: very nasty dummy virtual function
};
std::list<SpawnObserver*> SpawnObservable::obs;

struct Dummy {
  int i;
  Dummy() : i(13) {}
};

class A : public SpawnObservable {
  public:
    Dummy d;
    A() : SpawnObservable() {
      d.i = 23;
    }
    A(int i) : SpawnObservable() {
      d.i = i;
    }
};

class B : public SpawnObservable {
  public:
    B() { std::cout << "making B" << std::endl;}
    ~B() { std::cout << "killing B" << std::endl;}
};

class PrintSO : public SpawnObserver { // <-- Z
  void print(std::string prefix, SpawnObservable* so) {
    if (dynamic_cast<A*>(so)) {
      std::cout << prefix << so << " " << "A: " << (dynamic_cast<A*>(so))->d.i << std::endl;
    } else if (dynamic_cast<B*>(so)) {
      std::cout << prefix << so << " " << "B: " << std::endl;
    } else {
      std::cout << prefix << so << " " << "unknown" << std::endl;
    }
  }
  virtual void ctord(SpawnObservable* so) {
    print(std::string("[ctord] "),so);
  }
  virtual void dtord(SpawnObservable* so) {
    print(std::string("[dtord] "),so);
  }
};


int main(int argc, char** argv) {
  PrintSO pso;
  A::obs.push_back(&pso);
  B* pb;
  {
    std::cout << "entering scope 1" << std::endl;
    A a(33);
    A a2(34);
    B b;
    std::cout << "adresses: " << &a << ", " << &a2 << ", " << &b << std::endl;
    std::cout << "leaving scope 1" << std::endl;
  }
  {
    std::cout << "entering scope 1" << std::endl;
    A a;
    A a2(35);
    std::cout << "adresses: " << &a << ", " << &a2 << std::endl;
    std::cout << "leaving scope 1" << std::endl;
  }
  return 1;
}

Вывод:

entering scope 1
[ctord] 0x7fff1113c640 unknown
[ctord] 0x7fff1113c650 unknown
[ctord] 0x7fff1113c660 unknown
making B
adresses: 0x7fff1113c640, 0x7fff1113c650, 0x7fff1113c660
leaving scope 1
killing B
[dtord] 0x7fff1113c660 unknown
[dtord] 0x7fff1113c650 unknown
[dtord] 0x7fff1113c640 unknown
entering scope 1
[ctord] 0x7fff1113c650 unknown
[ctord] 0x7fff1113c640 unknown
adresses: 0x7fff1113c650, 0x7fff1113c640
leaving scope 1
[dtord] 0x7fff1113c640 unknown
[dtord] 0x7fff1113c650 unknown

Хочу подчеркнуть, что я прекрасно знаю , почему мое решение ведет себя так, как оно.У меня вопрос, есть ли у вас лучший подход к этому?

РЕДАКТИРОВАТЬ

В качестве дополнения к этому вопросу (и вдохновленный комментариями ниже), я быХотелось бы знать: почему вы думаете, что это ужасный подход?

В качестве дополнительного примечания: я пытаюсь добиться этого, установив обычный Наблюдатель в каждый созданный объект.

РЕДАКТИРОВАТЬ 2

Я приму ответ, который решает проблему 1 (жирный в перечислении выше) или описывает, почему все это очень плохая идея.

Ответы [ 3 ]

2 голосов
/ 07 ноября 2010

Используйте любопытно повторяющийся шаблон.

template<typename T> class watcher {
    typename std::list<T>::iterator it;
    watcher();
    ~watcher();
    void ctord(T*);
    void dtord(T*);    
};
template<typename T> class Observer {
public:

    typedef std::list<T*> ptr_list;
    static ptr_list ptrlist;
    typedef typename ptr_list::iterator it_type;
    it_type it;

    typedef std::list<watcher<T>*> watcher_list;
    static watcher_list watcherlist;
    typedef typename watcher_list::iterator watcher_it_type;

    Observer() {
       ptrlist.push_back(this);
       it_type end = ptrlist.end();
       end--;
       it = end;
       for(watcher_it_type w_it = watcherlist.begin(); w_it != watcherlist.end(); w_it++)
           w_it->ctord(this);
    }
    ~Observer() {
        ptrlist.erase(it);
       for(watcher_it_type w_it = watcherlist.begin(); w_it != watcherlist.end(); w_it++)
           w_it->ctord(this);
    }
};
class A : public Observer<A> {
};
class B : public Observer<B> {
};
class C : public A, public B, public Observer<C> {
    // No virtual inheritance required - all the Observers are a different type.
};
template<typename T> watcher<T>::watcher<T>() {
    Observer<T>::watcherlist.push_back(this);
    it = watcherlist.end();
    it--;         
}
template<typename T> watcher<T>::~watcher<T>() {
    Observer<T>::watcherlist.erase(it);
}
template<typename T> void watcher<T>::ctord(T* ptr) {
    // ptr points to an instance of T that just got constructed
}
template<typename T> void watcher<T>::dtord(T* ptr) {
    // ptr points to an instance of T that is just about to get destructed.
}

Не только это, но вы можете наследовать от Observer несколько раз, используя эту технику, поскольку два Observer<X> и Observer<Y> являются разными типами и, таким образом, не требуют наследования алмазов или чего-либо подобного. Кроме того, если вам нужны разные функции для Observer<X> и Observer<Y>, вы можете специализироваться.

Редактировать @ Комментарии:

класс C наследует от Observer<A> и Observer<B> через A и B соответственно. Это не должно знать или заботиться, наблюдают ли они или нет. Экземпляр C попадет во все три списка.

Что касается ctord и dtord, я фактически не вижу, какую функцию они выполняют. Вы можете получить список любого определенного типа, используя Observer :: ptrlist.

Отредактируйте еще раз: О-о-о-о, понятно. Извините, я редактирую еще немного. Чувак, это один из самых отвратительных кодов, которые я когда-либо писал. Вы должны серьезно подумать, не нужно ли это. Почему бы не сделать так, чтобы объекты, о которых нужно знать, были созданы другими?

1 голос
/ 08 ноября 2010

Проблема 1 не легко решается (на самом деле я думаю, что это невозможно исправить).Любопытно, что повторяющаяся идея шаблона ближе всего подходит к ее решению, потому что базовый класс кодирует производный тип, но вам придется добавить базу к каждому производному классу, если вы действительно настаиваете на знании производного типа при создании базы.

Если вы не возражаете против выполнения ваших реальных операций (я имею в виду не только бухгалтерии) или изучения списка вне конструктора или деструктора каждого объекта, вы можете (ре) построить только минимальный списоккогда операция должна быть выполнена.Это дает вам возможность использовать полностью сконструированный объект и упрощает решение проблемы 2.

Вы могли бы сделать это, сначала имея список объектов, которые были построены, но не включены«полный» список.И «полный» список будет содержать два указателя на построенный объект.Одним из них является указатель на базовый класс, который вы будете хранить в конструкторе Observable, возможно, несколько раз при создании одного объекта.Другой - void *, указывающий на наиболее производную часть объекта - используйте dynamic_cast<void *>, чтобы получить это - и используется, чтобы убедиться, что каждый объект появляется только один раз в списке.

Когда объект уничтожен, если у него есть несколько Observable баз, каждая попытается удалить себя из списков, а когда дело доходит до полного списка, удастся выполнить только одну, но это нормально, потому что каждая из них одинаково хороша какпроизвольная основа этого объекта.

Ниже следует некоторый код.

Ваш полный список объектов, итеративный, настолько простой, насколько позволяет std::map.(Каждый void * и каждый Observable * уникален, но в качестве ключа используется Observable *, так что запись в деструкторе Observable легко удалить.)

typedef std::map<Observable *, void *> AllObjects;
AllObjects allObjects;

ИВаш список объектов, которые были построены, но еще не добавлены в allObjects:

std::set<Observable *> recentlyConstructedObjects;

В конструкторе Observable добавьте новый объект в список ожидающих объектов:

recentlyConstructedObjects.insert(this);

В деструкторе Observable удалите объект:

// 'this' may not be a valid key, if the object is in 'allObjects'.
recentlyConstructedObjects.erase(this);

// 'this' may not be a valid key, if the object is in 'recentlyConstructedObjects',
// or this object has another Observable base object and that one got used instead.
allObjects.erase(this);

Прежде чем вы приступите к работе, обновите allObjects, если после этого были построены какие-либо объектыв последний раз оно обновлялось:

if(!recentlyConstructedObjects.empty()) {
    std::map<void *, Observable *> newObjects;
    for(std::set<Observable *>::const_iterator it = recentlyConstructedObjects.begin(); it != recentlyConstructedObjects.end(); ++it)
        allObjectsRev[dynamic_cast<void *>(*it)] = *it;

    for(std::map<void *, Observable *>::const_iterator it = newObjects.begin(); it != newObjects.end(); ++it)
        allObjects[it->second] = it->first;

    recentlyConstructedObjects.clear();
}

И теперь вы можете посетить каждый объект по одному разу:

for(std::map<Observable *,void *>::const_iterator it = allObjects.begin(); it != allObjects.end(); ++it) {
    // you can dynamic_cast<whatever *>(it->first) to see what type of thing it is
    //
    // it->second is good as a key, uniquely identifying the object
}

Ну ... теперь, когда я все это написал, яне уверен, решит ли это вашу проблему.Тем не менее было интересно рассмотреть.

(Эта идея решит одну из проблем с любопытным повторяющимся шаблоном, а именно, что у вас есть много базовых объектов на производный объект, и из-за этого сложнее распутать. (К сожалению,нет решения для большого количества базовых классов, извините.) Из-за использования dynamic_cast, конечно, не очень полезно, если вы вызываете его во время конструирования объекта, что, конечно, является преимуществом любопытно повторяющейся вещи: вы знаете производный тип во время строительства базы.

(Таким образом, если вы идете с этим стилем решения, И вы в порядке с выполнением операций вне стадии строительства / разрушения, И вы неНе говоря уже о (нескольких) базовых классах, занимающих место, вы, возможно, могли бы заставить каждый конструктор базы хранить некоторую специфичную для класса информацию - используя, возможно, typeid или черты - и объединять их вместе, когда вы строите большой список.должно быть простым, так как вы будете знать, какая базаобъекты соответствуют одному и тому же производному объекту.В зависимости от того, что вы пытаетесь сделать, это может помочь вам с проблемой 3.)

0 голосов
/ 07 ноября 2010

Обратите внимание на сигналы и слоты, особенно Повышение сигналов и слотов

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