从基 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 会为您检查。
在编写我的第一个软件时,我在使用虚函数、继承和指针时遇到了 "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 会为您检查。