[英]C++ unsafe cast workaround
在一个复杂的代码库中,我有一个非虚基类指针数组(基类没有虚方法)
考虑以下代码:
#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 *,但代价是失去了一般性。 (可能接下来我会尝试)
你看到另一种解决方案吗?
问题出在你的演员身上。 当您使用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
你必须考虑如何在内存中布置类。 TBase
很简单,只有四个字节,只有一个成员:
_ _ _ _
|_|_|_|_|
^
m_iData
TStaticDerived
是一样的。 但是, TVirtualDerived
完全不同。 它现在具有8的对齐,并且必须从vtable开始,包含析构函数的条目:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|
^ ^
vtable m_iData
因此,当您将vd
为void*
然后转换为TBase*
,您实际上将vtable的前四个字节(偏移地址转换为~TVirtualDerived()
)重新解释为m_iData
。 解决方案是首先对TBase*
执行static_cast
,它将返回指向vd
正确起始点TBase
的指针, 然后返回void*
:
vArray[2] = static_cast<TBase*>(&vd); // now, pointer is OK
忘记虚拟多态。 这是老式的方式。
在每个TBase中添加一个字节以指示打印方法中的类型和switch语句为“Do The Right Thing”。 (通过虚方法方法,这可以节省每个TBase的sizeof(指针)-1个字节。
如果添加一个字节仍然太昂贵,请考虑使用C / C ++位字段(任何人记住那些(笑))将类型字段压缩到其他不填充可用空间的字段(例如,具有最大值2 ^ 24 - 1)
你的代码将是丑陋的,真实的,但你严重的内存限制也是丑陋的。 有效的丑陋代码比失败的漂亮代码更好。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.