AutoCAD 3DMAX C语言 Pro/E UG JAVA编程 PHP编程 Maya动画 Matlab应用 Android
Photoshop Word Excel flash VB编程 VC编程 Coreldraw SolidWorks A Designer Unity3D
 首页 > C++

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

51自学网 2015-09-06 http://www.51zixue.net

 

  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++0x漫谈》系列之:右值引用  下一篇:C&C++论战之C++真的还有未来吗?