std::is_nothrow_move_constructible 判定类型T的移动构造函数是否具有noexcept异常说明,仅检查编译期签名而非运行时行为;若无移动构造则回退检查拷贝构造,且要求所有基类和成员均满足该特性。

std::is_nothrow_move_constructible 判定的是什么
它只检查类型 T 的移动构造函数是否被显式声明为 noexcept(或隐式满足 noexcept 条件),不运行任何代码,也不验证实际行为是否真的不抛异常——仅看编译期签名。
常见误解是以为它能“检测运行时是否抛异常”,其实完全不是:哪怕你手动在移动构造里写了 throw,只要没标 noexcept(false),而编译器推导出它是 noexcept(比如所有成员都可无异常移动),std::is_nothrow_move_constructible_v<t></t> 仍为 true。
- 判定依据是:移动构造函数的异常说明(exception specification),不是函数体内容
- 若类型没有移动构造函数,会退而使用拷贝构造;此时判定的是该拷贝构造是否
noexcept - 继承链中任一基类或成员的移动构造非
noexcept,整个类型就不是std::is_nothrow_move_constructible
为什么 vector::resize 或 swap 会依赖它
标准库容器(如 std::vector)在重新分配内存或交换时,会优先选择移动而非拷贝——但前提是移动操作是 noexcept。否则,为保障强异常安全(strong exception safety),它宁愿用更慢的拷贝。
例如:std::vector<t>::resize</t> 在扩容时,若 std::is_nothrow_move_constructible_v<t></t> 为 false,则必须用拷贝+析构方式转移元素,避免移动中途抛异常导致部分移动、状态不一致。
-
std::swap对自定义类型默认调用 ADL 找到的swap,但若未提供,会回退到移动+移动+移动(move-assign 风格),此时也要求移动构造和移动赋值都是noexcept - 即使你的移动构造逻辑上“不会抛”,但没加
noexcept,编译器就无法信任,标准库就不用它 - Clang 和 GCC 在
-O2下可能做优化,但异常安全性决策严格按标准来,不看实际汇编
怎么让自定义类通过这个检查
关键不是“实现不抛”,而是“告诉编译器它不抛”——通过显式 noexcept 说明,且确保所有参与移动的成员也都满足条件。
struct MyBuffer {
std::vector<int> data;
std::string name;
<pre class="brush:php;toolbar:false;">// ✅ 正确:显式 noexcept,且 data 和 name 的移动构造都是 noexcept
MyBuffer(MyBuffer&& other) noexcept
: data(std::move(other.data))
, name(std::move(other.name)) {}
// ❌ 错误:没写 noexcept,即使函数体空,也被视为 noexcept(false)
MyBuffer(MyBuffer&& other)
: data(std::move(other.data))
, name(std::move(other.name)) {}
};
- 成员变量的类型必须自身满足
std::is_nothrow_move_constructible_v,比如std::vector、std::string(C++11 起已保证) - 若含裸指针或 FILE* 等资源句柄,需自己写移动构造并标记
noexcept,且确保内部不调用可能抛异常的函数(如new、fopen) - 基类移动构造也必须是
noexcept;若基类没写,子类加了也没用
调试时发现 is_nothrow_move_constructible_v 是 false 怎么查
别猜,直接让编译器告诉你哪块坏了——用 static_assert 展开依赖链。
static_assert(std::is_nothrow_move_constructible_v<mybuffer>, "MyBuffer not nothrow move constructible"); // 编译失败时,错误信息通常指向第一个不满足的成员或基类</mybuffer>
如果错误不明确,逐个检查:
- 用
std::cout打印每个成员 - 注意
std::array的元素类型也要满足;std::optional<t></t>要求T可noexcept移动 - 第三方库类型(如
boost::variant)可能未标记noexcept,即使逻辑安全,也会拖垮整个链
最常被忽略的是:析构函数虽不参与此 trait 判定,但如果移动构造里调用了可能抛异常的清理逻辑(比如某成员的移动构造内部做了日志写入并 throw),那即便标了 noexcept,也是未定义行为——编译器不会拦,但程序可能 abort。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











