C++11下通过memcpy直接访问foo类唯一数据成员是否安全?
首先直接给结论:你的假设不成立,这种行为是未定义的,绝对不能依赖。下面分几个关键点详细解释:
1. sizeof(foo) == 4是特定环境的巧合,并非标准规定
你测试得到sizeof(foo)等于4,这只是当前编译器/平台的实现结果,C++11标准完全没有规定这个类的大小必须等于int的大小。
因为foo包含一个虚析构函数,编译器必须为它生成虚表指针(vptr)——这是一个隐藏的成员,用于实现多态逻辑。在大多数32位平台上,虚表指针占4字节,加上int data的4字节,sizeof(foo)应该是8;64位平台上则是16字节(8字节vptr + 8字节int)。
你得到4字节的结果,可能是编译器做了非标准的极端优化,但这绝对不是可移植的行为,换个编译器或平台就会失效。
2. &(this->data)取的是foo对象的起始地址,而非foo::data成员的地址
当T=foo时,bar<T>::data是一个完整的foo对象。&(this->data)获取的是这个foo对象的起始内存地址,而不是它的data成员的地址。
在标准的类内存布局实现中,foo对象的内存结构大致是:
[虚表指针] -> [int data]
所以&(this->data)指向的是虚表指针的位置,而非int data的位置。即使你的测试环境中sizeof(foo)等于4,也不代表两者地址重合——这只能说明当前编译器的布局特殊,但完全不被C++11标准保证。
3. 用memcpy处理非平凡类型是未定义行为
foo类因为有用户定义的虚析构函数,属于非平凡类型(non-trivial type)。根据C++11标准,memcpy只能安全地用于平凡类型(trivial type)——即那些可以通过逐字节拷贝来正确复制的类型(比如内置类型、没有自定义构造/析构/赋值的简单结构体)。
用memcpy拷贝非平凡类型的对象,会导致未定义行为:可能破坏对象的虚表指针,导致后续调用虚函数时崩溃,或者产生其他不可预测的结果。
正确的解决方案
要让bar模板类同时支持内置类型和foo类,你需要为自定义类型提供专门的处理逻辑,比如:
方案1:模板特化
为bar<foo>编写特化版本,明确访问foo的data成员:
template<> class bar<foo> { protected: foo data; public: bar() {} virtual ~bar() {} void read(unsigned char* readBuff, const std::size_t &readSize) { // 其他操作 // 明确访问foo的data成员 std::memcpy(readBuff, &(this->data.data), readSize); // 其他操作 } };
方案2:通用数据指针获取接口
定义一个辅助模板函数,对内置类型直接返回对象地址,对自定义类型返回其数据成员的地址:
#include <type_traits> template<typename T> typename std::enable_if<std::is_trivial<T>::value, const T*>::type get_data_ptr(const T& obj) { return &obj; } // 对foo的特化 template<> const int* get_data_ptr(const foo& obj) { return &obj.data; }
然后在bar的read方法中使用这个函数:
void read(unsigned char* readBuff, const std::size_t &readSize) { // 其他操作 const auto dataPtr = get_data_ptr(this->data); std::memcpy(readBuff, dataPtr, readSize); // 其他操作 }
这种方式更通用,后续添加其他自定义类型时,只需要特化get_data_ptr即可。
内容的提问来源于stack exchange,提问作者Benjamin Barrois

