用 std::is_trivially_copyable_v<T>(C++17起推荐)在编译期判断T是否平凡可复制:要求无非平凡特殊成员函数、无虚函数/虚基类、所有成员及基类均平凡可复制,且无引用或抽象类型成员。

怎么用 std::is_trivially_copyable 判断类是否平凡可复制
直接上结论:用 std::is_trivially_copyable_v<T>(C++17 起推荐)或 std::is_trivially_copyable<T>::value,它在编译期返回 true 当且仅当类型 T 满足所有平凡可复制条件。
注意这不是“能用 memcpy 安全拷贝”那么简单——它还隐含要求:没有非平凡的析构、拷贝/移动构造、拷贝/移动赋值;所有非静态成员和基类也必须是平凡可复制的;不能有虚函数、虚基类;非静态数据成员不能是引用或抽象类类型。
常见误判点:
- 有
const成员变量?✅ 允许(只要其他条件满足) - 有
std::string成员?❌ 不行,std::string非平凡 - 有默认构造函数但没写
= default?⚠️ 如果编译器生成的是非平凡版本(比如有虚函数),就失败
std::is_trivially_copyable 和 memcpy 的关系
它是 memcpy 安全使用的必要条件,但不是充分条件。即使 std::is_trivially_copyable_v<T> 为 true,也不能无脑 memcpy —— 还得确保对象处于有效生命周期内、对齐正确、不违反严格别名规则。
立即学习“C++免费学习笔记(深入)”;
典型适用场景:
- 把 POD 类型数组序列化到二进制 buffer(如网络传输、文件存取)
- 做零开销内存拷贝优化(比如 ring buffer 中的批量移动)
- 配合
std::vector<T>的底层内存操作(前提是T是 trivially copyable)
反例:std::vector<std::string> 不能整体 memcpy,因为 std::string 不满足该 trait;哪怕你只 memcpy 其内部 std::vector 的 data 指针,也会导致浅拷贝和 double-free。
为什么加了空虚函数就失效
一旦类声明了虚函数(哪怕只有 virtual ~A() = default;),std::is_trivially_copyable_v<A> 立刻变成 false。原因在于虚函数引入了虚表指针(vptr),而 vptr 在对象构造/析构过程中被动态修改,直接 memcpy 会复制一个可能已失效或未初始化的 vptr,导致后续调用虚函数时崩溃或未定义行为。
验证方式很简单:
struct A { virtual ~A() = default; };
static_assert(!std::is_trivially_copyable_v<A>); // ✅ 触发
如果真需要“带虚函数但又想 memcpy”,说明设计有问题——应该拆分接口(虚基类)和数据(平凡结构体),而不是强行塞在一起。
容易被忽略的继承与模板影响
派生类是否平凡可复制,不仅取决于自身,还取决于所有基类和成员。哪怕你写的派生类里什么都没加,只要某个基类是非平凡的,整个类就不是平凡可复制的。
模板类尤其危险:
-
std::array<int, 4>✅ 平凡(本质是 C 风格数组) -
std::array<std::string, 4>❌ 非平凡(std::string拖累) - 自定义模板如
Wrapper<T>:必须显式检查T是否 trivially copyable,并用= default声明所有特殊成员函数,否则编译器可能合成非平凡版本
建议在模板定义处加约束:
template <typename T>
requires std::is_trivially_copyable_v<T>
struct FastCopyBuffer { ... };
或者用 static_assert 提前报错,比运行时崩溃友好得多。
最麻烦的情况是跨编译单元的隐式实例化——trait 在一个 TU 里是 true,另一个 TU 因为头文件包含顺序不同导致基类定义不全,可能被误判为 false。务必保证所有相关定义完整可见。


















