C++11默认拷贝赋值运算符何时采用位拷贝而非成员拷贝?
在x86-64架构的GCC 13.1和Clang 16.0.0编译器中,copy<PrivateBase>函数采用成员拷贝,而copy<PublicBase>函数采用位拷贝。相关源码及汇编代码如下:
class PublicBase { public: int num; char c1; }; class PrivateBase { private: int num; char c1; }; template<typename T> __attribute_noinline__ void copy(T *dst, T *src) { *dst = *src; } template void copy(PublicBase *dst, PublicBase *src); template void copy(PrivateBase *dst, PrivateBase *src);
对应的汇编代码:
void copy<PublicBase>(PublicBase*, PublicBase*): mov rax, QWORD PTR [rsi] mov QWORD PTR [rdi], rax ret void copy<PrivateBase>(PrivateBase*, PrivateBase*): mov eax, DWORD PTR [rsi] mov DWORD PTR [rdi], eax movzx eax, BYTE PTR [rsi+4] mov BYTE PTR [rdi+4], al ret
C++11中的默认拷贝赋值运算符何时会使用位拷贝而非成员拷贝?is_trivially_copyable和is_pod似乎都无法直接给出答案。
is_trivially_copyable
根据cppreference中is_trivially_copyable的定义:
非潜在重叠子对象的可平凡拷贝类型对象,是唯一能通过
std::memcpy安全拷贝的C++对象。
PublicBase和PrivateBase都属于可平凡拷贝类型,且都不是潜在重叠子对象,但PrivateBase却采用成员拷贝而非位拷贝。
is_pod
当PublicBase或PrivateBase存在派生类时,PrivateBase的派生类会复用基类的填充空间,而PublicBase的派生类不会。因此PrivateBase采用成员拷贝是合理的——否则调用copy<PrivateBase>(derived, base)时,基类的填充空间可能会覆盖PrivateDerived::c2。
相关验证代码及输出如下:
class PublicDerived : public PublicBase { public: char c2; }; class PrivateDerived : public PrivateBase { private: char c2; }; int main() { std::cout << "sizeof(PublicBase)=" << sizeof(PublicBase) << std::endl; std::cout << "sizeof(PublicDerived)=" << sizeof(PublicDerived) << std::endl; std::cout << "sizeof(PrivateBase)=" << sizeof(PrivateBase) << std::endl; std::cout << "sizeof(PrivateDerived)=" << sizeof(PrivateDerived) << std::endl; return 0; } // Output: // sizeof(PublicBase)=8 // sizeof(PublicDerived)=12 // sizeof(PrivateBase)=8 // sizeof(PrivateDerived)=8
我对编译器如何决定是否复用基类填充空间存在疑惑。有相关问题提到,POD类型不会复用基类的填充空间。
根据cppreference中POD类的定义:
POD类是满足以下条件的类:
- C++11之前:
- 是聚合类(无私有或保护非静态数据成员)
- 无用户声明的拷贝赋值运算符
- 无用户声明的析构函数
- 无非POD类(或此类数组)或引用类型的非静态数据成员
- C++11及以后:
- 是平凡类
- 是标准布局类(所有非静态数据成员的访问控制权限一致)
- 无非POD类(或此类数组)类型的非静态数据成员
在C11之前,PrivateBase不是POD类型(因为它有私有数据成员),但从C11开始,它成为了POD类型(所有非静态数据成员的访问控制权限一致)。
类型特性验证代码及输出:
#include <type_traits> #include <iostream> int main() { std::cout << "PublicBase: is_standard_layout=" << std::is_standard_layout<PublicBase>::value << ", is_trivial=" << std::is_trivial<PublicBase>::value << ", is_pod=" << std::is_pod<PublicBase>::value << std::endl; std::cout << "PrivateBase: is_standard_layout=" << std::is_standard_layout<PrivateBase>::value << ", is_trivial=" << std::is_trivial<PrivateBase>::value << ", is_pod=" << std::is_pod<PrivateBase>::value << std::endl; } // Output: // PublicBase: is_standard_layout=1, is_trivial=1, is_pod=1 // PrivateBase: is_standard_layout=1, is_trivial=1, is_pod=1
内容的提问来源于stack exchange,提问作者Zihe Liu

