C++中抽象类和接口类的区别

来源:互联网 发布:hp61墨盒清零软件 编辑:程序博客网 时间:2024/04/30 05:26

(这段问答源自:http://blog.sina.com.cn/s/blog_49652a2d0100fk3n.html)//这是大神的回答,有心情可以膜拜下,最后有总结。

Bill Venners:

我在1991至1996这5年间,几乎一直仅仅使用C++编程。在那时,我认为多重继承唯一目的就是让我能够从多个基类中继承它们各自的数据和函数 — 不管是虚拟函数还是非虚拟函数。那时候,我和我使用C++的同事几乎从未想过可以使用一种不含任何数据而仅包含纯虚函数的类,也就是现在Java中被称为接口的东西。最近您好像又越来越多地提起了抽象类这个概念,我想问问是不是最近在实验的过程中发现了一些我们以前未曾注意到的对纯接口类进行多重继承的好处,抑或是您认为我们以前对抽象类重视得不够?

Bjarne Stroustrup:

我在对人们解释这个问题的过程中遇到了很多问题,而且我也一直不能理解为什么让人们理解这个问题是如此困难。自C++出现那天起,就存在着包含数据成员的类和不包含数据成员的类。在过去,人们强调利用一个最基础的设施以及该设施内部的东西来构造软件系统,而那个“最基本的设施”通常就是抽象基类。从80年代中叶到80年代末,那些仅由虚拟函数组合而成的类通常都被称为ABCs(Abstract Base Classes 抽象基类)。1987年,我在C++中加入了纯虚函数的概念,一个纯虚函数必须被其派生类重写。借助此概念,你可以在一个C++类中通过将其成员函数声明为纯虚函数的方法表明该类是一个纯接口类。从那以后,我就一直强调在C++中,有一种主要的使用类的方法就是让该类不包含任何状态,而仅仅作为一个接口。

从C++的角度来看,一个抽象类和一个接口之间没有任何区别。有时,我们习惯使用“纯抽象类”这个词来表示某个类仅仅只含有纯虚函数(不包含任何数据成员),它是抽象类的最常见的形式。当我试图向人们解释这个概念时,我发现如果我不先向他们介绍纯虚函数这个语言中被直接支持的概念,人们就很难接受它。有些人仅仅因为可以在基类中放入一些数据成员,就觉得他们必须这样做。他们这样做,就等于构造了经典的不稳定基类,当然同时也就招致该结构所带来的一切问题。当我向人们介绍C++中直接支持抽象基类的概念时,情况稍微好一些,不过仍然有许多人不能理解它。我认为这是由于我自身的原因所造成的教育上的失败 — 我低估了做这件事的难度。这与早些时候Simula社团在理解新概念上的失败异常相似。有些新概念难以理解,部分原因在于许多人并不是真的想去学习一些全新的东西,他们自以为自己已经知道了答案。而一旦以为自己已经知道了答案,再去学一些新东西就会变得非常困难了。在1991年的《The C++ Programming Language》第二版中,有几个例子描述了抽象类的概念,可不幸的是,我并没有在全书从头至尾都贯穿这个思想。

Bill Venners:

使用纯抽象类有什么好处?什么时候我们应该使用纯抽象类而不是使用更为普遍的多重继承?

Bjarne Stroustrup:

最明显的例子就是“多接口、单实现”,这是一种很常见的情况。例如,你的系统也许既需要序列化功能,也需要迭代功能,那么这两个功能都可以接口的形式利用抽象类提供。然后,如果需要提供一个支持序列化的容器,你只需要让容器类继承序列化抽象类和迭代抽象类就可以了,而这种多重继承的形式已被Java和C#采纳。

另一种通常需要使用多重继承的情况是仅仅通过多重继承将手头的一些类组合起来。它们每一个都没有特别复杂的语义,将其组合起来完全是出于使用上的方便。当然,你也可以使用委托的模式来完成这个工作,也就是说,你可以在对象中容纳一个指向真正实现某些功能的对象指针。这种方法虽然也不错,但每当你在间接对象中添加一个新方法时,你都需要在自己的类中对应地增加一个新方法。这种做法真让人头痛,而且也没有直截了当地表示出原本的想法,维护起来则更是费时费力。最后一种情况是你需要从两个类中分别继承它们各自的状态。在这种情况下,当这两个类都非常复杂或它们的语义相互影响时,你很容易陷入混乱之中。然而你可以通过减少过度继承的方法尽量减少这种情况发生的次数,而当你不可避免地需要使用继承时,你可以通过尽量减少过度使用多重继承达到目的,而如果到了连多重继承都是非要不可的时候,那么你应该尽量回避那些复杂的变数。总的来说,在对一个具体问题建立一个模型时,你应该让该模型尽量简单,但不致于过分简单。

有些人经常会说他并不需要多重继承,因为所有多重继承能做的事情都能通过单继承完成,只是要使用我上面提到的那个名为“委托”的小技巧而已。更进一步,你也并不需要任何继承,因为所有单继承能够完成的事都可以通过类之间的转发完成。实际上,你根本不需要任何类,因为你完全可以利用指针和数据结构来达到目的。可为什么你会想要建立类呢?什么时候使用语言内建设施比较方便?什么时候你宁愿用一种绕弯的方法呢?我见过有很多场合多重继承甚至是非常复杂的多重继承发挥了重要作用。总体上来说,我更喜欢使用语言提供的功能来处理事情。

我们应对复杂情形的另外一种方法是利用模板进行组合。具体而言就是提供多个模板参数,而每个参数都是一个完全独立的类,它们都是你能够进行组合的抽象的具体实现。这些类每一个都是完全独立的,只有最后的派生类才与它们中的每一个存在依赖关系。有时候在一个模板内部根据继承关系进行组合是很便捷的,而有时则需另想办法(例如你可以将每一个单独的类作为一个数据成员存储或仅存储它们各自的指针)。这里有一个你有时需要从多个类中继承状态的例子:你有一个配置器对象,它知道如何处理关于内存的分配和销毁的问题,你也有一个存取器对象,只要你把内存地址给它,它就能处理关于内存存取的问题。现在,你准备将他们都用于你的一个项目实现中,就让我们假设是一个操作矩阵的复杂函数吧,此时你至少已经拥有了两个状态量,可是并没有带来那些对多重继承心存疑虑的人所担心的那些问题。基本上,你用一些非常简单的词汇就可以将运作的情况解释清楚。


总结:

首先,我们应该明白,接口和抽象类是面向对象程序设计中的两个重要概念,是思想层面的东西,不是语言层面的,因此虽然题目中说是c++中抽象类和接口的区别,实际上在其他oo语言也存在相同的思想。

实际上,C++中并没有明确的接口的定义,与之等价的是纯虚类,既只有纯虚函数的类,而c++中抽象类的概念是,包含至少一个纯虚函数的类。由于java只支持单继承,所以出现了interface的定义,从而用来模拟多继承。

可以这样理解,按抽象程度递增的顺序说就是:普通类->抽象类(java中由abstract修饰的类)->接口(java中interface修饰的类)。

参考《Effective C++》条款31和条款34:

c++中interface class通常不带成员变量,也没有构造函数,只有一个visual析构函数以及一组pure visual函数,用来叙述整个接口。虽然类似Java和.net的interface,但是C++的interface class并不需要复旦Java和.net的interface所需负担的责任。例如:Java和.net都不允许在interface内实现成员变量或成员函数,但是C++不禁止这两样东西。

这里顺带说明接口继承和实现继承:

所谓接口继承就是派生类只继承函数的接口,也就是声明。而实现继承就是派生类同时继承函数的接口和实现。

声明一个纯虚函数(pure visual)的目的就是为了让派生类只继承函数接口,即接口继承。

声明一个非纯虚函数(impure visual)的目的是为了让派生类继承函数接口和缺省实现。

声明一个非虚函数(non visual)的目的是为了让派生类继承函数接口和一份强制实现。

推荐一篇很不错的博文:抽象类与接口的区别。

0 0
原创粉丝点击