在复杂的代码库中,我有一个非虚拟基类指针数组(该基类没有虚拟方法)

考虑以下代码:

#include <iostream>

using namespace std;

class TBase
{
    public:
        TBase(int i = 0) : m_iData(i) {}
        ~TBase(void) {}

        void Print(void) {std::cout << "Data = " << m_iData << std::endl;}

    protected:
        int     m_iData;
};

class TStaticDerived : public TBase
{
    public:
        TStaticDerived(void) : TBase(1) {}
        ~TStaticDerived(void)  {}
};

class TVirtualDerived : public TBase
{
    public:
        TVirtualDerived(void) : TBase(2) {}
        virtual ~TVirtualDerived(void) {} //will force the creation of a VTABLE
};

void PrintType(TBase *pBase)
{
    pBase->Print();
}

void PrintType(void** pArray, size_t iSize)
{
    for(size_t i = 0; i < iSize; i++)
    {
        TBase *pBase = (TBase*) pArray[i];
        pBase->Print();
    }
}


int main()
{
    TBase b(0);
    TStaticDerived sd;
    TVirtualDerived vd;

    PrintType(&b);
    PrintType(&sd);
    PrintType(&vd); //OK

    void* vArray[3];
    vArray[0] = &b;
    vArray[1] = &sd;
    vArray[2] = &vd; //VTABLE not taken into account -> pointer not OK
    PrintType(vArray, 3);

    return 0;
}

输出为(在Win64上与Mingw-w64 GCC 4.9.2编译):
Data = 0
Data = 1
Data = 2
Data = 0
Data = 1
Data = 4771632

失败的原因是每个TVirtualDerived实例都有一个指向虚拟表的指针,而TBase没有。因此,没有先前的类型信息(从void *到TBase *)向上广播到TBase是不安全的。

问题是,我一开始就无法避免将其转换为void *。
在基类上添加虚拟方法(例如析构函数)是可行的,但是会占用内存(我想避免)

语境:

我们正在非常受限的环境(内存受到严格限制)中实现信号/插槽系统。由于我们有数以百万计的对象可以发送或接收信号,因此这种优化是有效的(当然,当它起作用时)

问题:

我怎么解决这个问题?到目前为止,我发现:

1-在TBase中添加虚拟方法。可行,但是并不能真正解决问题,可以避免。而且效率低下(内存过多)

2-强制转换为TBase *,而不是强制转换为数组中的void *,这是以牺牲通用性为代价的。 (可能接下来我会尝试)

您看到其他解决方案了吗?

最佳答案

您必须考虑如何在内存中布置类。 TBase很简单,只有一个成员只有四个字节:

 _ _ _ _
|_|_|_|_|
 ^
 m_iData
TStaticDerived是相同的。但是,TVirtualDerived完全不同。现在它的对齐方式为8,并且必须先从一个vtable开始,其中包含析构函数的条目:
 _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|
 ^               ^
 vtable          m_iData

因此,当您将vd转换为void*,然后转换为TBase*时,您实际上将vtable的前四个字节(偏移量地址转换为~TVirtualDerived())重新解释为m_iData。解决方案是首先对static_cast做一个TBase*,这将返回一个指针,以纠正TBasevd的正确起点,然后返回void*:
vArray[2] = static_cast<TBase*>(&vd); // now, pointer is OK

10-01 01:58