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

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

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

template<typename T>
auto Add(T a, T b)->decltype(a+b)
{
return a + b;
} void TestAdd()
{
auto r1 = Add(, ); //
auto r2 = Add(1.2, 3.5); //4.7
auto r3 = Add(string("aa"), string("bb")); //aabb
}

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

//遍历xml文件,将标签IDAttr的值和AlgorithmTypeAttr属性值组成键值对,存到map中
void ParseA(const string& confFile, const string& xmlPath,unordered_multimap<uint32_t, int>& map)
{
property_tree::ptree ptree;
property_tree::read_xml(confFile, ptree);
auto kpichilds = ptree.get_child(xmlPath);
std::for_each(kpichilds.begin(), kpichilds.end(), [this, &map](const ptree::value_type& pair)
{
string str = pair.second.get<string>(IDAttr);
uint32_t key = strtol(str.data(), nullptr, ); int val = pair.second.get<int>(AlgorithmTypeAttr);
map.insert(make_pair(key, val));
});
} //遍历xml文件,将标签AlgorithmAttr属性值和其子节点的标签Attr属性值组成键值对,存入map中
void ParseB(const string& confFile, const string& xmlPath,unordered_multimap<uint32_t, string>& map)
{
property_tree::ptree ptree;
property_tree::read_xml(confFile, ptree);
auto kpichilds = ptree.get_child(xmlPath);
std::for_each(kpichilds.begin(), kpichilds.end(), [&map](const ptree::value_type& pair)
{
int type = pair.second.get<int>(AlgorithmAttr);
auto childs = pair.second.get_child(Tag);
for (auto item : childs)
{
bap.insert(make_pair(type, item.second.get<string> (Attr)));
}
});
}

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

//通过F屏蔽细小的行为差异,使得各个函数可以复用这段代码
template<typename F>
void Parse(const string& confFile, const string& xmlPath, const F& f)
{
property_tree::ptree ptree;
property_tree::read_xml(confFile, ptree);
auto kpichilds = ptree.get_child(xmlPath);
std::for_each(kpichilds.begin(), kpichilds.end(), f);
} //内部复用Parse逻辑
void ParseA(unordered_multimap<uint32_t, int>& map)
{
Parse(ConfFileName, KPIPath, [this, &map](const ptree::value_type& pair)
{
string str = pair.second.get<string>(IDAttr);
uint32_t key = strtol(str.data(), nullptr, ); int val = pair.second.get<int>(AlgorithmTypeAttr);
map.insert(make_pair(key, val));
};);
} //内部复用Parse逻辑
void ParseA(unordered_multimap<uint32_t, string>& map)
{
Parse(ConfFileName, KPIPath, [this, &map](const ptree::value_type& pair)
  {
  int type = pair.second.get<int>(AlgorithmAttr);
  auto childs = pair.second.get_child(Tag);
  for (auto item : childs)
  {
  bap.insert(make_pair(type, item.second.get<string>(Attr)));
  }
  };);
}

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

2.非继承的多态调用

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

struct IAggregate
{
  virtual int Aggregate(int x, int y) = ;
}; struct Add : public IAggregate
{
  int Aggregate(int x, int y)
  {
    return x+y;
  }
}; struct Sub : public IAggregate
{
  int Aggregate(int x, int y)
  {
    return x-y;
  }
}; void TestAggregate()
{
  //加法
  shared_ptr<IAggregate> ptrAdd(new Add);
  ptrAdd->Aggregate(,);   //减法
  shared_ptr<IAggregate> ptrSub(new Sub);
  ptrSub->Aggregate(,);
}

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

template<typename T, typename F>
auto Aggregate(const T& x, const T& y, F& f)->decltype(f(x,y))
{
  return f(x,y);
} void TestAggregate()
{
  //加法
  Aggregate(,, [](int x, int y){return x+y;});   //减法
  Aggregate(,, [](int x, int y){return x-y;});
}

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

  至此,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. Arduino通过I2C(SSD1306)驱动0.96寸12864OLED

    I2C驱动的128x64 OLED I2C (Inter-Integrated Circuit) 集成电路总线是I2CBus的简称, 是一种串行通信总线, 使用多主从架构. 飞利浦公司在1980年代为 ...

  2. Q值, 电感Q因素, 电感品质因素, Inductor Q, Quality Factor

    Q值基本概念 Q值, 品质因素, Quality Factor 是广泛使用于物理和工程领域的一个参数, 这指的是一个机械或非机械的组件里, 共振(谐振)的能量损失比例, 是衡量一个元件或谐振回路性能的 ...

  3. 数学之路-python计算实战(9)-机器视觉-图像插值仿射

    插值 Python: cv2.resize(src, dsize[, dst[, fx[, fy[, interpolation]]]]) → dst interpolation – interpol ...

  4. Node.js的一些基本概念

    1. Node.js简介 1.1 Node.js是什么 简单的说 Node.js 就是运行在服务端的 JavaScript. Node.js是一个能够在服务器端运行JavaScript的开放源代码.跨 ...

  5. Dubbo创建提供者&消费者工程

    1. 前言 Dubbo采用全Spring配置方式,透明化接入应用,对应用没有任何API侵入,只需用Spring加载Dubbo的配置即可,Dubbo基于Spring的Schema扩展进行加载.如果不想使 ...

  6. Javascript调试利器console的使用

    一.Console API Console.assert() 判断第一个参数是否为真,false的话抛出异常并且在console输出相应信息. Console.count() 以参数为标识记录调用的次 ...

  7. iOS手势的综合运用

    //自定义一个VIEW封装手势功能 // CustormView.m // gesterDemoo // // Created by ganchaobo on 13-7-13. // Copyrigh ...

  8. firefox插件卸载

    1.根据插件名进行搜索,搜索到相关dll后删除,重启firefox. 2.about:config--plugin.expose_full_path:true,然后about:plugins去查看插件 ...

  9. NFS客户端、服务器协商读写粒度(rsize、wsize)流程 【转】

    rsize和wsize决定了网络文件系统(NFS)一次网络交互所能够读写的数据块的大小,rsize和wsize的大小对网络文件系统(NFS)的性能有重要影响.rsize和wsize的大小是在用户配置的 ...

  10. 解决方案,org.hibernate.LazyInitializationException: could not initialize proxy - no Session

    org.hibernate.LazyInitializationException: could not initialize proxy - no Session org.hibernate.pro ...