【问题标题】:Static ctor/dtor observer for arb. C++ classes用于 arb 的静态 ctor/dtor 观察者。 C++ 类
【发布时间】:2010-11-06 23:13:28
【问题描述】:

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

此外,我至少有一个类Z,每当A(或派生类)类型的对象创建销毁时都必须通知它>。未来可能会有更多的类,YX,想要观察不同的物体。

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

我想出的是两个基类 SpawnObserverSpawnObservable,它们应该可以完成这项工作,但出于几个原因,我对它们非常不满意(请参阅附件对这些类的简化)。

  1. Z 收到通知时,由于创建基类的顺序,实际对象尚未不再存在/被摧毁。尽管可以在销毁对象时比较指针(以将它们从Z 中的某些数据结构中删除),但这在创建时不起作用,并且在具有多重继承时肯定不起作用。李>
  2. 如果您只想观察一堂课,比如A,您总是会收到全部通知(AB、...)。
  3. 您必须通过所有类显式地 if/else,因此您必须知道所有从 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

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

编辑

作为这个问题的延伸(并受到下面 cmets 的启发),我想知道: 为什么你认为这是一种糟糕的方法?

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

编辑 2

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

【问题讨论】:

  • 您可以对观察到的对象使用智能引用,还是需要继续使用现有类型AB等而不彻底重新定义它们?
  • 修改 A, B, ... 是可能的,只要我不必更改它们的派生类。

标签: c++ constructor destructor observer-pattern


【解决方案1】:

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

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&lt;X&gt;Observer&lt;Y&gt; 是不同的类型,因此不需要菱形继承或类似的东西。另外,如果您需要 Observer&lt;X&gt;Observer&lt;Y&gt; 的不同功能,您可以专攻。

编辑@评论:

C 类确实从 Observer&lt;A&gt;Observer&lt;B&gt; 分别通过 A 和 B 继承。它不需要知道或关心他们是否被观察。一个 C 实例最终会出现在所有三个列表中。

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

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

【讨论】:

  • 我认为你的XY 是我的AB,对吧?但是,我不明白,这如何解决问题 1(来自上述问题的列表)。你能澄清一下吗?
  • @bitmask:它解决了 (1),因为观察者的 ctorddtord 函数(DeadMG 未显示)将知道他们得到的指针是(嗯:是或将是)他们注册观察的类型的对象的基类子对象,因此不需要您的无效dynamic_casts。但是,您仍然必须小心处理指针的操作:您不能通过它们访问 A 或 B 的成员。
  • @bitmask:实际上,我不确定 DeadMG 的代码是否正确。我认为 Z 应该继承自 Observer&lt;A&gt;Observer&lt;B&gt;,并且需要另一个模板 Observable&lt;T&gt;,其 ctor 和 dtor 调用所有在 Observer&lt;T&gt; 中注册的观察者。然后A继承自Observable&lt;A&gt;,也就是CRTP。不过,重点是因为Observer 是一个模板,Observer&lt;A&gt;Observer&lt;B&gt; 是完全不同的类,它们自己的静态成员变量只列出想要观察其类型的对象。
  • @Steve:我编辑了。我想我不太明白 OP 的代码做了什么,因为它作为一个函数实在是太可怕了。
  • @DeadMG:我一直试图假装,直到最后一个警告,这是一个用于观察除构造/破坏之外的其他事物的框架,在“这并没有真正发生,这并没有真正发生”某种方式;-)想要观察整个类的每个实例仍然有点笨拙,但我可以在调试/诊断/日志记录中看到它。
【解决方案2】:

问题 1 并不容易解决(事实上我认为这是不可能解决的)。奇怪地反复出现的模板想法最接近解决它,因为基类对派生类型进行编码,但如果您真的坚持在构造基类时知道派生类型,则必须为每个派生类添加一个基类。

如果您不介意执行实际操作(我的意思是记账除外)或检查每个对象的构造函数或析构函数之外的列表,您可以让它(重新)构建最小列表,仅在操作时即将执行。这使您有机会使用完全构造的对象,并更容易解决问题 2。

您首先要列出已构建但不在“完整”列表中的对象列表。并且“完整”列表将包含每个构造对象的两个指针。一个是指向基类的指针,您将从Observable 构造函数中存储它,可能在单个对象的构造过程中多次存储。另一个是void *,指向对象的最衍生部分——使用dynamic_cast&lt;void *&gt; 检索它——用于确保每个对象在列表中只出现一次。

当一个对象被销毁时,如果它有多个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。)

【讨论】:

  • 我几乎接受了您对无法修复的回答,我现在也这么认为。但我实际上选择了 DeadMG 的 CRT 模式,所以我不得不接受那个 :)
  • 好吧,如果你发现了一些有用的东西,那么接受另一个答案是没有意义的 :) 听起来无论你做什么都会有点痛苦,祝你好运!
【解决方案3】:

看看信号和插槽,尤其是Boost Signals and Slots

【讨论】:

  • 我无法使用 boost 库。您能否详细说明您的意思是什么类型的信号和插槽?我似乎在您发布的参考资料中找不到任何合适的课程。
  • 信号和槽允许多个观察者以最小的干扰观察多个对象(以多对多的方式)。我认为这就是你所追求的。顺便说一句,你为什么不能使用 Boost?
  • 对,但是我想观察一些我还不知道的东西,所以我尝试修改观察者模式。 --- 由于项目决定不使用 Boost,我无法使用 Boost。
猜你喜欢
  • 2017-03-01
  • 2010-09-22
  • 1970-01-01
  • 1970-01-01
  • 2020-04-21
  • 1970-01-01
  • 1970-01-01
  • 2016-11-09
  • 2010-10-10
相关资源
最近更新 更多