为什么C ++ STL如此大量地基于模板?(而不是在* interfaces *上)


211

我的意思是,除了它的专有名称(标准模板库)之外...

C ++最初打算将OOP概念呈现到C中。也就是说,您可以根据其类和类层次结构来判断特定实体可以做什么和不可以做什么(无论它如何执行)。由于多重继承的问题,某些能力组合很难用这种方式描述,而且C ++以某种笨拙的方式(与Java等相比)支持接口的概念,但是它确实存在(并且可能是改进)。

然后,模板与STL一起发挥作用。STL似乎采用了经典的OOP概念,并使用模板来冲洗它们。

在使用模板来泛化类型的情况之间应该有所区别,其中类型主题与模板的操作无关(例如,容器)。有一个vector<int>完美的意义。

但是,在许多其他情况下(迭代器和算法),模板化类型应遵循“概念”(输入迭代器,正向迭代器等),其中概念的实际细节完全由模板的实现定义函数/类,而不是模板使用的类型的类,这在某种程度上是对OOP的反使用。

例如,您可以告诉函数:

void MyFunc(ForwardIterator<...> *I);

更新:由于原始问题尚不清楚,因此可以将ForwardIterator本身进行模板化以允许任何ForwardIterator类型。相反,将ForwardIterator作为概念。

仅通过查看其定义就可以期待一个正向迭代器,您需要在其中查看实现或以下文档:

template <typename Type> void MyFunc(Type *I);

我可以支持使用模板的两个主张:通过为每种使用的类型量身定制模板,而不是使用vtable,可以使编译后的代码更高效。模板可以与本机类型一起使用的事实。

但是,我正在寻找一个更深层的原因,为什么放弃传统的OOP来支持使用STL?(假设您读到了这么远:P)


4
您可以查看stackoverflow.com/questions/31693/…。可接受的答案很好地解释了哪些模板为您提供了泛型。
James McMahon

6
@乔纳斯:这没有道理。对高速缓存的约束花费了时钟周期,这就是为什么它很重要。归根结底,是时钟周期而不是缓存定义了性能。内存和高速缓存仅在影响所花费的时钟周期方面才是重要的。此外,该实验可以轻松完成。比较说一下,用等效的OOP / vtable方法用函子参数调用std :: for_Each。性能上的差异是惊人的。这就是为什么使用模板版本的原因。
jalf

7
而且没有理由为什么冗余代码会填满icache。如果我在程序中实例化vector <char>和vector <int>,为什么在处理vector <int>时将vector <char>代码加载到icache中?实际上,对vector <int>的代码进行了修整,因为它不必包含用于强制转换,vtable和间接寻址的代码。
jalf

3
亚历克斯·斯蒂芬诺夫(Alex Stepanov)解释了为什么继承与平等不能很好地融合在一起。
fredoverflow

6
@BerndJendrissek:嗯,亲爱的,但没有你自己。是的,如果实际使用过,则在内存带宽和缓存使用方面会增加代码成本。但是没有特别的理由期望同时使用a vector<int>vector<char>。他们可能会肯定,但是您可能同时使用任何两段代码。这与模板,C ++或STL无关。实例化中没有vector<int>要求vector<char>加载或执行代码的内容。
杰夫

Answers:


607

简短的答案是“因为C ++已经发展”。是的,早在70年代末,Stroustrup打算创建具有OOP功能的升级C,但这是很久以前的事情了。到1998年该语言标准化时,它不再是一种OOP语言。这是一种多范式语言。它当然对OOP代码提供了一些支持,但是它也覆盖了图林完整的模板语言,它允许编译时元编程,并且人们已经发现了通用编程。突然,OOP似乎并没有那么重要。不是时候我们可以使用模板和通用编程提供的技术来编写更简单,更简洁更高效的代码。

OOP不是圣杯。这是一个可爱的主意,与70年代的程序语言相比,它是一个很大的改进。坦白说,这并不是全部。在许多情况下,它笨拙且冗长,并且实际上并没有促进可重用的代码或模块化。

这就是为什么C ++社区今天对通用编程更感兴趣的原因,以及为什么 每个人都终于开始意识到函数式编程也非常聪明的原因。单独的OOP并不是很漂亮。

尝试绘制一个假设的“ OOP定义” STL的依赖图。彼此必须了解多少个班级?会有很多依赖。您是否可以仅包含vector标题,而不必iterator甚至获取iostream?STL使这变得容易。向量知道它定义的迭代器类型,仅此而已。STL算法一无所知。即使它们都接受迭代器作为参数,它们甚至不需要包括迭代器标头。那么哪个更模块化?

STL可能不遵循Java定义的OOP规则,但不能实现SOP。 OOP目标吗?它不能实现可重用性,低耦合,模块化和封装性吗?

而且不是实现这些目标 比OOP认证的版本更好地

至于为什么将STL引入语言,发生了几件事导致了STL。

首先,将模板添加到C ++。添加它们的原因与将泛型添加到.NET的原因大致相同。能够写“ T型容器”之类的东西而不丢掉类型安全性似乎是一个好主意。当然,他们选择的实现方式要复杂得多且功能强大。

然后人们发现他们添加的模板机制比预期的还要强大。有人开始尝试使用模板编写更通用的库。一种是受函数式编程启发的,另一种是使用C ++的所有新功能的。

他把它介绍给了C ++语言委员会,该委员会花了相当长的时间来适应它,因为它看起来是如此奇怪和不同,但最终意识到 它的效果比其他方面必须包括的传统OOP等效效果更好。因此,他们对其进行了一些调整,并将其纳入标准库。

这不是一个意识形态的选择,也不是“我们是否想成为非政府组织”的政治选择,而是一个非常务实的选择。他们对图书馆进行了评估,发现它运行良好。

无论如何,您提到支持STL的两个原因都是绝对必要的。

C ++标准库具有是有效的。如果它的效率不如等效的手动C代码,那么人们将不会使用它。这将降低生产率,增加发生错误的可能性,并且总体而言是一个坏主意。

STL 必须使用基本类型,因为基本类型是您在C语言中拥有的全部,并且它们是这两种语言的重要组成部分。如果STL不适用于本机阵列,那将是无用的

您的问题有一个强有力的假设,即OOP是“最佳”的。我很想知道为什么。您问为什么他们“放弃了经典的OOP”。我想知道为什么他们应该坚持下去。它会有哪些优势?


22
这是一个很好的文章,但我想强调一个细节。STL不是C ++的“产品”。实际上,作为一个概念,STL在C ++之前就已经存在,而C ++恰好是一种高效的语言,具有(几乎)足够的能力进行通用编程,因此STL是用C ++编写的。
Igor Krivokon 09年

17
由于评论不断提出,是的,我知道STL名称不明确。但是我想不出“在STL上建模的C ++标准库的一部分”的更好名称。即使严格不正确,标准库那部分的实际名称只是“ STL”。:)只要人们不使用STL作为整个标准库的名称(包括IOStreams和C stdlib标头),我就会很高兴。:)
jalf

5
@einpoklum您将从抽象基类中确切地得到什么?以std::set作为一个例子。它不继承自抽象基类。如何限制您的使用std::setstd::set因为a 不能从抽象基类继承,您有什么不能做的?
fredoverflow

22
@einpoklum请看一下Smalltalk语言,Al​​an Kay在发明术语OOP时将其设计为OOP语言。它没有接口。OOP与接口或抽象基类无关。您是否要说“ Java,它与C ++相比,更像是OOP的发明者所想到的,而C ++ 与C ++一样,与C ++ 的发明者相比,更像是OOP”?您的意思是说“ C ++不足以满足我的口味”。那是公平的,但与OOP 无关
2014年

8
@MasonWheeler如果这个答案是一堆公然的废话,那么您将不会看到全世界数百名开发人员对此投票+1,只有3个人这样做
panda-34

88

对于我认为您要提出的问题,最直接的答案是:C ++是一种OOP语言是一种错误的假设。

C ++是一种多范式语言。可以使用OOP原理进行编程,可以进行程序编程,可以进行通用编程(模板),而使用C ++ 11(以前称为C ++ 0x)甚至可以对某些东西进行功能上的编程。

C ++的设计者将其视为一种优势,因此他们认为,当通用编程更好地解决问题时,将C ++约束为纯粹的OOP语言就可以了,而且从更一般的角度来讲,这将是倒退的一步。


4
“使用C ++ 0x甚至可以对某些东西进行功能编程”-可以在没有这些功能的情况下进行功能编程,只是更加冗长。
乔纳斯·科尔克(JonasKölker)2009年

3
@Tyler的确,如果将C ++限制为纯OOP,则将剩下Objective-C。
Justicle

@TylerMcHenry:刚刚问了这个,我发现我已经说出了与您相同的答案!只有一点。我希望您会补充一个事实,即标准库不能用于编写面向对象的代码。
einpoklum 2013年

74

我的理解是Stroustrup最初更喜欢“ OOP风格”的容器设计,但实际上并没有发现其他方法。Alexander Stepanov是负责STL的人,他的目标不包括“使其面向对象”

这就是基本点:算法是在代数结构上定义的。我花了两年的时间才意识到,您必须通过在常规公理中增加复杂性要求来扩展结构的概念。...我相信迭代器理论对于计算机科学同样重要,就像环或Banach空间理论对于数学一样重要。每次查看算法时,我都会尝试查找定义该算法的结构。所以我想做的是一般性地描述算法。那就是我喜欢做的。我可以花一个月的时间来研究一种著名的算法,试图找到其通用表示形式。...

至少对我来说,STL代表了唯一可行的编程方式。确实,它与C ++编程完全不同,因为它在大多数教科书中都已被展示。但是,您知道,我不是在尝试使用C ++编程,而是在尝试找到正确的方法来处理软件。...

我有很多错误的开始。例如,在了解为什么该机制从根本上存在缺陷并且不应该使用该机制之前,我花了多年的时间试图为继承和虚拟化找到某种用途。我很高兴没有人看到所有中间步骤-其中大多数都很傻。

(他的确解释了为什么在继承和虚拟中,继承和虚拟化(又名面向对象设计)在其余的采访中“从根本上存在缺陷,不应使用”)。

在Stepanov将自己的库提交给Stroustrup之后,Stroustrup和其他人经过艰苦的努力将其纳入ISO C ++标准(相同的采访):

Bjarne Stroustrup的支持至关重要。Bjarne确实想要标准中的STL,如果Bjarne想要一些东西,他就会明白。...他甚至强迫我对STL进行更改,这是我永远不会为别人做的...他是我认识的最单心的人。他把事情做好了。他花了一段时间才了解STL的全部含义,但是当他这样做时,他准备将其推进。他还为STL做出了贡献,他坚持认为一种以上的编程方法是有效的-十多年来一直没有断断续续的炒作,并且在开发过程中追求灵活性,效率,重载和类型安全的结合使STL成为可能的模板。我想非常清楚地指出Bjarne是我们这一代的杰出语言设计师。


2
有趣的采访。可以肯定的是,我之前已经读过它,但是绝对值得再次读一遍。:)
杰夫

3
我读过的有关编程的最有趣的采访之一。尽管它让我
渴望

他对Java之类的语言提出了很多抱怨(“您不能在Java中编写一个通用的max(),它接受某种类型的两个参数并且具有相同类型的返回值”)仅与非常早期的版本有关语言,在添加泛型之前。即使从一开始,就知道最终会添加泛型,尽管(一旦找到了可行的语法/语义),所以他的批评在很大程度上是毫无根据的。是的,需要某种形式的泛型来维护静态类型语言的类型安全性,但是不是,这不会使OO一文不值。
盖伊

1
@SomeGuy他们并不是对Java本身的抱怨。他在谈论“ SmallTalk或Java的“标准” OO编程 ”。采访来自90年代后期(他提到他曾在SGI工作,他于2000年离开,在AT&T工作)。泛型仅在2004年的1.5版中添加到Java中,它们与“标准” OO模型有所不同。
melpomene

24

在对STL的作者Stepanov的采访中可以找到答案:

是。STL不是面向对象的。我认为面向对象几乎与人工智能一样是个骗局。我还没有看到来自这些面向对象的人的有趣代码。


漂亮的宝石;你知道那是哪一年吗?
2014年

2
@kos,根据web.archive.org/web/20000607205939/http://www.stlport.org/…链接页面的第一个版本是2001年6月7日发布的。页面底部的页面本身显示的是Copyright 2001- 2008。
alfC

@Kos Stepanov在第一个答案中提到在SGI工作。他于2000年5月离开SGI,所以大概采访时间比那年长。
melpomene

18

为什么对数据结构和算法库进行纯OOP设计会更好?OOP并不是万能的解决方案。

恕我直言,STL是我见过的最优雅的库:)

你的问题,

您不需要运行时多态性,对于STL而言,实际上使用静态多态性实现库是一个优势,这意味着效率。尝试编写通用的Sort或Distance或适用于所有容器的算法!您的Java排序将调用要通过n级动态执行的函数!

您需要装箱和拆箱之类的笨拙的东西来隐藏所谓的“纯OOP”语言的讨厌假设。

我在STL和模板上看到的唯一问题是可怕的错误消息。可以使用C ++ 0X中的Concepts解决。

将STL与Java中的Collections进行比较就像将Taj Mahal与我的房子进行比较:)


12
什么,泰姬玛哈陵小而优雅,而你的房子只有一座山那么大,一团糟?;)
杰夫

概念不再是c ++ 0x的一部分。某些错误消息可以使用来static_assert代替。
KitsuneYMG 2010年

GCC 4.6改进了模板错误消息,我认为4.7+更好。
大卫·斯通

概念本质上是OP所要求的“接口”。唯一的区别是,来自Concept的“继承”是隐式的(如果类具有所有正确的成员函数,则它自动是Concept的子类型)而不是显式的(Java类必须显式声明其实现了接口) 。但是,隐式和显式子类型都是有效的OO,并且某些OO语言具有隐式继承,其作用与Concepts相同。因此,这里所说的基本上是“ OO很烂:使用模板。但是模板有问题,因此请使用Concepts(OO)”。
盖伊

11

模板化类型应该遵循“概念”(输入迭代器,正向迭代器等),其中概念的实际细节完全由模板功能/类的实现定义,而不是由类型的类定义与模板一起使用,这是对OOP的反使用。

我认为您误解了模板对概念的预期用途。例如,前向迭代器是一个定义非常明确的概念。为了找到使类成为正向迭代器必须有效的表达式,以及它们的语义(包括计算复杂性),请查看标准或http://www.sgi.com/tech/stl/ForwardIterator.html。(您必须单击输入,输出和简单迭代器的链接才能看到全部内容)。

该文档是一个非常好的界面,并且在此处定义了“概念的实际细节”。它们不是由转发迭代器的实现定义的,也不是由使用转发迭代器的算法定义的。

STL和Java之间的接口处理方式有三点不同:

1)STL使用对象定义有效的表达式,而Java定义必须在对象上可调用的方法。当然,有效的表达式可能是方法(成员函数)的调用,但并非必须如此。

2)Java接口是运行时对象,而即使使用RTTI,STL概念在运行时也不可见。

3)如果无法使STL概念所需的有效表达式有效,则在实例化具有该类型的模板时会出现未指定的编译错误。如果您无法实现Java接口的必需方法,则会收到一个特定的编译错误,说明是这样。

第三部分是,如果您喜欢一种(编译时)“鸭子类型”:接口可以是隐式的。在Java中,接口是有些明确的:一类“是”可迭代,如果,如果它只是它实现了可迭代。编译器可以检查其方法的签名是否全部存在并且正确,但是语义仍然是隐式的(即,是否已进行了文档记录,但是只有更多代码(单元测试)可以告诉您实现是否正确)。

在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 ++是抽象类)。这意味着所有通用参数类型都必须适合层次结构。这对设计施加了不必要的约束,要求开发人员进行不合理的预见。例如,如果您编写一个泛型并且定义了一个类,那么除非我知道您指定的接口并从中派生我的类,否则其他人不能将我的类用作您的泛型的参数。太死板了

从另一角度来看,使用鸭子类型可以实现一个接口,而无需知道该接口是否存在。或者有人可以在咨询了您的文档以确保他们不要求您尚未做的事情之后,故意编写一个使您的类实现该接口的接口。那很灵活。


在显式声明的接口上,有两个词:类型类。(这已经是Stepanov“概念”的意思了。)
pyon

“如果无法使STL概念所需的有效表达式有效,则在实例化具有该类型的模板时会出现未指定的编译错误。” -这是错误的。std将与概念不匹配的内容传递给库通常是“格式错误,不需要诊断”。
Yakk-Adam Nevraumont

没错,我在玩“有效”一词时过于随意。我只是说,如果编译器无法编译所需的表达式之一,那么它将报告某些内容。
史蒂夫·杰索普

8

“对我来说,面向对象操作仅意味着消息传递,状态过程的本地保留和保护以及隐藏,以及万物的极端后期绑定。这可以在Smalltalk和LISP中完成。可能还有其他系统可以实现,但是我不知道他们。” -Smalltalk的作者Alan Kay。

C ++,Java和大多数其他语言与经典的OOP都相去甚远。话虽如此,对意识形态的争论并不是非常有效。C ++在任何意义上都不是纯粹的,因此它实现的功能在当时似乎很实用。


7

STL最初的目的是提供一个涵盖最常用算法的大型库-目标是提供可靠的行为和性能。模板是使该实现和目标可行的关键因素。

只是提供另一个参考:

艾尔·史蒂文斯(Al Stevens)在1995年3月采访DDJ的Alex Stepanov:

Stepanov解释了他对大型算法库的工作经验和选择,该库最终演变为STL。

告诉我们您对通用编程的长期兴趣

.....然后,我在Bell实验室工作,并在C ++库的C ++小组工作。他们问我是否可以用C ++做到这一点。当然,我不了解C ++,当然,我说可以。但是我不能用C ++做到这一点,因为1987年C ++没有模板,这对于启用这种编程风格至关重要。继承是获得通用性的唯一机制,但这还不够。

即使现在,C ++继承对于通用编程也没有多大用处。让我们讨论为什么。许多人试图使用继承来实现数据结构和容器类。众所周知,几乎没有成功的尝试。C ++继承以及与此相关的编程风格受到极大限制。不可能实现一个包含使用平等性之类的琐事的设计。如果从层次结构的根基类X开始,并在该类上定义一个虚拟相等运算符,该运算符接受类型X的参数,则从类X派生类Y。相等的接口是什么?它具有将Y与X进行比较的相等性。以动物为例(OO人喜欢动物),定义哺乳动物并从哺乳动物中衍生出长颈鹿。然后定义一个成员函数配合,动物与动物交配并归还动物的地方。然后,您从动物衍生出长颈鹿,当然,它具有一个功能配对,其中长颈鹿与动物配对并返回动物。绝对不是您想要的。尽管交配对C ++程序员可能不是很重要,但平等是最重要的。我不知道不使用某种相等性的单一算法。


5

基本问题

void MyFunc(ForwardIterator *I);

如何安全地获得迭代器返回的事物的类型?使用模板,这是在编译时为您完成的。


1
好吧,我要么:1.不要试图得到它,因为我正在编写通用代码。或者,2.使用C ++目前提供的任何反射机制来获取它。
einpoklum 2013年

2

暂时,让我们将标准库视为基本上是集合和算法的数据库。

如果您研究过数据库的历史,那么毫无疑问,您从一开始就知道数据库大多是“分层的”。分层数据库与经典的OOP非常接近,特别是单继承性,例如Smalltalk所使用的。

随着时间的流逝,显然可以使用分层数据库来建模几乎所有东西,但是在某些情况下,单继承模型是相当有限的。如果您有一扇木门,可以方便地将其视为门或某些原材料(钢,木材等)的一部分

因此,他们发明了网络模型数据库。网络模型数据库与多重继承非常接近。C ++完全支持多重继承,而Java支持有限形式(您只能从一个类继承,但也可以根据需要实现尽可能多的接口)。

分层模型数据库和网络模型数据库都已从通用用途中逐渐淡出(尽管有一些仍保留在相当特定的领域中)。在大多数情况下,它们已被关系数据库取代。

关系数据库接管的大部分原因是多功能性。关系模型在功能上是网络模型的超集(又是分层模型的超集)。

C ++在很大程度上遵循了相同的道路。单一继承与层次模型之间以及多重继承与网络模型之间的对应关系非常明显。C ++模板和层次模型之间的对应关系可能不太明显,但是无论如何它还是非常合适的。

我还没有看到它的正式证明,但是我相信模板的功能是多重继承(显然是单次继承的超集)所提供功能的超集。一个棘手的部分是模板大部分是静态绑定的,也就是说,所有绑定都发生在编译时,而不是运行时。这样,形式化证明继承提供了继承功能的超集,可能会有些困难和复杂(甚至可能是不可能的)。

无论如何,我认为这是C ++对其容器不使用继承的最真实的原因-没有真正的理由这样做,因为继承仅提供模板提供的功能的一部分。由于模板在某些情况下基本上是必需的,因此它们几乎也可以在任何地方使用。


0

您如何与ForwardIterator *进行比较?也就是说,您如何检查所拥有的物品是您要寻找的物品,还是您已经通过了它?

在大多数情况下,我会使用以下内容:

void MyFunc(ForwardIterator<MyType>& i)

这意味着我知道我要指向MyType,并且知道如何进行比较。虽然看起来像模板,但实际上不是(没有“ template”关键字)。


您可以只使用类型的<,>和=运算符,而不知道它们是什么(尽管这可能不是您的意思)
lhahne

根据上下文,这些可能没有任何意义,或者它们可能工作正常。在不了解MyType的情况下很难说出来,这大概是用户知道的,而我们却不知道。
Tanktalus

0

这个问题有很多很好的答案。还应该提到模板支持开放设计。在面向对象编程语言的当前状态下,在处理此类问题时必须使用访客模式,并且真正的OOP应该支持多个动态绑定。请参见P. Pirkelbauer等人的“ C ++的开放式多方法”。非常有趣的阅读。

模板的另一个有趣之处在于,它们也可以用于运行时多态。例如

template<class Value,class T>
Value euler_fwd(size_t N,double t_0,double t_end,Value y_0,const T& func)
    {
    auto dt=(t_end-t_0)/N;
    for(size_t k=0;k<N;++k)
        {y_0+=func(t_0 + k*dt,y_0)*dt;}
    return y_0;
    }

注意,如果Value是某种类型的向量(不是 std :: vector,则应调用该函数,该函数也将起作用)std::dynamic_array以避免混淆)

如果func很小,此功能将从内联中获取很多。用法示例

auto result=euler_fwd(10000,0.0,1.0,1.0,[](double x,double y)
    {return y;});

在这种情况下,您应该知道确切的答案(2.718 ...),但是很容易构造没有基本解的简单ODE(提示:在y中使用多项式)。

现在,您在中有一个大表达式func,并且在许多地方都使用了ODE求解器,因此您的可执行文件在任何地方都受到模板实例化的污染。该怎么办?首先要注意的是,常规函数指针有效。然后,您想添加currying,以便编写一个接口和一个显式实例化

class OdeFunction
    {
    public:
        virtual double operator()(double t,double y) const=0;
    };

template
double euler_fwd(size_t N,double t_0,double t_end,double y_0,const OdeFunction& func);

但以上实例仅适用于double,为什么不将接口编写为模板:

template<class Value=double>
class OdeFunction
    {
    public:
        virtual Value operator()(double t,const Value& y) const=0;
    };

并专门研究一些常见的价值类型:

template double euler_fwd(size_t N,double t_0,double t_end,double y_0,const OdeFunction<double>& func);

template vec4_t<double> euler_fwd(size_t N,double t_0,double t_end,vec4_t<double> y_0,const OdeFunction< vec4_t<double> >& func); // (Native AVX vector with four components)

template vec8_t<float> euler_fwd(size_t N,double t_0,double t_end,vec8_t<float> y_0,const OdeFunction< vec8_t<float> >& func); // (Native AVX vector with 8 components)

template Vector<double> euler_fwd(size_t N,double t_0,double t_end,Vector<double> y_0,const OdeFunction< Vector<double> >& func); // (A N-dimensional real vector, *not* `std::vector`, see above)

如果功能是首先围绕接口设计的,那么您将不得不从该ABC继承。现在您有了此选项,以及函数指针,lambda或任何其他函数对象。这里的关键是我们必须具有operator()(),并且我们必须能够在其返回类型上使用一些算术运算符。因此,如果C ++没有运算符重载,则模板机制在这种情况下会损坏。


-1

将接口与接口分离并且能够交换实现的概念并不是面向对象编程所固有的。我认为这是Microsoft COM等基于组件的开发中提出的一个想法。(请参阅对“什么是组件驱动的开发”的回答?)长大并学习C ++,人们就大肆宣传继承和多态性。直到90年代,人们才开始说“编程到一个“接口”,而不是一个“实现””和“偏爱“对象组成”而不是“类继承”。(顺便说一下,两者均引用自GoF)。

然后Java附带了内置的垃圾收集器和interface关键字,突然之间,实际上将接口和实现分开变得切实可行。在不知不觉中,这个想法就成为OO的一部分。C ++,模板和STL早于所有这些。


同意接口不只是OO。但是类型系统中的能力多态性是(在60年代的Simula中)。模块接口存在于Modula-2和Ada中,但我认为它们在类型系统中的运行方式有所不同。
andygavin
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.