从基类指针调用空虚函数失败
Calling empty virtual function from a base class pointer fails
在编写我的第一个软件时,我在玩虚函数、继承和指针时遇到了一种"奇怪"的行为。我有一个模板类 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
是有效的,并导致无操作。
- 函数返回时,带指针的复制构造函数失败
- 为什么 std::get<T> 其中 T 是调用 constexpr 函数失败的结果?
- 在 if 语句中调用重载构造函数失败
- 使用特征函数失败
- 为什么通过通用引用运算符 (&&) 将变量的引用传递给 Varadic 模板函数失败?
- 为什么我的代码在没有 chroot 函数的情况下工作,但使用 chroot 函数失败?
- 非常量调用 const 成员函数失败,只读位置C++
- 对于实际指针类型,用于检测类似指针(可取消引用)类型的模板函数失败
- C++模板化类默认构造函数失败
- STD :: MAP EMPLECE通过显式构造函数失败
- 为什么 std::apply 使用泛型函数失败
- 从返回绑定中获取函数失败
- 运算符 == 重载函数失败
- 打开不存在的文件时如何使流构造函数失败?
- 为线程构造函数传递引用以将其绑定到函数失败
- 显式DLL 1函数失败
- 模板参数扣除/替换使用Lambda表达式使用高阶函数失败
- 来自(QT)插件的调用函数失败
- 什么错误的逻辑导致我的链表的这个 push_back(..) 函数失败?
- 复制构造函数失败..重载,动态分配