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

Calling empty virtual function from a base class pointer fails

本文关键字:函数 失败 空虚 调用 基类 指针      更新时间:2023-10-16

在编写我的第一个软件时,我在玩虚函数、继承和指针时遇到了一种"奇怪"的行为。我有一个模板类 State,它在公共接口中具有以下功能

void State::saveState() {};

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

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

现在奇怪的事情 - 此代码的前两行应该会导致运行时错误或类似错误。如果此函数用于更改状态,那么它应该没问题。但它也用于为State::mActiveState分配一些对象/内存,这在这一点上是nullptr。但由于某种原因它没有崩溃,程序按预期执行。但是后来,我想覆盖从 State 继承的类之一中的 saveState() 函数,所以我向函数添加了一个虚拟标记(body 保持为空),并在派生类中为 saveState() 编写了一个定义。突然,这两行开始在启动时使应用程序崩溃(正如预期的那样)。从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();
}

这奏效了。当我将虚拟标签添加到 State 类中的 saveState() 函数时,它在启动时崩溃了。我的问题,为什么?

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

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

该函数什么

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

当你使函数虚拟时,程序必须取消引用对象以找到它的vtable(虚拟函数表),这意味着它取消引用nullptr,这就是你崩溃的原因。

你还问为什么删除空指针不会崩溃:这是因为,与免费不同,删除空指针是合法的——基本上删除会为你检查。

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

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