std::is_move_constructible 判定的是类型T能否通过T&&合法构造,即表达式T(std::declval<T&&>())是否有效;它包含显式移动构造、编译器合成移动构造,以及退化为const T&拷贝构造三种情况。

std::is_move_constructible
std::is_move_constructible 判定的是什么
它检查表达式 T(std::declval<t>())</t> 是否合法(SFINAE 友好),即:能否用一个 T&& 构造出一个新的 T 对象。这包括三种情况:
- 显式定义了移动构造函数(
T(T&&)) - 未定义拷贝/移动构造,且所有成员都可移动 → 编译器合成移动构造函数
- 没有移动构造,但有拷贝构造且接受
const T&→ 退化为调用拷贝构造(因为T&&可绑定到const T&)
也就是说,即使类删掉了移动构造(T(T&&) = delete;),只要拷贝构造存在,std::is_move_constructible_v<t></t> 仍可能为 true。
如何真正检测“是否定义了非删除的移动构造函数”
标准库不提供直接检测“是否有用户定义/编译器合成的非删除移动构造函数”的类型特征。但可通过组合判断逼近:
立即学习“C++免费学习笔记(深入)”;
-
std::is_move_constructible_v<t></t>为true(确保语法上可行) -
!std::is_trivially_move_constructible_v<t></t>(排除平凡移动,说明至少有自定义或非平凡合成行为) -
std::is_nothrow_move_constructible_v<t></t>可辅助判断是否依赖合成(合成的移动若成员都noexcept,则自身也是noexcept)
更严格的检测需借助 SFINAE + 检测特定构造函数签名,例如:
template<typename T>
constexpr bool has_non_deleted_move_ctor() {
return requires { typename std::remove_reference_t<decltype(
std::declval<T>().~T(), // 确保析构可用
void()
)>(); } &&
requires { T(std::declval<T&&>()); };
}但该写法在 C++20 前不可靠,且无法区分“合成”和“用户定义”。实践中,多数场景只需关注 std::is_move_constructible 和 std::is_nothrow_move_constructible 的组合语义。
常见误判场景与坑
以下代码会让 std::is_move_constructible_v<MyClass> 返回 true,但其实没移动语义:
struct MyClass {
MyClass(const MyClass&) { /* 拷贝构造 */ }
MyClass& operator=(const MyClass&) = default;
};原因:MyClass(MyClass&&) 未定义 → 编译器不合成(因已定义拷贝构造),但 MyClass(MyClass&&) 仍能绑定到 const MyClass&,所以 MyClass(std::declval<MyClass&&>()) 合法。
- 定义了拷贝构造/赋值 → 移动构造不会被自动合成(C++11 规则)
- 移动构造被
= delete→std::is_move_constructible_v<T>为false(除非拷贝构造可接受右值) - 基类或成员禁用移动 → 即使派生类没写
= delete,合成移动构造也会失败,导致std::is_move_constructible_v<T>为false
实际使用建议
在泛型代码中做优化分支时,别只看 std::is_move_constructible,应结合意图选择:
- 想“安全地 move 且期望性能提升” → 优先用
std::is_nothrow_move_constructible_v<T>,它要求移动不抛异常,通常意味着真正启用了移动语义 - 做 trait 分发(如容器 push_back)→ 同时检查
std::is_rvalue_reference_v<U&&>和std::is_move_constructible_v<T>,再 fallback 到拷贝 - 调试时怀疑移动没生效 → 打印
std::is_move_constructible_v<T>、std::is_trivially_move_constructible_v<T>、std::is_nothrow_move_constructible_v<T>三者结果,比对差异
真正决定是否发生移动的是调用点的实参类型和目标类型的构造函数匹配结果,类型特征只是静态线索——最易忽略的,是“有移动构造声明,但被 = delete 或因其他成员不可移动而隐式删除”这类静默失效。


















