我的意思是,除了它的义务名称(标准模板库)......
C++最初的目的是将OOP概念呈现给C.即:您可以基于其类和类层次结构来判断特定实体可以做什么和不可行(不管它是如何做的).由于多重继承的问题,以及C++以某种笨拙的方式支持接口的概念(与java等相比),某些能力组合更难以这种方式描述,但它存在(并且可能是改善).
然后模板与STL一起发挥作用.STL似乎采用了经典的OOP概念,并使用模板代替了它们.
在使用模板来概括类型的情况之间应该有区别,其中类型自身与模板的操作无关(例如,容器).有一个vector
完美的感觉.
但是,在许多其他情况下(迭代器和算法),模板化类型应该遵循"概念"(输入迭代器,前向迭代器等等),其中概念的实际细节完全由模板的实现定义函数/类,而不是与模板一起使用的类的类,这是对OOP的一种反对使用.
例如,您可以告诉函数:
void MyFunc(ForwardIterator<...> *I);
更新:由于在原始问题中不清楚,因此ForwardIterator可以自行模板化以允许任何ForwardIterator类型.相反,将ForwardIterator作为一个概念.
只需通过查看其定义来预期转发迭代器,您需要查看实现或文档:
templatevoid MyFunc(Type *I);
我可以支持使用模板的两个声明:通过为每个使用的类型定制编译模板,而不是使用vtable,可以提高编译代码的效率.并且模板可以与本机类型一起使用.
但是,我正在寻找一个更为深刻的理由,为什么放弃传统的OOP而不是模仿STL呢?(假设你读到那么远:P)
简短的回答是"因为C++已经开始了".是的,早在70年代末期,Stroustrup打算创建一个具有OOP功能的升级版C,但这是很久以前的事了.到1998年语言标准化时,它已不再是OOP语言.这是一种多范式的语言.它当然对OOP代码有一些支持,但它也覆盖了图灵完整的模板语言,它允许编译时元编程,并且人们已经发现了泛型编程.突然间,OOP似乎并不那么重要.通过使用模板和通用编程可用的技术,我们无法编写更简单,更简洁,更高效的代码.
OOP不是圣杯.这是一个可爱的想法,它在70年代被发明时比程序语言有了很大的改进.但老实说,并不是所有它都被破解了.在许多情况下,它是笨拙和冗长的,并没有真正促进可重用的代码或模块化.
这就是为什么C++社区今天对通用编程更感兴趣,以及为什么每个人终于开始意识到函数式编程也非常聪明.OOP本身就不是一个美丽的景象.
尝试绘制假设的"OOP-ified"STL的依赖图.有多少班级需要了解彼此?会有很多依赖.你是否能够只包括vector
标题,而不是iterator
甚至没有进入iostream
?STL使这很容易.向量知道它定义的迭代器类型,这就是全部.STL算法一无所知.它们甚至不需要包含迭代器头,即使它们都接受迭代器作为参数.哪个更模块化呢?
STL可能不遵循OOP的规则,因为Java定义它,但它不能实现OOP 的目标吗?它不能实现可重用性,低耦合,模块化和封装吗?
它不是比OOP-ified版本更好地实现这些目标吗?
至于为什么STL被采用到语言中,发生了一些导致STL的事情.
首先,模板被添加到C++中.添加它们的原因与将泛型添加到.NET的原因大致相同.能够在不丢弃类型安全的情况下编写诸如"T型容器"之类的东西似乎是一个好主意.当然,他们确定的实施是非常复杂和强大的.
然后人们发现他们添加的模板机制比预期的更强大.有人开始尝试使用模板来编写更通用的库.一个受功能编程启发,一个使用C++的所有新功能.
他把它呈现给了C++语言委员会,他们花了很长时间才习惯它,因为它看起来很奇怪和不同,但最终意识到它比传统的OOP等价物更好用.所以他们对它进行了一些调整,并将其应用到标准库中.
这不是一种意识形态的选择,它不是"我们想成为OOP还是不是OOP"的政治选择,而是一种非常务实的选择.他们对图书馆进行了评估,发现它运作良好.
无论如何,你提到赞成STL的两个原因都是绝对必要的.
C++标准库具有是有效的.如果效率低于等效的手动C代码,那么人们就不会使用它.这会降低生产力,增加错误的可能性,总体而言只是一个坏主意.
并且STL 必须使用原始类型,因为原始类型是C中的所有类型,并且它们是两种语言的主要部分.如果STL不能与本机数组一起使用,那将毫无用处.
你的问题有一个强烈的假设,即OOP是"最好的".我很想知道为什么.你问为什么他们"放弃了古典OOP".我想知道为什么他们应该坚持下去.它有哪些优势?
我认为你在问/抱怨的最直接的答案是:C++是一种OOP语言的假设是错误的假设.
C++是一种多范式语言.它可以使用OOP原理进行编程,可以通过程序进行编程,可以进行一般编程(模板),使用C++ 11(以前称为C++ 0x),甚至可以对某些功能进行编程.
C++的设计者认为这是一个优势,所以他们会争辩说,当泛型编程更好地解决问题时,将C++限制为纯粹的OOP语言,而且更通用的是,它将向后退一步.
我的理解是Stroustrup最初更喜欢"OOP风格"的容器设计,事实上没有其他任何方法可以做到这一点.Alexander Stepanov是负责STL的人,他的目标不包括"使其面向对象":
这是基本点:算法是在代数结构上定义的.我花了两年时间才意识到你必须通过在常规公理中增加复杂性要求来扩展结构的概念....我认为迭代理论是计算机科学的核心,因为环或Banach空间的理论是数学的核心.每次我查看算法时,我都会尝试找到一个定义它的结构.所以我想要做的就是一般地描述算法.这就是我喜欢做的事情.我可以花一个月时间研究一个众所周知的算法,试图找到它的通用表示....
STL,至少对我来说,是编程可能的唯一方式.实际上,它与C++编程完全不同,因为它被呈现并且仍然在大多数教科书中呈现.但是,你看,我并没有尝试用C++编程,我试图找到正确的方法来处理软件....
我有很多错误的开始.例如,在我理解为什么这种机制存在根本缺陷并且不应该被使用之前,我花了数年时间试图找到继承和虚拟的一些用途.我很高兴没有人能看到所有的中间步骤 - 其中大多数都非常愚蠢.
(他确实解释了为什么继承和虚拟 - 也就是面向对象的设计"从根本上是有缺陷的,不应该在接下来的采访中使用").
一旦Stepanov将他的图书馆呈现给Stroustrup,Stroustrup和其他人就经历了艰难的努力,将其纳入ISO C++标准(同一次采访):
Bjarne Stroustrup的支持至关重要.Bjarne真的希望STL成为标准,如果Bjarne想要什么,他就会得到它.......他甚至强迫我改变STL,这是我永远不会为其他人做的......他是我认识的最单一的人.他把事情做好了.他花了一段时间才明白STL究竟是什么,但当他这样做时,他已经准备好推动它了.他还为STL做出了贡献,他认为不止一种编程方式是有效的 - 反对十多年的高手和炒作,并追求灵活性,效率,重载和类型安全的结合.使STL成为可能的模板.我想非常清楚地说明Bjarne是我这一代卓越的语言设计师.
答案可以在STL的作者Stepanov的采访中找到:
是.STL不是面向对象的.我认为面向对象几乎与人工智能一样是一种骗局.我还没有看到来自这些OO人员的一段有趣的代码.
为什么纯数据结构和算法库的OOP设计会更好?OOP并不是解决所有问题的方法.
恕我直言,STL是我见过的最优雅的图书馆:)
对于你的问题,
你不需要运行时多态性,STL实际上使用静态多态实现库是一个优势,这意味着效率.尝试编写一个通用的Sort或Distance或者适用于所有容器的算法!您的Sort in Java会调用通过n级动态执行的函数!
你需要像Boxing和Unboxing这样的愚蠢的东西来隐藏所谓的纯OOP语言的令人讨厌的假设.
我在STL中看到的唯一问题是,模板通常是可怕的错误消息.这将使用C++ 0X中的Concepts来解决.
将STL与Java中的集合进行比较就像将Taj Mahal与我的房子进行比较:)
模板化类型应该遵循"概念"(输入迭代器,前向迭代器等等),其中概念的实际细节完全由模板函数/类的实现定义,而不是由类型的类定义与模板一起使用,这是对OOP的一种反对使用.
我认为你误解了模板对概念的预期用途.例如,Forward Iterator是一个非常明确的概念.要查找必须有效的表达式以使类成为正向迭代器,以及它们的语义(包括计算复杂性),请查看标准或http://www.sgi.com/tech/stl/ForwardIterator.html(你必须按照Input,Output和Trivial Iterator的链接来查看全部内容).
该文档是一个非常好的界面,"概念的实际细节"就在那里定义.它们不是由前向迭代器的实现定义的,也不是由使用前向迭代器的算法定义的.
STL和Java之间处理接口的方式有三个方面:
1)STL使用对象定义有效表达式,而Java定义必须在对象上可调用的方法.当然,有效的表达式可能是方法(成员函数)调用,但它不一定是.
2)Java接口是运行时对象,而STL概念在运行时即使使用RTTI也不可见.
3)如果未能使STL概念的所需有效表达式生效,则在使用该类型实例化某个模板时会出现未指定的编译错误.如果未能实现Java接口的必需方法,则会出现特定的编译错误.
第三部分是如果你喜欢一种(编译时)"鸭子打字":接口可以是隐式的.在Java中,接口有点明确:当且仅当它表示它实现了Iterable时,类"is"是Iterable.编译器可以检查其方法的签名是否全部存在且正确,但语义仍然是隐式的(即它们是否有文档记录,但只有更多代码(单元测试)可以告诉您实现是否正确).
在C++中,就像在Python中一样,语义和语法都是隐式的,尽管在C++中(如果你得到强类型的预处理器,在Python中)你会得到编译器的一些帮助.如果程序员需要通过实现类进行类似Java的接口声明,那么标准方法是使用类型特征(并且多重继承可以防止这种过于冗长).与Java相比,缺少的是一个单独的模板,我可以用我的类型实例化,当且仅当所有必需的表达式对我的类型有效时才会编译.这会告诉我是否已经实现了所有必需的位,"在我使用之前".这是一种方便,但它不是OOP的核心(它仍然不测试语义,测试语义的代码自然也会测试有问题的表达式的有效性).
STL可能或可能不足以满足您的口味,但它确实将界面与实现完全分开.它确实缺乏Java对接口进行反射的能力,并且它以不同方式报告违反接口要求的情况.
你可以告诉函数...只需通过查看它的定义来预期转发迭代器,你需要查看实现或文档...
我个人认为,如果使用得当,隐式类型是一种强度.该算法说明了它对模板参数的作用,实现者确保这些工作正常:它正是"接口"应该做的共同点.此外,对于STL,您不太可能使用,例如,std::copy
基于在头文件中查找其前向声明.程序员应该根据其文档来计算函数所需的内容,而不仅仅是函数签名.在C++,Python或Java中都是如此.在任何语言中键入都可以实现什么限制,并且尝试使用键入来执行它不执行的操作(检查语义)将是一个错误.
也就是说,STL算法通常以一种方式命名它们的模板参数,这样就可以清楚地知道需要什么概念.然而,这是为了在文档的第一行提供有用的额外信息,而不是使前向声明更具信息性.您需要知道的事项多于可以封装在参数类型中的内容,因此您必须阅读文档.(例如,在采用输入范围和输出迭代器的算法中,输出迭代器可能需要基于输入范围的大小以及其中的值来为一定数量的输出提供足够的"空间".尝试强类型化. )
这是关于明确声明的接口的Bjarne:http://www.artima.com/cppsource/cpp0xP.html
在泛型中,参数必须是从泛型定义中指定的接口(C++等效于接口是抽象类)派生的类.这意味着所有通用参数类型必须适合层次结构.这对设计施加了不必要的限制,这需要开发人员不合理的远见.例如,如果你编写一个泛型并且我定义了一个类,那么人们就不能使用我的类作为你的泛型的参数,除非我知道你指定的接口并从中派生了我的类.这很刻板.
从另一个角度来看,使用duck typing你可以在不知道接口存在的情况下实现接口.或者有人可以故意编写一个界面,以便你的课程实现它,咨询你的文档,看他们没有要求你还没有做任何事情.这很灵活.
"OOP对我来说只意味着消息传递,本地保留和保护以及状态进程的隐藏,以及所有事物的极端后期绑定.它可以在Smalltalk和LISP中完成.可能还有其他系统可以实现,但是我不知道他们." - Smalltalk的创始人Alan Kay.
C++,Java和大多数其他语言都远非经典的OOP.也就是说,争论意识形态并不是非常有成效.C++在任何意义上都不是纯粹的,所以它实现的功能似乎在当时具有实用意义.
STL开始的目的是提供一个涵盖最常用算法的大型库 - 具有良好行为和性能的目标.模板是使实现和目标可行的关键因素.
只是提供另一个参考:
Al Stevens采访Alex Stepanov,1995年3月DDJ:
http://www.sgi.com/tech/stl/drdobbs-interview.html
Stepanov解释了他的工作经验和对大型算法库的选择,最终演变为STL.
告诉我们您对通用编程的长期兴趣
.....然后,我在贝尔实验室获得了一份工作在C++库的C++小组工作.他们问我是否可以用C++来做.当然,我不知道C++,当然,我说我可以.但我不能用C++来做,因为在1987年,C++没有模板,这对于实现这种编程风格至关重要.继承是获得通用性的唯一机制,但还不够.
即使是现在,C++继承对泛型编程也没有多大用处.我们来讨论一下原因.许多人试图使用继承来实现数据结构和容器类.正如我们现在所知,成功的尝试几乎没有.C++继承以及与之相关的编程风格受到极大限制.实现一种包含平等使用它的设计是不可能的.如果从层次结构的根开始使用基类X,并在此类上定义一个虚拟相等运算符,该运算符采用类型X的参数,则从类X派生类Y.相等的接口是什么?它具有将Y与X进行比较的平等性.以动物为例(OO人喜欢动物),定义哺乳动物并从哺乳动物中获取长颈鹿.然后定义成员函数配偶,其中动物与动物交配并返回动物.然后你从动物中获取长颈鹿,当然,它有一个功能伴侣,长颈鹿与动物交配并返回动物.这绝对不是你想要的.虽然交配对C++程序员来说可能不是很重要,但是平等是.我不知道一种算法,其中没有使用某种类型的相等性.
基本问题
void MyFunc(ForwardIterator *I);
你如何安全地获得迭代器返回的东西的类型?使用模板,这是在编译时为您完成的.