ChatGPT解决这个技术问题 Extra ChatGPT

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

纯虚函数是那些具有 pure-specifier ( = 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 的问题。

D
Deduplicator

在 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 定义联系起来,或者没有探讨为什么上述思维/逻辑/观点可能不会导致禁止身体定义,或者实现 out-of 的使用线定义改变了原地定义的思路。这种不一致是问题的核心。如果我们之间仍然存在沟通鸿沟,那么我想我们都已尽力而为——没有造成任何伤害:-)。
如果他只是将 =0 移到 virtual 之后,它就会起作用,所以 virtual =0 void func() { // code };将 =0 放在您可能也需要它的位置(即虚拟旁边),尽管语法看起来很有趣。
正如托尼所说,您似乎已经回答了“纯虚函数可能没有任何定义。为什么?”这个问题。而不是“纯虚函数可能没有内联定义(即使它们可能有外联定义)的问题。为什么?”
A
Armen Tsirunyan

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


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

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

声明函数 inline 并提供显式内联体(在类外部)是合法的,因此显然没有反对在类内部声明的唯一实际含义。

我没有看到语法中引入潜在的歧义或冲突,因此没有逻辑理由原位排除函数定义。

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


t
towi

欢迎你说好的猜测?

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

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

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

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


C
CashCow

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

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

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

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

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

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

对“为什么”不允许这样做的实际问题的答案实际上只是因为标准委员会这么说,但我的回答对我们无论如何都试图实现的目标提供了一些启示。


关于无法从 Derived::anyOtherFunc() 调用它的假设是不正确的