抽象之源
抽象之源
C++的抽象能力在各种语言中算得上出类拔萃的。正如大地是Titan的力量之源,C++的抽象能力也有它的技术基础。
泛型编程可以算作最重要的C++特性。在“C With Class”时代,C++等抽象能力相比Java之类的OOP语言,并没什么过人之处。但在应用的刺激下(最早是io stream),C++引入模板,以实现参数化类型的需求,也就是“泛型”的功能。然而,C++革命性的进步得益于Alexander Stepanov在STL所做的贡献。
泛型提供了一种“泛化”编程手段。在泛型编程中,我们被赋予了一种面向一类问题,而非单个的具体问题,的编程能力。然而,仅仅“泛化”,还不足以提供充分的抽象能力。实际上,C++的独特,而又强大的抽象能力,来源于两个相对的概念:泛化和特化。
泛化,可以看作处理具备某种特征的一类类型的能力。而特化,则是一个相反的过程:处理一个具体的问题。
如同道家的阴阳变化造就世间万物,泛化和特化的有机组合,构成了C++的抽象体系。可以通过一个简单的(已经被用烂的)例子演示这种组合:
void swap(int& a, int& b) {
int t=a;
a=b;
b=t;
}
函数swap交换两个int类型变量的值。考虑到类型无穷无尽,不可预测(谁知道swap的使用者会创造除什么类型进行交换)。所以,为了避免无穷无尽的“来料加工”,我们必须创建一个对任何类型都有效的算法。于是,模板出现了:
//代码#1
template <typename T>
void swap(T& a, T& b) {
T t(a);
a=b;
b=t;
}
泛化展示出了它的威力:写一次代码,套所有类型(当然得符合要求:可以赋值,拥有复制构造函数)。
但事情并没有完结。对于某些类型,这个swap模板是个着实笨拙的算法。比如matrix:
matrix a,b;
swap(a,b);
此时swap的代码等价于这样一个函数:
//代码#2
void swap_matrix(matrix& a, matrix& b) {
matrix t(a);
a=b;
b=t;
}
这里会产生至少一个临时变量,并且伴随着大量的数据拷贝。对与C++的使用者而言,这是邪恶的犯罪行为。现在,假设matrix有一个成员函数swap()。该函数可以在O(1)的复杂度下交换两个matrix对象的内容。利用这个特性,可以大幅提升某些特定类型的swap性能。下一步需要扩展swap,使其可以针对matrix做特别处理。
具体的方法有两种。先看较传统的重载:
//代码#3
void swap(matrix& a, matrix& b) {
a.swap(b);
}
另一种更摩登的方法是采用模板特化:
//代码#4
template <>
void swap <matrix> (T& a, T& b) {
a.swap(b);
}
两种方法都对matrix生成了一个专用的swap函数,这便是一种特化。使用时,也无需关心那个类型用哪个版本的算法,只管调用便是了,其他的都交给swap的作者和C++去吧:
int ia=10, ib=3;
matrix ma, mb;
…
swap(ia, ib);//使用原始的swap()版本,通过临时对象交换数据
swap(ma, mb);//使用对象上的swap()函数
在实际应用中,类似matrix的笨重的类型多如牛毛。最典型的就是STL里的容器,从vector到map都是这副德行。
对所有这些类型挨个儿重载或特化,绝对不是令人愉快的工作。何况也无法预测算法的使用者会搞出什么样古怪的类型来。而且,这种针对性的特化让泛型编程的优点荡然无存,代码重用也就无从谈起。
问题的关键在于泛化(特化)的粒度。在实际开发中,泛化和特化并非有你无我的绝对排斥关系。形象起见,用一个彩色木棍做比喻。想像有一根木棍,一头是红色的,另一头是蓝色的,木棍的颜色从红色渐渐过渡到蓝色。当然啦,中间的颜色应该是紫色。我们假设,蓝色表示泛化,红色表示特化。从红色那头开始,颜色越来越蓝,也就是越来越泛化。
在上面的swap()案例中,代码#1给出了完全泛化的模板(纯蓝色)。而代码#3和#4给出的则是针对一个具体类型的完全特化的版本(纯红色)。
然而,我们所面临的多数问题并非这种纯红/纯蓝模式所能解决的。我们需要的是紫色、红紫色,或者蓝紫色的模式。也就是中间粒度的泛化。具体而言,我们需要的是针对具备某种特性的类型,而不是所有类型或某个具体类型,的泛化和特化。
C++的模板局部特化机制,提供了稍细粒度的泛化(特化)能力。下面是几个典型的案例:
template <typename T> class X {…};//一般情况下使用这个模板,除非…
template <typename T> class X <T&> {…};//当类型参数是引用时,使用这个模板;
template <typename T> class X <T*> {…};//当类型参数是指针时,使用这个模板;
template <typename R, typename P> class X <R (P)> {…};//当类型参数是形如R (P)//的函数时,使用这个模板
…
这种特化功能已经很强大了。但是,它并不能完全满足我们的要求。因为这种形式仅仅提供了针对类型大类的泛化(特化)功能,如指针、引用、函数、数组等等。但无法直接提供更细粒度的泛化和特化能力。
具体到swap()案例,我们需要这样一种特化:对所有拥有T::swap(T const&)成员函数的类型做特化。这种泛化需求在目前的C++中也只能通过一些复杂、机巧的手段获得:
struct true_ { static const bool result=true;};
struct false_ { static const bool result=false;};
template <typename T> struct has_swap_mem : false_ {};
template <> struct has_swap_mem <matrix> : public true_{};
template <typename T> struct has_swap_mem <vector <T> > : public true_{};
template <typename T> struct has_swap_mem <map <T> > : public true_{};
…
template <typename T, bool has_swap>
struct do_swap
{
void operator(T& a, T& b) {
T t(a);
a=b;
b=t;
}
}
template <typename T>
struct do_swap <T, true>
{
void operator(T& a, T& b) {
a.swap(b);
}
}
template <T>
void swap(T& a, T& b) {
do_swap <T, has_swap_mem <T> ::result> ()(a, b);
}
通过这种所谓“tagged dispatch”手法,我们可以手工地为每一个类型配备一个(或一组)traits类(结构),其中包含类型的特征描述。然后利用模板(局部)特化技术,以traits类的特征值进行编译时分派。在此基础上,我们可以进一步增加类型的特性描述,进一步细化swap的泛化粒度。比如,对于数组,必须使用循环,而无法直接用临时变量执行交换等等。
这种技术是有效的,但着实复杂。而且工作量巨大,因为需要为每个类型写traits类。但随着C++09中concept的引入,这些问题迎刃而解。
简单的讲,concept描述了一个类型在对外接口上的特征(关于concept的细节,可以看http://blog.csdn.net/pongba/archive/2007/08/04/1726031.aspx,及其参考文献)。比如,我们可以用这样一个concept描述“具备swap()成员函数的类型”:
auto concept has_swap <typename T> {
void T::swap(T&);
}
这样,swap()算法便可以写成:
//代码#5
template <typename T>
void swap(T& a, T& b) {
T t(a);
a=b;
b=t;
}
//代码#6
template <has_swap T>
void swap(T& a, T& b) {
a.swap(b);
}
如果有些类型是通过exchange(),而不是swap(),来交换内容。那么只需创建一个新的concept,并编写相应的swap()版本即可:
auto concept has_exchange <typename T> {
void T::exchange(T &);
}
//代码#7
template <has_exchange T>
void swap(T& a, T& b) {
a.swap(b);
}
就这么简单,无需更多的代码。编译器会忠实地履行类型匹配的职责。于是,下面的调用,会以皆大欢喜的方式执行:
int ia=10, ib=3;
matrix ma, mb;
XXX x1,x2;//XXX通过exchange交换
…
swap(ia, ib);//使用#5,用临时变量。
swap(ma, mb);//使用#6,用swap成员。
swap(x1, x2);//使用#7,用exchange成员。
这样已经相当理想了,但还是不够。C++09中,还将提供更进一步的模板约束细化手段。如果一个类型的作者非常地道地让他的类型既包含swap()成员,又包含exchange()成员,那么swap()的调用存在二义性。为此,C++09可以通过将多个concept联合使用,加以解决:
//代码#8:
template <has_swap T>
requires has_exchange <T>
void swap(T& a, T& b) {
a.swap(b);
}
//或等价的
template <typename T>
requires has_swap <T> && has_exchange <T>
void swap(T& a, T& b) {…}
另一种方法是通过 ! 操作符“去除”类型的某种特性:
//将代码#7改为
template <has_exchang T>
requires ! has_swap <T>
void swap(T& a, T& b) {
a.exchange(b);
}
它表示对于只包含exchange()成员,不包含swap()成员的类型使用这个版本,包含swap()成员或同时包含swap()和exchange()成员的类型,依旧使用代码#6。
[解决办法]
顶一下。
期待C++在C++09浴火重生。
[解决办法]
UP,学习 ~
[解决办法]
又见牛帖,up & mark
[解决办法]
强烈期待C++0x
[解决办法]
学习
[解决办法]
MARK 期待