Chinaunix首页 | 论坛 | 博客
  • 博客访问: 18688963
  • 博文数量: 7460
  • 博客积分: 10434
  • 博客等级: 上将
  • 技术积分: 78178
  • 用 户 组: 普通用户
  • 注册时间: 2008-03-02 22:54
文章分类

全部博文(7460)

文章存档

2011年(1)

2009年(669)

2008年(6790)

分类: C/C++

2008-05-31 08:28:30

C++的抽象能力在各种语言中算得上出类拔萃的。正如大地是Titan的力量之源,C++的抽象能力也有它的技术基础。

泛型编程可以算作最重要的C++特性。在“C With Class”时代,C++等抽象能力相比SmallTalk之类的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

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++的使用者而言,这是邪恶的犯罪行为。现在,假设
[NextPage]

matrix有一个成员函数swap()。该函数可以在O(1)的复杂度下交换两个matrix对象的内容。利用这个特性,可以大幅提升某些特定类型的swap性能。下一步需要扩展swap,使其可以针对matrix做特别处理。

具体的方法有两种。先看较传统的重载:

//代码#3

void swap(matrix& a, matrix& b) {

   a.swap(b);

}

另一种更摩登的方法是采用模板特化:

//代码#4

template<>

void swap(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()函数

在实际应用中,类似[Page]matrix的笨重的类型多如牛毛。最典型的就是STL里的容器,从vector到map都是这副德行。

对所有这些类型挨个儿重载或特化,绝对不是令人愉快的工作。何况也无法预测算法的使用者会搞出什么样古怪的类型来。而且,这种针对性的特化让泛型编程的优点荡然无存,代码重用也就无从谈起。

问题的关键在于泛化(特化)的粒度。在实际开发中,泛化和特化并非有你无我的绝对排斥关系。形象起见,用一个彩色木棍做比喻。想像有一根木棍,一头是红色的,另一头是蓝色的,木棍的颜色从红色渐渐过渡到蓝色。当然啦,中间的颜色应该是紫色。我们假设,蓝色表示泛化,红色表示特化。从红色那头开始,颜色越来越蓝,也就是越来越泛化。

在上面的swap()案例中,代码#1给出了完全泛化的模板(纯蓝色)。而代码#3和#4给出的则是针对一个具体类型的完全特化的版本(纯红色)。

然而,我们所面临的多数问题并非这种纯红/纯蓝模式所能解决的。我们需要的是紫色、红紫色,或者蓝紫色的模式。也就是中间粒度的泛化。具体而言,我们需要的是针对具备某种特性的类型,而不是所有类型或某个具体类型,的泛化和特化。

C++的模板局部特化机制,提供了稍细粒度的泛化(特化)能力。下面是几个典型的案例:

template class X {…};//一般情况下使用这个模板,除非…

template class X {…};//当类型参数是引用时,使用这个模板;

template class X {…};//当类型参数是指针时,使用这个模板;

[NextPage]

ctemplate class X {…};//当类型参数是形如R (P)                                                 //的函数时,使用这个模板

这种特化功能已经很强大了。但是,它并不能完全满足我们的要求。因为这种形式仅仅提供了针对类型大类的泛化(特化)功能,如指针、引用、函数、数组等等。但无法直接提供更细粒度的泛化和特化能力。

具体到swap()案例,我们需要这样一种特化:对所有拥有T::swap(T const&)成员函数的类型做特化。这种泛化需求在目前的C++中也只能通过一些复杂、机巧的手段获得:

struct true_ { static const bool result=true;};

struct false_ { static const bool result=false;};

template struct has_swap_mem : false_ {};

template<> struct has_swap_mem : public true_{};

template struct has_swap_mem > : public true_{};

template struct has_swap_mem > : public true_{};

template

struct do_swap

{

   void operator(T& a, T& b) {[Page]

       T t(a);

       a=b;

       b=t;

   }

}

template

struct do_swap

{

   void operator(T& a, T& b) {

       a.swap(b);

   }

}

template

void swap(T& a, T& b) {

   do_swap::result>()(a, b);

}

通过这种所谓“tagged dispatch”手法,我们可以手工地为每一个类型配备一个(或一组)traits类(结构),其中包含类型的特征描述。然后利用模板(局部)特化技术,以traits类的特征值进行编译时分派。在此基础上,我们可以进一步增加类型的特性描述,进一步细化swap的泛化粒度。比如,对于数组,必须使用循环,而无法直接用临时变量执行交换等等。

这种技术是有效的,但着实复杂。而且工作量巨大,因为需要为每个类型写traits类。但随着C++09中concept的引入,这些问题迎刃而解。

简单的讲,concept描述了一个类型在对外接口上的特征

[NextPage]

/1726031.aspx,及其参考文献)。比如,我们可以用这样一个concept描述“具备swap()成员函数的类型”:

auto concept has_swap {

   void T::swap(T&);

}

这样,swap()算法便可以写成:

//代码#5

template

void swap(T& a, T& b) {

   T t(a);

   a=b;

   b=t;

}

//代码#6

template

void swap(T& a, T& b) {

   a.swap(b);

}

就这么简单,无需更多的代码。编译器会忠实地履行类型匹配的职责。于是,下面的调用,会以皆大欢喜的方式执行:

int ia=10, ib=3;

matrix ma, mb;

swap(ia, ib);  //使用#5,用临时变量。

swap(ma, mb);  //使用#6,用swap成员。

这样已经相当理想了,但还是不够。C++09中,还将提供更进一步的模板约束细化手段:将多个concept联合使用:

template

   requires CopyAssignable

void swap(T& a, T& b) {…}

//或等价的

template

   requires CopyConstructible && CopyAssignable

void swap(T& a, T& b) {…}

甚至可以用 ! 操作符“去除”类型的某种特性:

//具备默认构造函数,但没有复制构造函数的类型:

requires DefaultConstructible && !CopyConstructible

通过不同concept间的&&组合和 && ! 组合,我们便可以很精确地描述一个类型的特征。反过来,当我们需要编写一个泛型算法或模板时,也可以很精确地用concept描述所需要的类型的特征。这种能力带来了两大好处,其一是强化了类型检测,消除了错误信息的问题,简化了学习和使用。[Page]

另一方面,便是为我们提供了非常自由的泛型粒度控制。在C++等现在语言中,类型已经不仅仅是一种数据构造的描述。类型具备了“行为”(通过成员函数和相关的自由函数)。在C++中,一个类型所具备的的“行为”便是这个类型的特征。而concept通过描述类型的成员函数和相关的自由函数,以确定某一类类型的“行为”,进而明确它们的特征。

当concept参与到重载和模板特化中,便使得我们可以针对不同特征的类型编写不同的实现代码,同时却又能维持统一的语义。比如,前面的swap()案例中,无论那种实现,临时变量,还是成员函数,

[NextPage]

swap()的语义完全相同,都是交换两个对象的内容。但不同的类型,则可以采用完全不同的方式实现。swap()的使用者无需关心应当使用swap_by_temp()还是swap_by_mem()。他们需要关心的仅仅是swap()的语义:把两个对象的内容互换,而无需知道实现。但却又能得到最高效,最恰当的算法。

这种相同语义,不同实现的模式,对于简化库的使用,优化库代码起着至关重要的作用。不仅仅在基础库,在应用库,甚至是日常的应用程序开发中,都能发挥巨大的作用。

对C++而言,其异于他人的抽象能力来源于对泛型编程的支持。泛型编程提供了面向一类问题编程的能力。当现实问题以类型的形式描述后,类型泛化便成为语言抽象能力的精髓。泛型编程的核心便是面向泛化的,而非具体的类型编程。

但在泛型编程中,简单地面向所有类型泛化,并没有太大价值。和尽管引入了泛型,但其应用范围也仅仅局限于提供容器而已。因为,它们只能泛化,而不能特化。对于泛化粒度的精确控制,是泛型编程实用化的关键。毕竟,大多数事物除了共性外,还存在着诸多特例。C++09中即将引入的concept便是将各种类型按其特性分类的有效工具。同时,更赋予我们针对不同类型分类编写程序的能力。届时,泛型编程将会进入真正的自由王国。

阅读(319) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~