arb的静态ctor / dtor观察者。 C ++类

时间:2010-11-06 23:13:29

标签: c++ constructor destructor observer-pattern

我有一系列的类AB,...有很多派生类,这些类是在我不想改变的模块中创建的。

此外,我至少有一个类Z,只要类型A(或派生类)的对象创建,就必须通知它毁即可。将来,可能会有更多类YX想要观察不同的对象。

我正在寻找一种方便的方法来解决这个问题。 乍一看,问题似乎微不足道,但我现在有点卡住了。

我想出的是两个基类SpawnObserverSpawnObservable应该做的工作,但我对它们非常不满意有几个原因(参见这些类的简化)

  1. 当通知Z时,由于基类的顺序,实际对象尚未不再存在创建/销毁。尽管在销毁对象时可以比较指针(将它们从Z中的某些数据结构中删除),但是在创建它时它不起作用,并且当你有多个继承时肯定不起作用。
  2. 如果您只想观察一个课程,比如说A,则会始终通知您所有课程(AB,...)。
  3. 您必须在所有类中明确if / else,因此您必须知道SpawnObservable继承的所有类,这非常糟糕。
  4. 以下是我尝试修改最基本功能的类,您需要了解这些功能才能理解我的问题。简而言之:你只是继承自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
    

    我想强调,我完全清楚为什么我的解决方案的行为方式。我的问题是你是否有更好的方法来做到这一点。

    修改

    作为这个问题的延伸(受以下评论的启发),我想知道: 为什么你认为这是一种可怕的方法?

    作为补充说明:我试图通过这个来完成在每个创建的对象中安装普通的Observer。

    编辑2

    我会接受一个解决问题1的答案(上面列举中的粗体字)描述为什么整件事情都是一个非常糟糕的主意。

3 个答案:

答案 0 :(得分:2)

使用奇怪的重复模板模式。

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类DOES分别从Observer<A>Observer<B>通过A和B继承。它不需要知道或关心它们是否被观察到。 C实例将最终出现在所有三个列表中。

至于ctord和dtord,我实际上并没有看到它们执行的功能。您可以使用Observer :: ptrlist获取任何特定类型的列表。

再次编辑:噢,我明白了。请原谅我,我编辑了一些。伙计,这是我写过的一些最可怕的代码。你应该认真考虑不需要它。为什么不只是让需要了解其他对象的对象进行创建呢?

答案 1 :(得分:1)

问题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的使用,当然,如果你在一个对象的构造过程中调用它并没什么用处,这当然是好奇的重复出现的好处:你知道在派生期间的派生类型基地的建设。

(所以,如果你使用那种风格的解决方案,并且你可以在构建/破坏阶段之外执行你的操作,并且你不介意(多个)基类占用空间,你可以也许让每个base的构造函数存储一些特定于类的信息 - 使用typeid,或者或者traits - 并在构建更大的列表时将它们合并在一起。这应该是直截了当的,因为你将知道哪些基础对象对应于相同的派生对象。根据您尝试做的事情,这可能会对问题3有所帮助。)

答案 2 :(得分:0)

请特别注意信号和广告位Boost Signals and Slots