在编写我的第一个软件时,我在使用虚函数,继承和指针时遇到了“奇怪”的行为。我所拥有的是模板类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()
函数,所以我在函数中添加了一个虚拟标记(正文保持为空),并为saveState()
写了一个定义。派生类。突然,这两行在启动时开始崩溃应用程序(正如预期的那样)。从saveState()
删除虚拟代码会使应用再次运行。
为什么没有调用成员函数并删除nullptr
导致崩溃,直到saveState()
成员函数被声明为虚拟(空体,而不是纯虚拟)?
此代码重新创建问题
class State
{
public:
void saveState() {};
}
class DState : public State
{
saveState() {};
}
int main()
{
State* state = nullptr;
state->saveState();
delete state;
state = new DState();
}
这很有用。我将虚拟标记添加到State类中的saveState()
函数的那一刻,它在启动时崩溃了。我的问题,为什么?
答案 0 :(得分:3)
class State
{
public:
void saveState() {};
}
//...
State* state = nullptr;
state->saveState();
//...
因为saveState在这里是非虚拟的,所以你只是调用一个静静地将this
指针作为第一个参数的函数。
该函数什么都不做,所以什么都没发生,实际上编译器甚至可以完全忽略函数和调用它。如果你的函数试图引用一个成员变量,它就会崩溃。您基本上调用了未定义的行为并且很幸运 - 主要是因为您在函数中没有做任何事情。
当你使虚函数成功时,程序必须取消引用该对象以找到它的 vtable (虚函数表),这意味着它取消引用nullptr,这就是为什么你崩溃了。
你还问为什么删除一个空指针不会崩溃:那是因为,与free不同,删除空指针是合法的 - 基本上删除会对你进行检查。
答案 1 :(得分:1)
通过nullptr
调用空方法具有未定义的行为,但不必使程序崩溃,因为没有数据被访问。调用虚函数虽然需要查找vtable,这会带来你看到的行为。但即使你的程序没有崩溃,它也不会使它有效。
另一方面,删除nullptr
有效并导致无操作。