本次要讲的是如何通过泛型函数来简化我们的程序。
  泛型函数除了之前介绍的一些优点外还有两个重要的优点

1.消除重复逻辑,提高程序的内聚性和健壮性

  泛型函数在某种程度上用来弥补泛型类型的不足。通过泛型类型T可以消除一些重复代码,当不同的类型具有相同的行为时,通过泛型擦除类型,达到消除重复简化代码的目的。例如:

  1. template<typename T>
  2. auto Add(T a, T b)->decltype(a+b)
  3. {
  4. return a + b;
  5. }
  6.  
  7. void TestAdd()
  8. {
  9. auto r1 = Add(, ); //
  10. auto r2 = Add(1.2, 3.5); //4.7
  11. auto r3 = Add(string("aa"), string("bb")); //aabb
  12. }

  我们可以通过一个泛型Add函数对不同的类型:int、double和string做加运算,而不用针对每种类型写一个Add函数,复用了Add函数,消除了重复。本质 上通过T擦出了类型,使得我们可以复用这些不同类型的共同行为。也就是说T可以消除类型不同而行为相同导致的重复,这在很多时候可以有效的消除重复代码,但是有时候却不能消 除某些函数的重复逻辑。比如,两个函数中大部分逻辑都是相同的,只是某个处理步骤略有不同时就无能为力了。也就是说大部分逻辑相同,只是某个行为有差异,只是通过泛型类型T就帮不上忙了,因为泛型类型T只能擦除类型,而不能屏蔽不同的行为。比如:

  1. //遍历xml文件,将标签IDAttr的值和AlgorithmTypeAttr属性值组成键值对,存到map中
  2. void ParseA(const string& confFile, const string& xmlPath,unordered_multimap<uint32_t, int>& map)
  3. {
  4. property_tree::ptree ptree;
  5. property_tree::read_xml(confFile, ptree);
  6. auto kpichilds = ptree.get_child(xmlPath);
  7. std::for_each(kpichilds.begin(), kpichilds.end(), [this, &map](const ptree::value_type& pair)
  8. {
  9. string str = pair.second.get<string>(IDAttr);
  10. uint32_t key = strtol(str.data(), nullptr, );
  11.  
  12. int val = pair.second.get<int>(AlgorithmTypeAttr);
  13. map.insert(make_pair(key, val));
  14. });
  15. }
  16.  
  17. //遍历xml文件,将标签AlgorithmAttr属性值和其子节点的标签Attr属性值组成键值对,存入map中
  18. void ParseB(const string& confFile, const string& xmlPath,unordered_multimap<uint32_t, string>& map)
  19. {
  20. property_tree::ptree ptree;
  21. property_tree::read_xml(confFile, ptree);
  22. auto kpichilds = ptree.get_child(xmlPath);
  23. std::for_each(kpichilds.begin(), kpichilds.end(), [&map](const ptree::value_type& pair)
  24. {
  25. int type = pair.second.get<int>(AlgorithmAttr);
  26. auto childs = pair.second.get_child(Tag);
  27. for (auto item : childs)
  28. {
  29. bap.insert(make_pair(type, item.second.get<string> (Attr)));
  30. }
  31. });
  32. }

  这两个函数大部分逻辑都是相似的,只是在最后组成键值对时稍有差异,这两个函数中存在重复代码和重复逻辑(遍历生成键值对),重复代码比较好消除,但是重复逻辑该如何消除呢?这时我们就需要通过泛型的函数来屏蔽行为的差异,使得逻辑保持一致,各处仍然可以复用该逻辑代码。重构后代码是这样的:

  1. //通过F屏蔽细小的行为差异,使得各个函数可以复用这段代码
  2. template<typename F>
  3. void Parse(const string& confFile, const string& xmlPath, const F& f)
  4. {
  5. property_tree::ptree ptree;
  6. property_tree::read_xml(confFile, ptree);
  7. auto kpichilds = ptree.get_child(xmlPath);
  8. std::for_each(kpichilds.begin(), kpichilds.end(), f);
  9. }
  10.  
  11. //内部复用Parse逻辑
  12. void ParseA(unordered_multimap<uint32_t, int>& map)
  13. {
  14. Parse(ConfFileName, KPIPath, [this, &map](const ptree::value_type& pair)
  15. {
  16. string str = pair.second.get<string>(IDAttr);
  17. uint32_t key = strtol(str.data(), nullptr, );
  18.  
  19. int val = pair.second.get<int>(AlgorithmTypeAttr);
  20. map.insert(make_pair(key, val));
  21. };);
  22. }
  23.  
  24. //内部复用Parse逻辑
  25. void ParseA(unordered_multimap<uint32_t, string>& map)
  26. {
  27. Parse(ConfFileName, KPIPath, [this, &map](const ptree::value_type& pair)
  28.   {
  29.   int type = pair.second.get<int>(AlgorithmAttr);
  30.   auto childs = pair.second.get_child(Tag);
  31.   for (auto item : childs)
  32.   {
  33.   bap.insert(make_pair(type, item.second.get<string>(Attr)));
  34.   }
  35.   };);
  36. }

  重构后,细微的行为差异通过泛型的函数F来屏蔽了,具体的有差异的行为在外面定义,从而获得了逻辑上的一致,并且增强了灵活性。也许有人觉得如此费心思去消除差异来重用一段逻辑代码未免有点过头了。其实不然,高质量的代码就是要消除重复逻辑,应该所有的逻辑只在一个地方制定,尽可能多的复用已有逻辑。消除重复逻辑也可以使得我们的代码具有较高的内聚性,可以做到一个bug只修改一个地方,也间接的让外面的调用接口都充当了测试函数,提高了程序的健壮性。关于消除重复逻辑的好处不多说了。让我们看看泛型函数如何实现非继承的多态调用吧。

2.非继承的多态调用

  通过泛型函数可以不需继承就可以实现多态,因为F可以在外面定义,所以你可以调用相同的接口而实现不同的行为,达到多态调用的目的。比如:有一个聚合算法接口,通过派生分别实现加和减。

  1. struct IAggregate
  2. {
  3.   virtual int Aggregate(int x, int y) = ;
  4. };
  5.  
  6. struct Add : public IAggregate
  7. {
  8.   int Aggregate(int x, int y)
  9.   {
  10.     return x+y;
  11.   }
  12. };
  13.  
  14. struct Sub : public IAggregate
  15. {
  16.   int Aggregate(int x, int y)
  17.   {
  18.     return x-y;
  19.   }
  20. };
  21.  
  22. void TestAggregate()
  23. {
  24.   //加法
  25.   shared_ptr<IAggregate> ptrAdd(new Add);
  26.   ptrAdd->Aggregate(,);
  27.  
  28.   //减法
  29.   shared_ptr<IAggregate> ptrSub(new Sub);
  30.   ptrSub->Aggregate(,);
  31. }

  通过泛型函数的写法是这样的:

  1. template<typename T, typename F>
  2. auto Aggregate(const T& x, const T& y, F& f)->decltype(f(x,y))
  3. {
  4.   return f(x,y);
  5. }
  6.  
  7. void TestAggregate()
  8. {
  9.   //加法
  10.   Aggregate(,, [](int x, int y){return x+y;});
  11.  
  12.   //减法
  13.   Aggregate(,, [](int x, int y){return x-y;});
  14. }

  我们可以看到通过泛型函数实现多态调用更简洁,消除了继承的强耦合,也提高了性能。

  至此,c++11改进我们的程序之简化我们的程序系列的介绍告一段落了,接下来,会介绍c++11如何优化我们的程序性能以及c++11工程实践系列,敬请关注。

  c++11 boost技术交流群:296561497,欢迎大家来交流技术。

(原创)C++11改进我们的程序之简化我们的程序(八)的更多相关文章

  1. (原创)C++11改进我们的程序之简化我们的程序(二)

    这次要讲的是:C++11如何通过组合函数来简化我们的程序.关于组合函数,大家可能对这个概念有点陌生.组合函数是将N个一元函数组成一种更复杂的函数,每个函数的返回值作为参数传给下一个函数,直到传到最后一 ...

  2. C++11改进我们的程序之简化我们的程序1

    C++11改进我们的程序之简化我们的程序(一) C++11在很多方面可以简化我们的程序开发,我会在“简化我们的程序”这一系列的博文中一一讲到,敬请关注.这次要讲的是:C++11如何通过获取函数模板的返 ...

  3. (原创)C++11改进我们的程序之简化我们的程序(四)

    这次要讲的是:c++11统一初始化.统一begin()/end()和for-loop循环如何简化我们的程序 初始化列表 c++11之前有各种各样的初始化语法,有时候初始化的时候还挺麻烦,比较典型的如v ...

  4. (原创)C++11改进我们的程序之简化我们的程序(三)

    这次要讲的是:C++11如何通过auto.decltype和返回值后置来简化我们的程序. auto和c#中的var类似,都是在初始化时自动推断出数据类型.当某个变量的返回值难于书写时,或者不太确定返回 ...

  5. (原创)C++11改进我们的程序之简化我们的程序(一)

    C++11在很多方面可以简化我们的程序开发,我会在“简化我们的程序”这一系列的博文中一一讲到,敬请关注.这次要讲的是:C++11如何通过获取函数模板的返回值类型来简化我们的程序.在谈到简化之前,我们先 ...

  6. (原创)c++11改进我们的模式之改进代理模式,实现通用的AOP框架

    c++11 boost技术交流群:296561497,欢迎大家来交流技术. 本次要讲的时候如何改进代理模式,具体来说是动态代理模式,动态代理模式一般实现AOP框架,不懂AOP的童鞋看这里.我前面的博文 ...

  7. (原创)c++11改进我们的模式之改进命令模式

    模式虽然精妙,却难完美,比如观察者模式中观察者生命周期的问题:比如访问者模式中循环依赖的问题等等:其它很多模式也存在这样那样的一些不足之处,如使用场景受限.实现复杂.不够简洁.不够通用等.但我觉得不足 ...

  8. (原创)c++11改进我们的模式之改进访问者模式

    本次讲c++11改进我们的模式之改进访问者模式 访问者模式是GOF23个设计模式中比较复杂的模式之一,但是它的功能也很强大,非常适合稳定的继承层次中对象的访问,可以在不修改被访问对象的情况下,动态添加 ...

  9. (原创)C++11改进我们的程序之简化我们的程序(七)

    这次要讲的内容是:c++11中的tuple(元组).tuple看似简单,其实它是简约而不简单,可以说它是c++11中一个既简单又复杂的东东,关于它简单的一面是它很容易使用,复杂的一面是它内部隐藏了太多 ...

随机推荐

  1. 用C读取json文件

    a jconf_t * read_jconf(const char *file) { static jconf_t conf; // 清空数据 memset(&conf, , sizeof(j ...

  2. ios实例开发精品文章推荐(8.14)

    1.iOS源码:俄罗斯方块实现简单的俄罗斯方块游戏.<ignore_js_op> 下载地址:http://www.apkbus.com/android-124628-1-1.html 2. ...

  3. zabbix邮件告警之 通过shell脚本发送告警

    说明:本文讲如何通过shell脚本实现zabbix发送告警邮件,共有5步1.设置mailx账号:是配置mailx的发信账号2.zabbix服务器端编写邮件发送脚本:是增加zabbix的告警方式,增加通 ...

  4. vim学习笔记(一)—— vim安装方法

    一.完全卸载vim的方法 sudo apt-get remove --purge vim (--purge 是完全删除,会连配置文件一起删除) 二.Vim前言——————“世界上只有三种编辑器,EMA ...

  5. 【Redis】解析Redis和Java传递数据

    在Java中使用Redis之前需要导入 jedis.jar 包,由于Redis是基于key-value进行数据存储,java中的数据存储到Redis中有许多方式,这里笔者介绍采用JSON字符串和对象序 ...

  6. 【C语言】练习1-21

    题目来源:<The C programming language>中的习题 练习1-21:编写程序entab,将空格串替换为最好数量的制表符和空格,但要保持单词之间的间隔不变. 思路: 对 ...

  7. logstash向elasticsearch写入数据,如何指定多个数据template

    之前在配置从logstash写数据到elasticsearch时,指定单个数据模板没有问题,但是在配置多个数据模板时候,总是不成功,后来找了很多资料,终于找到解决办法,就是要多加一个配置项: temp ...

  8. 使用 TensorFlow 的起始步骤

    1 学习目标: 学习基本的 TensorFlow 概念 在 TensorFlow 中使用 LinearRegressor 类并基于单个输入特征预测各城市街区的房屋价值中位数 使用均方根误差 (RMSE ...

  9. 认识LINQ的第一步---从查询表达式开始

    学习和使用C#已经有2个月了,在这两个月的学习中,深刻体会到,C#这门语言还真不适合编程初学者学习,因为它是吸取了很多其他语言,不仅是面向对象,还包括函数式语言的很多特性,导致它变成特性大爆炸的语言. ...

  10. Oracle 12C -- 清空audit记录

    1.使用job清空 SQL> dbms_audit_mgmt.create_purge_job ( audit_trail_type=> DBMS_AUDIT_MGMT.AUDIT_TRA ...