C++派生类调用基类方法时如何强制调用基类版本虚函数
问题场景
给定如下C++示例代码:
class A{ public: void f1() { /* * Other functions that happen here. * These calls needs to be made at A's f1() function * And cannot be in derived class. */ f2(); } virtual void f2() { std::cout << "I am base class" << std::endl; } }; class B : A { public: void f2() { std::cout << "I am derived class" << std::endl; } void f3() { A::f1(); } }; int main() { B b; b.f3(); return 0; }
代码默认运行输出为:
I am derived class
约束条件
- 完全不能修改类A的任何代码实现
- f2()必须通过f1()调用,类B的f3()不允许直接调用f2(),且必须调用A类的f1()——因为A::f1()包含派生类无法重写的必要逻辑,类B不可重写f1()
- 常规方案无效说明:把实例做
static_cast转换为A类型后再调用f1(),依然会触发多态调用派生类重写的f2(),普通的派生类调用父类函数的方案不适用于本场景。
核心需求:调用B::f3()时强制触发A::f2(),也就是调用基类版本函数时,强制该基类函数内部调用的所有虚函数都使用基类版本。
可行方案
标准C++层面没有专门的语法直接实现这个需求,实际工程里可以按场景选下面两种方案:
方案1:切片构造临时A实例(标准兼容,无未定义行为)
如果A::f1()里的逻辑不需要修改原B实例的成员状态,或者允许逻辑运行在临时对象上,直接在栈上构造一个和当前实例A部分成员一致的纯A类型临时对象,调用这个临时对象的f1()即可。临时对象的动态类型就是A,内部的虚调用会直接落到A的实现上:
void f3() { A tmp = *this; // 切片构造纯A临时对象,拷贝当前实例的A段成员数据 tmp.f1(); // 此处f1()内部调用的f2()必然是A::f2() }
这个方案完全符合C++标准,不存在未定义行为,缺点是f1()的所有操作都作用在临时对象上,执行结束后临时对象销毁,不会改动原B实例的状态。
方案2:临时替换虚表指针(依赖编译器ABI,生产环境可用)
如果要求A::f1()的逻辑直接作用在当前B实例上,可以利用主流C++编译器的通用实现规则:GCC、Clang使用的Itanium ABI,以及MSVC使用的Windows ABI,都把虚表指针存在对象内存的起始位置。只要在调用A::f1()前临时把这个指针替换为A类的虚表地址,调用完成后立刻恢复原B类的虚表指针,就能让f1()执行期间的所有虚调用都落到A类的实现上。
以GCC/Clang的Itanium ABI为例,实现代码如下:
class B : A { private: static void* getAVtable() { A a; return *reinterpret_cast<void**>(&a); } public: void f2() { std::cout << "I am derived class" << std::endl; } void f3() { void* originVtable = *reinterpret_cast<void**>(this); // 保存当前B类的虚表指针 void* aVtable = getAVtable(); *reinterpret_cast<void**>(this) = aVtable; // 临时替换为A类虚表 try { A::f1(); // 执行期间所有虚调用都会走A类版本 } catch (...) { *reinterpret_cast<void**>(this) = originVtable; // 异常场景下也要保证虚表恢复 throw; } *reinterpret_cast<void**>(this) = originVtable; // 恢复原虚表 } };
这个方案在主流编译器的生产环境中已经被广泛验证可用,没有实际运行问题,但属于依赖编译器具体实现的行为,不在C++标准的保证范围内。
内容的提问来源于stack exchange,提问作者Hadi
相关产品推荐
相关产品推荐

