简体   繁体   English

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

[英]C++ unsafe cast workaround

In a complex codebase, I have an array of non-virtual base class pointer (the base class has no virtual methods) 在一个复杂的代码库中,我有一个非虚基类指针数组(基类没有虚方法)

Consider this code: 考虑以下代码:

#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;
}

The output is (compiled with Mingw-w64 GCC 4.9.2 on Win64): 输出是(在Win64上使用Mingw-w64 GCC 4.9.2编译):

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

The reason of the failure is that each instance of TVirtualDerived has a pointer to the virtual table, which TBase has not. 失败的原因是TVirtualDerived的每个实例都有一个指向虚拟表的指针,TBase没有。 So up-casting to TBase without previous type information (from void* to TBase*) is not safe. 因此,在没有先前类型信息(从void *到TBase *)的情况下向TBase上传是不安全的。

The thing is that I cannot avoid casting to void* in the first place. 事情是,我不能避免首先出现无效*。 Adding a virtual method (destructor for example) on the base class works, but at a memory cost (which I want to avoid) 在基类上添加虚方法(例如析构函数)可以工作,但是内存成本(我想避免)

Context: 语境:

we are implementing a signal/slot system, in a very constrained environment (memory severely limited). 我们正在一个非常有限的环境中实现信号/插槽系统(内存严重受限)。 Since we have several millions object which can send or receive signals, this kind of optimization is effective (when it works, of course) 由于我们有数百万个可以发送或接收信号的对象,这种优化是有效的(当它工作时,当然)

Question: 题:

How can I solve this problem? 我怎么解决这个问题? So far, I have found: 到目前为止,我发现:

1 - add a virtual method in TBase. 1 - 在TBase中添加虚拟方法。 Works, but it does not really solve the problem, it avoids it. 工作,但它并没有真正解决问题,它避免了它。 And it is inefficient (too much memory) 这是低效的(太多的内存)

2 - casting to TBase* instead of casting to void* in the array, at the expense of a loss of generality. 2 - 转换为TBase *而不是在阵列中转换为void *,但代价是失去了一般性。 (probably what I will try next) (可能接下来我会尝试)

Do you see another solution? 你看到另一种解决方案吗?

The problem is in you cast. 问题出在你的演员身上。 As you use a C type cast through void , it is equivalent to a reinterpret_cast, which can be poor when subclassing. 当您使用C类型转换为void时 ,它等同于reinterpret_cast,在子类化时可能很差。 In the first part, type is accessible to compiler and your casts are equivalent to static_cast. 在第一部分中,编译器可以访问类型,并且您的强制转换等同于static_cast。

But I cannot understand why you say that you cannot avoid casting to void* in the first place . 但是我无法理解为什么你说你不能避免在第一时间失去* As PrintType internally will convert the void * to a TBase * , you could as well pass a TBase ** . 由于PrintType在内部将void *转换为TBase * ,您也可以传递TBase ** In that case it will work fine : 在这种情况下,它将正常工作:

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);

Alternatively, if you want to use a void ** array, you must explicitely make sure that what you put in it are only TBase * and not pointer to subclasses : 或者,如果使用void **数组,则必须明确确保放入其中的内容仅为TBase * 而不是指向子类的指针

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

Those both method correctly output : 这两种方法都正确输出:

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

You have to consider how the class is laid out in memory. 你必须考虑如何在内存中布置类。 TBase is easy, it's just four bytes with one member: TBase很简单,只有四个字节,只有一个成员:

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

TStaticDerived is the same. TStaticDerived是一样的。 However, TVirtualDerived is totally different. 但是, TVirtualDerived完全不同。 It now has an alignment of 8 and has to start up front with a vtable, containing an entry for the destructor: 它现在具有8的对齐,并且必须从vtable开始,包含析构函数的条目:

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

So when you cast vd to void* and then to TBase* , you are effectively reinterpreting the first four bytes of your vtable (the offset address into ~TVirtualDerived() ) as m_iData . 因此,当您将vdvoid*然后转换为TBase* ,您实际上将vtable的前四个字节(偏移地址转换为~TVirtualDerived() )重新解释为m_iData The solution is to first do a static_cast to TBase* , which will return a pointer to correct starting point of TBase in vd and then to void* : 解决方案是首先对TBase*执行static_cast ,它将返回指向vd正确起始点TBase的指针, 然后返回void*

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

Forget virtual polymorphism. 忘记虚拟多态。 Do it the old fashioned way. 这是老式的方式。

Add one byte to each TBase to indicate type and a switch statement in the print method to "Do The Right Thing." 在每个TBase中添加一个字节以指示打印方法中的类型和switch语句为“Do The Right Thing”。 (this saves you sizeof(pointer) -1 bytes per TBase over the virtual method approach. (通过虚方法方法,这可以节省每个TBase的sizeof(指针)-1个字节。

If adding a byte is still too expensive, consider using C/C++ bit fields (anyone remember those (grin)) to squeeze the type field into some other field that doesn't fill the space available (for example an unsigned integer that has a maximum value of 2^24 - 1) 如果添加一个字节仍然太昂贵,请考虑使用C / C ++位字段(任何人记住那些(笑))将类型字段压缩到其他不填充可用空间的字段(例如,具有最大值2 ^ 24 - 1)

You code will be ugly, true, but your severe memory constraints are ugly, too. 你的代码将是丑陋的,真实的,但你严重的内存限制也是丑陋的。 Ugly code that works is better than beautiful code that fails. 有效的丑陋代码比失败的漂亮代码更好。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM