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.pswp.cn/diannao/36723.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/36723.shtml
英文地址,請注明出處:http://en.pswp.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、指令、動態路由、按鈕級別權限控制等…