C++ std::swap的设计方法与规则简析

1 很高兴分享,上一节遗留下swap问题

下面就简单分享下自己的想法,更详细的可以参考更专业的书籍。

C++ weak_ptr用法和简析(最后是Boost对应的源码,我会提及一些哈)
https://blog.csdn.net/weixin_39956356/article/details/110141462

2 缺省的内置std::swap的问题?

缺省的swap由内置std::swap提供,很普通的交换,好似下面的代码
要求: 只需要T支持拷贝构造和拷贝赋值即可

namespce std{
	template <typename T>
	void swap(T& a, T& b) {
		T temp(a);
		a = b;
		b = temp;
	}
};

问题: 为了缩小一个类的大小,往往把真实数据放在其他地方,在类中用指针指向它,这就是所谓的pimpl手法(pointer to implementation),如下,一个简单的案例。

class A{
public:
	// 正如上面提到的,需要支持拷贝构造、拷贝赋值
	A(const A& rhs);
	A& operator=(const A& rhs);

private:
	AImpl* pImpl;	// 指向真实的数据
};

很显然,如果交换两个A对象的值,仅需要交换AImpl即可。 但是如果使用std::swap,std::swap并不知道只需要交换指针就好了,它不仅复制三个A对象,而且复制三个AImpl对象,这是很低效的!!!

回顾std::swap的实现,我们可以确定std::swap是异常安全的!

下面是解决方案,针对pimpl手法,提高swap的速率。Boost中weak_ptr也是使用了这种技术

3 pimpl手法下的类(模板)提高swap的速率

模板类
游戏规则如下:

  1. 在类中public中提供一个swap成员函数,让它高效的交换两个对象的值,绝不能抛异常BOOST_SP_NOEXCEPT
  2. 在template命名空间实现一个non-member swap,并调用第一步的swap

下面以weak_ptr为例,分析如何使用。
分析:是否满足pimpl手法?
很显然,满足,真正的数据并没有在weak_ptr中,仅保存了一个指针而已。

// weak_ptr的源代码,无关的内容都已经删除了
namespace boost
{

template<class T> class weak_ptr
{
private:
    // Borland 5.5.1 specific workarounds
    typedef weak_ptr<T> this_type;

public:
    typedef typename boost::detail::sp_element< T >::type element_type;

	// 第一步STEP1 在类中public中提供一个swap成员函数,让它高效的交换两个对象的值
    void swap(this_type & other) BOOST_SP_NOEXCEPT {
        std::swap(px, other.px);	// STEP3
        pn.swap(other.pn);		// STEP4
    }

private:
    element_type * px;            // contained pointer
    boost::detail::weak_count pn; // reference counter
};  // weak_ptr

// 第二步STEP2  在template命名空间实现一个non-member swap,并调用第一步的swap
template<class T> void swap(weak_ptr<T> & a, weak_ptr<T> & b) BOOST_SP_NOEXCEPT {
    a.swap(b);	// 这个swap调用的是第一步public中的swap!!!
}

} // namespace boost

4 分析weak_ptr中swap

我们一点一点分析

	weak_ptr<T> wp1;
	weak_ptr<T> wp2;
	swap(wp1,wp2);	//这个swap怎么工作的呢?

调用顺序:
开始—>STEP2—>STEP1—>STEP3—>STEP4

4.1 开始的时候为什么不调用std::swap?

因为weak_ptr中已经实现了swap,所以按照匹配流程,就不会匹配到全局std::swap

4.2 STEP3和STEP4调用的swap该如何理解呢?

其实这个问题的核心是,你自己写的类有没有swap,如果有就调用它(这里称为Case1),如果没有就会匹配全局std::swap(这里称为Case2)。在自己,我们可以知道

  1. STEP3,很显然明确指定了std::swap,即Case2
  2. STEP4,使用的是Case1,哈哈,因为pn是一个weak_count对象,在weak_count类中已经实现了swap

注意:下面是weak_count类下的swap!!!

    void swap(weak_count & r) BOOST_SP_NOEXCEPT
    {
        sp_counted_base * tmp = r.pi_;
        r.pi_ = pi_;
        pi_ = tmp;
    }

4.3 STEP3为什么要明确指定std::swap?

这个问题简单,思考一下,px是什么呢?是element_type *,对于不同的模板参数element_type *不一样,更不可能穷尽为其都写一个swap,所以就用全局std::swap来进行交换。

4.4 再谈第一步STEP1?

针对public的swap而言,下面是一般化的情况

    void swap(A& other) xxxx_NOEXCEPT {
		using std::swap;	// 这个必要
		swap(pImpl, other.pImpl)
    }

提前引用std::swap,因此下面的swap,会先到类中寻找,找不到用全局哈。

5 结论

  1. std::swap效率不高,但是绝对安全
  2. 满足pimpl手法下的template怎么做?

如下
3. 在类中public中提供一个swap成员函数,让它高效的交换两个对象的值,绝不能抛异常BOOST_SP_NOEXCEPT
4. 在template命名空间实现一个non-member swap,并调用第一步的swap

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页