C++swap交换两个对象值
C++里swap不是“交换”,是“移交所有权”
刚学C++时,我写过这样的代码:
int a = 10, b = 20;
std::swap(a, b);
看着变量值真被调换了,心里踏实了——原来swap就是个“值对调工具”。
直到某天调试一个自定义类的swap,程序在析构时崩了,堆内存重复释放,报错信息像一串冷笑话:double free or corruption (out)。
我才意识到:swap从来不是“把两个对象的内容擦掉再抄一遍”,而是让它们彼此交出资源的控制权。
这背后藏着C++最朴素也最锋利的设计哲学:避免无谓拷贝,优先转移所有权。
标准库里的std::swap<T>默认行为,其实是三步“移动”操作:
- 构造临时对象(用a初始化)
- 把a的内容替换成b的(通过移动赋值或拷贝赋值)
- 把b的内容替换成临时对象的
但这个流程只在T支持拷贝/移动时才安全。如果T管理着文件句柄、动态数组或网络连接,默认swap会触发两次深拷贝——而你根本不需要拷贝,你只想“换岗”。
举个真实例子:写一个轻量级字符串包装类TinyString,内部用char*加长度管理内存:
class TinyString {
char* data_;
size_t len_;
public:
TinyString(const char* s) : len_(strlen(s)) {
data_ = new char[len_ + 1];
strcpy(data_, s);
}
~TinyString() { delete[] data_; } // 关键:析构释放内存
// ... 拷贝构造/赋值略
};
如果没提供特化版本,std::swap(TinyString{}, TinyString{})会走默认路径:
先拷贝构造临时对象 → 再两次拷贝赋值 → 最后临时对象析构 → 原对象析构时,data_已是野指针。
崩溃不是偶然,是必然。
解决方法不是“重写swap函数”,而是告诉编译器:“别拷,直接换指针”。
标准做法是在类作用域内声明非成员swap函数,并置于与类相同的命名空间中:
namespace mylib {
class TinyString { /* ... */ };
void swap(TinyString& a, TinyString& b) noexcept {
using std::swap;
swap(a.data_, b.data_);
swap(a.len_, b.len_);
}
} // namespace mylib
注意三点:
- 必须是非成员函数(ADL查找机制依赖这点);
- 必须和类在同一个命名空间(否则ADL找不到);
- 加上
noexcept(让标准算法如std::sort敢放心调用它)。
这样,当你写mylib::TinyString x, y; swap(x, y);,编译器通过ADL自动找到你写的版本,零拷贝,四次指针交换,毫秒完成。
更进一步,如果你的类支持移动语义(有移动构造/移动赋值),其实连这个手动swap都可以省——只要确保移动操作是noexcept的,std::swap会自动退化为移动+移动+移动,效率几乎等价。但手动特化仍是底线保障:它不依赖移动语义是否完备,也不受编译器优化程度影响。
实际项目中,还常遇到模板类的swap问题。比如自己实现了一个Stack<T>,内部用std::vector<T>存数据:
template<typename T>
class Stack {
std::vector<T> storage_;
public:
void swap(Stack& other) noexcept {
storage_.swap(other.storage_); // 直接复用vector的高效swap
}
};
这里的关键是:不要在模板类内部只写成员函数swap(),而要额外提供非成员版本:
template<typename T>
void swap(Stack<T>& a, Stack<T>& b) noexcept {
a.swap(b);
}
否则,泛型算法(比如std::rotate)在实例化时可能找不到合适的swap,被迫回落到低效的拷贝版本。
最后说个容易被忽略的细节:swap的异常安全性。
如果两个对象的交换过程可能抛异常(比如某个移动构造意外new失败),整个swap就不可靠。因此,所有手动实现的swap都应标注noexcept,且内部调用的每个子操作也必须是noexcept的。
检查方式很简单:在函数体开头加一句static_assert(noexcept(a.data_ = nullptr));——编译不过?说明底层资源管理本身就不满足强异常安全,得先修根子。
回到最初那个int a=10, b=20;的例子。它之所以稳如老狗,是因为内置类型swap本质就是汇编级的寄存器交换,没有资源、没有生命周期、没有副作用。
但C++的魅力恰恰在于:当你走出基本类型,每一步交换都在提醒你——你在操作的不是值,是责任。
谁拥有内存,谁关闭文件,谁释放锁……swap不是魔术,是契约的即时移交。
下次看到swap,别只盯着结果是否“对调”,先问一句:
这次移交,有没有人忘了交接钥匙?


还没有评论,来说两句吧...