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

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. Android Studio 错误: 非法字符: &#39;\ufeff&#39; 解决方式|错误: 须要class, interface或enum

    在导入eclipse项目到Android Studio出现这种错误, 非法字符: '\ufeff' 解决方式|错误: 须要class, interface或enum.查阅后了解到Eclipse能够智能 ...

  2. chrome配置文件校验初始化隐含參数的逆向

     这篇文章接上一篇文章进一步升华:花了4个小时获得该信息的计算方式 比方在 chrome文件夹下的\Chrome\User Data\Default文件夹下的Secure Preferences,须要 ...

  3. 大道至简的C语言内存管理

    C语言内存的开辟和释放需要程序员自己来实现. 而C语言的内存开辟和释放只在stdlib.h里面提供了四个函数,这么简洁的设计就足以完成一切工作. C++里面各种类型的指针漫天飞舞,显得啰嗦冗余. ca ...

  4. Lua协程学习

    按照书上码了下,但运行有问题,暂时不知道原因: function send (x) coroutine.yield(x) end function producer() return coroutin ...

  5. php安装 出现Sorry, I cannot run apxs. ***错误解决方法

    # tar zvxf php-5.1.2.tar.gz# cd php-5.1.2# ./configure --prefix=/usr/local/php --with-mysql=/usr/loc ...

  6. HDU 3032 Nim or not Nim? (sg函数)

    Nim or not Nim? Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)T ...

  7. Android-- ArrayAdapter用法举例(转载)

    近期很多Android开发者来函表示对ArrayAdapter和BaseAdapter的区别不是很清楚,这里Android123简单说下他们的关系和用处,ArrayAdapter是从BaseAdapt ...

  8. svn开发常用整理

    1.删除tortoise svn中的账号信息 其实tortoise svn也是将账号信息存放在本地的配置文件中 在不同的操作系统下,操作基本类似,首先我们来看一下windows下如何操作的. 以win ...

  9. Eclipse配置SQL Explorer插件和数据库

    1.下载SQL Explore插件,地址:http://www.sqlexplorer.org/,下载第三个.复制到eclipse插件相应文件夹重新启动,下载RCP插件能够直接使用exe 2.下载JD ...

  10. SpringBoot多跨域请求的支持(JSONP)

    在我们做项目的过程中,有可能会遇到跨域请求,所以需要我们自己组装支持跨域请求的JSONP数据,而在4.1版本以后的SpringMVC中,为我们提供了一个AbstractJsonpResponseBod ...