纯虚函数是那些具有 pure-specifier ( = 0;
) 的虚成员函数
C++03 的第 10.4 条第 2 段告诉我们什么是抽象类,作为旁注,如下:
[注意:函数声明不能同时提供纯说明符和定义——尾注] [示例:
struct C {
virtual void f() = 0 { }; // ill-formed
};
——结束示例]
对于不太熟悉这个问题的人,请注意纯虚函数可以有定义,但上述条款禁止此类定义出现内联(词法上班级)。 (对于定义纯虚函数的用法,您可能会看到,例如 this GotW)
现在对于所有其他种类和类型的函数,它可以提供一个类内定义,而且这个限制乍一看似乎是绝对人为的和莫名其妙的。想想看,第二眼和随后的一瞥似乎都是这样 :) 但我相信如果没有具体原因,限制就不会存在。
我的问题是:有人知道这些具体原因吗?也欢迎好的猜测。
笔记:
MSVC 确实允许 PVF 具有内联定义。所以不要惊讶:)
这个问题中的单词 inline 不是指 inline 关键字。它应该是词汇上的意思
virtual void f() = 0 try { } catch(...) { }
在 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 和软推理能力。我推测没有人有足够的兴趣向委员会提出关于取消这种纯粹的句法限制并跟进所有工作的提案。所以还是这样。
你不应该对标准化委员会这么有信心。并非每件事都有深刻的理由来解释它。有些事情之所以如此,是因为起初没有人想到其他事情,并且在没有人认为改变它足够重要之后(我认为这里就是这种情况);对于足够老的东西,它甚至可能是第一个实现的产物。有些是进化的结果——一次有一个深刻的原因,但原因被删除了,最初的决定也没有重新考虑(这里也可能是这种情况,最初的决定是因为任何定义纯函数被禁止)。有些是不同 POV 之间谈判的结果,结果缺乏连贯性,但这种缺乏被认为是达成共识所必需的。
很好的猜测......好吧,考虑到这种情况:
声明函数 inline 并提供显式内联体(在类外部)是合法的,因此显然没有反对在类内部声明的唯一实际含义。
我没有看到语法中引入潜在的歧义或冲突,因此没有逻辑理由原位排除函数定义。
我的猜测:纯虚函数体的使用是在= 0 | { ... }
语法制定之后实现的,而语法根本没有修改。值得考虑的是,有很多关于语言更改/增强的建议——包括那些让这样的事情更合乎逻辑和一致的建议——但是被某人挑选并写成正式建议的数量要少得多,而且数量要少得多。委员会有时间考虑并相信编译器供应商将准备实施的那些,再次小得多。像这样的事情需要一个拥护者,也许你是第一个看到其中问题的人。要了解此过程,请查看 http://www2.research.att.com/~bs/evol-issues.html。
欢迎你说好的猜测?
我认为 声明 中的 = 0
来自于考虑到实现。这个定义很可能意味着,您在类信息的 RTTI 的 vtbl
中获得了一个 NULL
条目——在运行时存储类的 成员函数地址的位置。
但实际上,当在 *.cpp
文件中放置函数的定义 时,会在链接器的目标文件中引入 name:*.o
中的地址文件在哪里可以找到特定的功能。
然后,基本链接器不再需要了解 C++。即使您将其声明为 = 0
,它也可以链接在一起。
我想我读到这可能是您所描述的,尽管我忘记了行为:-) ...
撇开析构函数不谈,纯虚函数的实现是一件奇怪的事情,因为它们永远不会以自然的方式被调用。即,如果您有一个指向您的 Base 类的指针或引用,则底层对象将始终是一些覆盖该函数的 Derived,并且始终会被调用。
实际调用实现的唯一方法是使用派生类重载之一中的 Base::func() 语法。
实际上,在某些方面,这使它成为内联的更好目标,因为在编译器想要调用它的时候,总是很清楚正在调用哪个重载。
此外,如果纯虚函数的实现被禁止,那么 Base 类中的一些其他(可能受保护的)非虚函数将有一个明显的解决方法,您可以从派生函数中以常规方式调用。当然,范围的限制会更少,因为您可以从任何函数中调用它。
(顺便说一句,我假设 Base::f()
只能从 Derived::f()
使用此语法调用,而不能从 Derived::anyOtherFunc()
调用。我对这个假设是否正确?)。
从某种意义上说,纯虚拟析构函数是另一回事。它被用作一种技术,只是为了防止有人在其他地方没有任何纯虚函数的情况下创建派生类的实例。
对“为什么”不允许这样做的实际问题的答案实际上只是因为标准委员会这么说,但我的回答对我们无论如何都试图实现的目标提供了一些启示。
Derived::anyOtherFunc()
调用它的假设是不正确的
= 0
网站的狭窄背景下,您所说的内容是连贯的。我的困惑源于您没有将其与 out-of-line 定义联系起来,或者没有探讨为什么上述思维/逻辑/观点可能不会导致禁止身体定义,或者实现 out-of 的使用线定义改变了原地定义的思路。这种不一致是问题的核心。如果我们之间仍然存在沟通鸿沟,那么我想我们都已尽力而为——没有造成任何伤害:-)。