| 订阅 | 在线投稿
分享
 
 
当前位置: 王朝网络 >> c/c++ >> 瘦身前后——兼谈C++语言进化
 

瘦身前后——兼谈C++语言进化

2008-06-01 02:10:58 编辑來源:互联网 繁體版 评论
 
 
  前一阵子写了一篇文章,提到语言进化的职责之一,就是去除语言中的tricks(职责之二是去除非本质复杂性)。

  常看我blog的朋友肯定记得我曾写过的boost源码剖析系列。本来这个系列是打算成书的,但随着对C++的熟悉发生了一些转变,对语言级技术的热衷逐渐消退,再回过头来看boost库中的一些组件,发现原本觉得很有写的必要的东西顿时消失了。Scott Meyers的主页上也列有一个写Boost Under The Hood的计划,一直也不见成文,兴许也有类似的原因。

  一门语言应该是“Make simple things simple, make complex things possible”的。当我们用语言来表达思想的时候,这门语言应该能够提供这样的能力:即让我们能够最直接地表达我们的意思,多一分则太多,少一分则太少,好比古人形容美女:增一分则太肥,减一分则太瘦。

  这个问题上,有一个我认为是广泛的误解,就是“KISS便意味着要精简语言,并避免在编码中使用‘高阶’语言特性”。对此有一句话我觉得说得好:你不能通过从一门语言中去掉东西来增加表达力。高阶特性是一面利刃,用得不好固然伤了自己,但这并不表明就没有用。任何东西都是在它真正适用的地方适用,霸王硬上弓的话弓断弦崩反而伤及自身。所以,仅仅因为高阶特性轻易误用(而且高阶特性的确也轻易吸引人去用且轻易误用,不过这是另一个问题),就断然在任何地方都不用并宣称这样才是KISS的话,便因噎废食了。举个例子,高阶函数是有用的,假如在真正需要高阶函数的地方不用高阶函数,那不是KISS,只能让解决方案(或者更确切地说,workaround)更复杂。lambda函数是有用的,但假如在真正需要lambda的地方不使用lambda,也只能导致更复杂更不直观的workarounds。OOP是有用的,但假如你的程序本来就只是简单的“数据+操作”你偏要硬上OOP的话,不仅多了编码时间,而且还降低程序的可见度和可维护性,后者就意味着项目的money。拿C++来说,这是一个广为诟病的问题。C++的偏向底层的应用领域决定了有不少地方使用C++其实就是“数据+操作”,然而很多人却因为用的是C++编译器,便忍不住去使用高级特性,结果把本来简单的事情复杂化——我自己就有不少次这样的经历:用了一大堆类之后,做完了回过头来再看,这些类都干嘛来着?需要吗?最要害的就是要清楚自己做的是什么事情,以及什么工具才是对你所做的事情最适合的。

  说到这里不妨顺便说说另一个误解:“假如我反正用不着C++里面的高级特性,那还不如用C罢了”,鉴于C/C++的应用领域,的确有不少地方是可以用C++的C部分完成得很好的,所以这个误解被传播得还是蛮广泛的。这里的一个微妙的忽视在于:用C的话,你就用不到许多很好的C++库了。用C++的话,你完全可以在你自己的编码中不使用高阶特性(说实话,这需要清醒的头脑和丰富的经验,以及克制能力),但你还是可以利用众多的C++库来简化你的工作的:假如一个transform明明可以搞定的你偏要写一个for出来难道能叫KISS?假如一个vector就能避免绝大多数内存治理漏洞和简化内存治理工作你偏偏要手动malloc/free那能叫KISS(我见过不少用C++编码却到处都是malloc/free的)?假如最直接的方式是gc你偏偏要绕一大堆弯子才能保证正确释放那也不叫KISS(等C++09吧)。假如一个for_each(readdir_sequence(".", readdir_sequence::files), ::remove);能搞定的你偏要写:

  

  // in C

  DIR* dir = opendir(".");

  if(NULL != dir)

  {

  strUCt dirent* de;

  for(; NULL != (de = readdir(dir)); )

  {

  struct stat st;

  if( 0 == stat(de->d_name, &st) &&

  S_IFREG == (st.st_mode & S_IFMT))

  {

  remove(de->d_name);

  }

  }

  closedir(dir);

  }

  那能叫KISS?

  总之还是那句话:明确知道你想要表达的是什么并用最简洁(在不损害轻易理解性的前提下)的方式去表达它。但我认为,最KISS不代表最原始。

  进化——两个例子

  先举一个平易近人的例子(Walter Bright——D语言发明者——曾在他的一个presentation中使用这个例子),假如我们想要遍历一个数组,在C里面我们是这么做(或者用指针,不过指针有指针自己的问题):

  

  int arr[10];

  … // initialize arr

  for(int i = 0; i < 10; ++i)

  {

  int value = arr[i];

  …

  printf

  }

  这个貌似简单的循环其实有几个主要的问题:

  1. 下标索引不应该是int,而应该是size_t,int未必能足够存放一个数组的下标。

  2. value的类型依靠于arr内元素的类型,违反DRY,假如arr的类型改变为long或unsigned,就可能发生截断。

  3. 这种for只能对数组工作,假如是另一个自定义容器就不行了。

  在现代C++里面,则是这么做:

  

  for(std::vector<int>::iterator

  iter = v.begin();

  iter != v.end();

  ++iter) {

  …

  }

  其实最大的问题就是一天三遍的写,麻烦。for循环的这个问题上篇讲auto的时候也提到。

  Walter Bright然后就把D里面支持的foreach拿出来对比(当然,支持foreach的语言太多了,这也说明了这个结构的高效性)。

  

  foreach(i; v) {

  …

  }

  不多不少,刚好表达了意思:对v中的每个元素i做某某事情。

  这个例子有人说太Na?ve了,其实我也赞成,的确,天天不知道有多少程序员写下一个个的循环结构,究竟有多少出了上面提到的三个问题呢?最大的问题恐怕还是数组越界。此外大家也都亲身体验过违反DRY原则的后果:改了一处地方的类型,编译,发现到处都是类型错误,结果一通“查找——替换”是免不了的了,谁说程序员的时间是宝贵的来着?

  既然这个例子太Nave,那就说一个不那么Nave的。Java为什么要加入closure?以C++STL为例,假如我们要:

  transform(v1.begin(), v1.end(), v2.begin(), v3.begin(), _1 + _2);

  也就是说将v1和v2里面的元素对应相加然后放到v3当中去。这里用了boost.lambda,但大家都知道boost.lambda又是一个经典的鸡肋。_1 + _2还算凑活,一旦表达式复杂了,或者其中牵涉到对其它函数的调用了,简直就是一场噩梦,比如说我们想把v1和v2中相应元素这样相加:f(_1) + f(_2),其中f是一个函数或仿函数,可以做加权或者其它处理,那么我们可以像下面这样写吗:

  transform(…, f(_1) + f(_2));

  答案是不行,你得这样写:

  

  transform(…,

  boost::bind(std::plus<int>(), boost::bind(f, _1), boost::bind(f, _1))

  );

  Lisper们笑了,Haskeller们笑了,就连Javaer们都笑了。It’s not even funny! 这显然违反了“simple things should be simple”原则。

  假如不想卷入C++ functional的噩梦的话,你也可以这么写:

  

  struct Op

  {

  int operator()(int a1, int a2) { return f(a1) + f(a2); }

  };

  transform(…, Op());

  稍微好一点,但这种做法也有很严重的问题。

  为什么Java加入closure,其实还是一个语法问题。从严格意义上,Java的anonymous class已经可以实现出一样的功能了,正如C++的functor一样。然而,代码是给人看的,语言是给人用来写代码的,代码的主要代价在维护,维护则需要阅读、理解。写代码的人不希望多花笔墨来写那些自己本不关心的东西,读代码的人也希望“所读即所表”,不想看到代码里面有什么弯子,最好是自然语言自然抽象才好呢。

  所以,尽管closure是一颗语法糖,但却是一颗很甜很甜的糖,因为有了closure你就可以写:

  

  transform(…, <>(a1, a2){ f(a1) + f(a2) });

  Simple things should be simple!

  此外,closure最强大的好处还是在于对局部变量的方便的引用,设想我们想要创建的表达式是:

  

  int weight1 = 0.3, weight2 = 0.6;

  transform(…, f(_1)*weight1 + f(_2)*weight2);

  当然,上面的语句是非法的,不过使用closure便可以写成:

  

  int weight1 = 0.3, weight2 = 0.6;

  transform(…, <&>(_1, _2){ f(_1)*weight1 + f(_2)*weight2 } );

  用functor class来实现同样的功能则要麻烦许多,一旦麻烦,就会error-prone,一旦error-prone,就会消耗人力,而人力,就是金钱。

  C++09也有希望加入lambda,不过这是另一个话题,下回再说。

  The Real Deal——variadic templates

  C++的callback类,Google一下,没有一打也有半打。其中尤数boost.function实现得最为灵活周到。然而,就在其灵活周到的接口下面,却是让人不忍卒读的实现;03年的时候我写的第一篇boost源码剖析就是boost.function的,当时还觉得能看懂那样的代码牛得不行...话说回来,那篇文章主要剖析了两个方面,一个是它对不同参数的函数类型是如何处理的,第二个是一个type-erase设施。其中第一个方面就占去了大部分的篇幅。

  简而言之,要实现一个泛型的callback类,就必须实现以下最常见的应用场景:

  

  function<int(int, int)> caller = f;

  int r = caller(1, 2); // call f

  为此function类模板里面肯定要有一个operator(),然而,接下来,如何定义这个operator()就成了问题:

  

  template<Signature>

  class function

  {

  operator()(???);

  };

  ???处填什么?返回值处的???可以解决,用一个traits:typename result_type<Signature>::type,但参数列表处的???呢?

  boost采用的办法也是C++98唯一的办法,就是为不同参数个数的Signature进行特化:

  

  template<typename R, typename T1>

  class function<R(T1)>

  {

  R operator()(T1 a1);

  };

  template<typename R, typename T1, typename T2>

  class function<R(T1, T2)>

  {

  R operator()(T1 a1, T2 a2);

  };

  template<typename R, typename T1, typename T2, typename T3>

  class function<R(T1, T2, T3)>

  {

  R operator()(T1 a1, T2 a2, T3 a3);

  };

  … // 再写下去页宽不够了,打住…

  如此一共N(N由一个宏控制)个版本。

  这种做法有两个问题:一,函数的参数个数始终还是受限的,你作出N个特化版本,那么对N+1个参数的函数就没辙了。boost::tuple也是这个问题。二,代码重复。每个特化版本里面除了参数个数不同之外基本其它都是相同的;boost解决这个问题的办法是利用宏,宏本身的一大堆问题就不说了,你只要打开boost.function的主体实现代码就知道有多糟糕了,近一千行代码,其中涉及元编程和宏技巧无数,可读性可以说基本为0。好在这是个标准库(boost.function将加入tr1)不用你维护,假如是你自己写了用的库,恐怕除了你谁也别想动了。所以第二个问题其实就是可读性可维护性问题,用Matthew Wilson的说法就是可发现性和透明性的问题,这是一个很严重的问题,许多C++现代库因为这个问题而遭到诟病。

  现在,让我们来看一看加入了variadic templates之后的C++09实现:

  

  template<typename R, typename... Args>

  struct invoker_base {

  virtual R invoke(Args...) = 0;

  virtual ~invoker_base() { }

  };

  template<typename F, typename R, typename... Args>

  struct functor_invoker : public invoker_base<R, Args...>

  {

  eXPlicit functor_invoker(F f) : f(f) { }

  R invoke(Args... args) { return f(args...); }

  private:

  F f;

  };

  template<typename Signature>

  class function;

  template<typename R, typename... Args>

  class function<R (Args...)>

  {

  public:

  template<typename F>

  function(F f) : invoker(0)

  {

  invoker = new functor_invoker<F, R, Args...>(f);

  }

  R operator()(Args... args) const

  {

  return invoker->invoke(args...);

  }

  private:

  invoker_base<R, Args...>* invoker;

  };

  整个核心实现就这些!一共才36行!加上析构函数拷贝构造函数等边角料一共也就70行!更重要的是,整个代码清楚无比,所有涉及到可变数目个模板参数的地方都由variadic templates代替。“Args…”恰如其分的表达了我们想要表达的意思——多个参数(数目不管)。与C++98的boost.function实现真是天壤之别!

  这里function_invoker是用的type-erase手法,具体可参见我以前写的boost.any源码剖析,或上篇讲auto的,或《C++ Template Metaprogramming》(内有元编程慎入!)。type-erase手法是像C++这样的弱RTTI支持的语言中少数真正实用的手法,某种程度上设计模式里面的adapter模式也是type-erase的一个变种。

  假如还觉得不够的话,可以参考variadic-templates的主页,上面的variadic templates proposal中带了三个tr1实现,分别是tuple,bind,function,当然,variadic-templates的好处远远不仅仅止于这三个实现,从本质上它提供了一种真正直接的表达意图的工具,完全避开了像下面这种horrible的workaround:

  

  template<class T1>

  cons(T1& t1, const null_type&, const null_type&, const null_type&,

  const null_type&, const null_type&, const null_type&,

  const null_type&, const null_type&, const null_type&)

  : head (t1) {}

  tuple的C++98实现,代码近千行。利用variadic-templates实现,代码仅百行。

  和这种更horrible的workaround:

  

  template<class R, class F, class A1, class A2, class A3, class A4, class A5, class A6>

  _bi::bind_t<R, F, typename _bi::list_av_6<A1, A2, A3, A4, A5, A6>::type>

  BOOST_BIND(boost::type<R>, F f, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6)

  {

  typedef typename _bi::list_av_6<A1, A2, A3, A4, A5, A6>::type list_type;

  return _bi::bind_t<R, F, list_type>(f, list_type(a1, a2, a3, a4, a5, a6));

  }

  小小的boost.bind,实现代码逾两千行,其间重复代码无数。用了variadic-templates,实现不过百行。

  BTW. variadic templates在C++大会上一次性几乎全数投票通过。lambda能不能进标准则要看几个提案者的工作。目前还没有Wording出来。不过只要出了wording想必也会像variadic templates那样压倒性通过的。
 
 
 
上一篇《C++计算四则表达式的模板》
下一篇《C&C++论战之C++真的还有未来吗?》
 
 
 
 
 94.*.*.* 发表于2012-07-20 22:15:43
  At last, someone comes up with the "right" asnwer!
  回复
  
  
 
 
 
日版宠物情人插曲《Winding Road》歌词

日版宠物情人2017的插曲,很带节奏感,日语的,女生唱的。 最后听见是在第8集的时候女主手割伤了,然后男主用嘴帮她吸了一下,插曲就出来了。 歌手:Def...

兄弟共妻,我成了他们夜里的美食

老钟家的两个儿子很特别,就是跟其他的人不太一样,魔一般的执着。兄弟俩都到了要结婚的年龄了,不管自家老爹怎么磨破嘴皮子,兄弟俩说不娶就不娶,老父母为兄弟两操碎了心...

如何磨出破洞牛仔裤?牛仔裤怎么剪破洞?

把牛仔裤磨出有线的破洞 1、具体工具就是磨脚石,下面垫一个硬物,然后用磨脚石一直磨一直磨,到把那块磨薄了,用手撕开就好了。出来的洞啊很自然的。需要猫须的话调几...

我就是扫描下图得到了敬业福和爱国福

先来看下敬业福和爱国福 今年春节,支付宝再次推出了“五福红包”活动,表示要“把欠大家的敬业福都还给大家”。 今天该活动正式启动,和去年一样,需要收集“五福”...

冰箱异味产生的原因和臭味去除的方法

有时候我们打开冰箱就会闻到一股异味,冰箱里的这种异味是因为一些物质发出的气味的混合体,闻起来让人恶心。 产生这些异味的主要原因有以下几点。 1、很多人有这种习...

 
 
 
  前一阵子写了一篇文章,提到语言进化的职责之一,就是去除语言中的tricks(职责之二是去除非本质复杂性)。   常看我blog的朋友肯定记得我曾写过的boost源码剖析系列。本来这个系列是打算成书的,但随着对C++的熟悉发生了一些转变,对语言级技术的热衷逐渐消退,再回过头来看boost库中的一些组件,发现原本觉得很有写的必要的东西顿时消失了。Scott Meyers的主页上也列有一个写Boost Under The Hood的计划,一直也不见成文,兴许也有类似的原因。   一门语言应该是“Make simple things simple, make complex things possible”的。当我们用语言来表达思想的时候,这门语言应该能够提供这样的能力:即让我们能够最直接地表达我们的意思,多一分则太多,少一分则太少,好比古人形容美女:增一分则太肥,减一分则太瘦。   这个问题上,有一个我认为是广泛的误解,就是“KISS便意味着要精简语言,并避免在编码中使用‘高阶’语言特性”。对此有一句话我觉得说得好:你不能通过从一门语言中去掉东西来增加表达力。高阶特性是一面利刃,用得不好固然伤了自己,但这并不表明就没有用。任何东西都是在它真正适用的地方适用,霸王硬上弓的话弓断弦崩反而伤及自身。所以,仅仅因为高阶特性轻易误用(而且高阶特性的确也轻易吸引人去用且轻易误用,不过这是另一个问题),就断然在任何地方都不用并宣称这样才是KISS的话,便因噎废食了。举个例子,高阶函数是有用的,假如在真正需要高阶函数的地方不用高阶函数,那不是KISS,只能让解决方案(或者更确切地说,workaround)更复杂。lambda函数是有用的,但假如在真正需要lambda的地方不使用lambda,也只能导致更复杂更不直观的workarounds。OOP是有用的,但假如你的程序本来就只是简单的“数据+操作”你偏要硬上OOP的话,不仅多了编码时间,而且还降低程序的可见度和可维护性,后者就意味着项目的money。拿C++来说,这是一个广为诟病的问题。C++的偏向底层的应用领域决定了有不少地方使用C++其实就是“数据+操作”,然而很多人却因为用的是C++编译器,便忍不住去使用高级特性,结果把本来简单的事情复杂化——我自己就有不少次这样的经历:用了一大堆类之后,做完了回过头来再看,这些类都干嘛来着?需要吗?最要害的就是要清楚自己做的是什么事情,以及什么工具才是对你所做的事情最适合的。   说到这里不妨顺便说说另一个误解:“假如我反正用不着C++里面的高级特性,那还不如用C罢了”,鉴于C/C++的应用领域,的确有不少地方是可以用C++的C部分完成得很好的,所以这个误解被传播得还是蛮广泛的。这里的一个微妙的忽视在于:用C的话,你就用不到许多很好的C++库了。用C++的话,你完全可以在你自己的编码中不使用高阶特性(说实话,这需要清醒的头脑和丰富的经验,以及克制能力),但你还是可以利用众多的C++库来简化你的工作的:假如一个transform明明可以搞定的你偏要写一个for出来难道能叫KISS?假如一个vector就能避免绝大多数内存治理漏洞和简化内存治理工作你偏偏要手动malloc/free那能叫KISS(我见过不少用C++编码却到处都是malloc/free的)?假如最直接的方式是gc你偏偏要绕一大堆弯子才能保证正确释放那也不叫KISS(等C++09吧)。假如一个for_each(readdir_sequence(".", readdir_sequence::files), ::remove);能搞定的你偏要写: // in C DIR* dir = opendir("."); if(NULL != dir) { strUCt dirent* de; for(; NULL != (de = readdir(dir)); ) { struct stat st; if( 0 == stat(de->d_name, &st) && S_IFREG == (st.st_mode & S_IFMT)) { remove(de->d_name); } }  closedir(dir); }   那能叫KISS?   总之还是那句话:明确知道你想要表达的是什么并用最简洁(在不损害轻易理解性的前提下)的方式去表达它。但我认为,最KISS不代表最原始。   进化——两个例子   先举一个平易近人的例子(Walter Bright——D语言发明者——曾在他的一个presentation中使用这个例子),假如我们想要遍历一个数组,在C里面我们是这么做(或者用指针,不过指针有指针自己的问题): int arr[10]; … // initialize arr for(int i = 0; i < 10; ++i) { int value = arr[i]; … printf }   这个貌似简单的循环其实有几个主要的问题:   1. 下标索引不应该是int,而应该是size_t,int未必能足够存放一个数组的下标。   2. value的类型依靠于arr内元素的类型,违反DRY,假如arr的类型改变为long或unsigned,就可能发生截断。   3. 这种for只能对数组工作,假如是另一个自定义容器就不行了。   在现代C++里面,则是这么做: for(std::vector<int>::iterator iter = v.begin(); iter != v.end(); ++iter) { … }   其实最大的问题就是一天三遍的写,麻烦。for循环的这个问题上篇讲auto的时候也提到。   Walter Bright然后就把D里面支持的foreach拿出来对比(当然,支持foreach的语言太多了,这也说明了这个结构的高效性)。 foreach(i; v) { … }   不多不少,刚好表达了意思:对v中的每个元素i做某某事情。   这个例子有人说太Na?ve了,其实我也赞成,的确,天天不知道有多少程序员写下一个个的循环结构,究竟有多少出了上面提到的三个问题呢?最大的问题恐怕还是数组越界。此外大家也都亲身体验过违反DRY原则的后果:改了一处地方的类型,编译,发现到处都是类型错误,结果一通“查找——替换”是免不了的了,谁说程序员的时间是宝贵的来着?   既然这个例子太Nave,那就说一个不那么Nave的。Java为什么要加入closure?以C++STL为例,假如我们要: transform(v1.begin(), v1.end(), v2.begin(), v3.begin(), _1 + _2);   也就是说将v1和v2里面的元素对应相加然后放到v3当中去。这里用了boost.lambda,但大家都知道boost.lambda又是一个经典的鸡肋。_1 + _2还算凑活,一旦表达式复杂了,或者其中牵涉到对其它函数的调用了,简直就是一场噩梦,比如说我们想把v1和v2中相应元素这样相加:f(_1) + f(_2),其中f是一个函数或仿函数,可以做加权或者其它处理,那么我们可以像下面这样写吗: transform(…, f(_1) + f(_2));   答案是不行,你得这样写: transform(…, boost::bind(std::plus<int>(), boost::bind(f, _1), boost::bind(f, _1)) );   Lisper们笑了,Haskeller们笑了,就连Javaer们都笑了。It’s not even funny! 这显然违反了“simple things should be simple”原则。   假如不想卷入C++ functional的噩梦的话,你也可以这么写: struct Op { int operator()(int a1, int a2) { return f(a1) + f(a2); } }; transform(…, Op());   稍微好一点,但这种做法也有很严重的问题。   为什么Java加入closure,其实还是一个语法问题。从严格意义上,Java的anonymous class已经可以实现出一样的功能了,正如C++的functor一样。然而,代码是给人看的,语言是给人用来写代码的,代码的主要代价在维护,维护则需要阅读、理解。写代码的人不希望多花笔墨来写那些自己本不关心的东西,读代码的人也希望“所读即所表”,不想看到代码里面有什么弯子,最好是自然语言自然抽象才好呢。   所以,尽管closure是一颗语法糖,但却是一颗很甜很甜的糖,因为有了closure你就可以写: transform(…, <>(a1, a2){ f(a1) + f(a2) }); Simple things should be simple!   此外,closure最强大的好处还是在于对局部变量的方便的引用,设想我们想要创建的表达式是: int weight1 = 0.3, weight2 = 0.6; transform(…, f(_1)*weight1 + f(_2)*weight2);   当然,上面的语句是非法的,不过使用closure便可以写成: int weight1 = 0.3, weight2 = 0.6; transform(…, <&>(_1, _2){ f(_1)*weight1 + f(_2)*weight2 } );   用functor class来实现同样的功能则要麻烦许多,一旦麻烦,就会error-prone,一旦error-prone,就会消耗人力,而人力,就是金钱。   C++09也有希望加入lambda,不过这是另一个话题,下回再说。 The Real Deal——variadic templates   C++的callback类,Google一下,没有一打也有半打。其中尤数boost.function实现得最为灵活周到。然而,就在其灵活周到的接口下面,却是让人不忍卒读的实现;03年的时候我写的第一篇boost源码剖析就是boost.function的,当时还觉得能看懂那样的代码牛得不行...话说回来,那篇文章主要剖析了两个方面,一个是它对不同参数的函数类型是如何处理的,第二个是一个type-erase设施。其中第一个方面就占去了大部分的篇幅。   简而言之,要实现一个泛型的callback类,就必须实现以下最常见的应用场景: function<int(int, int)> caller = f; int r = caller(1, 2); // call f   为此function类模板里面肯定要有一个operator(),然而,接下来,如何定义这个operator()就成了问题: template<Signature> class function { operator()(???); };   ???处填什么?返回值处的???可以解决,用一个traits:typename result_type<Signature>::type,但参数列表处的???呢?   boost采用的办法也是C++98唯一的办法,就是为不同参数个数的Signature进行特化: template<typename R, typename T1> class function<R(T1)> { R operator()(T1 a1); }; template<typename R, typename T1, typename T2> class function<R(T1, T2)> { R operator()(T1 a1, T2 a2); }; template<typename R, typename T1, typename T2, typename T3> class function<R(T1, T2, T3)> { R operator()(T1 a1, T2 a2, T3 a3); }; … // 再写下去页宽不够了,打住…   如此一共N(N由一个宏控制)个版本。   这种做法有两个问题:一,函数的参数个数始终还是受限的,你作出N个特化版本,那么对N+1个参数的函数就没辙了。boost::tuple也是这个问题。二,代码重复。每个特化版本里面除了参数个数不同之外基本其它都是相同的;boost解决这个问题的办法是利用宏,宏本身的一大堆问题就不说了,你只要打开boost.function的主体实现代码就知道有多糟糕了,近一千行代码,其中涉及元编程和宏技巧无数,可读性可以说基本为0。好在这是个标准库(boost.function将加入tr1)不用你维护,假如是你自己写了用的库,恐怕除了你谁也别想动了。所以第二个问题其实就是可读性可维护性问题,用Matthew Wilson的说法就是可发现性和透明性的问题,这是一个很严重的问题,许多C++现代库因为这个问题而遭到诟病。   现在,让我们来看一看加入了variadic templates之后的C++09实现: template<typename R, typename... Args> struct invoker_base { virtual R invoke(Args...) = 0; virtual ~invoker_base() { } }; template<typename F, typename R, typename... Args> struct functor_invoker : public invoker_base<R, Args...> { eXPlicit functor_invoker(F f) : f(f) { } R invoke(Args... args) { return f(args...); } private: F f; }; template<typename Signature> class function; template<typename R, typename... Args> class function<R (Args...)> { public: template<typename F> function(F f) : invoker(0) { invoker = new functor_invoker<F, R, Args...>(f); } R operator()(Args... args) const { return invoker->invoke(args...); } private: invoker_base<R, Args...>* invoker; };   整个核心实现就这些!一共才36行!加上析构函数拷贝构造函数等边角料一共也就70行!更重要的是,整个代码清楚无比,所有涉及到可变数目个模板参数的地方都由variadic templates代替。“Args…”恰如其分的表达了我们想要表达的意思——多个参数(数目不管)。与C++98的boost.function实现真是天壤之别!   这里function_invoker是用的type-erase手法,具体可参见我以前写的boost.any源码剖析,或上篇讲auto的,或《C++ Template Metaprogramming》(内有元编程慎入!)。type-erase手法是像C++这样的弱RTTI支持的语言中少数真正实用的手法,某种程度上设计模式里面的adapter模式也是type-erase的一个变种。   假如还觉得不够的话,可以参考variadic-templates的主页,上面的variadic templates proposal中带了三个tr1实现,分别是tuple,bind,function,当然,variadic-templates的好处远远不仅仅止于这三个实现,从本质上它提供了一种真正直接的表达意图的工具,完全避开了像下面这种horrible的workaround: template<class T1> cons(T1& t1, const null_type&, const null_type&, const null_type&, const null_type&, const null_type&, const null_type&, const null_type&, const null_type&, const null_type&) : head (t1) {}   tuple的C++98实现,代码近千行。利用variadic-templates实现,代码仅百行。   和这种更horrible的workaround: template<class R, class F, class A1, class A2, class A3, class A4, class A5, class A6> _bi::bind_t<R, F, typename _bi::list_av_6<A1, A2, A3, A4, A5, A6>::type> BOOST_BIND(boost::type<R>, F f, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6) { typedef typename _bi::list_av_6<A1, A2, A3, A4, A5, A6>::type list_type; return _bi::bind_t<R, F, list_type>(f, list_type(a1, a2, a3, a4, a5, a6)); }   小小的boost.bind,实现代码逾两千行,其间重复代码无数。用了variadic-templates,实现不过百行。   BTW. variadic templates在C++大会上一次性几乎全数投票通过。lambda能不能进标准则要看几个提案者的工作。目前还没有Wording出来。不过只要出了wording想必也会像variadic templates那样压倒性通过的。
󰈣󰈤
 
 
 
  免责声明:本文仅代表作者个人观点,与王朝网络无关。王朝网络登载此文出于传递更多信息之目的,并不意味着赞同其观点或证实其描述,其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
 
 
情人节的清纯女生(9)
情人节的清纯女生(8)
情人节的清纯女生(7)
情人节的清纯女生(6)
山东蓬莱海边组照
一探哲蚌 II
一探哲蚌 III
古长城的一角
 
>>返回首页<<
 
 
 为你推荐
 
 
 
 转载本文
 UBB代码 HTML代码
复制到剪贴板...
 
 热帖排行
 
 
 
 
©2005- 王朝网络 版权所有