一个由非虚函数导致的隐藏Bug:窗口显示异常问题排查与解决

一个由非虚函数导致的隐藏Bug:窗口显示异常问题排查与解决

问题背景

在基于FreeRTOS的嵌入式GUI系统中,从充电界面切换回普通界面时,菜单窗口无法正常显示。虽然日志显示 setVisible(TRUE) 被调用,但菜单界面仍然不可见。

问题现象

通过日志分析发现:

复制代码
onWinCtrl: MenuWin - final setVisible(1)

onWinCtrl 中菜单窗口被设置为可见,但界面未显示。进一步调试发现,MenuWindow::setVisible() 中的关键逻辑(恢复子菜单状态)没有被执行。

问题分析

代码结构

cpp 复制代码
// BaseWindow.h
class BaseWindow {
public:
    void setVisible(boolean bShow);  // 注意:不是虚函数
    // ...
};

// MenuWindow.h
class MenuWindow : public BaseWindow {
public:
    void setVisible(boolean bVisible);  // 重写基类方法
    // ...
};

// MenuWindow.cpp
void MenuWindow::setVisible(boolean bVisible) {
    BaseWindow::setVisible(bVisible);
    
    if (!bVisible) {
        // 隐藏所有子菜单
        // ...
        return;
    }
    
    // 关键逻辑:恢复当前选中的子菜单
    MenuSubWindow* target = m_pCurrentSubWin;
    if (target == nullptr) {
        target = GetSubWindowByType(s_s32CurSelectionId);
        m_pCurrentSubWin = target;
    }
    
    if (target != nullptr) {
        target->ShowImmediate();  // 这个调用没有被执行!
    }
}

调用路径

MainWindow::onWinCtrl() 中:

cpp 复制代码
BaseWindow **pstWin = gstWinCtrlMap[s32Loop].pstWin;  // BaseWindow** 类型
// ...
(*pstWin)->setVisible(bShowFlag);  // 通过基类指针调用

根本原因

C++ 多态机制要求:

  1. 基类方法必须是虚函数(virtual
  2. 通过基类指针或引用调用

BaseWindow::setVisible() 不是虚函数时:

  • 通过 BaseWindow* 指针调用,会调用 BaseWindow::setVisible()
  • 即使子类重写了该方法,也不会被调用
  • 这是函数隐藏(Function Hiding),不是多态

技术细节

cpp 复制代码
// 情况1:非虚函数(当前代码)
BaseWindow* pWin = new MenuWindow(...);
pWin->setVisible(TRUE);  // 调用 BaseWindow::setVisible()

// 情况2:虚函数(修复后)
virtual void setVisible(boolean bShow);  // 基类声明为虚函数
BaseWindow* pWin = new MenuWindow(...);
pWin->setVisible(TRUE);  // 调用 MenuWindow::setVisible()

调试过程

1. 日志分析

发现 onWinCtrl 中调用了 setVisible(1),但菜单未显示。

2. 添加调试日志

MenuWindow::setVisible() 中添加日志:

cpp 复制代码
void MenuWindow::setVisible(boolean bVisible) {
    SKLOG_I(("MenuWindow::setVisible(%d) called", bVisible));
    // ...
}

结果:该日志未出现,说明方法未被调用。

3. 确认调用路径

检查 onWinCtrl 中的调用:

cpp 复制代码
(*pstWin)->setVisible(bShowFlag);  // pstWin 是 BaseWindow**

由于 setVisible 不是虚函数,实际调用的是 BaseWindow::setVisible(),而不是 MenuWindow::setVisible()

4. 验证假设

对比两种调用方式:

cpp 复制代码
// 方式1:通过基类指针(当前代码)
(*pstWin)->setVisible(bShowFlag);  // 调用 BaseWindow::setVisible()

// 方式2:通过子类指针(验证用)
gpstMenuWin->setVisible(bShowFlag);  // 调用 MenuWindow::setVisible()

方式2可以正常工作,证实了问题。

解决方案

方案1:将基类方法改为虚函数(推荐)

cpp 复制代码
// BaseWindow.h
class BaseWindow {
public:
    virtual void setVisible(boolean bShow);  // 添加 virtual 关键字
    // ...
};

优点:

  • 符合面向对象设计原则
  • 支持多态,所有子类都能正确重写
  • 一劳永逸,不需要特殊处理

方案2:特殊处理(临时方案)

cpp 复制代码
// MainWindow.cpp
if (*pstWin == (BaseWindow*)gpstMenuWin) {
    gpstMenuWin->setVisible(bShowFlag);  // 直接调用子类方法
} else {
    (*pstWin)->setVisible(bShowFlag);
}

缺点:

  • 需要为每个重写了 setVisible 的子类添加特殊处理
  • 代码维护性差
  • 不符合开闭原则

经验总结

1. 虚函数的使用原则

在基类中,如果方法可能被派生类重写并需要多态行为,应声明为虚函数:

  • 析构函数:如果基类指针可能指向派生类对象,基类析构函数必须是虚函数
  • 需要多态的方法:如 setVisibleonDrawonEvent

2. 调试技巧

  • 在关键方法入口添加日志,确认是否被调用
  • 对比不同调用方式的行为差异
  • 理解C++多态机制,区分函数隐藏和函数重写

3. 代码审查要点

  • 检查基类中可能被重写的方法是否声明为虚函数
  • 检查通过基类指针调用的方法是否支持多态
  • 使用 override 关键字明确重写意图,让编译器检查

4. 最佳实践

cpp 复制代码
// 推荐写法
class BaseWindow {
public:
    virtual void setVisible(boolean bShow);  // 虚函数
    virtual ~BaseWindow();  // 虚析构函数
};

class MenuWindow : public BaseWindow {
public:
    void setVisible(boolean bVisible) override;  // 使用 override 明确意图
    ~MenuWindow() override;
};

技术要点

  1. C++多态机制:虚函数是实现运行时多态的基础
  2. 函数隐藏 vs 函数重写:非虚函数是隐藏,虚函数是重写
  3. 虚函数表(vtable):虚函数通过虚函数表实现动态绑定
  4. 性能考虑:虚函数调用有轻微性能开销,但在GUI框架中可接受

结论

这个bug源于对C++多态机制的理解不足。将 setVisible 改为虚函数后,问题得到解决。在面向对象设计中,需要多态的方法应声明为虚函数,这是基本设计原则。


关键词:C++多态、虚函数、函数隐藏、嵌入式GUI、FreeRTOS、窗口管理、面向对象设计

相关推荐
Azxcc02 小时前
c++ core guidelines解析--让接口易于使用
开发语言·c++
亭上秋和景清2 小时前
指针进阶: 回调函数
开发语言·前端·javascript
helloworddm2 小时前
NSIS编写C/C++扩展
c语言·开发语言·c++
曹牧2 小时前
Java:Jackson库序列化对象
java·开发语言·python
MediaTea2 小时前
Python:依赖倒置原则(DIP)
开发语言·python·依赖倒置原则
Meteors.2 小时前
安卓进阶——原理机制
android·java·开发语言
AllinGold2 小时前
vscode等IDE使用ssh远程连接云服务器ECS,连接不上的通常问题
bug
深圳佛手2 小时前
LangChain 提供的搜素工具SerpAPIWrapper介绍
开发语言·人工智能·python
apihz3 小时前
反向DNS查询与蜘蛛验证免费API接口详细教程
android·开发语言·数据库·网络协议·tcp/ip·dubbo