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

Phi*_*tor 5 c++ multiple-inheritance

我想知道在编译时是否可以计算基类偏移量。当然,在运行时很容易做到,因为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;
}
Run Code Online (Sandbox Code Playgroud)

在这里,pI1和 和pI1_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 */ }
};
Run Code Online (Sandbox Code Playgroud)

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

编辑:

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

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

}
Run Code Online (Sandbox Code Playgroud)

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

眠りネ*_*ネロク 2

编译器可能会在CClas基类IInterface1和之间引入填充IInterface2

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

以下语句可能会产生错误的地址:

IInterface2* pI2 = reinterpret_cast<IInterface2*>(reinterpret_cast<char*>(&instance) + offsetI2


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