问题背景

开始正文之前,做一些背景铺垫,方便读者了解我的工程需求。我的项目是一个客户端消息分发中心,在连接上消息后台后,后台会不定时的给我推送一些消息,我再将它们转发给本机的其它桌面产品去做显示。后台为了保证消息一定可以推到客户端,它采取了一种重复推送的策略,也就是说,每次当我重新连接上后台时,后台会把一段时间内的消息都推给我、而不论这些消息之前是否已经推送过,如果我不加处理的直接推给产品,可能造成同一个消息重复展示多次的问题。为此,我在接收到消息后,会将它们保存在进程中的一个容器中,当有新消息到达时,会先在这个容器里检查有没有收到这条消息,如果有,就不再转发。

  1. 1 namespace GCM {
  2. 2 class server_msg_t
  3. 3 {
  4. 4 public:
  5. 5 void dump(char const* prompt);
  6. 6
  7. 7 std::string appname;
  8. 8 std::string uid;
  9. 9 std::string msgid;
  10. 10 time_t recv_first = 0;
  11. 11 time_t recv_last = 0;
  12. 12 int recv_cnt = 0;
  13. 13 };
  14. 14
  15. 15 class WorkEngine
  16. 16 {
  17. 17 public:
  18. 18 WorkEngine();
  19. 19 ~WorkEngine();
  20. 20
  21. 21 private:
  22. 22 // to avoid server push duplicate messages to same client.
  23. 23 // note this instance is only accessed when single connection to server arrives message, so no lock needed..
  24. 24 std::vector<server_msg_t> m_svrmsgs;
  25. 25 };
  26. 26 }

上面的是经过简化以后的代码,m_svrmsgs 成员存储的就是接收到的所有的后台消息,server_msg_t 代表的就是一个后台消息,appname、uid 用来定位发给哪个产品的哪个实例;msgid 用来唯一的标识一个消息;recv_first、recv_last、recv_cnt 分别表示消息接收的首次时间、最后时间以及重复接收次数。那么现在一个很现实的问题就是,我需要把这些消息序列化到永久存储上去,以便进程重启后这些信息还在。这里我使用了 sqlite 数据库,与此相关的代码封装在了 WorkEngine 的成员函数中,很容易想到的一种函数声明方式是这样:

  1. 1 namespace GCM {
  2. 2 class server_msg_t
  3. 3 {
  4. 4 public:
  5. 5 void dump(char const* prompt);
  6. 6
  7. 7 std::string appname;
  8. 8 std::string uid;
  9. 9 std::string msgid;
  10. 10 time_t recv_first = 0;
  11. 11 time_t recv_last = 0;
  12. 12 int recv_cnt = 0;
  13. 13 };
  14. 14
  15. 15 class WorkEngine
  16. 16 {
  17. 17 public:
  18. 18 WorkEngine();
  19. 19 ~WorkEngine();
  20. 20
  21. 21 protected:
  22. 22 int db_store_server_msg (std::vector<server_msg_t> const& vec);
  23. 23 int db_fetch_server_msg (std::vector<server_msg_t> & vec);
  24. 24
  25. 25 private:
  26. 26 // to avoid server push duplicate messages to same client.
  27. 27 // note this instance is only accessed when single connection to server arrives message, so no lock needed..
  28. 28 std::vector<server_msg_t> m_svrmsgs;
  29. 29 };
  30. 30 }
  31. 31

像 line 22-23 展示的那样,直接使用 std::vector<server_msg_t> 这个容器作为参数(有的人可能觉得我多此一举,直接在函数里访问 m_svrmsgs 成员不就行了,为什么要通过参数传递呢?可能这个例子不太明显,但是确实存在一些情况容器是作为局部变量而非成员变量存在的,这里出于说明目的做了一些简化)。但是我觉得这样写太死板了,万一以后我换了容器呢,这里是不是还要改?也许是泛型算法看多了,总感觉这样写不够“通用”。但是如果写成下面这样,还是换汤不换药:

  1. int db_store_server_msg (std::vector<server_msg_t>::iterator beg, std::vector<server_msg_t>::iterator end);

参考标准库 std::copy 算法,将其改造一番,结果就成了这个样子:

  1. template <class InputIterator>
  2. int db_store_server_msg(InputIterator beg, InputIterator end);

叫成员函数模板,还是成员模板函数,还是模板成员函数……说不清楚,反正就是成员函数+模板函数。实现的话可以这样写:

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

可以看到,核心代码就是对迭代器区间作遍历 (line 15)。调用方也是非常简洁:

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

一行搞定,看起来已经大功告成了,毫无难度可言,那么这篇文章想要说明什么呢?别着急,真正的难点在于从数据库恢复数据。首先直接使用迭代器是不行了,因为我们现在要往容器里插入元素,迭代器只能遍历元素,一点帮助也没有。但是相信读者一定看过类似这样的代码:

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

为了在容器尾部插入元素,标准库算法借助了 back_inserter 这个东东。于是自然而然的想到,我们这里能不能声明 back_inserter 作为输入参数呢? 例如像这样:

  1. template <class OutputIterator>
  2. int db_fetch_server_msg(OutputIterator it);

模板实现这样写:

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

其实核心就是一句对 back_inserter 的赋值语句 (line 22)。调用方同样是一行搞定:

  1. db_fetch_server_msg (std::back_inserter(m_svrmsgs));

模板声明与模板实现的分离

上面的代码可以正常通过编译,但前提是模板实现与模板调用位于同一文件。考虑到这个类之前已经有许多逻辑,我决定将与数据库相关的内容,转移到一个新的文件(engine_db.cpp),来减少单个文件的代码量。调整后的文件结构如下:

  1. + engine.h WorkEngine 声明
  2. + engine.cppWorkEngine 实现 (包含 engine.h
  3. + engine_db.cppWorkEngine::db_xxx 模板实现 (包含 engine.h

重新编译,报了一个链接错误:

  1. 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)

很明显是模板调用时找不到对应的链接所致。此时需要使用“模板显示实例化”在 engine_db.cpp 文件中强制模板生成对应的代码实体,来和 engine.cpp 中的调用点进行链接。需要在该文件开始处加入下面两行代码:

  1. using namespace GCM;
    template int WorkEngine::db_fetch_server_msg<std::back_insert<std::vector<server_msg_t> > >(std::back_insert<std::vector<server_msg_t> >);

注意模板成员函数显示实例化的语法,我专门查了下《cpp primer》,格式为:

  1. template return_type CLASS::member_func<type1, type2, ……> (type1, type2, ……);

对应到上面的语句,就是使用 std::back_insert<std::vector<server_msg_t> > 代替原来的 OutputIterator 类型,来告诉编译器显示生成这样一个函数模板实例。注意这里相同的类型要写两遍,一遍是函数模板参数,一遍是函数参数。然而这个显示实例化语法却没有通过编译:

  1. 1>engine_db.cpp(15): error C2061: 语法错误: 标识符“back_inserter
  2. 1>engine_db.cpp(15): error C2974: 'GCM::WorkEngine::db_fetch_server_msg' : 模板 对于 'OutputIterator'是无效参数,应为类型
  3. 1> f:\gdpclient\src\gcm\gcmsvc\workengine.h(137) : 参见“GCM::WorkEngine::db_fetch_server_msg”的声明
  4. 1>engine_db.cpp(15): error C3190: 具有所提供的模板参数的“int GCM::WorkEngine::db_fetch_server_msg(void)”不是“GCM::WorkEngine”的任何成员函数的显式实例化
  5. 1>engine_db.cpp(15): error C2945: 显式实例化不引用模板类专用化

百思不得其解。出去转了一圈,呼吸了一点新鲜空气,脑袋突然灵光乍现:之前不是有一长串的链接错误吗,把那个里面的类型直接拿来用,应该能通过编译!说干就干,于是有了下面这一长串显示实例化声明:

  1. template int 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> > >)

过分的是 —— 居然通过编译了!再仔细看看这一长串类型声明,貌似只是把 vector 展开了而已,我用“浓缩版”的 vector 再声明一次试下有什么变化:

  1. template int GCM::WorkEngine::db_fetch_server_msg<std::back_insert_iterator<std::vector<server_msg_t> > >(std::back_insert_iterator<std::vector<server_msg_t> >);

居然也通过了。看来只是用 back_insert_iterator 代替了 back_inserter 就好了,back_insert_iterator 又是一个什么鬼?查看 back_inserter 定义,有如下发现:

  1. 1 template<class _Container> inline back_insert_iterator<_Container> back_inserter(_Container& _Cont)
  2. 2 { // return a back_insert_iterator
  3. 3 return (_STD back_insert_iterator<_Container>(_Cont));
  4. 4 }

貌似 back_inserter 就是一个返回 back_insert_iterator 类型的模板函数,与 std::make_pair(a,b) 和  std::pair <A,B> 的关系很像,因为这里要的是一个类型,所以不能直接传 back_inserter 这个函数给显示实例化的声明。好,到目前我止,我们实现了用一个 inserter 或两个 iterator 参数代替笨拙的容器参数、并可以将声明、调用、实现分割在三个不同的文件中,已经非常完美。美中不足的是,模板显示实例化还有一些啰嗦,这里使用 typedef 定义要实例化的类型,将上面的语句改造的更清晰一些:

  1. typedef std::back_insert_iterator<std::vector <server_msg_t> > inserter_t;
  2. template int WorkEngine::db_fetch_server_msg<inserter_t>(inserter_t);

同理,对 db_store_server_msg 进行同样的改造:

  1. typedef std::vector <std::string, server_msg_t>::iterator iterator_t;
  2. template int WorkEngine::db_store_server_msg<iterator_t>(iterator_t, iterator_t);

这样是不是更完美了?

使用 map 代替 vector

在使用过程中,发现使用 map 可以更快更方便的查询消息是否已经在容器中,于是决定将消息容器定义变更如下:

  1. std::map<std::string, server_msg_t> m_servmsgs;

其中 map 的 value 部分与之前不变,增加的 key 部分为 msgid。这样改了之后,遍历时要使用 "it->second." 代替 "it->";插入元素时需要使用 “*it = std::make_pair (sm.msgid, sm)” 代替 “*it = sm”。做完上述修改,我发现程序仍然编译不通过。经过一番排查,发现原来是 back_inserter 不能适配 map 容器。因为 back_inserter 对应的 back_insert_iterator 在 = 操作符中会调用容器的 push_back 接口,而这个接口仅有 vector、list、deque 几个容器支持,map 是不支持的。怎么办呢,幸好已经有好心人写好了 map 的插入器 —— map_inserter:

  1. 1 #pragma once
  2. 2
  3. 3 namespace std
  4. 4 {
  5. 5 template <class _Key, class _Value, class _Compare>
  6. 6 class map_inserter {
  7. 7
  8. 8 public:
  9. 9 typedef std::map<_Key, _Value, _Compare> map_type;
  10. 10 typedef typename map_type::value_type value_type;
  11. 11
  12. 12 private:
  13. 13 map_type &m_;
  14. 14
  15. 15 public:
  16. 16 map_inserter(map_type &_m)
  17. 17 : m_(_m)
  18. 18 {}
  19. 19
  20. 20 public:
  21. 21 template <class _K, class _V, class _Cmp>
  22. 22 class map_inserter_helper {
  23. 23 public:
  24. 24 typedef map_inserter<_K, _V, _Cmp> mi_type;
  25. 25 typedef typename mi_type::map_type map_type;
  26. 26 typedef typename mi_type::value_type value_type;
  27. 27
  28. 28 map_inserter_helper(map_type &_m)
  29. 29 :m_(_m)
  30. 30 {}
  31. 31
  32. 32 const value_type & operator= (const value_type & v) {
  33. 33 m_[v.first] = v.second;
  34. 34 return v;
  35. 35 }
  36. 36 private:
  37. 37 map_type &m_;
  38. 38 };
  39. 39
  40. 40 typedef map_inserter_helper<_Key, _Value, _Compare> mi_helper_type;
  41. 41 mi_helper_type operator* () {
  42. 42 return mi_helper_type(m_);
  43. 43 }
  44. 44
  45. 45 map_inserter<_Key, _Value, _Compare> &operator++() {
  46. 46 return *this;
  47. 47 }
  48. 48
  49. 49 map_inserter<_Key, _Value, _Compare> &operator++(int) {
  50. 50 return *this;
  51. 51 }
  52. 52
  53. 53 };
  54. 54
  55. 55 template <class _K, class _V, class _Cmp>
  56. 56 map_inserter<_K, _V, _Cmp> map_insert(std::map<_K, _V, _Cmp> &m) {
  57. 57 return map_inserter<_K, _V, _Cmp>(m);
  58. 58 }
  59. 59 };

这段代码我是从网上抄来的,具体请参考下面的链接:std::map 的 inserter 实现。然而不幸的是,这段代码“残疾”了,不知道是作者盗链、还是没有输入完整的原因,这段代码有一些先天语法缺失,导致它甚至不能通过编译,在我的不懈“脑补”过程下,缺失的部分已经通过高亮部位补齐了,众位客官可以直接享用~

特别需要说明的是,最有技术含量的缺失发生在 line 37 的一个引用符,如果没有加入这个,虽然可以通过编译,但在运行过程中,inserter 不能向 map 中插入元素,会导致从数据库读取完成后得到空的 map。我一直尝试查找这个文章的原文,但是一无所获,对于互联网传播过程中发现这样驴头马嘴的讹误事件,本人表示非常痛心疾首(虽然我不是很懂,但你也不能坑我啊)……

好了,话归正题,有了 map_inserter 后,我们就可以这样声明了:

  1. typedef std::map_inserter<std::string, server_msg_t, std::less<std::string> > inserter_t;
  2. template int WorkEngine::db_fetch_server_msg<inserter_t>(inserter_t);

对于这个 map_inserter 实现,我们需要传递 map 的三个模板参数,而不是 map 本身这个参数,我不太清楚是一种进步、还是一种退步,反正这个 map_inserter 有点儿怪,没有封装成 map_insert_iterator + map_inserter 的形式,和标准库的实现水平还是有差异的,大家将就看吧。调用方也需要进行一些微调:

  1. db_fetch_server_msg(std::map_inserter<std::string, server_msg_t, std::less <std::string> >(m_svrmsgs));

看看,没有标准库实现的简洁吧,到底是山寨货啊~ 幸好我们已经封装了 inserter_t 类型,可以改写成这样:

  1. db_fetch_server_msg(inserter_t(m_svrmsgs));

简洁多了。现在我们再看下项目的文件组成:

  1. + map_inserter.hpp map_inserter 声明+实现
  2. + engine.h WorkEngine 声明 (包含 map_inserter.hpp)
  3. + engine.cppWorkEngine 实现 (包含 engine.h)
  4. + engine_db.cppWorkEngine::db_xxx 模板实现 (包含 engine.h)
  5. ……

这里为了降低复杂度,将 map_inserter 放在头文件中进行共享,类似于标准库头文件的使用方式。

使用普通模板函数代替类成员模板函数

本文的最后,我们再回头看一下上面例子中的两个成员模板函数,发现它们并没有使用到类中的其它成员,其实完全可以将它们独立成两个普通模板函数去调用,例如改成这样:

  1. 1 namespace GCM {
  2. 2 class server_msg_t
  3. 3 {
  4. 4 public:
  5. 5 void dump(char const* prompt);
  6. 6
  7. 7 std::string appname;
  8. 8 std::string uid;
  9. 9 std::string msgid;
  10. 10 time_t recv_first = 0;
  11. 11 time_t recv_last = 0;
  12. 12 int recv_cnt = 0;
  13. 13 };
  14. 14
  15. 15 class WorkEngine
  16. 16 {
  17. 17 public:
  18. 18 WorkEngine();
  19. 19 ~WorkEngine();
  20. 20
  21. 21 private:
  22. 22 // to avoid server push duplicate messages to same client.
  23. 23 // note this instance is only accessed when single connection to server arrives message, so no lock needed..
  24. 24 std::vector<server_msg_t> m_svrmsgs;
  25. 25 };
  26. 26
  27. 27 template <class InputIterator>
  28. 28 int db_store_server_msg(InputIterator beg, InputIterator end);
  29. 29 template <class OutputIterator>
  30. 30 int db_fetch_server_msg(OutputIterator it);
  31. 31
  32. 32 typedef std::map <std::string, server_msg_t>::iterator iterator_t;
  33. 33 typedef std::map_inserter<std::string, server_msg_t, std::less<std::string> > inserter_t;
  34. 34 }

将模板函数声明从类中移到类外(line 27-30),同时修改 engine_db.cpp 中两个类的定义和显示实例化语句,去掉类限制(WorkEngine::):

  1. template int db_fetch_server_msg<inserter_t>(inserter_t);
  2. template int db_store_server_msg<iterator_t>(iterator_t, iterator_t);

调用处不需要修改。再次编译报错:

  1. 1>engine_db.cpp(16): warning C4667: int GCM::db_fetch_server_msg(GCM::inserter_t)”: 未定义与强制实例化匹配的函数模板
  2. 1>engine_db.cpp(17): warning C4667: int GCM::db_store_server_msg(GCM::iterator_t,GCM::iterator_t)”: 未定义与强制实例化匹配的函数模板
  3. 1> 正在创建库 F:\gdpclient\src\gcm\Release\gcmsvc.lib 和对象 F:\gdpclient\src\gcm\Release\gcmsvc.exp
  4. 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)
  5. 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)

前两个 warning 是因为由成员函数变为普通函数后,显示实例化需要放在函数实现后面,我们将这两条语句调整到文件末尾就好了。对于后面两个链接 error,百思不得其解,后来使用一个非常简单的 test 模板函数做试验,发现是命名空间搞的鬼,需要在每个函数的定义和显示实例化语句前加上命名空间限定(GCM::):

  1. template int GCM::db_fetch_server_msg<inserter_t>(inserter_t);
  2. template int GCM::db_store_server_msg<iterator_t>(iterator_t, iterator_t);

可以看到,类成员模板函数和普通模板函数差别还是蛮大的,因为类本身也是一种命名空间,它的出现简化了其中成员函数的寻址。

结语

其实本文讲解了一种通用的通过 iterator 读取容器、通过 inserter 插入容器元素的方法,这种方式较之直接传递容器本身“优雅”不少,虽然不能实现 100% 无缝切换容器,但是也提供了极大的灵活性。特别是还研究了如何将这种方式实现的模板函数在不同文件中分别声明与实现,达到解除代码耦合的目的,具有较强的实用性。当然,这里仅仅是使用了模板实例化的方式,如果遇到模板不同的 TYPE 需要使用不同的函数实现的话,你可能还要遭遇模板特化语法(包括全特化与偏特化),那样复杂度还会上升,这里没有做进一步探索。

参考

[1]. C++ 11 Lambda表达式

[2]. std::map 的 inserter 实现

[3]. C++ 模板类的声明与实现分离问题(模板实例化)

[4]. C++函数模板的编译方式

[5]. c++函数模板声明与定义相分离

[6]. C++模板之函数模板实例化和具体化

[7]. C++ 函数模板 实例化和具体化

[8]. C++模板之隐式实例化、显示实例化、隐式调用、显示调用和模板特化详解

[9]. c++模板函数声明和定义分离

[10]. C++模板编程:如何使非通用的模板函数实现声明和定义分离

如何优雅的传递 stl 容器作为函数参数来实现元素插入和遍历?的更多相关文章

  1. 不要在公共接口中传递STL容器

    最近的一个项目,是开发一个framework,提供给公司内部不同的产品线使用. 之间遇到的一个问题,就是STL容器的使用, 而结论是不要在公共接口中传递STL容器: 这里说的STL容器,但主要则是指容 ...

  2. C++ //拷贝构造函数调用时机//1.使用一个已经创建完毕的对象来初始化一个新对象 //2.值传递的方式给函数参数传值 //3.值方式返回局部对象

    1 //拷贝构造函数调用时机 2 3 4 #include <iostream> 5 using namespace std; 6 7 //1.使用一个已经创建完毕的对象来初始化一个新对象 ...

  3. C语言 数组做函数参数不传数组个数的遍历方法

    //数组做函数参数不传数组个数的遍历方法 #include<stdio.h> #include<stdlib.h> #include<string.h> void ...

  4. STL容器 成员函数 时间复杂度表

    Sequence containers Associative containers   Headers <vector> <deque> <list> <s ...

  5. DLL中传递STL参数(如Vector或者list等)会遇到的问题[转载]

    最近的一个项目中遇到了调用别人的sdk接口(dll库)而传给我的是一个vector指针,用完之后还要我来删除的情况.这个过程中首先就是在我的exe中将其vector指针转为相应指针再获取vector中 ...

  6. DLL中传递STL参数,vector对象作为dll参数传递等问题(转)

    STL跨平台调用会出现很多异常,你可以试试. STL使用模板生成,当我们使用模板的时候,每一个EXE,和DLL都在编译器产生了自己的代码,导致模板所使用的静态成员不同步,所以出现数据传递的各种问题,下 ...

  7. STL容器与拷贝构造函数

    所有容器提供的都是“value语意”而非“reference语意”.容器内进行元素的安插操作时,内部实施的是拷贝操作,置于容器内.因此STL容器 的每一个元素都必须能够拷贝.---<<C+ ...

  8. C语言 函数参数不确定时 需要用到va_start和va_end函数

    1.在C中,当我们无法列出传递函数的所有实参的类型和数目时,可以用省略号指定参数表 void foo(...);void foo(parm_list,...); 这种方式和我们以前认识的不大一样,但我 ...

  9. c++ stl容器set成员函数介绍及set集合插入,遍历等用法举例

    c++ stl集合set介绍 c++ stl集合(Set)是一种包含已排序对象的关联容器.set/multiset会根据待定的排序准则,自动将元素排序.两者不同在于前者不允许元素重复,而后者允许. 1 ...

随机推荐

  1. 百度前端技术学院-基础-day22-24

    第二十二天到第二十四天:JavaScript里面的居民们 task1 题目: <div> <label>Number A:<input id="radio-a& ...

  2. 关于Switch Case的优化

    switch case虽然是代替if else而出现的,并不好维护,有时候使用switch还不如使用if else. 但没有别的东西能代替switch case了吗?答案当然是否定的,现在就有两种方式 ...

  3. ES6新增api随记

    求一个数组中的最大值 Math.max.apply(null,[1,2,3,4])=>Math.max(...[1,2,3]) 讲一个数组中的元素全部添加到另一个数组中 let arr=[1,2 ...

  4. DVWA各等级命令注入漏洞

    漏洞描述 在web程序中,因为业务功能需求要通过web前端传递参数到后台服务器上执行,由于开发人员没有对输入进行严格过滤,导致攻击者可以构造一些额外的"带有非法目的的"命令,欺骗后 ...

  5. [OI笔记]杂题整理1(基础篇~)

    算是开学第四周啦,之前的三周大概过了一遍基础图论和数学相关的内容.这篇随笔打算口胡一些近期做感觉比较好的数学相关的题目 因为这段时间主要是看紫书学的,所以其实会有些出自UVA的例题,如果需要题目但是觉 ...

  6. Python高级语法-深浅拷贝-总结(4.2.1)

    @ 目录 1.说明 2.代码 关于作者 1.说明 任何可变数据类型都牵扯到深浅拷贝 但是元组,常数等,不可变数据类型,无论浅拷贝,深拷贝都是指向 不管如何嵌套,一旦牵扯到可变数据类型,都会有深浅区别 ...

  7. AD PCB模块复用

    该文档为原创,转发需注明出处!https://www.cnblogs.com/brianblog/ 在画图的时候如果遇到PCB中有多个模块原理图是一模一样的时候,我们便会想能不能偷点懒,只画一个模块, ...

  8. Jetty web server 远程共享缓冲区泄漏漏洞学习

    https://www.secpulse.com/archives/4911.html https://www.tiejiang.org/11628.html http://blog.gdssecur ...

  9. zabbix学习(一)——LNMP环境搭建及zabbix安装

    第一部分:LNMP环境搭建 一.环境说明: OS:   centos7.6_x64nginx:nginx-1.16.0php:   php-7.1.11mysql:mysql-5.6.44 zabbi ...

  10. 豆瓣读书top250数据爬取与可视化

    爬虫–scrapy 题目:根据豆瓣读书top250,根据出版社对书籍数量分类,绘制饼图 搭建环境 import scrapy import numpy as np import pandas as p ...