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

时间:2010-11-13 21:08:00

标签: c++ pure-virtual language-lawyer

纯虚函数是虚拟的成员函数,具有纯指定符= 0;

C ++ 03的第10.4条第2款 告诉我们抽象类是什么,并作为旁注,以下内容:

[注意:函数声明不能​​同时提供纯指定符和定义 - 尾注] [例如:

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

-end example]

对于那些不熟悉此问题的人,请注意 纯虚函数可以有定义 ,但上述条款禁止此类定义以内联方式显示(词汇上的词汇)。 (有关定义纯虚函数的用法,例如,this GotW

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

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

注意:

  • MSVC确实允许PVF具有内联定义。所以不要惊讶:)
  • 此问题中的inline一词并未引用 inline 关键字。它应该是指词汇

5 个答案:

答案 0 :(得分:52)

在SO帖子“Why pure virtual function is initialized by 0?”中,Jerry Coffin提供了Bjarne Stroustrup的The Design & Evolution of C++第13.2.3节中的引用,其中我添加了一些我认为相关部分的重点:

  

选择了好奇的=0语法   引入的明显替代方案   一个新的关键字纯或抽象,因为   那时我没有机会得到   接受新关键字。如果我   建议纯,版本2.0将有   没有抽象类的。   给出了更好的语法之间的选择   和抽象类,我选择了抽象   类。而不是冒险延迟和   引发某些争斗   纯,我用传统的C和C ++   使用0表示的惯例   “不在那里。” =0语法符合   我的观点是函数体是   函数的初始化程序以及   (简单但通常足够)   视图的虚拟功能集   被实现为一个向量   函数指针。 [...]

因此,在选择语法时,Bjarne将函数体视为声明符的一种初始化器部分,而=0作为初始化器的替代形式,表示“无身体”(或在他的身体中)单词,“不存在”)。

理所当然地,人们不能同时指出“没有”并且有一个身体 - 在这个概念图中。

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

现在,就我的心灵感应力而言,google-foo和软推理都是如此。我猜测没有人有兴趣能够向委员会提出关于解除这种纯粹的语法限制的建议,并跟进所需的所有工作。因此,它仍然是这样。

答案 1 :(得分:8)

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

答案 2 :(得分:7)

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

  • 声明函数内联并提供一个明确的内联体(在类之外)是合法的,因此显然没有人反对在类中声明的唯一实际含义。
  • 我发现语法中没有引入任何歧义或冲突,因此没有合理排除函数定义 in situ 的原因。

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

答案 3 :(得分:2)

欢迎你说好猜?

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

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

基本链接器确实需要了解C ++。它可以直接链接在一起,即使您将其声明为= 0

我想我读到你所描述的可能,虽然我忘了这个行为: - )......

答案 4 :(得分:0)

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

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

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

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

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

从某种意义上说,纯粹的虚拟析构函数是一个不同的故事。它被用作一种技术,只是为了防止有人创建派生类的实例而不在其他地方有任何纯虚函数。

对于“为什么”不允许的实际问题的答案实际上仅仅是因为标准委员会这样说,但我的回答揭示了我们正在努力实现的目标。