C++的模板(九):模板的实例化问题

前文子系统中的例子, SubSystem内部用了STL库的map模板:

cpp 复制代码
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类型,就事论事而言,到这里只是一个整数数据的简单包装:

cpp 复制代码
class Event {
public:
        int ev_id;
        ~Event(){printf("~Event(id_%d)\n", ev_id);}
};

那么直接用Event类而不是用Event指针来构造map是不是更有效?确实。在不考虑将来有Event派生类的情况下,在这个例子可以这样改进。这样:

cpp 复制代码
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<"运算符重载引起的问题。加上它就行了。几百个错误,两三句话就改完了:

cpp 复制代码
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名称空间定义的模板,特化需要在同一名称空间进行:

cpp 复制代码
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模板(是的,继承就不用写代码了),等等,都行:

cpp 复制代码
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:

cpp 复制代码
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参数:

cpp 复制代码
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看起来最合适。这就把它挪过去:

cpp 复制代码
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只向它传一个参数,另一个参数要自己想办法了:

cpp 复制代码
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迭代器:

cpp 复制代码
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类型少了个==运算,把它补上。这样也通过了。

相关推荐
熬夜苦读学习5 分钟前
Linux文件系统
linux·运维·服务器·开发语言·后端
菜鸟一枚在这13 分钟前
深度解析建造者模式:复杂对象构建的优雅之道
java·开发语言·算法
阿巴~阿巴~32 分钟前
多源 BFS 算法详解:从原理到实现,高效解决多源最短路问题
开发语言·数据结构·c++·算法·宽度优先
CoderCodingNo1 小时前
【GESP】C++二级真题 luogu-b3924, [GESP202312 二级] 小杨的H字矩阵
java·c++·矩阵
奔跑吧邓邓子2 小时前
【Python爬虫(34)】Python多进程编程:开启高效并行世界的钥匙
开发语言·爬虫·python·多进程
刃神太酷啦2 小时前
堆和priority_queue
数据结构·c++·蓝桥杯c++组
Heris992 小时前
2.22 c++练习【operator运算符重载、封装消息队列、封装信号灯集】
开发语言·c++
----云烟----2 小时前
C/C++ 中 volatile 关键字详解
c语言·开发语言·c++
yuanpan3 小时前
23种设计模式之《组合模式(Composite)》在c#中的应用及理解
开发语言·设计模式·c#·组合模式
BanLul3 小时前
进程与线程 (三)——线程间通信
c语言·开发语言·算法