从基 class 指针调用空虚函数失败

Calling empty virtual function from a base class pointer fails

在编写我的第一个软件时,我在使用虚函数、继承和指针时遇到了 "strange" 行为。我有一个模板 class 状态,在 public 接口

中有以下功能
void State::saveState() {};

应用首次启动时,为了设置默认状态,它在管理器函数中调用了一个 setState() 函数,大致如下

case(some enum):
{
    mActiveState->saveState();
    delete mActiveState; 
    mActiveState = new SomeStateClassDerivedFromState();
    mActiveState->setup(some arguments);
    break;
}

现在奇怪的是 - 这段代码的前两行应该会导致 运行-time 错误,或类似的错误。如果这个函数是用来改变状态的,那应该没问题。但它也被用于 State::mActiveState 的一些 object/memory 的分配,此时是 nullptr。但由于某种原因它没有崩溃,程序按预期执行。但是后来,我想在继承自 State 的 classes 之一中覆盖 saveState() 函数,所以我在函数中添加了一个虚拟标记(主体保持为空),并为 saveState() 在导出的 class 中。突然,这两行代码开始在启动时使应用程序崩溃(正如预期的那样)。从 saveState() 中删除虚拟标签使应用程序再次 运行。

为什么在 saveState() 成员函数被声明为虚拟(空函数体,而不是纯虚拟)之前调用成员函数并删除 nullptr 不会导致崩溃?

这段代码重现了问题

class State
{
public:
void saveState() {};
}

class DState : public State
{
saveState() {};
}

int main()
{
State* state = nullptr;
state->saveState();
delete state;
state = new DState();
}

这成功了。当我将虚拟标记添加到状态 class 中的 saveState() 函数时,它在启动时崩溃了。又是我的问题,为什么?

通过 nullptr 调用空方法具有未定义的行为,但不会导致程序崩溃,因为不会访问任何数据。尽管调用虚函数需要查找 vtable,这会带来您所看到的行为。但即使您的程序没有崩溃,它也不会使其有效。

另一方面,删除 nullptr 是有效的并导致无操作。

class State
{
public:
    void saveState() {};
}
//...
    State* state = nullptr;
    state->saveState();
//...

因为 saveState 在这里是非虚拟的,所以您只是简单地调用一个函数,该函数悄悄地将 this 指针作为第一个参数。

该函数什么都不做,所以什么也没有发生,事实上,编译器甚至可以完全忽略该函数和对它的调用。如果你的函数试图引用一个成员变量,它就会崩溃。您基本上已经调用了未定义的行为并且很幸运 - 主要是因为您在函数中什么也没做。

当您将函数设为虚函数时,程序必须取消引用对象才能找到它的 vtable(虚函数 table),这意味着它取消引用 nullptr,这就是你崩溃的原因。

您还问为什么删除空指针不会崩溃:那是因为,与 free 不同,删除空指针是合法的——基本上 delete 会为您检查。