前文子系統中的例子, SubSystem內部用了STL庫的map模板:
template <class Event, class Response>
class SubSystem{
public:map<Event*, Response*> table;
public:void bind(Event *e, Response *r);void unbind(Event *e);
public:int OnMessage(Event *e);
};
而作為Key來使用的Event類型,就事論事而言,到這里只是一個整數數據的簡單包裝:
class Event {
public:int ev_id;~Event(){printf("~Event(id_%d)\n", ev_id);}
};
那么直接用Event類而不是用Event指針來構造map是不是更有效?確實。在不考慮將來有Event派生類的情況下,在這個例子可以這樣改進。這樣:
template <class Event, class Response>
class SubSystem{
public:map<Event, Response*> table;
public:void bind(Event *e, Response *r);void unbind(Event *e);
public:int OnMessage(Event *e);
};
同時,成員函數中指針的使用也要相應調整。這樣SubSystem類就改完了。編譯… 。預計直接通過,但編譯報了很多錯,刷了幾屏都翻不過來… 。用過STL庫工程師們都有過這種經驗吧!
什么原因呢,STL中的容器,對用作key的類型是有些講究的,key必須能夠比較,而這里的Event類沒寫“operator<”運算。這就導致模板對key引用發生問題,模板內的一些函數或變量沒法生成,發生雪崩效應,進而導致更多的引用錯誤報出來。編程中遇到這種情況,不用緊張,報的都是虛假的錯誤,只要找到模板的參數類,看看哪里沒寫完整,輕輕一改所有錯誤就會立即消失。
這里檢查看到是Event類少了“operator<”運算符重載引起的問題。加上它就行了。幾百個錯誤,兩三句話就改完了:
class Event {
public:int ev_id;~Event(){printf("~Event(id_%d)\n", ev_id);}bool operator<(const Event &e) const { return ev_id<e.ev_id; }
};
當然這不是唯一的辦法。如果不想改Event ,改less也可以。錯誤都是因為缺少“operator<”運算符,模板中的less實例化失敗引起的。接著產生了連鎖反應。直接把針對 Event的特殊的less加上就解決了問題。用less特化。因為less是std名稱空間定義的模板,特化需要在同一名稱空間進行:
class Event {
public:int ev_id;~Event(){printf("~Event(id_%d)\n", ev_id);}
};
namespace std{
template <>
struct less<Event> {bool operator()(const Event &e, const Event &e1) const {return e.ev_id<e1.ev_id;}
};
}
這樣Event就不用改。或者,不從std名稱空間改,重開一個less模板,如果參數類型是Event就特化,否則,就繼承 std::less模板(是的,繼承就不用寫代碼了),等等,都行:
namespace dts {
template <typename T>
struct less : std::less<T> {
};
template <>
struct less<Event> {bool operator()(const Event &e, const Event &e1) const {return e.ev_id<e1.ev_id;}
};
}template <class Event, class Response>
class SubSystem{
public:map<Event, Response*,dts::less<Event> > table;
public:void bind(Event *e, Response *r);void unbind(Event *e);
public:int OnMessage(Event *e);
};
map模板是可以重新設置less參數的。這樣,問題就解決了。
另外,還有個更狠的辦法,可以叫編譯器立即閉嘴。向STL容器傳入自定義類型的指針,而不是自定義類型本身。因為指針直接帶有容器需要的所有運算符,這樣編譯器就再也不會報錯了。
但這樣容器的find函數也就不能再用了。恰好子系統的例子中就有一個這樣的find,現在就來看看find:
Event *find(list<Event*> &l, Event &e)
{list<Event*>::iterator i;for(i=l.begin(); i!=l.end(); i++) {if ((*i)->ev_id==e.ev_id) return *i;}return 0;
}
list中存的是Event的指針,STL庫的find需要相同的類型,也就是用Event的指針去找,如果找到,就給你一個你本來就已經有了的Event指針。看起來這像個悖論。但庫的邏輯就是這樣。所以子系統的例子就自己寫了一個find。
如果不想自己寫,還可以用STL庫的find_if模板。它有個pred參數。這是重載了()運算符的仿函數。仿函數唯一的功能就是重載了()運算符。除此以外就是初始化。下面的Match就是仿函數,用來匹配find_if模板的pred參數:
struct Match {Event ev;bool operator()(Event* e) {return ev.ev_id==e->ev_id;}
};
Event *find(list<Event*> &l, Event &e)
{list<Event*>::iterator i;Match m;m.ev=e;i = std::find_if(l.begin(), l.end(), m);if(i!=l.end())return *i;return 0;
}
find_if的比較就很靈活了。但現在Match出現在全局名稱空間。如果不想這個小不點污染名稱空間,可以把它挪到任何一個關聯的類里面去。而class Event看起來最合適。這就把它挪過去:
class Event {
public:int ev_id;~Event(){printf("~Event(id_%d)\n", ev_id);}struct Match {Event *ev;bool operator()(Event* e) {return ev->ev_id==e->ev_id;}};
};
看起來最合適,卻需要改一改。因為放在這里 Event成了未定義完成的類, Match中不能直接用,所以改成用它的指針。相關代碼也調整一下。這樣就好了。
當然也可以向pred傳入普通的比較函數,但find_if只向它傳一個參數,另一個參數要自己想辦法了:
Event *find(list<Event*> &l, Event &e)
{iterator i;static int ev_id;struct Match{static bool p(Event *e) {return ev_id==e->ev_id;}};ev_id=e.ev_id;i = std::find_if(l.begin(), l.end(), Match::p);if( i!=l.end()) return *i;return 0;
}
如果也不想用這種方法,還有沒有別的辦法?答案是,有的。最后還是可以回到STL的標準的find上來。直接用當然是不行,但是可以重載iterator迭代器:
class iterator: public list<Event*>::iterator{
public:Event& operator*() {return *(list<Event*>::iterator::operator*());}iterator &operator=(list<Event*>::iterator i) {list<Event*>::iterator::operator=(i);return *this;}
} ;Event *find(list<Event*> &l, Event &e)
{::iterator i, begin, end;begin= l.begin();end= l.end();i= std::find(begin, end, e);if(i!=end) return &*i;return 0;
}
iterator 這個名字跟std預定義的名字沖突了。所以用的時候帶上了作用域分辨符,否則就不是這里的class iterator了。因為重載了*,return &*i; 意思就不是return i; 了。最后編譯后報告Event類型少了個==運算,把它補上。這樣也通過了。