`
helloyesyes
  • 浏览: 1304285 次
  • 性别: Icon_minigender_2
  • 来自: 武汉
文章分类
社区版块
存档分类
最新评论

抽象之源

阅读更多

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

泛型编程可以算作最重要的C++特性。在“C With Class”时代,C++等抽象能力相比SmallTalk之类的OOP语言,并没什么过人之处。但在应用的刺激下(最早是io stream),C++引入模板,以实现参数化类型的需求,也就是“泛型”的功能。然而,C++革命性的进步得益于Alexander StepanovSTL上所做的贡献。

泛型提供了一种“泛化”编程手段。在泛型编程中,我们被赋予了一种面向一类问题,而非单个的具体问题,的编程能力。然而,仅仅“泛化”,还不足以提供充分的抽象能力。实际上,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里的容器,从vectormap都是这副德行。

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

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

在上面的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++09concept的引入,这些问题迎刃而解。

简单的讲,concept描述了一个类型在对外接口上的特征(关于concept的细节,可以看http://blog.csdn.net/pongba/archive/<chsdate w:st="on" isrocdate="False" islunardate="False" day="4" month="8" year="2007">2007/08/04</chsdate>/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);

}

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

int ia=10, ib=3;

matrix ma, mb;

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

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

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

template<CopyConstructible T>

requires CopyAssignable<T>

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

//或等价的

template<typename T>

requires CopyConstructible<T> && CopyAssignable<T>

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

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

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

requires DefaultConstructible<T> && !CopyConstructible<T>

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

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

concept参与到重载和模板特化中,便使得我们可以针对不同特征的类型编写不同的实现代码,同时却又能维持统一的语义。比如,前面的swap()案例中,无论那种实现,临时变量,还是成员函数,swap()的语义完全相同,都是交换两个对象的内容。但不同的类型,则可以采用完全不同的方式实现。swap()的使用者无需关心应当使用swap_by_temp()还是swap_by_mem()。他们需要关心的仅仅是swap()的语义:把两个对象的内容互换,而无需知道实现。但却又能得到最高效,最恰当的算法。

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

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

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

分享到:
评论

相关推荐

    C# winform 抽象工厂源代码DEMO 项目文件

    C# winform 抽象工厂源代码DEMO 项目文件 C# winform 抽象工厂源代码DEMO 项目文件 C# winform 抽象工厂源代码DEMO 项目文件 C# winform 抽象工厂源代码DEMO 项目文件

    简单抽象工厂源代码示例

    在“简单抽象工厂源代码示例”中,我们可能看到一个简单的工厂类,用于生成不同类型的对象,如学生管理中的`Student`对象。抽象工厂模式通常包括以下组件: 1. **抽象工厂接口**(AbstractFactory):这是核心接口...

    数据库访问抽象接口源代码

    数据库访问抽象接口源代码是一种设计模式,用于在不同的数据库管理系统(DBMS)之间提供统一的访问方式。在C语言中实现这样的接口,可以确保开发者无需关心底层数据库的具体实现,只需关注业务逻辑,从而提高代码的...

    数据结构之数据抽象和问题求解-C++源代码

    本资源包"数据结构之数据抽象和问题求解-C++源代码"提供了一套详细的学习材料,帮助我们理解和应用这些概念。 数据抽象是软件工程中的一个关键概念,它允许程序员创建与底层实现细节无关的高级数据表示。在数据结构...

    C++数据抽象和问题求解源代码

    《C++数据抽象和问题求解源代码》是第六版的配套编程资源,主要涵盖了C++编程语言在解决数据结构和算法问题时的应用。这份源代码集为学习者提供了实践和理解C++中数据抽象这一核心概念的机会。下面将详细讨论相关...

    接口和抽象类使用详细实例源代码

    依赖倒置原则(Dependency Inversion Principle,DIP)是面向对象设计的四大原则之一,它提倡高层模块不应该依赖于低层模块,两者都应该依赖于抽象。这样做的好处是提高了代码的可扩展性和可维护性,降低了耦合度。...

    三层架构抽象工厂示例(源代码)

    1. 源代码文件:展示了如何在C#中实现抽象工厂模式以及三层架构的具体应用。 2. 抽象工厂模式文档:详细解释了抽象工厂模式的概念、工作原理及其在三层架构中的作用。 3. 视频教程:可能是一个手动搭建三层架构的...

    抽象工厂模式 源代码

    抽象工厂之新解 虚拟案例 中国企业需要一项简单的财务计算:每月月底,财务人员要计算员工的工资。 员工的工资 = (基本工资 + 奖金 - 个人所得税)。这是一个放之四海皆准的运算法则。 为了简化系统,我们假设...

    抽象工厂模式代码

    抽象工厂模式是一种创建型设计模式,它提供了一个接口,用于创建相关或依赖对象的家族,而无需指定具体类。在Java编程中,这种模式经常被用来实现跨平台或者跨框架的代码,因为不同的平台或框架可能需要不同的实现,...

    accp抽象工厂示例源代码

    在标题提到的"acco5.0 S2 .net C#三层 抽象工厂示例源代码"中,我们可以推测这是一个基于.NET框架5.0(或更高版本)的C#项目,采用了三层架构(通常包括表示层、业务逻辑层和数据访问层),并且展示了抽象工厂模式的...

    用c++做的抽象工厂方法 程序源代码

    用抽象工厂做的程序,里面用到了简单的抽象类模式,做了一个工厂模式开发的c++版本程序!

    抽象工厂模式反射配置文件实现通用数据源

    抽象工厂模式反射配置文件实现通用数据源,抽象工厂模式反射配置文件实现通用数据源

    Java源代码:抽象类和接口

    在Java编程语言中,抽象类和接口是两种重要的面向对象设计概念,它们允许我们定义规范,为其他类提供模板或行为指南。让我们深入探讨这两个概念及其在Java中的应用。 首先,我们来理解抽象类。在Java中,抽象类是一...

    设计模式,抽象工厂模式 源代码

    抽象工厂模式是设计模式中的一种创建型模式...在压缩包`AbstractFactory`中,很可能包含了这些类的实现,通过阅读源代码,我们可以深入理解抽象工厂模式的实现细节,以及如何在实际项目中运用这个模式来解决具体问题。

    一种基于抽象语法树的C#源代码SQL注入漏洞检测算法.pdf

    《一种基于抽象语法树的C#源代码SQL注入漏洞检测算法》这篇学术论文探讨了如何利用抽象语法树(AST)技术来检测C#源代码中的SQL注入漏洞。SQL注入是网络安全领域的一个重大威胁,它允许攻击者通过输入恶意SQL语句来...

    Qt/C++抽象类和纯虚函数讲解示例源代码

    该资源是博主博客的源代码,博客上有详细讲解Qt/C++关于纯虚函数和抽象基类原理讲解和示例用法解释,博客地址如下: https://blog.csdn.net/naibozhuan3744/article/details/94488200 其中编译环境为QtCreator4.5.0...

    Springboot+mybatis多数据源整合 抽象基础类

    本教程将详细探讨如何在Spring Boot项目中集成MyBatis并实现多数据源的抽象基础类,以及如何利用ThreadLocal来实现数据源的动态切换。 首先,我们需要理解Spring Boot中的数据源配置。Spring Boot默认支持多种...

    Java实例化一个抽象类对象的方法教程

    通过注解处理器,可以在编译期间生成源代码,从而实现对抽象类的扩展或代理。例如,你可以定义一个注解`@MockApi`,然后在处理器中分析带有这个注解的类,生成一个新的实现类,这个新类将包含原始抽象类和接口的实现...

Global site tag (gtag.js) - Google Analytics