C++11中基于PIMPL idiom的类是否仍需自定义std::swap?
C++11中PIMPL类是否还需要自定义std::swap?
你的观察完全正确——在C11及之后的标准里,用std::unique_ptr实现PIMPL的类,默认的std::swap已经能表现得非常高效,Scott Meyers在《Effective C》里的建议确实是针对C++11之前的场景。
为什么旧建议不再是必须的?
在C++11引入移动语义之前,std::swap的默认实现确实会触发三次对象拷贝:
将a拷贝到temp,b拷贝到a,temp拷贝到b
对于PIMPL类来说,这意味着三次深拷贝(因为拷贝对象时会拷贝内部的raw指针,然后可能还要拷贝指针指向的实际数据),性能开销极大。所以那时必须特化std::swap来直接交换内部指针,避免深拷贝。
但C++11之后,情况变了:
std::unique_ptr本身的swap操作是O(1)的,只交换指针本身,没有任何深拷贝- 默认的
std::swap实现会优先利用移动构造和移动赋值,对于持有unique_ptr的类,默认swap本质上就是交换两个unique_ptr,和你手动写的自定义swap效果完全一致
举个简单的例子,你的PIMPL类大概是这样:
class Widget { public: Widget(); // ... 其他成员 private: struct Impl; std::unique_ptr<Impl> pImpl; };
调用std::swap(w1, w2)时,底层就是直接交换两个pImpl指针,没有任何额外开销,效率拉满。
什么时候还需要自定义swap?
虽然默认swap已经足够,但自定义友元swap也有它的好处:
- ADL(参数依赖查找)支持:在泛型代码中,通常会用
using std::swap; swap(a, b);这种写法,它会优先查找当前命名空间下的swap函数。如果你定义了类的友元swap,泛型代码会直接调用它,而不是退回到std::swap——这在语义上更清晰,也能避免一些潜在的意外(比如未来你的类添加了其他需要交换的成员)。 - 明确的noexcept说明:
unique_ptr的swap是noexcept的,自定义swap可以显式标记noexcept,让容器(比如std::vector)在使用swap时获得更好的异常安全保证。
自定义swap的代码也非常简洁,完全不会增加太多实现复杂度:
class Widget { // ... 原有成员 friend void swap(Widget& lhs, Widget& rhs) noexcept { using std::swap; swap(lhs.pImpl, rhs.pImpl); } };
总结
- 如果你只是在自己的代码里直接使用
std::swap,并且类仅用unique_ptr实现PIMPL,完全不需要自定义swap,默认实现已经足够高效。 - 如果你追求更完善的ADL支持、明确的异常说明,或者未来可能扩展类的成员,那么添加一个简单的友元swap是个不错的选择,但这不是强制要求。
内容的提问来源于stack exchange,提问作者Ben
相关产品推荐
相关产品推荐

