C++不安全的强制转换解决方法
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没有。因此,在没有先前类型信息的情况下向上转换到TBase(从void*到TBase*)是不安全的。
问题是,我一开始就无法避免铸造虚空。在基类上添加一个虚拟方法(例如析构函数)是可行的,但需要花费内存(我想避免)
上下文:
我们正在一个非常受限的环境(内存严重受限)中实现一个信号/时隙系统。由于我们有数百万个可以发送或接收信号的对象,这种优化是有效的(当然,当它工作时)
问题:
我该如何解决这个问题?到目前为止,我发现:
1-在TBase中添加一个虚拟方法。有效,但它并不能真正解决问题,而是避免了问题。而且它效率低下(内存太多)
2-铸造到TBase*,而不是铸造到阵列中的void*,代价是失去通用性。(可能是我下一步要尝试的)
你看到其他解决方案了吗?
问题出在您的铸件上。当您通过void使用C类型强制转换时,它相当于repret_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
CCD_ 7是相同的。然而,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添加一个字节以指示类型,并在打印方法中切换一条语句为"Do the Right Thing"(这为您节省了sizeof(指针)-与虚拟方法相比,每个TBase可节省1个字节。
如果添加一个字节仍然太昂贵,可以考虑使用C/C++位字段(任何人都记得这些(grin))将类型字段压缩到其他无法填充可用空间的字段中(例如,最大值为2^24-1的无符号整数)
你的代码会很难看,没错,但你严重的内存限制也很难看。有效的丑陋代码比失败的漂亮代码要好。
- 将 const 转换为 const char* 无效,我该如何解决?使用 gcc7 时失败
- 如何解决隐式转换丢失整数精度:'size_t'(又名"无符号长")到'int'警告?
- 如何在构建对象堆栈时解决转换错误?
- 从 int 中剥离位时,编译器会警告一个转换,但不警告其他转换.有解决方法吗?
- C++ C4244 =':从"std::streamsize"转换为"无符号短",可能会丢失数据;有什么解决办法吗?
- 转换为非标量误差是什么意思?我该如何解决?
- 如何将这种递归解决方案转换为分而治之?
- 如何解决ostream_iterator转换问题?
- 将多个非原始递归调用转换为迭代解决方案
- C++ 03 类模板 这是转换构造函数还是转换运算符?以及如何声明解决此问题的方法
- 如何解决此错误(将WCHAR_T转换为BSTR)
- 我在 TSP 的C++解决方案中遇到转换错误
- 由于字符串到JSON转换而产生的异常如何解决
- 如何解决错误:从" int* "到" int "的转换无效
- 窄转换编译警告.如何解决它
- 解决隐藏歧义的不明确用户定义转换
- 如何在C VS2012中转换或创建.NET V3.5解决方案(特别是工具集)
- C++不安全的强制转换解决方法
- 如何解决转换构造函数和普通构造函数之间的歧义
- 数据转换解决方案