虚继承与类成员函数指针大小问题及FakeIt模拟类疑问
虚继承类的成员函数指针与FakeIt模拟问题解答
问题复现代码
用户在MSVC下编写的测试代码:
template <typename T> void CanMock() { class SimpleType {}; static_assert(sizeof(void(SimpleType::*)()) == sizeof(void(T::*)()), "Can't mock a type with multiple inheritance or with " "non-polymorphic base class"); } int main() { class base {}; class derived : public base {}; class derivedVirtual : virtual public base {}; CanMock<derived>(); CanMock<derivedVirtual>(); // MSVC下触发断言失败 }
FakeIt测试用例:
TEST_CASE("Mock") { struct base {}; struct derivedVirtual : virtual public base { virtual int A() = 0; }; fakeit::Mock<derivedVirtual> mock; // MSVC编译失败,Clang可通过 }
疑问解答
1. 虚继承为何会增加成员函数指针的大小?
在MSVC的实现中,普通成员函数指针仅需存储函数的内存地址。但对于虚继承的类,成员函数指针需要额外携带虚基类的偏移量——因为虚基类在对象内存布局中的位置不是固定的(不同派生类继承同一个虚基类时,偏移可能不同),调用成员函数时必须先通过偏移量计算出虚基类的实际地址,才能正确执行函数逻辑。这种额外的信息存储,直接导致了虚继承类的成员函数指针尺寸大于普通无继承类的成员函数指针。
2. 为何Clang中断言不会失败?
这是不同编译器的ABI(应用二进制接口)实现差异:Clang对成员函数指针做了统一化设计,无论类是否虚继承,非虚成员函数指针的尺寸都保持一致(例如64位系统下都是8字节)。而MSVC的实现则区分了普通继承和虚继承的场景,针对虚继承类的成员函数指针使用了更大的存储结构,所以触发了断言。两种实现都符合C++标准,只是厂商的设计选择不同。
3. 虚继承与类的可模拟性的关联?
FakeIt这类Mock框架的核心是通过劫持或替换类的虚函数表(vtable),来模拟虚函数的行为。但虚继承类的对象布局非常复杂:虚基类的位置不固定,vtable中还包含额外的虚基类偏移信息,这会大幅增加框架生成代理类的难度,甚至可能导致内存布局错乱、调用逻辑异常。FakeIt中的这个断言是一种防御性检查,用来避免处理这类复杂布局带来的潜在问题,因此直接禁止了对虚继承类的模拟。
解决方案
针对FakeIt模拟虚继承类的需求,可尝试以下方案:
- 调整类继承结构:如果业务逻辑允许,将虚继承改为普通继承,或者把需要模拟的虚函数迁移到非虚继承的基类中,绕过FakeIt的断言限制。
- 手动实现Mock类:不依赖FakeIt框架,自行编写继承自
derivedVirtual的Mock类,重写纯虚函数并实现测试所需的逻辑,直接在测试中使用该手动Mock类。 - 临时修改FakeIt源码(不推荐):找到FakeIt中对应的
static_assert代码并注释掉,但这种修改会随着框架版本更新被覆盖,且可能引发未知的运行时错误,仅适合临时测试场景。
内容的提问来源于stack exchange,提问作者Dundo
相关产品推荐
相关产品推荐

