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

前文子系统中的例子, 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类型少了个==运算,把它补上。这样也通过了。

  • 10
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值