C ++不安全的强制转换方法

时间:2015-05-12 21:25:32

标签: c++ virtual

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

考虑以下代码:

#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投射是不安全的。

首先,我无法避免将其视为无效*。 在基类上添加虚方法(例如析构函数)可以工作,但内存成本(我想避免)

上下文:

我们正在一个非常有限的环境中实施信号/插槽系统(内存严重受限)。由于我们有数百万个可以发送或接收信号的对象,这种优化是有效的(当它工作时,当然)

问题:

我该如何解决这个问题?到目前为止,我发现:

1 - 在TBase中添加虚拟方法。工作,但它并没有真正解决问题,它避免了它。它效率低下(内存太多)

2 - 转换为TBase *而不是在数组中转换为void *,但代价是失去了一般性。 (可能接下来我会尝试)

你看到另一种解决方案吗?

3 个答案:

答案 0 :(得分:4)

问题出在你的演员身上。当您使用C类型转换到void 时,它等同于reinterpret_cast,在子类化时可能很差。在第一部分中,编译器可以访问类型,并且您的强制转换等同于static_cast。

但是我无法理解为什么你说你无法避免在第一时间内无效* 。由于PrintType在内部会将void *转换为TBase *,因此您也可以传递TBase **。在这种情况下,它将正常工作:

void PrintType(TBase** pArray, size_t iSize)
{
    for(size_t i = 0; i < iSize; i++)
    {
        TBase *pBase = pArray[i];
        pBase->Print();
    }
}
...
    TBase* vArray[3];
    vArray[0] = &b;
    vArray[1] = &sd;
    vArray[2] = &vd; //VTABLE not taken into account -> pointer not OK
    PrintType(vArray, 3);

或者,如果您想要使用void **数组,则必须明确确保放入其中的内容仅为TBase * ,而不是指向子类

void* vArray[3];
vArray[0] = &b;
vArray[1] = static_cast<TBase *>(&sd);
vArray[2] = static_cast<TBase *>(&vd);
PrintType(vArray, 3);

这两种方法都正确输出:

Data = 0
Data = 1
Data = 2
Data = 0
Data = 1
Data = 2

答案 1 :(得分:3)

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

 _ _ _ _
|_|_|_|_|
 ^
 m_iData

TStaticDerived是一样的。但是,TVirtualDerived完全不同。它现在的对齐为8,并且必须使用vtable启动,包含析构函数的条目:

 _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|
 ^               ^
 vtable          m_iData

因此,当您将vd转换为void*然后转换为TBase*时,您实际上是将vtable的前四个字节(偏移地址转换为~TVirtualDerived())重新解释为m_iData。解决方案是首先执行static_castTBase*,这将返回指向TBase中的vd然后的正确起点的指针void*

vArray[2] = static_cast<TBase*>(&vd); // now, pointer is OK

答案 2 :(得分:0)

忘记虚拟多态。这是老式的方式。

在每个TBase中添加一个字节,以指示打印方法中的类型和switch语句,以“做正确的事”。&#34; (通过虚方法方法,这可以节省每个TBase的sizeof(指针)-1个字节。

如果添加一个字节仍然太昂贵,请考虑使用C / C ++位字段(任何人记住那些(笑))将类型字段压缩到其他不填充可用空间的字段中(例如无符号整数,其最大值为2 ^ 24 - 1)

你的代码将是丑陋的,真实的,但你严重的内存限制也是丑陋的。丑陋的代码比失败的漂亮代码更好。