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那样压倒性通过的。 
2/2 首页 上一页 1 2 |