std::swap是C++标准库定义在头文件中的函数模板,用于安全高效交换两个同类型对象的值;它默认基于移动语义实现,对容器特化版本通过交换内部指针达常数时间复杂度,是资源管理、异常安全及泛型算法(如sort)的必备工具。

std::swap 是什么,什么时候必须用它
std::swap 是 C++ 标准库提供的通用交换函数,定义在 <utility></utility> 头文件里(C++11 起)。它不是“可有可无的语法糖”,而是在资源管理、异常安全、移动语义中起关键作用的工具。
- 它默认通过移动构造 + 移动赋值实现高效交换,对自定义类型,如果你没提供特化版本,它会尝试调用
std::move和赋值运算符 - 对内置类型(如
int、double),编译器通常会内联优化成几条寄存器指令,不比手写临时变量慢 - 不要手动写
tmp = a; a = b; b = tmp;—— 这对类类型可能触发两次拷贝,且不保证异常安全
常见错误现象:std::swap(a, b) 编译失败,报错类似 “no matching function for call to ‘swap’”,大概率是忘了 #include <utility></utility>(尤其在旧项目中误以为 <algorithm></algorithm> 或 <vector></vector> 会间接包含它)
自定义类型怎么让 swap 更快
如果你的类管理了动态内存、文件句柄或其它独占资源,应当为它提供 swap 的非成员函数特化(或友元),并标记为 noexcept。这是 RAII 类型的常规实践。
- 特化必须在命名空间
std内声明(但仅限于对用户定义类型的特化) - 必须是非模板函数,不能是重载
- 强烈建议加
noexcept,否则std::vector::resize等操作可能退化为拷贝而非移动
namespace std {
void swap(MyClass& a, MyClass& b) noexcept {
using std::swap;
swap(a.data_, b.data_);
swap(a.size_, b.size_);
}
}- 不要在类内部只写一个
member_swap()就完事:标准算法(如std::sort)只认非成员swap - 如果你用了
std::unique_ptr等已优化过的成员,直接用默认std::swap就行,无需特化
std::swap 和 std::iter_swap、std::ranges::swap 的区别
三者用途不同,混用容易出逻辑错误:
立即学习“C++免费学习笔记(深入)”;
-
std::swap(a, b):交换两个对象本身,最常用 -
std::iter_swap(it1, it2):交换两个迭代器所指元素,等价于std::swap(<em>it1, </em>it2);常用于手写排序或重排算法 -
std::ranges::swap(C++20):和std::swap行为一致,但参与 ADL 更可靠;一般不用主动调,除非你在写泛型范围算法
性能影响:三者底层都走同一套移动逻辑,差异只在参数解引用层级。但 std::iter_swap 对输入迭代器要求更高——如果迭代器不支持赋值(比如某些输入流迭代器),它会编译失败。
容易被忽略的陷阱:右值引用传参、ADL 失效、宏冲突
std::swap 看似简单,实际踩坑点很隐蔽:
- 传入右值会编译失败:
std::swap(get_temp_object(), x)不合法,因为swap参数是左值引用;若需交换临时值,请先绑定到引用或改用移动语义逻辑 - ADL(参数依赖查找)可能绕过
std::swap:如果你在某个命名空间里定义了同名swap函数,又没用using std::swap引入,编译器可能选错版本 - 某些旧头文件(如 Windows 的
windef.h)定义了#define swap something,导致std::swap被文本替换而报错;解决方式是在包含系统头之前#undef swap,或用括号包住:(std::swap)(a, b)
最麻烦的情况是:你写了正确的特化,但忘记加 noexcept,结果 std::vector 在扩容时因无法确认交换不抛异常,被迫降级为拷贝——这种性能倒退很难被测试覆盖到。


















