(一)any 在写程序中,我们往往需要在运行期保存一个“泛型”的对象的指针,也就是说这个指针可能是任何对象的指针,这种情况下,我们一般都是把这个指针cast成一个void*或者一个int,然后在需要的时候再cast回来,这样往往缺乏型别安全,况且我们的程序员都喜欢使用C的转型操作而非C++的转型操作,从而导致一个T*指针被cast成一个U*的指针而编译器却不做任何表示,为了确保型别安全,这个时候我们应该在转型前进行型别检查,看这样的转型是否正确。而boost::any就帮助我们完成了这件事。他的具体实现原理是这样的:
template<typename valueType> class any { ... placeholder * content; //一个接口指针,你可以先不管 };
template<typename valueType> any(const valueType & value) : content(new holder<valueType>(value)) {}
template<typename valueType> any & operator=(const valueType & rhs) { any(rhs).swap(*this); return *this; }
上面的原理就是通过模板的实参演绎,把对象的型别通过“伪拷贝构造函数”和“伪等于操作符”传递给一个nest class,这个class就是:
template<typename valueType> class holder;
holder保存了一个我们传递给他的值,并暴露一个获得std::type_info&和克隆自己的接口,这样就把刚才编译器通过我们传递的对象而进行的实参演绎得到型别信息通过运行期的std::type_info导了出来。如下:
class placeholder { public: virtual ~placeholder() {}
virtual const std::type_info & type() const = 0;
virtual placeholder * clone() const = 0; };
template<typename valueType> class holder : public placeholder { public: holder(const valueType & value) : held(value) { }
virtual const std::type_info & type() const { return typeid(valueType); }
virtual placeholder * clone() const { return new holder(held); }
valueType held; };
最后,我们得到了型别信息,但是我们还没有使用他,boost通过提供了下面的帮助函数来方便我们的使用:请看核心的一个:
template<typename valueType> valueType * any_cast(any * operand) { return operand && operand->type() == typeid(valueType) ? &static_cast<any::holder<valueType> *>(operand->content)->held : 0; }
我们获得了一个方便的工具,更重要的是我们获得了一种把一个对象的型别进行封装的方法,具体的方法如下: 1。型别和对象不是同一个级的,我们传入对象的时候必须指定型别,但是我们却可以把这个指定过程委托给编译器来完成(实参演绎) 2。我们只能使用型别来“保存”型别,但是我们却可以使用一个内建型别来帮助我们保存型别,从而使外部型别是唯一的。 3。要从多个型别导出方法,可以使用接口来模糊型别的差异
(二)bind 我们在写程序的时候经常需要把一些调用延迟到以后,这也就是所谓的“command”模式,一个典型的例子就是绘图时候的脏矩形管理,我们在调用画图函数的时候并不直接画图,而是创建一个command,然后把这些command保存到一个队列中,然后在最终需要画图的时候再计算出脏矩形,然后再画图,向下面这样:
struct DrawCommand { Image* m_iamge; RECT m_rect; int m_flag;
DrawCommand(Image* iamge, RECT& rect, int flag) : m_iamge(image), m_rect(rect), m_flag(flag) { assert(image); }
void Draw(RECT& rect) { assert(m_iamge); m_iamge->Draw_Impl(rect, m_flag); } }
class DirtyRect_Drawer { private: std::vector<DrawCommand*> m_draw_queue; RECT m_dirtyrect;
private: void CalcDirtyRect() { ... //use m_draw_queue to calc m_dirtyrect } static void Draw(DrawCommand* p) { assert(p); p->Draw(m_dirtyrect); delete p; }
public: void push(DrawCommand* p) { assert(p); m_draw_queue->push_back(p); } void FlushDraw() { CalcDirtyRect(); std::for_each(m_draw_queue.begin(), m_draw_queue.end(), Draw); m_draw_queue.clear(); } };
上面的DrawCommand应该使用自己的内存分配方案,也就是要重载new,不然你会受到效率的惩罚,因为C++的默认new实在是慢得要死。并且DirtyRect_Drawer应该是个单件。现在我们就将就向下面这样使用:
namespace { DirtyRect_Drawer _Drawer; };
extern Image* image1; extern Image* image2;
int main() { RECT rect1(0, 0, 100, 100), rect2(50, 50, 100, 100); _Drawer.push(new DrawCommand(image1, rect1, SIMPLE_DRAW)); _Drawer.push(new DrawCommand(image1, rect2, SIMPLE_DRAW)); _Drawer.FlushDraw(); }
就是这样,但是当这样的Command越来越多的时候,代码重复就越来越大,实现也是枯燥无味。这个时候我们需要一个可以泛用的方案。这就是function和bind。
我们先来看看他的效果,然后再来看他的实现原理。
typedef boost::function<void(RECT&)> DrawCommand;
class DirtyRect_Drawer { private: std::vector<DrawCommand*> m_draw_queue; RECT m_dirtyrect;
private: void CalcDirtyRect() { ... //use m_draw_queue } static void Draw(DrawCommand* p) { assert(p); (*p)(m_dirtyrect); delete p; }
public: void push(DrawCommand* p) { assert(p); m_draw_queue->push_back(p); } void FlushDraw() { CalcDirtyRect(); std::for_each(m_draw_queue.begin(), m_draw_queue.end(), Draw); m_draw_queue.clear(); } };
namespace { DirtyRect_Drawer _Drawer; };
extern Image* image1; extern Image* image2;
int main() { RECT rect1(0, 0, 100, 100), rect2(50, 50, 100, 100); _Drawer.push( new DrawCommand(&Image::Draw, image1, rect1, SIMPLE_DRAW) ); _Drawer.push( new DrawCommand(&Image::Draw, image1, rect2, SIMPLE_DRAW) ); _Drawer.FlushDraw(); }
是不是很方便,DrawCommand根本就不用写,他本质上就是:boost::_bi::bind_t< void, Image::*, list3<image*, RECT&, int> >,我们用boost::function<void(RECT&)>来保存他,而Command实际上就是:operator (RECT&)。
不仅如此,bind还提供了只指定一部分参数的方法:
void kao(int i, float j) { }
int i = 10;
boost::bind(&kao, _1, 10.0f)(i);
除此之外还有很多强大的能力,你可以自己去看boost的DOC,现在我们来看一看他的实现原理:
Command模式是保存了一部分的参数的值,并在以后执行这个Command的时候使用。我们先来看看这是怎么实现的,也就是上面的传递的&Image::Draw, image1, rect1, SIMPLE_DRAW是如何保存的,他们是不同的型别,我只介绍一下他的基本原理,所以对代码会进行删改和润色,但是代码的设计原理是不会变的。
他大概的原理是这样的:
我们先来看看实际的bind函数,我仅用接受两个参数的bind函数示范。
template<class R, class F, class A1, class A2> _bi::bind_t<R, F, typename _bi::list_av_2<A1, A2>::type> bind(F f, A1 a1, A2 a2) { typedef typename _bi::list_av_2<A1, A2>::type list_type; return _bi::bind_t<R, F, list_type> (f, list_type(a1, a2)); }
1。首先,我们把所有参数的值和一些占位符传给bind函数,bind函数把这些参数值保存在一个叫list_av_2的型别里,调用如下:
boost::bind(&kao, _1, 10.0f);
namespace _bi { //////////////////////////////////////////////////////////list_av_2 template<class A1, class A2> struct list_av_2 { typedef typename add_value<A1>::type B1; //add_value是一个把原始型别提炼出来的类 typedef typename add_value<A2>::type B2; typedef list2<B1, B2> type; }; //////////////////////////////////////////////////////////add_value,实现原理就是模板偏特化 template<class T> struct add_value { typedef value<T> type; }; template<class T> struct add_value< value<T> > { typedef value<T> type; }; template<class T> struct add_value< reference_wrapper<T> > { typedef reference_wrapper<T> type; }; template<int I> struct add_value< arg<I> > { typedef boost::arg<I> type; }; template<int I> struct add_value< arg<I> (*) () > { typedef boost::arg<I> (*type) (); }; template<class R, class F, class L> struct add_value< bind_t<R, F, L> > { typedef bind_t<R, F, L> type; }; ////////////////////////////////////////////////////////// }; //////////////////////////////////////////////////////////arg,他的用处是占位,并为以后识别提供信息 namespace boost { template<int I> class arg { }; }; //////////////////////////////////////////////////////////统一名称 namespace { static boost::arg<1> _1; static boost::arg<2> _2; ... }; //////////////////////////////////////////////////////////
2。我们发现上面的list_av_2仅仅是做了一个转接的作用,他把真实的型别提炼出来并把他做成一个list2传给bind_t,现在通过把operator()把参数传给bind_t,如下: int i = 0; boost::bind(&kao, _1, 10.0f)(i);
namespace _bi { //////////////////////////////////////////////////////////type template<class T> class type {}; //////////////////////////////////////////////////////////bind_t template<class R, class F, class L> class bind_t { public: bind_t(F f, L const & l): f_(f), l_(l) {} //把函数和仿函数传递,以及参数的值列表传给bind_t
typedef typename result_traits<R, F>::type result_type; //获得优化并正确的返回值型别
result_type operator()() //暴露给我们调用的接口 { list0 a; return l_(type<result_type>(), f_, a); } //我们利用result_type来识别参数列表相同但返回值不同的函数,但是如果result_type很大,或者引用其他的资源的话 //那么将付出很大代价,所以是用type<result_type>()来识别。
result_type operator()() const //暴露给我们调用的接口 { list0 a; return l_(type<result_type>(), f_, a); } ...A1就省略了 template<class A1, class A2> result_type operator()(A1 & a1, A2 & a2) //暴露给我们调用的接口 { list2<A1 &, A2 &> a(a1, a2); return l_(type<result_type>(), f_, a); }
template<class A1, class A2> result_type operator()(A1 & a1, A2 & a2) const //暴露给我们调用的接口 { list2<A1 &, A2 &> a(a1, a2); return l_(type<result_type>(), f_, a); } ...A1,A2,A3等
private: F f_; //函数和仿函数 L l_; //listN的参数列表 }; ////////////////////////////////////////////////////////// };
而在operator()又创建一个list2,这个list2的实例通过新传进的参数值和以前的list2的实例的参数值进行合并,把原来的list2实例里所有_1,_2,..的地方替换成在operator()里传进的参数。这样就相当方便,这通过重载的operator[]来实现,我们通过接受不同型别的operator[]来识别不同位置的参数,也就是_1,_2,...和其他参数型别
我去掉了这个类里的一些代码,他们主要是这个主题无关的一些代码
namespace _bi { //////////////////////////////////////////////////////////value template<class T> class value { public: value(T const & t): t_(t) {}
T & get() { return t_; } T const & get() const { return t_; }
private: T t_; }; //////////////////////////////////////////////////////////list2 template<class A1, class A2> class list2 { public: list2(A1 a1, A2 a2): a1_(a1), a2_(a2) {} //在这一步把参数值保存进list2
A1 operator[] (boost::arg<1>) const { return a1_; } A2 operator[] (boost::arg<2>) const { return a2_; } template<class T> T & operator[] (value<T> & v) const { return v.get(); } template<class T> T const & operator[] (value<T> const & v) const { return v.get(); } template<class T> T & operator[] (reference_wrapper<T> const & v) const { return v.get(); } ... template<class R, class F, class A> R operator()(type<R>, F f, A & a) const { return unwrap(f, 0)(a[a1_], a[a2_]); //unwarp的目的是把真正的型别从一些包装型别里分离出来 } ...
private: A1 a1_; A2 a2_; }; //////////////////////////////////////////////////////////unwrap template<class F> inline F & unwrap(F & f, long) //利用偏特化 { return f; } template<class F> inline F & unwrap(reference_wrapper<F> & f, int) //利用偏特化 { return f; } template<class F> inline F & unwrap(reference_wrapper<F> const & f, int) //利用偏特化 { return f; } ////////////////////////////////////////////////////////// };
namespace _bi { //////////////////////////////////////////////////////////bind_t template<class R, class F, class L> class bind_t { public: bind_t(F f, L const & l): f_(f), l_(l) {} ... template<class A1> result_type operator()(A1 & a1) { list1<A1 &> a(a1); return l_(type<result_type>(), f_, a); } template<class A1, class A2> result_type operator()(A1 & a1, A2 & a2) const { list2<A1 &, A2 &> a(a1, a2); return l_(type<result_type>(), f_, a); //在这里,我们创建一个list2,然后把我们的参数传递给他, //然后就是list2<A1, A2>::operator,然后就是unwrap(f, 0)(a[_1], a[_2]) //然后就unwrap(f, 0)(a1_, a2_) } ... private: F f_; L l_; }; ////////////////////////////////////////////////////////// };
你可能已经发现了,我们调用的bint_t的operator()的参数个数和list2的operator()的参数个数不一样,实际上他的流程是这样的:
boost::bind(&kao, _1, 10.0f);把函数和仿函数,以及参数传给bind_t里的一个list2,这个list2保存的值是:
a1_--->_1,a2_--->10.0f
然后boost::bind(&kao, _1, 10.0f)(i);把参数i通过operator(A1& a)传递给bind_t,这个时候又生成一个list1,他保存的值是:
a1_--->i
然后把这个list1传递通过list2的operator()给原来的list2,而list2在内部调用unwrap(f, 0)(a[a1_], a[a2_]);,这个a就是刚才传递给list2的list1,这个时候会调用相对参数i而调用list1<A1>::operator[](boost::arg<1>),这个实际上返回i。相对list2本身保存的参数a2_而调用list1<A1>::operator[](value<T> & v),这个实际上返回list2本身保存的参数a2_。从而使前面的调用实际上成为:
unwrap(f, 0)(i, 10.0f);
这就就达到了目的,也就是说我们可以向下面这样进行调用:
void kao(int i, int j, int k, float p) { }
int i = 0; boost::bind(&kao, _1, _1, _1, 10.0f)(i);
上面的调用效果实际上是:
kao(i, i, i, 10.0f);
(三)lambda 这是一个绝妙的库,我在学习他的时候第一个反映就是震惊,“什么,代码还可以这样写!?”,但是在思考以后我的反映是对作者的想象力的赞叹和恒心的佩服,为什么呢?请先看个简单的例子,然后你自然就明白了。
我们写代码的时候经常写出这样的代码:
using namespace std;
extern list<int&> m_list;
template <class T> struct plus { T& i;
plus(T& ii) : i(ii) { }
void operator()(const T& j) const { cout << j; cout << (i + j); } }
int i = 0;
for_each(m_list.begin(), m_list.end(), plus(i));
我们已经很满足了,不是吗?但是请你看看下面的代码,他和上面是样的效果:
using namespace std; using namespace boost::lambda;
extern list<int&> m_list;
int i = 0;
for_each(m_list.begin(), m_list.end(), ( cout << _1, cout << (_1 + i) ) );
也许你和我一样,第一感觉是震惊,“这TMD是怎么回事!?”,但是当你听我解释他工作原理的时候你又会马上发现这一却是多么的自然。因为这个库实在很庞大,所以我只介绍他基本原理,至于代码就不直接讨论。
首先,你必须坚持一点:C++并没有发疯,这完全是符合C++标准的。你会说,“标准!?原始代码都出现在函数参数列表里了,还正常!?”我们知道for_each只接受函数或者仿函数作为他的第三个参数,也就是说( cout << _1, cout << (_1 + i) )返回一个仿函数,但是怎么看也不是一个函数啊,这就是我赞叹作者想象力的地方,你别忘了C++的操作符重载,上面代码实际上重载了operator+, operator<<,operator,。他们都返回一个lambda的仿函数并接受仿函数作为参数。“什么?,也能被重载!?”是的,他可以,只是很少人这么做。lambda的作者重载了所有能重载的操作符,还写了不少判断函数,这就是我佩服他恒心的地方。你又看到了_1,他的作用和bind里的_1的作用大同小异,都是一个占位符。你可以自己去看代码。到这里,你已经不会再为见到下面的代码而吃惊了。
sort(vp.begin(), vp.end(), *_1 > *_2);
for_each(vp.begin(), vp.end(), cout << constant('\n') << *_1);
bool flag = true; int i = 0; (_1 || ++_2)(flag, i);
struct A { int d; }; A* a = new A(); (_1 ->* &A::d)(a);
struct B { int foo(int); }; B* b = new B(); (_1 ->* &B::foo)(b)(1);
int i = 0; int j; var_type<int>::type vi(var(i)), vj(var(j)); for_each(a.begin(), a.end(), (vj = _1, _1 = vi, vi = vj));
int a[5][10]; int i; for_each(a, a+5, for_loop(var(i)=0, var(i)<10, ++var(i), _1[var(i)] += 1));
for_each(a.begin(), a.end(), if(_1 % 2 == 0)[ cout << _1 ])
std::for_each(v.begin(), v.end(), ( switch_statement ( _1, case_statement<0>(std::cout << constant("zero")), case_statement<1>(std::cout << constant("one")), default_statement(cout << constant("other: ") << _1) ), cout << constant("\n") ) );
还有很多,但是除了极为简单的情况,我们推荐使用以外,其他情况都不推荐,他不仅会在效率上惩罚你,因为有多少个操作符实际上就有多少个函数调用和参数传递,而且根本不能调试,并且还造成复用困难,对不了解lambda的人来说就更是不可理解了。
因为水平平庸,必然有错误的理解,请高手不吝指出为谢。《解析boost》 |