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

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

C ++ 03 第10.4条第2款告诉我们什么是抽象类,作为一个附注,下面是:

[注:一个函数声明不能​​同时提供一个纯粹的说明符和一个定义 – 结束注释] [例子:

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

– 例子]

对于那些对这个问题不太熟悉的人,请注意, 纯虚函数可以有定义,但是上面提到的条款禁止这样的定义出现在内联(lexically in-class)。 (对于定义纯虚函数的使用,您可能会看到,例如, GotW )

现在所有其他types的function都可以提供一个一stream的定义,而这种限制似乎是乍看起来绝对是人为的和无法解释的。 想想看,似乎是这样的第二次和随后的一瞥:)但我相信如果没有一个具体的原因这个限制不会在那里。

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

笔记:

  • MSVC确实允许PVF具有内联定义。 所以不要感到惊讶:)
  • 这个问题中的内联单词并不是指inline关键字。 这应该是词汇在课堂上

在SO线程中, “为什么纯虚函数被初始化为0?” Jerry Coffin提供了Bjarne Stroustrup的“C ++的devise与演化”一节§13.2.3中的引用,我在其中join了一些我认为相关的部分的重点:

奇怪的=0语法被select,而不是引入一个新的关键字纯粹或抽象的明显的select,因为在那个时候我看不到接受一个新的关键字的机会。 如果我build议纯粹的,发行2.0将没有抽象类的运送。 鉴于更好的语法和抽象类之间的select,我select了抽象类。 我没有冒险拖延和招致一些纯粹的战斗,而是使用传统的C和C ++约定来使用0来表示“不存在”。 =0语法符合我的观点,即函数体是一个函数的初始化器 ,也是一套虚拟函数(简单的,但通常是足够的)的视图,被实现为一个函数指针向量。 […]

所以,在select语法的时候,Bjarne正在把一个函数体当作一种声明器的初始化部分,而=0作为一个初始化器的替代forms,一个表示“没有身体”(或者用他的话来说,“不存在” )。

在这个概念图中,人们不能既指出“不存在”,又有一个身体。

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

现在,就我的心灵感应力而言,google-foo和soft-reasoning都是如此。 我推测没有人有兴趣向这个委员会提出一个关于这个纯粹的句法限制的build议,并且跟进所有的工作。 因此,它仍然是这样。

你不应该对标准化委员会有这么多的信心。 并不是所有事情都有很深的理由来解释它。 有些事情是因为一开始没有人想到,没有人认为改变它是很重要的(我认为这是事实)。 对于旧的东西,甚至可能是第一次实施的人造物。 有些是进化的结果 – 有一个深刻的原因,但原因被删除,最初的决定没有再次重新考虑(也可能是这种情况,最初的决定是因为任何定义纯function被禁止)。 有些是不同的POV之间的谈判的结果,结果缺乏一致性,但这种缺乏被认为是达成共识的必要条件。

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

  • 声明内联函数并提供一个明确的内联体(在类之外)是合法的,所以显然不反对在类内部声明的唯一实际含义。
  • 我没有发现在语法中引入了潜在的歧义或冲突,所以没有原则上排除函数定义的逻辑原因。

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

好的猜测,欢迎你说?

我认为宣言中= 0来自实施。 这个定义很可能意味着你在RTTI的类信息的vtbl中获得了一个NULL条目 – 一个类的成员函数的运行时地址被存储的位置。

但是实际上,当在你的*.cpp文件中放入一个函数的定义时,你需要在链接器的目标文件中引入一个名字*.o文件中的一个地址,用于查找特定的函数。

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

我想我已经读了,可能是你描述的,尽pipe我忘记了这个行为: – )…

抛开析构函数,纯虚函数的实现是一件很奇怪的事情,因为它们从来不会以自然的方式被调用。 即如果你有一个指针或引用你的基类,底层对象将总是一些派生覆盖函数,并将始终被调用。

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

实际上,在某些方面,这使得它成为内联的一个更好的目标,因为在编译器想要调用它的地方,总是清楚哪个超载被调用。

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

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

在某种意义上,纯虚拟析构函数是一个不同的故事。 它被用作一种简单的技术来防止某人创build派生类的一个实例,而在别处没有任何纯虚函数。

对“为什么”这个实际问题的答案实际上只是因为标准委员会这样说,但是我的回答为我们正在努力实现的目标提供了一些启示。