类型是平凡可移动的当且仅当同时满足std::is_trivially_move_constructible_v和std::is_trivially_move_assignable_v为true,二者缺一不可;仅std::is_trivial_v为true不充分,因其要求更严格。

如何用 std::is_trivially_move_constructible 和 std::is_trivially_move_assignable 判断平凡可移动
类型是否“平凡可移动”,C++ 标准没有直接提供 std::is_trivially_movable 这样的单一 trait。必须同时满足两个条件:平凡移动构造 + 平凡移动赋值。缺一不可。
注意:std::is_trivially_move_constructible<t></t> 检查的是 T(T&&) 是否为 trivial(即编译器生成的默认移动构造函数,且所有子对象也满足),不是“是否存在该函数”。同理,std::is_trivially_move_assignable<t></t> 检查 T& operator=(T&&) 是否 trivial。
- 两者都为
true→ 类型是平凡可移动的(可被memmove级别优化) - 任一为
false→ 不是平凡可移动;哪怕你写了= default,只要成员里有非平凡移动语义的子对象(如std::string、std::vector),结果就是false - 对
const、引用、函数类型等,这两个 trait 通常为false(不满足移动语义前提)
为什么不能只看 std::is_trivial<t></t>
std::is_trivial<t></t> 是更强的条件:要求类型同时满足平凡默认构造、平凡拷贝、平凡移动、平凡析构,且无虚函数/虚基类等。但平凡可移动只要求移动相关操作是 trivial 的——它允许类型有非平凡的拷贝或默认构造。
典型反例:struct S { S() { /* 非平凡构造 */ } int x; }; —— std::is_trivial_v<s></s> 是 false,但 std::is_trivially_move_constructible_v<s></s> 和 std::is_trivially_move_assignable_v<s></s> 都是 true(因为移动操作由编译器隐式生成且无副作用)。
- 误用
std::is_trivial会过度保守,把实际可平凡移动的类型判为否 - 尤其在实现泛型容器(如自定义 vector)做 memcpy 优化路径时,错判会导致安全漏洞或性能退化
- 标准库中
std::vector的移动实现在满足平凡可移动时就走memmove分支,依据正是这两个 trait
常见踩坑:自定义移动函数破坏平凡性
只要显式声明了移动构造函数或移动赋值运算符(哪怕写成 = default),该函数就不再是“implicit”——但关键在于:= default 在类定义内且满足条件时,仍可产生 trivial 函数;而一旦出现在类外定义,或类中有 const 成员 / 引用成员 / 不可移动成员,则 trivial 性立即丢失。
- 错误写法:
std::is_trivially_move_constructible_v
- 危险写法:
struct Oops { std::string s; Oops(Oops&& o) : s(std::move(o.s)) {} }; - 安全写法:
struct Good { std::string s; Good(Good&&) = default; };
运行时不可检测,必须编译期断言
平凡可移动是编译期属性,无法靠 if 或 typeid 判断。必须用 static_assert 或 constexpr if 做分支。
例如在模板中启用 memcpy 优化:
std::is_pod
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











