使用 c++ 模板顯示實(shí)例化解決模板函數(shù)聲明與實(shí)現(xiàn)分離的問題

開始正文之前,做一些背景鋪墊,方便讀者了解我的工程需求。我的項(xiàng)目是一個(gè)客戶端消息分發(fā)中心,在連接上消息后臺后,后臺會(huì)不定時(shí)的給我推送一些消息,我再將它們轉(zhuǎn)發(fā)給本機(jī)的其它桌面產(chǎn)品去做顯示。后臺為了保證消息一定可以推到客戶端,它采取了一種重復(fù)推送的策略,也就是說,每次當(dāng)我重新連接上后臺時(shí),后臺會(huì)把一段時(shí)間內(nèi)的消息都推給我、而不論這些消息之前是否已經(jīng)推送過,如果我不加處理的直接推給產(chǎn)品,可能造成同一個(gè)消息重復(fù)展示多次的問題。為此,我在接收到消息后,會(huì)將它們保存在進(jìn)程中的一個(gè)容器中,當(dāng)有新消息到達(dá)時(shí),會(huì)先在這個(gè)容器里檢查有沒有收到這條消息,如果有,就不再轉(zhuǎn)發(fā)。

1namespace GCM { 2class server_msg_t? 3? ? { 4public: 5voiddump(charconst* prompt);? 6 7std::string appname;? 8std::string uid;? 9std::string msgid; 10time_t recv_first =0; 11time_t recv_last =0; 12intrecv_cnt =0; 13? ? };1415class WorkEngine16? ? {17public:18? ? ? ? WorkEngine();19~WorkEngine();2021private:22// to avoid server push duplicate messages to same client.23// note this instance is only accessed when single connection to server arrives message, so no lock needed..24std::vector m_svrmsgs;25? ? };26}

上面的是經(jīng)過簡化以后的代碼,m_svrmsgs 成員存儲(chǔ)的就是接收到的所有的后臺消息,server_msg_t 代表的就是一個(gè)后臺消息,appname、uid 用來定位發(fā)給哪個(gè)產(chǎn)品的哪個(gè)實(shí)例;msgid 用來唯一的標(biāo)識一個(gè)消息;recv_first、recv_last、recv_cnt 分別表示消息接收的首次時(shí)間、最后時(shí)間以及重復(fù)接收次數(shù)。那么現(xiàn)在一個(gè)很現(xiàn)實(shí)的問題就是,我需要把這些消息序列化到永久存儲(chǔ)上去,以便進(jìn)程重啟后這些信息還在。這里我使用了 sqlite 數(shù)據(jù)庫,與此相關(guān)的代碼封裝在了 WorkEngine 的成員函數(shù)中,很容易想到的一種函數(shù)聲明方式是這樣:

1namespace GCM { 2class server_msg_t? 3? ? { 4public: 5voiddump(charconst* prompt);? 6 7std::string appname;? 8std::string uid;? 9std::string msgid; 10time_t recv_first =0; 11time_t recv_last =0; 12intrecv_cnt =0; 13? ? };1415class WorkEngine16? ? {17public:18? ? ? ? WorkEngine();19~WorkEngine();2021protected:22intdb_store_server_msg (std::vectorconst& vec); 23intdb_fetch_server_msg (std::vector & vec);2425private:26// to avoid server push duplicate messages to same client.27// note this instance is only accessed when single connection to server arrives message, so no lock needed..28std::vector m_svrmsgs;29? ? };30}31

像 line 22-23 展示的那樣,直接使用 std::vector<server_msg_t> 這個(gè)容器作為參數(shù)(有的人可能覺得我多此一舉,直接在函數(shù)里訪問 m_svrmsgs 成員不就行了,為什么要通過參數(shù)傳遞呢?可能這個(gè)例子不太明顯,但是確實(shí)存在一些情況容器是作為局部變量而非成員變量存在的,這里出于說明目的做了一些簡化)。但是我覺得這樣寫太死板了,萬一以后我換了容器呢,這里是不是還要改?也許是泛型算法看多了,總感覺這樣寫不夠“通用”。但是如果寫成下面這樣,還是換湯不換藥:

intdb_store_server_msg (std::vector::iterator beg, std::vector::iterator end);

參考標(biāo)準(zhǔn)庫 std::copy 算法,將其改造一番,結(jié)果就成了這個(gè)樣子:

template intdb_store_server_msg(InputIterator beg, InputIterator end);

叫成員函數(shù)模板,還是成員模板函數(shù),還是模板成員函數(shù)……說不清楚,反正就是成員函數(shù)+模板函數(shù)。實(shí)現(xiàn)的話可以這樣寫:

1namespace GCM { 2template 3int WorkEngine::db_store_server_msg(InputIterator beg, InputIterator end) 4? ? { 5intret =0, rowid =0;? 6? ? ? ? qtl::sqlite::database db(SQLITE_TIMEOUT); 7 8try 9? ? ? ? {10? ? ? ? ? ? db.open(get_db_path().c_str(), NULL);11writeInfoLog("open db for store server msg OK");1213? ? ? ? ? ? db.begin_transaction();1415for(auto it = beg; it != end; ++it)16? ? ? ? ? ? {17// 1th, insert or update user info18rowid = db.insert_direct("replace into server_msg (appname, uid, msgid, first_recv, last_recv, count) values (?, ?, ?, ?, ?, ?);", 19it->appname, it->uid, it->msgid, it->recv_first, it->recv_last, it->recv_cnt);2021ret++; 22? ? ? ? ? ? }2324? ? ? ? ? ? db.commit();25? ? ? ? ? ? db.close();26writeInfoLog("replace into %d records", ret); 27? ? ? ? }28catch(qtl::sqlite::error &e)29? ? ? ? {30writeInfoLog("manipute db for store server msg error: %s", e.what());31? ? ? ? ? ? db.rollback();32? ? ? ? ? ? db.close();33return-1;34? ? ? ? }3536return ret; 37? ? }38}

可以看到,核心代碼就是對迭代器區(qū)間作遍歷 (line 15)。調(diào)用方也是非常簡潔:

db_store_server_msg(m_svrmsgs.begin(), m_svrmsgs.end());

一行搞定,看起來已經(jīng)大功告成了,毫無難度可言,那么這篇文章想要說明什么呢?別著急,真正的難點(diǎn)在于從數(shù)據(jù)庫恢復(fù)數(shù)據(jù)。首先直接使用迭代器是不行了,因?yàn)槲覀儸F(xiàn)在要往容器里插入元素,迭代器只能遍歷元素,一點(diǎn)幫助也沒有。但是相信讀者一定看過類似這樣的代碼:

正在上傳... 取消

1intmain (void) 2{ 3intarr[] = {1,3,5,7,11 };? 4? ? std::vector vec;? 5std::copy (arr, arr +sizeof(arr) /sizeof(int), std::back_inserter(vec)); 6for(auto it = vec.begin (); it != vec.end (); ++ it)? 7printf ("%d\n", *it);? 8 9return0; 10}

正在上傳... 取消

為了在容器尾部插入元素,標(biāo)準(zhǔn)庫算法借助了 back_inserter 這個(gè)東東。于是自然而然的想到,我們這里能不能聲明 back_inserter 作為輸入?yún)?shù)呢? 例如像這樣:

template intdb_fetch_server_msg(OutputIterator it);

模板實(shí)現(xiàn)這樣寫:

正在上傳... 取消

1namespace GCM { 2template 3int WorkEngine::db_fetch_server_msg(OutputIterator it) 4? ? { 5intret =0; 6? ? ? ? qtl::sqlite::database db(SQLITE_TIMEOUT); 7 8try 9? ? ? ? {10? ? ? ? ? ? db.open(get_db_path().c_str(), NULL);11writeInfoLog("open db for fetch server msg OK");1213db.query("select appname, uid, msgid, first_recv, last_recv, count from server_msg", 14[&ret, &it](std::stringconst& appname, std::stringconst& uid, std::stringconst& msgid, time_t first_recv, time_t last_recv,int count) {15? ? ? ? ? ? ? ? ? ? server_msg_t sm; 16sm.appname = appname; 17sm.uid = uid; 18sm.msgid = msgid; 19sm.recv_first = first_recv; 20sm.recv_last = last_recv; 21sm.recv_cnt = count; 22*it = sm;23++ret; 24? ? ? ? ? ? }); 2526? ? ? ? ? ? db.close();27writeInfoLog("query %d records", ret);28? ? ? ? }29catch(qtl::sqlite::error &e)30? ? ? ? {31writeInfoLog("manipute db for store server msg error: %s", e.what());32? ? ? ? ? ? db.close();33return-1;34? ? ? ? }3536return ret;37? ? }38}

正在上傳... 取消

其實(shí)核心就是一句對 back_inserter 的賦值語句 (line 22)。調(diào)用方同樣是一行搞定:

db_fetch_server_msg (std::back_inserter(m_svrmsgs));

模板聲明與模板實(shí)現(xiàn)的分離

上面的代碼可以正常通過編譯,但前提是模板實(shí)現(xiàn)與模板調(diào)用位于同一文件。考慮到這個(gè)類之前已經(jīng)有許多邏輯,我決定將與數(shù)據(jù)庫相關(guān)的內(nèi)容,轉(zhuǎn)移到一個(gè)新的文件(engine_db.cpp),來減少單個(gè)文件的代碼量。調(diào)整后的文件結(jié)構(gòu)如下:

+ engine.h: WorkEngine 聲明

+ engine.cpp:WorkEngine 實(shí)現(xiàn) (包含 engine.h)

+ engine_db.cpp:WorkEngine::db_xxx 模板實(shí)現(xiàn) (包含 engine.h)

重新編譯,報(bào)了一個(gè)鏈接錯(cuò)誤:

1>workengine.obj : error LNK2001: 無法解析的外部符號 "protected: int __thiscall GCM::WorkEngine::db_fetch_server_msg<class std::back_insert_iterator<class std::vector<class GCM::server_msg_t,class std::allocator<class GCM::server_msg_t> > > >(class std::back_insert_iterator<class std::vector<class GCM::server_msg_t,class std::allocator<class GCM::server_msg_t> > >)" (??$db_fetch_server_msg@V?$back_insert_iterator@V?$vector@Vserver_msg_t@GCM@@V?$allocator@Vserver_msg_t@GCM@@@std@@@std@@@std@@@WorkEngine@GCM@@IAEHV?$back_insert_iterator@V?$vector@Vserver_msg_t@GCM@@V?$allocator@Vserver_msg_t@GCM@@@std@@@std@@@std@@@Z)

很明顯是模板調(diào)用時(shí)找不到對應(yīng)的鏈接所致。此時(shí)需要使用“模板顯示實(shí)例化”在 engine_db.cpp 文件中強(qiáng)制模板生成對應(yīng)的代碼實(shí)體,來和 engine.cpp 中的調(diào)用點(diǎn)進(jìn)行鏈接。需要在該文件開始處加入下面兩行代碼:

using namespace GCM;

templateintWorkEngine::db_fetch_server_msg > >(std::back_insert >);

注意模板成員函數(shù)顯示實(shí)例化的語法,我專門查了下《cpp primer》,格式為:

template return_type CLASS::member_func (type1,type2, ……);

對應(yīng)到上面的語句,就是使用 std::back_insert<std::vector<server_msg_t> > 代替原來的 OutputIterator 類型,來告訴編譯器顯示生成這樣一個(gè)函數(shù)模板實(shí)例。注意這里相同的類型要寫兩遍,一遍是函數(shù)模板參數(shù),一遍是函數(shù)參數(shù)。然而這個(gè)顯示實(shí)例化語法卻沒有通過編譯:

1>engine_db.cpp(15): error C2061: 語法錯(cuò)誤: 標(biāo)識符“back_inserter”

1>engine_db.cpp(15): error C2974: 'GCM::WorkEngine::db_fetch_server_msg' : 模板 對于 'OutputIterator'是無效參數(shù),應(yīng)為類型

1>? ? ? ? ? f:\gdpclient\src\gcm\gcmsvc\workengine.h(137) : 參見“GCM::WorkEngine::db_fetch_server_msg”的聲明

1>engine_db.cpp(15): error C3190: 具有所提供的模板參數(shù)的“int GCM::WorkEngine::db_fetch_server_msg(void)”不是“GCM::WorkEngine”的任何成員函數(shù)的顯式實(shí)例化

1>engine_db.cpp(15): error C2945: 顯式實(shí)例化不引用模板類專用化

百思不得其解。出去轉(zhuǎn)了一圈,呼吸了一點(diǎn)新鮮空氣,腦袋突然靈光乍現(xiàn):之前不是有一長串的鏈接錯(cuò)誤嗎,把那個(gè)里面的類型直接拿來用,應(yīng)該能通過編譯!說干就干,于是有了下面這一長串顯示實(shí)例化聲明:

templateintGCM::WorkEngine::db_fetch_server_msg > > >(classstd::back_insert_iterator > >)

過分的是 —— 居然通過編譯了!再仔細(xì)看看這一長串類型聲明,貌似只是把 vector 展開了而已,我用“濃縮版”的 vector 再聲明一次試下有什么變化:

templateintGCM::WorkEngine::db_fetch_server_msg > >(std::back_insert_iterator >);

居然也通過了??磥碇皇怯?back_insert_iterator 代替了 back_inserter 就好了,back_insert_iterator 又是一個(gè)什么鬼?查看 back_inserter 定義,有如下發(fā)現(xiàn):

1template inline back_insert_iterator<_Container>back_inserter(_Container& _Cont)2{// return a back_insert_iterator3return(_STDback_insert_iterator<_Container>(_Cont));4}

貌似 back_inserter 就是一個(gè)返回 back_insert_iterator 類型的模板函數(shù),與 std::make_pair(a,b) 和? std::pair <A,B> 的關(guān)系很像,因?yàn)檫@里要的是一個(gè)類型,所以不能直接傳 back_inserter 這個(gè)函數(shù)給顯示實(shí)例化的聲明。好,到目前我止,我們實(shí)現(xiàn)了用一個(gè) inserter 或兩個(gè) iterator 參數(shù)代替笨拙的容器參數(shù)、并可以將聲明、調(diào)用、實(shí)現(xiàn)分割在三個(gè)不同的文件中,已經(jīng)非常完美。美中不足的是,模板顯示實(shí)例化還有一些啰嗦,這里使用 typedef 定義要實(shí)例化的類型,將上面的語句改造的更清晰一些:

typedef std::back_insert_iterator > inserter_t;

template intWorkEngine::db_fetch_server_msg(inserter_t);

同理,對 db_store_server_msg 進(jìn)行同樣的改造:

typedef std::vector ::iterator iterator_t;

template intWorkEngine::db_store_server_msg(iterator_t, iterator_t);

這樣是不是更完美了?

使用 map 代替 vector

在使用過程中,發(fā)現(xiàn)使用 map 可以更快更方便的查詢消息是否已經(jīng)在容器中,于是決定將消息容器定義變更如下:

std::map m_servmsgs;

其中 map 的 value 部分與之前不變,增加的 key 部分為 msgid。這樣改了之后,遍歷時(shí)要使用 "it->second." 代替 "it->";插入元素時(shí)需要使用 “*it = std::make_pair (sm.msgid, sm)” 代替 “*it = sm”。做完上述修改,我發(fā)現(xiàn)程序仍然編譯不通過。經(jīng)過一番排查,發(fā)現(xiàn)原來是 back_inserter 不能適配 map 容器。因?yàn)?back_inserter 對應(yīng)的 back_insert_iterator 在 = 操作符中會(huì)調(diào)用容器的 push_back 接口,而這個(gè)接口僅有 vector、list、deque 幾個(gè)容器支持,map 是不支持的。怎么辦呢,幸好已經(jīng)有好心人寫好了 map 的插入器 —— map_inserter:

正在上傳... 取消

1#pragmaonce 2 3namespace std 4{ 5template 6class map_inserter { 7 8public: 9typedef std::map<_Key, _Value, _Compare> map_type;10? ? ? ? typedef typename map_type::value_type value_type;1112private:13map_type &m_;1415public:16map_inserter(map_type &_m)17? ? ? ? ? ? : m_(_m)18? ? ? ? {}1920public:21template22class map_inserter_helper {23public:24typedef map_inserter<_K, _V, _Cmp> mi_type;25? ? ? ? ? ? typedef typename mi_type::map_type map_type;26? ? ? ? ? ? typedef typename mi_type::value_type value_type;2728map_inserter_helper(map_type &_m)29? ? ? ? ? ? ? ? :m_(_m)30? ? ? ? ? ? {}3132constvalue_type &operator= (constvalue_type & v) {33m_[v.first] = v.second;34return v;35? ? ? ? ? ? }36private:37map_type&m_;38? ? ? ? };3940typedef map_inserter_helper<_Key, _Value, _Compare> mi_helper_type;41mi_helper_typeoperator* () {42return mi_helper_type(m_);43? ? ? ? }4445map_inserter<_Key, _Value, _Compare> &operator++() {46return*this;47? ? ? ? }4849map_inserter<_Key, _Value, _Compare> &operator++(int) {50return*this;51? ? ? ? }5253? ? };5455template56map_inserter<_K, _V, _Cmp> map_insert(std::map<_K, _V, _Cmp> &m) {57returnmap_inserter<_K, _V, _Cmp>(m);58? ? }59};

正在上傳... 取消

這段代碼我是從網(wǎng)上抄來的,具體請參考下面的鏈接:std::map 的 inserter 實(shí)現(xiàn)。然而不幸的是,這段代碼“殘疾”了,不知道是作者盜鏈、還是沒有輸入完整的原因,這段代碼有一些先天語法缺失,導(dǎo)致它甚至不能通過編譯,在我的不懈“腦補(bǔ)”過程下,缺失的部分已經(jīng)通過高亮部位補(bǔ)齊了,眾位客官可以直接享用~

特別需要說明的是,最有技術(shù)含量的缺失發(fā)生在 line 37 的一個(gè)引用符,如果沒有加入這個(gè),雖然可以通過編譯,但在運(yùn)行過程中,inserter 不能向 map 中插入元素,會(huì)導(dǎo)致從數(shù)據(jù)庫讀取完成后得到空的 map。我一直嘗試查找這個(gè)文章的原文,但是一無所獲,對于互聯(lián)網(wǎng)傳播過程中發(fā)現(xiàn)這樣驢頭馬嘴的訛誤事件,本人表示非常痛心疾首(雖然我不是很懂,但你也不能坑我?。?/p>

好了,話歸正題,有了 map_inserter 后,我們就可以這樣聲明了:

typedef std::map_inserter > inserter_t;

template intWorkEngine::db_fetch_server_msg(inserter_t);

對于這個(gè) map_inserter 實(shí)現(xiàn),我們需要傳遞 map 的三個(gè)模板參數(shù),而不是 map 本身這個(gè)參數(shù),我不太清楚是一種進(jìn)步、還是一種退步,反正這個(gè) map_inserter 有點(diǎn)兒怪,沒有封裝成 map_insert_iterator + map_inserter 的形式,和標(biāo)準(zhǔn)庫的實(shí)現(xiàn)水平還是有差異的,大家將就看吧。調(diào)用方也需要進(jìn)行一些微調(diào):

db_fetch_server_msg(std::map_inserter >(m_svrmsgs));

看看,沒有標(biāo)準(zhǔn)庫實(shí)現(xiàn)的簡潔吧,到底是山寨貨啊~ 幸好我們已經(jīng)封裝了 inserter_t 類型,可以改寫成這樣:

db_fetch_server_msg(inserter_t(m_svrmsgs));

簡潔多了?,F(xiàn)在我們再看下項(xiàng)目的文件組成:

1

2

3

4

5

+ map_inserter.hpp: map_inserter 聲明+實(shí)現(xiàn)

+ engine.h: WorkEngine 聲明 (包含 map_inserter.hpp)

+ engine.cpp:WorkEngine 實(shí)現(xiàn) (包含 engine.h)

+ engine_db.cpp:WorkEngine::db_xxx 模板實(shí)現(xiàn) (包含 engine.h)

……

這里為了降低復(fù)雜度,將 map_inserter 放在頭文件中進(jìn)行共享,類似于標(biāo)準(zhǔn)庫頭文件的使用方式。

使用普通模板函數(shù)代替類成員模板函數(shù)

本文的最后,我們再回頭看一下上面例子中的兩個(gè)成員模板函數(shù),發(fā)現(xiàn)它們并沒有使用到類中的其它成員,其實(shí)完全可以將它們獨(dú)立成兩個(gè)普通模板函數(shù)去調(diào)用,例如改成這樣:

正在上傳... 取消

1namespaceGCM {2classserver_msg_t3{4public:5void dump(charconst*prompt);67? ? ? ? std::stringappname;8? ? ? ? std::stringuid;9? ? ? ? std::stringmsgid;10? ? ? ? time_t recv_first =0;11? ? ? ? time_t recv_last =0;12int recv_cnt =0;13};1415classWorkEngine16{17public:18WorkEngine();19? ? ? ? ~WorkEngine();2021private:22//to avoid server push duplicate messages to same client.23//note this instance is only accessed when single connection to server arrives message, so no lock needed..24? ? ? ? std::vectorm_svrmsgs;25};2627? ? template 28intdb_store_server_msg(InputIterator beg, InputIterator end);29? ? template 30intdb_fetch_server_msg(OutputIterator it);3132? ? typedef std::map ::iterator iterator_t;33? ? typedef std::map_inserter >inserter_t;34 }

正在上傳... 取消

將模板函數(shù)聲明從類中移到類外(line 27-30),同時(shí)修改 engine_db.cpp 中兩個(gè)類的定義和顯示實(shí)例化語句,去掉類限制(WorkEngine::):

templateintdb_fetch_server_msg(inserter_t);

template intdb_store_server_msg(iterator_t, iterator_t);

調(diào)用處不需要修改。再次編譯報(bào)錯(cuò):

1>engine_db.cpp(16): warning C4667: “int GCM::db_fetch_server_msg(GCM::inserter_t)”: 未定義與強(qiáng)制實(shí)例化匹配的函數(shù)模板

1>engine_db.cpp(17): warning C4667: “int GCM::db_store_server_msg(GCM::iterator_t,GCM::iterator_t)”: 未定義與強(qiáng)制實(shí)例化匹配的函數(shù)模板

1>? ? 正在創(chuàng)建庫 F:\gdpclient\src\gcm\Release\gcmsvc.lib 和對象 F:\gdpclient\src\gcm\Release\gcmsvc.exp

1>workengine.obj : error LNK2001: 無法解析的外部符號 "int __cdecl GCM::db_fetch_server_msg<class std::map_inserter<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >,class GCM::server_msg_t,struct std::less<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > > > >(class std::map_inserter<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >,class GCM::server_msg_t,struct std::less<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > > >)" (??$db_fetch_server_msg@V?$map_inserter@V?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@Vserver_msg_t@GCM@@U?$less@V?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@2@@std@@@GCM@@YAHV?$map_inserter@V?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@Vserver_msg_t@GCM@@U?$less@V?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@2@@std@@@Z)

1>workengine.obj : error LNK2001: 無法解析的外部符號 "int __cdecl GCM::db_store_server_msg<class std::_Tree_iterator<class std::_Tree_val<struct std::_Tree_simple_types<struct std::pair<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > const ,class GCM::server_msg_t> > > > >(class std::_Tree_iterator<class std::_Tree_val<struct std::_Tree_simple_types<struct std::pair<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > const ,class GCM::server_msg_t> > > >,class std::_Tree_iterator<class std::_Tree_val<struct std::_Tree_simple_types<struct std::pair<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > const ,class GCM::server_msg_t> > > >)" (??$db_store_server_msg@V?$_Tree_iterator@V?$_Tree_val@U?$_Tree_simple_types@U?$pair@$$CBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@Vserver_msg_t@GCM@@@std@@@std@@@std@@@std@@@GCM@@YAHV?$_Tree_iterator@V?$_Tree_val@U?$_Tree_simple_types@U?$pair@$$CBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@Vserver_msg_t@GCM@@@std@@@std@@@std@@@std@@0@Z)

前兩個(gè) warning 是因?yàn)橛沙蓡T函數(shù)變?yōu)槠胀ê瘮?shù)后,顯示實(shí)例化需要放在函數(shù)實(shí)現(xiàn)后面,我們將這兩條語句調(diào)整到文件末尾就好了。對于后面兩個(gè)鏈接 error,百思不得其解,后來使用一個(gè)非常簡單的 test 模板函數(shù)做試驗(yàn),發(fā)現(xiàn)是命名空間搞的鬼,需要在每個(gè)函數(shù)的定義和顯示實(shí)例化語句前加上命名空間限定(GCM::):

templateintGCM::db_fetch_server_msg(inserter_t);

template intGCM::db_store_server_msg(iterator_t, iterator_t);

亞馬遜測評 www.yisuping.com

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容