在C++中,什么会导致纯虚函数调用?

33

我教授C++编程课程,我已经见过足够多的错误类别,以至于我对如何诊断常见的C++错误有了良好的感觉。然而,有一种主要类型的错误,我的直觉并不特别准确:什么样的编程错误会导致调用纯虚函数? 我见过最常见的导致这种错误的情况是从基类构造函数或析构函数中调用虚函数。在帮助调试学生代码时,还有其他需要注意的情况吗?


其他可能是从基类的某些成员函数中调用它,还有什么呢?但那不是错误! :| - Nawaz
这确实是我的问题。 :-) 可能没有另一种方法来触发纯虚函数调用,我的主要问题是是否有其他方法。 - templatetypedef
4个回答

28
“导致此错误最常见的原因是在基类构造函数或析构函数中调用虚函数。”
当一个对象被构造时,指向虚函数表的指针最初指向最高的超类,并且只有在中间类完成构造后才更新。因此,在子类完成构造之前,您可能会意外地调用纯虚拟实现。这可以是最终的子类,也可以是任何中间类。
如果您跟随部分构造的对象(例如由于异步或线程操作而导致的竞态条件)的指针,则可能会发生这种情况。
如果编译器认为它知道指向基类的指针指向的真实类型,它可能会合理地绕过虚拟分派。如果您执行某些未定义行为,如reinterpret cast,可能会使编译器混淆。
在销毁过程中,随着派生类的销毁,虚拟分派表应该恢复,因此可能再次调用纯虚拟实现。
在销毁后,“悬挂”的指针或引用通过继续使用对象可能会调用纯虚拟函数,但在这种情况下没有定义的行为。

实际上,在构造函数和析构函数中禁用虚拟调用 - 在这里解释 - 因此,调用不是虚拟的,链接器错误会发生。任何非虚拟分派都会导致链接器错误。但是,我同意对于部分构建(或损坏)的对象,当虚拟表格简单不正确时。然而,这些情况需要动态技巧,编译器无法看到(如线程处理,reinterpret_cast等)。 - uvsmtid
@uvsmtid:“实际上”意味着您正在纠正我所做的某些错误断言,但我没有提到构造函数和析构函数,只提到了构造析构。构造包括在处理构造函数的初始化列表和主体之前进行基类和非静态数据成员初始化;在销毁期间,它基本上是相反的。可以按照此处所示的方式分派到纯虚函数,而无需线程、异步、reinterpret_cast等。 - Tony Delroy
从您提供的链接或其他方式,我无法想象链接器错误是如何表现出来的。您能否用一些代码向我展示一下?也许可以使用我在上面评论中提供的coliru网站、ideone.com或您喜欢的任何其他网站... 干杯 - Tony Delroy

8
以下是几种可能会发生纯虚函数调用的情况:
  1. 使用悬空指针 - 指针不指向有效对象,因此它指向的虚表只是随机的内存,其中可能包含NULL。
  2. 错误的类型转换 - 使用static_cast转换为错误类型(或C风格的转换)也可能导致您所指向的对象在其虚表中没有正确的方法(在这种情况下,它确实是一个虚表,与前一选项不同)。
  3. DLL已被卸载 - 如果您持有的对象是在已被卸载的共享对象文件(DLL、so、sl)中创建的,则该内存现在可以被清零。

三不就是得到一的一种方式吗? - GManNickG
2
纯函数调用处理程序通常是一个特殊的函数,而不是NULL。 - ephemient
@GMan,基本上是的,也许它不值得一个单独的项目符号,但比一般情况更容易诊断。 - Motti
@ephemient,我不知道那个,据我所知,使用VC会为这种情况生成一个纯虚函数调用。 - Motti
3
有趣的是你提到了这个。VC定义了一个名为“_purecall”的函数(只会打印诊断信息并中止程序),并将其放置在纯虚函数的虚函数表槽位上。这与跟踪指向NULL的函数指针不完全相同。 - ephemient

1
除了其他答案之外:
如果派生类没有声明虚函数表,也会发生这种情况。
对于Visual Studio而言,如果使用__declspec(novtable)或_ATL_NO_VTABLE等方式定义,就可能会出现这种情况。在使用MFC技术时也可能会发生。
如果以这种方式定义类,虚函数表将被填充为_purecall。

当我选择将一个类声明为ATL对象后不将其作为ATL对象时,我遇到了这个问题。 我忘记删除了_ATL_NO_VTABLE,这是一个非常难以发现的错误。 - Daniel Bauer

0

这种情况可能发生在对象的引用或指针指向 NULL 位置时,您使用对象引用或指针调用类中的虚函数。例如:

std::vector <DerivedClass> objContainer;  
if (!objContainer.empty()) 
   const BaseClass& objRef = objContainer.front();  
// Do some processing using objRef and then you erase the first
// element of objContainer
objContainer.erase(objContainer.begin());   
const std::string& name = objRef.name();  
// -> (name() is a pure virtual function in base class, 
// which has been implemented in DerivedClass).

此时存储在 objContainer[0] 中的对象不存在。当虚拟表被索引时,找不到有效的内存位置。因此,会发出运行时错误,提示“调用了纯虚函数”。


是的,我尝试过并且出现了一个错误:“调用纯虚方法”。你看到了什么错误? - cppcoder
这个例子是不完整的,所以我还没有重构它。但是我猜测,您正在为一个悬空指针调用虚函数,我很快会尝试一下。顺便说一句:这是未定义的行为,因此可能会以不同的方式崩溃。 - Wolf

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接