纯虚函数可能没有内联定义。为啥?

Posted

技术标签:

【中文标题】纯虚函数可能没有内联定义。为啥?【英文标题】:Pure virtual functions may not have an inline definition. Why?纯虚函数可能没有内联定义。为什么? 【发布时间】:2011-05-09 15:35:47 【问题描述】:

纯虚函数是那些具有纯说明符= 0;)的虚成员函数

C++03 的第 10.4 条第 2 段 告诉我们什么是抽象类,并附带说明以下内容:

[注意:函数声明不能​​同时提供纯说明符和定义 ——尾注] [例子:

struct C 
virtual void f() = 0  ; // ill-formed
;

——结束示例]

对于不太熟悉这个问题的人,请注意纯虚函数可以有定义,但上述条款禁止此类定义出现在行内 (类中的词汇)。 (对于定义纯虚函数的用法,您可能会看到,例如,this GotW)

现在对于所有其他种类和类型的函数,它可以提供一个类内定义,而且这个限制乍一看似乎完全是人为的和莫名其妙的。想想看,第二眼和随后的眼光似乎都是这样 :) 但我相信如果没有具体原因,限制就不会存在。

我的问题是:有人知道这些具体原因吗? 好的也欢迎猜测。

注意事项:

MSVC 确实允许 PVF 具有内联定义。所以不要惊讶:) 这个问题中的单词inline 不是指inline 关键字。它应该是指在类中的词法

【问题讨论】:

使用函数 try 块时看起来有点奇怪:virtual void f() = 0 try catch(...) 这里有一个猜测:创建一个不允许它的编译器更简单,并且鉴于其非常罕见的用法,这是一个简单的决定。一些编译器(你引用了 MSVC)确实允许它的事实仅仅意味着一些编译器作者不会被额外的工作所困扰。 @Downvoter:这个问题是主观的和有争议的?不是一个真正的问题?格式/公式不正确?让我知道,以便我改进以满足您的高标准 当然“更难”。这是函数声明构造中必须允许的附加符号。诚然,这并不是所有的那么困难,但未完成的工作可以节省时间,无论价值有多大或多小。无论如何,我没有说这是一个很好的猜测,只是一个猜测。 @Armen,我花了点时间考虑了一下,也许我对这个问题太敏感了,所以:我仍然不喜欢提供实现的纯虚函数。因此,我真的很喜欢 GotW 将他们的文章命名为 (Im)pure Virtual Functions。 :-) 如果我碰巧遇到它,我很高兴知道它的存在。为问题 +1。 【参考方案1】:

在 SO 线程 "Why is a pure virtual function initialized by 0?" Jerry Coffin 提供了 Bjarne Stroustrup 的 The Design & Evolution of C++ 第 13.2.3 节中的这句话,我在其中添加了一些我认为相关的部分的重点:

之所以选择奇怪的=0 语法,而不是引入新关键字 pure 或 abstract 的明显替代方案,因为当时我认为没有机会接受新关键字。如果我建议 pure,Release 2.0 将在没有抽象类的情况下发布。考虑到更好的语法和抽象类之间的选择,我选择了抽象类。我没有冒险延迟和招致某些关于 pure 的争论,而是使用了传统的 C 和 C++ 约定,使用 0 来表示“不存在”。 =0 语法符合我认为函数体是函数的初始化器的观点,也符合作为向量实现的虚函数集(简单但通常足够)的观点函数指针。 […]

因此,在选择语法时,Bjarne 将函数体视为声明符的一种初始化部分,而 =0 作为初始化的另一种形式,表示“没有主体”(或者用他的话来说, “不在那里”)。

理所当然地,一个人不能既表示“不存在”又拥有一个身体——在那个概念图中。

或者,仍然在那个概念图中,有两个初始化器。

现在,这就是我的心灵感应能力、google-foo 和软推理能力。我推测没有人足够感兴趣™ 向委员会提出关于取消这种纯粹的句法限制并跟进所有工作的提案。所以还是这样。

【讨论】:

哇... 21 票(到目前为止),除非我错过了什么,否则你没有说一个字来解决这个问题(这实际上就是为什么离线定义是可能但内联不行)。 @Tony:很抱歉您不理解我的回答的相关性。我已经尽力了。它对你不起作用。我不知道如何说得更清楚。我们之间可能存在沟通鸿沟。 有 8 小时的思考时间,我发现在 = 0 网站的狭窄背景下,您所说的内容是连贯的。我的困惑源于您没有将其与 out-of-line 定义联系起来,或者没有探讨为什么上述思维/逻辑/观点可能不会导致禁止那里的 body 定义,或者实现 out-of 的使用线定义改变了原地定义的思路。这种不一致是问题的核心。如果我们之间仍然存在沟通鸿沟,那么我想我们都已尽力而为——没有造成任何伤害:-)。 如果他只是将 =0 移动到 virtual 之后,它就会起作用,所以 virtual =0 void func() // code ;将 =0 放在您可能想要的位置(即虚拟旁边),尽管语法看起来很有趣。 就像 Tony 说的,您似乎已经回答了“纯虚函数可能没有任何定义。为什么?”而不是“纯虚函数可能没有内联定义(即使它们可能有外联定义)的问题。为什么?”【参考方案2】:

撇开析构函数不谈,纯虚函数的实现是一件很奇怪的事情,因为它们永远不会以自然的方式被调用。即,如果您有一个指向您的 Base 类的指针或引用,则底层对象将始终是一些覆盖函数的 Derived,并且始终会被调用。

真正获取要调用的实现的唯一方法是使用派生类重载之一中的 Base::func() 语法。

实际上,在某些方面,这使它成为内联的更好目标,因为在编译器想要调用它的时候,总是很清楚正在调用哪个重载。

此外,如果纯虚函数的实现被禁止,那么 Base 类中的一些其他(可能受保护的)非虚函数的明显变通方法可以直接从派生函数中以常规方式调用。当然,范围的限制会更少,因为您可以从任何函数调用它。

(顺便说一句,我假设Base::f() 只能从Derived::f() 而不是Derived::anyOtherFunc() 使用此语法调用。我的假设是否正确?)。

从某种意义上说,纯虚拟析构函数是另一回事。它被用作一种技术,只是为了防止有人在其他地方没有任何纯虚函数的情况下创建派生类的实例。

对于“为什么”不允许这样做的实际问题的答案实际上只是因为标准委员会这么说,但我的回答还是对我们正在努力实现的目标有所启发。

【讨论】:

关于无法从Derived::anyOtherFunc() 调用它的假设是不正确的【参考方案3】:

很好的猜测......好吧,考虑到情况:

将函数声明为内联并提供显式内联主体(在类外部)是合法的,因此对于在类内部声明的唯一实际含义显然没有异议。 我认为语法中没有引入潜在的歧义或冲突,因此没有逻辑理由原位排除函数定义。

我的猜测:纯虚函数体的使用是在= 0 | ... 语法制定之后实现的,而语法根本没有修改。值得考虑的是,有很多关于语言更改/增强的提案——包括那些让这样的事情更合乎逻辑和一致的提案——但是被某人挑选并写成正式提案的数量要少得多,而且数量要少得多。委员会有时间考虑并相信编译器供应商将准备实施的那些,再次小得多。像这样的事情需要一个拥护者,也许你是第一个看到其中问题的人。要了解此过程,请查看http://www2.research.att.com/~bs/evol-issues.html

【讨论】:

【参考方案4】:

欢迎你说好猜测?

我认为声明中的= 0 来自于考虑到实现。这个定义很可能意味着,您在类信息的 RTTI 的vtbl 中获得了一个NULL 条目——该位置在运行时存储类的成员函数的地址。

但实际上,当在*.cpp 文件中放置函数的定义 时,会在链接器的目标文件中引入一个名称*.o 文件在哪里可以找到特定的函数。

然后,基本链接器不再需要了解 C++。即使您将其声明为= 0,它也可以链接在一起。

我想我读到这可能是您所描述的,尽管我忘记了行为:-)...

【讨论】:

【参考方案5】:

您不应该对标准化委员会如此信任。并非每件事都有深刻的理由来解释它。有些事情之所以如此,是因为起初没有人有其他想法,并且在没有人认为改变它足够重要之后(我认为这里就是这种情况);对于足够老的东西,它甚至可能是第一个实现的产物。有些是进化的结果——一次有一个深刻的原因,但原因被删除了,最初的决定也没有重新考虑(这里也可能是这种情况,最初的决定是因为任何定义纯函数被禁止)。有些是不同 POV 之间协商的结果,结果缺乏连贯性,但这种缺乏被认为是达成共识所必需的。

【讨论】:

根据 D&E 的说法,在与 AT&T 的内部 C++ 用户讨论之后,Stroustrup 将纯虚拟添加到 C++ 中,这是在 6 月发布的 cfront 2.0 发布前几周的最后一件事1989 年。同一个消息来源,ANSI X3J16(后来与 ISO WG21 一起组成了我们现在所知的标准委员会)的第一次会议在半年后,即 1989 年 12 月举行。(我以为我读到了一个原因因为不允许抽象函数的内联定义,但我在 D&E 中找不到它。)所以这不是由标准委员会决定的。 @AProgrammer:感谢您的猜测。但我仍然要等待以“所以,是的,我今天打电话给 Bjarne,他说……”开头的答案:) 所以,是的,我今天给 Bjarne 打了电话,但他没有接。 ;-) @Armen 我不认为这是困扰 Bjarne 的问题。干杯, @David:在过去的 15 年里,我用大约六封关于 C++ 的电子邮件来困扰他。他总是回应,而且非常及时。我怀疑像 Armen 这样的问题可能每天都会有几十个收到他的收件箱,所以它可能不会得到回答。但我不会完全否定这个想法。

以上是关于纯虚函数可能没有内联定义。为啥?的主要内容,如果未能解决你的问题,请参考以下文章

为啥纯虚函数初始化为0?

为啥我们需要 C++ 中的纯虚析构函数?

Hello,C++纯虚函数和抽象类

纯虚函数,抽象类

多态—— 纯虚函数和抽象类

纯虚函数和抽象类