在编译时计算基类的偏移量

Calculate offset of base class at compile time

本文关键字:偏移量 基类 计算 编译      更新时间:2023-10-16

我想知道在编译时是否可以计算基类偏移量。当然,这在运行时相当容易,因为可以利用static_cast的功能,并且偏移量只是指向派生类的指针的基本指针之间的差异。

我第一次尝试在编译时获取它,如下所示:

struct InterfaceRoot {};
struct IInterface1 : InterfaceRoot {
virtual void MethodI1() = 0;
};
struct IInterface2 : InterfaceRoot {
virtual void MethodI2() = 0;
};
class CClass : public IInterface1, public IInterface2 {
virtual void MethodI1() override { /* do something */ }
virtual void MethodI2() override { /* do something */ }     
};
int main() {
CClass instance;
constexpr int offsetI1 = 0; //first base class has no offset
constexpr int offsetI2 = sizeof(IInterface1);
//check pointer values against static_cast
IInterface1* pI1 = reinterpret_cast<IInterface1*>(reinterpret_cast<char*>(&instance) + offsetI1);
IInterface2* pI2 = reinterpret_cast<IInterface2*>(reinterpret_cast<char*>(&instance) + offsetI2);
IInterface1* pI1_static_cast = static_cast<IInterface1*>(&instance);
IInterface2* pI2_static_cast = static_cast<IInterface2*>(&instance);
return 0;
}

在这里,pI1pI1_static_cast如预期相等。霍维尔弗,pI2pI2_static_cast不平等!?

我可以通过向InterfaceRoot添加一个虚拟函数或将其全部排除在外来解决此问题。这是什么原因呢?

如果我像这样设置继承树,它适用于上述方法:

struct InterfaceRoot {
virtual ~InterfaceRoot() {}
};
struct IInterface1 : InterfaceRoot {
virtual void MethodI1() = 0;
};
struct IInterface2 : InterfaceRoot {
virtual void MethodI2() = 0;
};
class CClass : public IInterface1, public IInterface2 {
virtual void MethodI1() override { /* do something */ }
virtual void MethodI2() override { /* do something */ }
};

有人知道这是为什么吗?顺便说一下,我正在使用Visual Studio 2017。有没有另一种方法可以在编译时实现我的目标,或者我最好在运行时计算 ofset 并有较小的运行时开销?

编辑:

一个有效的运行时实现可能如下所示:

template<typename Derived, typename Base>
inline int CalcBaseOffset() {
const static int s_off = (reinterpret_cast<char*>(static_cast<Base*>(reinterpret_cast<Derived*>(0x10000000))) - reinterpret_cast<char*>(0x10000000));
return s_off;
};
int main() {
//...
int offsetI1_RT = CalcBaseOffset<CClass, IInterface1>();
int offsetI2_RT = CalcBaseOffset<CClass, IInterface2>();
// add offsets to pointer like in the code sample above
}

这种方法会产生准确的结果,但代价是运行时开销很小(如果无法在编译时计算偏移量以消除此开销,则运行时开销是可以接受的)。

编译器可以在基类IInterface1IInterface2之间的CClas中引入填充。

基本上X: sizeof(CClas)>= sizeof(IInterface1) + sizeof(IInterface2)

然后,以下语句可能会屈服于错误的地址:

面2* pI2 = reinterpret_cast<IInterface2*>(reinterpret_cast<char*>(&instance) + 偏移I2


X请注意,如果基类没有virtual成员函数并且由于空基优化而为空(即没有数据成员),则这可能不成立。