当前位置: 首页 > news >正文

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

http://www.lryc.cn/news/385173.html

相关文章:

  • Clickhouse Projection
  • 放烟花短视频素材去哪里找?去哪里下载?烟花素材网分享
  • 爬虫笔记14——爬取网页数据写入MongoDB数据库,以爱奇艺为例
  • Jenkins教程-10-发送飞书测试报告通知
  • Swift开发——简单App设计
  • Python操作mysql
  • 监控易产品升级动态:V7.6.6.15版本全面升级
  • Vue3 + Element-plus + TS —— 动态表格自由编辑
  • 虚拟机配置桥接模式
  • 星戈瑞DSPE-SS-PEG-CY7近红外花菁染料
  • LeetCode:503. 下一个更大元素 II(Java 单调栈)
  • 代码重构:解读重构概念及重构实战
  • java.util.Optional类介绍
  • PhotoShop自动生成号码牌文件
  • 02逻辑代数与硬件描述语言基础
  • OpenGL3.3_C++_Windows(21)
  • clickhouse学习
  • MySQL高级-索引-使用规则-前缀索引
  • 外星生命在地球的潜在存在:科学、哲学与社会的交织
  • 使用FRP 0.58版本进行内网穿透的详细教程
  • 0000电子技术基础概述
  • vscode+platformio使用STC官方库进行51单片机开发 -- 中断异常
  • 探索Android架构设计
  • 基于matlab的不同边缘检测算子的边缘检测
  • CentOS安装ntp时间同步服务
  • 【Linux进阶】UNIX体系结构分解——操作系统,内核,shell
  • PageOffice国产版在线编辑word文件
  • Bitmap位图数据排列方式
  • 重磅消息:ONLYOFFICE8.1版本桌面编辑器发布:功能完善的 PDF 编辑器、幻灯片版式、改进从右至左显示、新的本地化选项等
  • 16进制数按位修改