【问题标题】:What can cause a pure virtual function call in C++?什么会导致 C++ 中的纯虚函数调用?
【发布时间】:2011-06-04 12:06:28
【问题描述】:

我教了一门 C++ 编程课程,我已经看到了足够多的错误类别,我对如何诊断常见的 C++ 错误有了很好的感觉。但是,有一种主要类型的错误我的直觉不是特别好:哪些编程错误会导致调用纯虚函数?我见过的最常见的错误是调用虚函数来自基类构造函数或析构函数的函数。在帮助调试学生代码时,我还应该注意哪些其他事项?

【问题讨论】:

  • 其他人可能会从基类的某些成员函数中调用它,还有什么可能呢?但这不是一个错误! :|
  • 这就是我的问题所在。 :-) 可能没有其他方法可以触发纯虚拟呼叫,我的主要问题是是否有。

标签: c++ pure-virtual


【解决方案1】:

“我见过的导致这种情况的最常见错误是从基类构造函数或析构函数调用虚函数。”

构造对象时,指向虚拟调度表的指针最初指向最高超类,并且仅在中间类完成构造时更新。因此,您可能会意外调用纯虚拟实现,直到子类(具有自己的覆盖函数实现)完成构造为止。这可能是派生最多的子类,或者介于两者之间的任何地方。

如果您遵循指向部分构造对象的指针(例如,由于异步或线程操作而处于竞争状态),则可能会发生这种情况。

如果编译器有理由认为它知道基类指针指向的真实类型,它可能会合理地绕过虚拟调度。你可能会因为做一些未定义的行为而混淆它,比如重新解释演员表。

在销毁过程中,由于派生类被销毁,虚拟调度表应该被恢复,因此可以再次调用纯虚拟实现。

销毁后,通过“悬空”指针或引用继续使用对象可能会调用纯虚函数,但在这种情况下没有定义的行为。

【讨论】:

  • 实际上,虚拟调用在构造函数和析构函数中被禁用 - explained here - 因此,调用不是虚拟的并且会发生链接器错误。任何非虚拟调度都会导致链接器错误。但是当虚拟表完全不正确时,我同意部分构造(或损坏)的对象。但是,这些情况需要编译器看不到的动态技巧(如上所述的线程、reinterpret_cast 等)。
  • @uvsmtid:“实际上”意味着您正在纠正我所做的一些错误断言,但我没有提到构造函数和析构函数,只有 construction破坏。构造包括基类和非静态数据成员的初始化,然后再处理构造函数的初始化列表和主体;在破坏过程中反向几乎相同。可以像here 所示那样调度纯虚函数,而无需线程、异步、reinterpret_cast 等。
  • 另外,从您提供的链接或其他方面 - 我无法想象链接器错误是如何表现出来的。你能告诉我一些代码吗?也许我在上面的评论中链接到的 coliru 网站,ideone.com 或任何你喜欢的......干杯
【解决方案2】:

下面是一些可能发生纯虚拟呼叫的情况。

  1. 使用悬空指针 - 指针不是有效对象,因此它指向的虚拟表只是可能包含 NULL 的随机内存
  2. Bad cast 使用 static_cast 到错误的类型(或 C 风格的转换)也可能导致您指向的对象在其虚拟表中没有正确的方法(在这种情况下至少是真的与前一个选项不同的虚拟表)。
  3. DLL 已卸载 - 如果您持有的对象是在已再次卸载的共享对象文件(DLL、so、sl)中创建的,则现在可以将内存清零

【讨论】:

  • 三个不只是获得一个的方法吗?
  • purecall 处理程序通常是一个特殊函数,而不是 NULL。
  • @GMan,几乎是的,也许它不值得一个单独的项目符号,但它比一般情况更容易诊断
  • @ephemient,我不知道,使用 VC 的 AFIK 会为此场景生成纯虚拟调用。
  • @Motti:你提到这很有趣。 VC 定义了一个函数_purecall(它只是通过诊断中止)并将其放入 vtable 插槽中以用于纯虚拟方法。这和跟随一个指向 NULL 的函数指针不太一样。
【解决方案3】:

例如,当对象的引用或指针指向 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] 中的对象不存在。当虚拟表被索引时,没有找到有效的内存位置。因此,会发出运行时错误,说“调用了纯虚函数”。

【讨论】:

  • 是的,我尝试使用并收到错误“调用纯虚拟方法”。你看到了什么错误?
  • 这个例子不完整,所以我还没有重构它。但正如我猜想的那样,你正在为一个悬空指针调用一个虚函数,我很快就会试试这个。顺便说一句:这是未定义的行为,因此它可能会以不同的方式崩溃。
猜你喜欢
  • 1970-01-01
  • 2010-11-07
  • 2012-06-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-08-27
  • 2018-01-30
相关资源
最近更新 更多