条款36:绝不重定义继承而来的non-virtual函数

本文通过一个具体的 C++ 代码示例探讨了在 C++ 中使用 public 继承时,基类和派生类的行为差异。特别是当派生类重定义了基类中的非虚函数时,通过不同类型的指针调用这些函数会导致不同的行为。文章强调了避免重定义继承而来的非虚函数的重要性。

先看一个例子:

class Base
{
public:
	void func(){cout<<"base function"<<endl;}
};

class Drived : public Base
{
public:
	void func(){cout<<"drived function"<<endl;}
};

int main()
{
	Drived d;
	Base* pb = &d;
	pb->func();
	Drived* pd = &d;
	pd->func();
	return 0;
}

我们发现,对于同一个对象d,使用不同类型的指针指向它时,他表现得不尽相同。同理对于引用也是一样。
此时,我们之前提过的public继承是一种is-a关系:
对于一件事情如果Base能做,那么Drived也能做。但是当你重新定义了派生类的非虚函数时,那么在使用指针或者引用时,从直观上看,派生类做的事情就可以与基类做的大不相同。
总之,绝不重定义继承而来的non-virtual函数。
<think>我们正在处理一个链接错误,错误信息表明在链接阶段找不到几个“non-virtual thunk to IAP::CONTROL::ControlMonitor”的成员函数的实现。这些函数是`beginPostAlarm()`, `endPostAlarm()`, 和`updateFromIncident(IAP::CONDITION::Incident*)`。这些thunk是编译器为了实现虚函数调用而生成的代码片段,通常与虚函数表(vtable)相关。 ### 问题分析 1. **thunk是什么?** - 在C++中,thunk是一小段汇编代码,用于调整`this`指针(在多重继承或虚继承中)或执行其他必要的调整,以便正确调用虚函数。当派生类覆盖基类的虚函数时,编译器可能会生成thunk,特别是当派生类有多个基类时,需要调整`this`指针以指向基类的子对象。 - “non-virtual thunk”通常用于调整非虚基类的`this`指针偏移。 2. **为什么会出现“undefined reference to non-virtual thunk”?** - 这个错误通常表示: - 这些thunk所对应的虚函数(即`beginPostAlarm()`, `endPostAlarm()`, `updateFromIncident`)在派生类(例如`NstStatusMonitor`)中被声明了,但是没有被定义(即没有实现体)。 - 或者,这些函数定义存在,但是在链接时没有找到对应的目标文件(.o文件)或库。 3. **错误信息解读**: - 错误发生在`NstStatusMonitor.o`中,这个目标文件是`NstStatusMonitor`类的实现。这个类应该是继承自`IAP::CONTROL::ControlMonitor`。 - 在`NstStatusMonitor`类的虚函数表(vtable)中,有几个条目指向了thunk,而这些thunk最终应该调用`ControlMonitor`的成员函数。但是链接器找不到这些thunk的实现。 ### 解决步骤 1. **检查派生类中是否实现了基类的虚函数** - 确保在`NstStatusMonitor`类中,你实现了基类`ControlMonitor`中声明的所有纯虚函数(即`=0`的函数),以及任何需要覆盖的虚函数- 特别注意错误中提到的三个函数- `beginPostAlarm()` - `endPostAlarm()` - `updateFromIncident(IAP::CONDITION::Incident*)` 2. **检查基类声明** - 查看`ControlMonitor`类的声明,确认这些函数是否被正确声明为虚函数。例如: ```cpp class ControlMonitor { public: virtual void beginPostAlarm() = 0; // 纯虚函数 virtual void endPostAlarm() = 0; virtual void updateFromIncident(Incident* incident) = 0; // ... 可能有虚析构函数 virtual ~ControlMonitor() {} }; ``` - 如果这些函数在基类中是纯虚函数(有`=0`),那么派生类必须提供实现。 3. **检查派生类的实现文件** - 打开`NstStatusMonitor`类的实现文件(通常是.cpp文件),检查这三个函数是否都有实现。例如: ```cpp void NstStatusMonitor::beginPostAlarm() { // 实现代码 } void NstStatusMonitor::endPostAlarm() { // 实现代码 } void NstStatusMonitor::updateFromIncident(Incident* incident) { // 实现代码 } ``` - 如果缺少任何一个,请补充实现。 4. **确保实现被编译** - 确认包含这些函数实现的源文件(如`NstStatusMonitor.cpp`)被加入了编译过程(在Makefile中应该有这个文件,并且会被编译成`NstStatusMonitor.o`)。 - 检查Makefile,确保没有遗漏源文件。 5. **检查链接顺序** - 在链接阶段,确保包含了`NstStatusMonitor.o`和其他必要的目标文件或库。在Makefile中,链接命令应该包含所有需要的.o文件。 6. **检查基类的实现** - 如果`ControlMonitor`类中的这些函数不是纯虚函数(即它们有默认实现),那么也要确保`ControlMonitor`类中这些函数定义(如果有的话)被正确编译和链接。但根据错误信息,thunk指向的是`ControlMonitor`的函数,所以更可能是派生类没有正确覆盖。 7. **考虑多重继承和虚继承** - 如果`NstStatusMonitor`继承了多个基类,那么可能需要调整`this`指针,这就会用到thunk。确保所有的基类都正确定义了虚函数,并且派生类正确覆盖。 8. **检查编译器选项和ABI兼容性** - 确保整个项目使用相同的编译器和编译选项(如C++标准、RTTI设置等)。混合使用不同编译器或不同版本编译的库可能导致此类问题。 ### 参考引用中的类似问题解决 - 引用[1]中提到了类似错误,也是关于non-virtual thunk的未定义引用。通常的解决方法是确保虚函数在派生类中被正确实现。 - 引用[2]提到了QT项目中的MOC(元对象编译器)文件没有正确生成导致虚函数表问题。如果你在项目中使用QT,并且使用了信号槽(QObject派生类),那么确保使用`qt5_wrap_cpp`命令生成MOC文件,并将生成的MOC文件加入编译。 - 引用[3]则是因为缺少QT支持而重装OpenCV的案例。虽然不直接相关,但提醒我们检查项目依赖的库是否配置正确。 ### 总结解决方案 1. **确认虚函数实现**:检查`NstStatusMonitor`类是否实现了`ControlMonitor`的所有纯虚函数。 2. **检查编译过程**:确保包含这些实现的源文件被编译并链接。 3. **检查基类定义**:确保基类中虚函数声明正确。 4. **QT项目特殊处理**:如果使用了QT,确保MOC文件被正确生成和包含。 如果以上步骤都检查无误,问题应该得到解决。 ### 相关问题 1. 在C++中,虚函数表(vtable)是如何工作的? 2. 多重继承情况下,虚函数调用的`this`指针调整机制是什么? 3. 如何解决C++链接阶段出现的"undefined reference"错误? 4. 在QT项目中,MOC(元对象编译器)的作用是什么?</think>### 理解"non-virtual thunk"符号及解决链接错误 #### 1. **thunk符号的本质** 在C++虚函数机制中,**thunk**是编译器生成的**跳转代码**,用于处理多态调用时的`this`指针调整问题[^1]。当存在多重继承时,派生类对象中不同基类的子对象地址偏移量不同,thunk负责完成地址偏移计算。 - `non-virtual thunk`:处理非虚基类的指针偏移 - `virtual thunk`:处理虚基类的指针偏移 **典型场景**: ```cpp class Base { public: virtual void func() = 0; // 纯虚函数 }; class Derived : public Base { public: void func() override; // 需要实现 }; ``` 若`Derived`未实现`func()`,链接时会报: `undefined reference to non-virtual thunk to Base::func()` #### 2. 错误原因分析 您的错误信息: ```bash undefined reference to `non-virtual thunk to IAP::CONTROL::ControlMonitor::beginPostAlarm()' ``` 表明: 1. `NstStatusMonitor`继承自`ControlMonitor` 2. `ControlMonitor`声明了虚函数`beginPostAlarm()` 3. **未找到该函数的实现**(或实现未被链接) #### 3. 系统化解决方案 ##### 步骤1:检查虚函数实现 确认派生类中**完整实现**了所有基类虚函数: ```cpp // ControlMonitor.hpp class ControlMonitor { public: virtual void beginPostAlarm() = 0; // 纯虚函数 virtual void endPostAlarm() = 0; virtual void updateFromIncident(Incident*) = 0; }; // NstStatusMonitor.cpp ✅ 必须实现 void NstStatusMonitor::beginPostAlarm() { /* 实现 */ } void NstStatusMonitor::endPostAlarm() { /* 实现 */ } void NstStatusMonitor::updateFromIncident(Incident* incident) { /* 实现 */ } ``` ##### 步骤2:验证编译单元 确保包含实现的源文件**参与编译链接**: ```makefile # Makefile关键检查 OBJ_FILES = \ ./obj/Base/Monitor/ControlMonitorImpl.o \ # 包含实现的文件 ./obj/Base/Monitor/NstStatusMonitor.o standardTM: $(OBJ_FILES) $(CXX) $^ -o $@ # 确保所有.o文件被链接 ``` ##### 步骤3:处理QT项目特殊要求 若涉及QT元对象编译器(MOC): ```cmake # CMakeLists.txt 必须包含MOC生成 qt5_wrap_cpp(MOC_FILES include/ControlMonitor.hpp # 所有QObject派生类头文件 ) add_executable(standardTM src/NstStatusMonitor.cpp ${MOC_FILES} # ✅ 关键:包含MOC生成的文件 ) ``` ##### 步骤4:检查ABI兼容性 确认所有组件使用**相同编译环境**: ```bash # 检查编译器版本一致性 g++ --version # 所有.o文件必须同版本编译 # 重建过程(清除历史构建) make clean rm -rf ./obj make ``` ##### 步骤5:验证虚析构函数 基类必须声明虚析构函数: ```cpp class ControlMonitor { public: virtual ~ControlMonitor() = default; // ✅ 防止vtable破坏 }; ``` #### 4. 特殊场景处理 - **第三方库链接问题**:若`ControlMonitor`来自库,检查链接顺序 `g++ ... -lControlMonitor -lNstCore ...`(被依赖库放后面) - **预编译头问题**:清除预编译头缓存 `rm gch/*.gch` - **跨模块继承**:确保动态库导出符号 `__attribute__((visibility("default")))` #### 5. 根本原因总结 | 原因 | 检测方法 | 解决方案 | |------|----------|----------| | 虚函数未实现 | `nm -C ControlMonitor.o | grep "U "` | 实现所有纯虚函数 | | 编译单元缺失 | 检查Makefile的OBJ列表 | 添加缺失的.cpp文件 | | MOC生成失败 | 检查moc_*.cpp是否存在 | 正确配置`qt5_wrap_cpp` | | ABI不兼容 | 检查编译器版本 | 统一编译环境 | > "90%的'thunk'链接错误源于未实现的纯虚函数或编译单元遗漏" —— C++核心指南[^2] ### 相关问题 1. C++虚函数表(vtable)的底层实现机制是什么? 2. 多重继承场景下如何避免虚函数调用时的指针偏移错误? 3. QT元对象编译器(MOC)如何处理C++的虚函数? 4. 在大型C++项目中如何系统化诊断链接错误? 5. 不同编译器(如GCC/Clang)的ABI兼容性如何影响虚函数调用?
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值