Answers:
如果您尝试从构造函数或析构函数进行虚拟函数调用,则可能会导致它们。由于您不能从构造函数或析构函数进行虚拟函数调用(派生类对象尚未构建或已被销毁),因此它将调用基类版本,在纯虚拟函数的情况下,它不会不存在。
(在此处查看现场演示)
class Base
{
public:
Base() { doIt(); } // DON'T DO THIS
virtual void doIt() = 0;
};
void Base::doIt()
{
std::cout<<"Is it fine to call pure virtual function from constructor?";
}
class Derived : public Base
{
void doIt() {}
};
int main(void)
{
Derived d; // This will cause "pure virtual function call" error
}
doIt()
构造函数中的调用很容易被虚拟化并Base::doIt()
静态分配,这只会导致链接器错误。我们真正需要的是一种情况,其中动态调度期间的动态类型是抽象基本类型。
Base::Base
调用非虚拟方法f()
,后者又调用了(纯)虚拟doIt
方法。
与从具有纯虚函数的对象的构造函数或析构函数中调用虚函数的标准情况一样,如果在对象被销毁后调用虚函数,则还可以获得纯虚函数调用(至少在MSVC上) 。显然,这是一件很不好的尝试,但是如果您使用抽象类作为接口并且搞砸了,那么您可能会发现。如果您使用的是引用计数接口,并且有引用计数错误,或者在多线程程序中有对象使用/对象破坏竞争的情况,则这种可能性更大。关于这些purecall的事情是通常不容易弄清发生了什么,因为检查ctor和dtor中虚拟调用的“通常可疑对象”会变得干净。
为了帮助调试此类问题,您可以在各种版本的MSVC中替换运行时库的purecall处理程序。您可以通过为自己的函数提供以下签名来做到这一点:
int __cdecl _purecall(void)
和链接它,然后再链接运行时库。这使您可以控制检测到纯调用时发生的情况。一旦有了控制权,您就可以执行比标准处理程序更有用的操作。我有一个处理程序,可以提供纯调用发生位置的堆栈跟踪;有关更多详细信息,请参见此处:http : //www.lenholgate.com/blog/2006/01/purecall.html。
(请注意,您也可以调用_set_purecall_handler()在某些版本的MSVC中安装处理程序)。
_purecall()
,则通常不会在调用已删除实例的方法时发生调用__declspec(novtable)
。这样一来,完全有可能在删除对象后调用覆盖的虚拟方法,这可能会掩盖问题,直到以其他形式咬住您。该_purecall()
陷阱是你的朋友!
我遇到了这样的情况:由于损坏的对象而调用了纯虚函数,Len Holgate
已经有了一个很好的答案,我想用一个示例添加一些颜色:
派生类析构函数将vptr点重置为具有纯虚函数的基类vtable,因此,当我们调用虚函数时,它实际上会调用纯虚函数。
发生这种情况的原因可能是明显的代码错误,或者是多线程环境中竞争条件的复杂场景。
这是一个简单的示例(关闭优化功能的g ++编译-简单的程序可以轻松地优化掉):
#include <iostream>
using namespace std;
char pool[256];
struct Base
{
virtual void foo() = 0;
virtual ~Base(){};
};
struct Derived: public Base
{
virtual void foo() override { cout <<"Derived::foo()" << endl;}
};
int main()
{
auto* pd = new (pool) Derived();
Base* pb = pd;
pd->~Derived();
pb->foo();
}
堆栈跟踪如下所示:
#0 0x00007ffff7499428 in __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:54
#1 0x00007ffff749b02a in __GI_abort () at abort.c:89
#2 0x00007ffff7ad78f7 in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#3 0x00007ffff7adda46 in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#4 0x00007ffff7adda81 in std::terminate() () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#5 0x00007ffff7ade84f in __cxa_pure_virtual () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#6 0x0000000000400f82 in main () at purev.C:22
突出:
如果对象被完全删除,这意味着调用了析构函数,并且回收了memroy,Segmentation fault
那么当内存返回到操作系统时,我们可能会简单地得到a ,而程序则无法访问它。因此,这种“纯虚函数调用”场景通常发生在将对象分配到内存池上的情况下,而删除对象时,底层内存实际上并没有被OS回收,它仍然可以被进程访问。
我猜是由于某种内部原因(为某种运行时类型信息可能需要)为抽象类创建了一个vtbl,并且出现问题并由实际对象获取。这是一个错误。仅此而已就意味着不可能发生的事情。
纯粹的猜测
编辑:看起来我在有关案件中错了。OTOH IIRC某些语言允许vtbl调用出构造函数析构函数。
我使用VS2010,每当尝试直接从公共方法调用析构函数时,在运行时都会收到“纯虚函数调用”错误。
template <typename T>
class Foo {
public:
Foo<T>() {};
~Foo<T>() {};
public:
void SomeMethod1() { this->~Foo(); }; /* ERROR */
};
因此,我将〜Foo()内部的内容移到了单独的私有方法中,然后它像一个魅力一样起作用了。
template <typename T>
class Foo {
public:
Foo<T>() {};
~Foo<T>() {};
public:
void _MethodThatDestructs() {};
void SomeMethod1() { this->_MethodThatDestructs(); }; /* OK */
};
如果您使用Borland / CodeGear / Embarcadero / Idera C ++ Builder,则可以实施
extern "C" void _RTLENTRY _pure_error_()
{
//_ErrorExit("Pure virtual function called");
throw Exception("Pure virtual function called");
}
在调试时,在代码中放置一个断点,并在IDE中查看调用堆栈,否则,如果您具有相应的工具,则将调用堆栈记录在异常处理程序(或该函数)中。我个人为此使用MadExcept。
PS。原始函数调用位于[C ++ Builder] \ source \ cpprtl \ Source \ misc \ pureerr.cpp中
这是一种偷偷摸摸的实现方式。今天我基本上发生了这种情况。
class A
{
A *pThis;
public:
A()
: pThis(this)
{
}
void callFoo()
{
pThis->foo(); // call through the pThis ptr which was initialized in the constructor
}
virtual void foo() = 0;
};
class B : public A
{
public:
virtual void foo()
{
}
};
B b();
b.callFoo();