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.mzph.cn/diannao/36723.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

10位时间戳、13位时间戳、17位时间戳,以及在JavaScript中的格式转换

一、介绍 1、10位时间戳 2、13位时间戳 3、17位时间戳 4、时间戳转换工具 二、13位时间戳的转换 1、转标准日期 2、转格式化日期 三、10位时间戳的转换 1、转标准日期 2、转格式化日期 四、17位时间戳的转换 1、解析思路 2、解析过程 &#xff08;1&#xff09;统…

C++系统编程篇——Linux第一个小程序--进度条

&#xff08;1&#xff09;先引入一个概念&#xff1a;行缓冲区 \r和\n \r表示回车 \n表示回车并换行 ①代码一 #include<stdio.h> #include<unistd.h> int main()…

django学习入门系列之第三点《伪类简单了解》

文章目录 hover&#xff08;伪类&#xff09;after&#xff08;伪类&#xff09;往期回顾 hover&#xff08;伪类&#xff09; 伪类指的是用冒号加的 hover样式指的是&#xff0c;当用户光标移动到设定区域后&#xff0c;所执行的用法 如&#xff1a; <!DOCTYPE html>…

【C语言】函数无参数有返回值、有参数无返回值、有参数有返回值

文章目录 前言C语言函数的分类和使用无参数有返回值的函数有参数无返回值的函数有参数有返回值的函数 总结 前言 在C语言中&#xff0c;函数是一种重要的组织代码的方式。根据函数的参数和返回值&#xff0c;我们可以将函数分为三类&#xff1a;无参数有返回值、有参数无返回值…

清理未使用的镜像和容器

删除未使用的镜像和容器&#xff1a; docker system prune -a清理构建缓存&#xff1a; Docker 会缓存构建过程中使用的中间镜像&#xff0c;可以通过以下命令清理它们&#xff1a; docker builder prune定期清理旧镜像&#xff1a; 定期运行以下命令清理旧镜像&#xff1a; …

通过代理从ARDUINO IDE直接下载开发板包

使用免费代理 实现ARDUINO IDE2.3.2 下载ESP8266/ESP32包 免费代理 列表 测试代理是否可用的 网站 有时&#xff0c;代理是可用的&#xff0c;但依然有可能找不到开发板管理器的资料包。 可以多换几个代理试试。 代理的配置 文件 -> 首选项 -> 网络 进入后做如下配置…

2024百度之星第二场-小度的01串

补题链接&#xff1a; 码蹄集 一道经典线段树板子题。 区间修改01置换&#xff0c;区间查询子串权值。 唯一区别&#xff0c;权值要求的是相邻字符都不同所需修改的最小字符个数。 我们在线段树节点上分别维护当前连续区间&#xff1a; 奇数位是0的个数&#xff08;j0&…

K8S两种安装方式如何选择?

K8S两种安装方式如何选择&#xff1f;\nKubeadm VS kubernetes 二进制\n\n1、kubeadm 方式部署&#xff08;推荐&#xff09;\n推荐理由&#xff1a;\n\n官方推荐&#xff1a;kubeadm 是 Kubernetes 官方提供的工具&#xff0c;用于快速搭建生产级别的 Kubernetes 集群&#xf…

python读取hdf4文件

记录一下使用xarray读取hdf4&#xff08;not hdf5&#xff09;过程中遇到的问题. 目的: 读取hdf4 file的matadata遇到的问题&#xff1a;使用xarray.open_dataset()失败解决方法&#xff1a;使用pyhdf.SD代替 import os from pyhdf.SD import SD, SDC import xarray as xr im…

ios CCNSDate.m

// // CCNSDate.h // CCFC // // Created by xichen on 11-12-17. // Copyright 2011年 ccteam. All rights reserved. //#import <Foundation/Foundation.h>interface NSDate(cc)// 获取系统时间(yyyy-MM-dd HH:mm:ss.SSS格式)(NSString *)getSystemTimeStr;// prin…

记录Spring Boot中的API请求参数读取方式

一、背景 项目开发中经常使用Spring Boot开发API&#xff0c;所以读取请求参数是服务端编码中最基本最常见的操作项&#xff0c;Spring Boot中也提供多种机制来满足不同的API设计要求。接下来就记录一下项目中用过的6种请求参数读取方式。 RequestParam 用来加载请求URL中&q…

2024年6月24日-6月30日(ue5肉鸽视频p16-p25)

试过重点放在独立游戏上&#xff0c;有个indienova独立游戏团队是全职的&#xff0c;由于他们干了几个月&#xff0c;节奏暂时跟不上&#xff0c;紧张焦虑了。五一时也有点自暴自弃了&#xff0c;实在没必要&#xff0c;按照自己的节奏走即可。精力和时间也有限&#xff0c;放在…

Python和tkinter实现的字母记忆配对游戏

Python和tkinter实现的字母记忆配对游戏 因为这个小游戏用到了tkinter&#xff0c;先简要介绍一下它。tkinter是Python的标准GUI(图形用户界面)库&#xff0c;它提供了一种简单而强大的方式来创建图形界面应用程序。它提供了创建基本图形界面所需的所有工具&#xff0c;同时保…

OSI七层模型TCP/IP四层面试高频考点

OSI七层模型&TCP/IP四层&面试高频考点 1 OSI七层模型 1. 物理层&#xff1a;透明地传输比特流 在物理媒介上传输原始比特流&#xff0c;定义了连接主机的硬件设备和传输媒介的规范。它确保比特流能够在网络中准确地传输&#xff0c;例如通过以太网、光纤和无线电波等媒…

什么是有效的电子签名?PDF电子签名怎样具备法律效力?

电子签名逐渐成为商务文书和法律文件中不可或缺的一部分。《电子签名法》自2005年4月1日起施行&#xff0c;这一立法是中国信息化法律的重要里程碑&#xff0c;为电子签名应用奠定了法律基础。电子签名不仅仅是一种技术手段&#xff0c;更是一种法律认可的签名形式。那么究竟什…

js生成UUID确保数据的唯一性

在JavaScript中&#xff0c;生成UUID&#xff08;Universally Unique Identifier&#xff09;通常用于确保数据的唯一性。 以下是一个简单的使用JavaScript生成UUID的示例&#xff0c;它基于RFC 4122版本4&#xff08;随机UUID&#xff09;的算法&#xff1a; function gener…

Python私教张大鹏 PyWebIO通过事件回调实现表格的编辑和删除功能

从上面可以看出&#xff0c;PyWebIO把交互分成了输入和输出两部分&#xff1a;输入函数为阻塞式调用&#xff0c;会在用户浏览器上显示一个表单&#xff0c;在用户提交表单之前输入函数将不会返回&#xff1b;输出函数将内容实时输出至浏览器。这种交互方式和控制台程序是一致的…

学习TTS遇到的问题2 什么是TCN模型

学习TTS遇到的问题2 什么是TCN模型 什么是TCN模型怎么理解 TCN中的 dilation&#xff1f;什么是 Dilation具体例子数学表达作用例子代码示例 什么是TCN模型 https://juejin.cn/post/7262269863343079479 https://blog.csdn.net/weixin_57726558/article/details/132163074 由下…

出手便是王炸,曙光存储将高端存储推向新高度

二十年磨一剑&#xff0c;今朝试锋芒。 近日&#xff0c;曙光存储重磅发布全球首个亿级IOPS集中式全闪存储FlashNexus&#xff0c;正式宣告进入高端存储市场。 作为存储产业皇冠上的明珠&#xff0c;高端存储一向以技术难度大、市场准入门槛高和竞争格局稳定著称&#xff0c;…

从0-1搭建一个web项目(package.json)详解

本章分析package.json文件详解 本文主要对packge.json配置子文件详解 ObJack-Admin一款基于 Vue3.3、TypeScript、Vite3、Pinia、Element-Plus 开源的后台管理框架。在一定程度上节省您的开发效率。另外本项目还封装了一些常用组件、hooks、指令、动态路由、按钮级别权限控制等…