我有一系列的类A
,B
,...有很多派生类,这些类是在我不想改变的模块中创建的。
此外,我至少有一个类Z
,只要类型A
(或派生类)的对象创建或,就必须通知它毁即可。将来,可能会有更多类Y
,X
想要观察不同的对象。
我正在寻找一种方便的方法来解决这个问题。 乍一看,问题似乎微不足道,但我现在有点卡住了。
我想出的是两个基类SpawnObserver
和SpawnObservable
应该做的工作,但我对它们非常不满意有几个原因(参见这些类的简化)
Z
时,由于基类的顺序,实际对象尚未或不再存在创建/销毁。尽管在销毁对象时可以比较指针(将它们从Z
中的某些数据结构中删除),但是在创建它时它不起作用,并且当你有多个继承时肯定不起作用。 A
,则会始终通知您所有课程(A
,B
,...)。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
我想强调,我完全清楚为什么我的解决方案的行为方式。我的问题是你是否有更好的方法来做到这一点。
修改
作为这个问题的延伸(受以下评论的启发),我想知道: 为什么你认为这是一种可怕的方法?
作为补充说明:我试图通过这个来完成在每个创建的对象中安装普通的Observer。
编辑2
我会接受一个解决问题1的答案(上面列举中的粗体字)或描述为什么整件事情都是一个非常糟糕的主意。
答案 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