《Effective C++》条款25:考虑写出一个不抛异常的swap函数

swap是两个元素交换的函数,在STL中也有对应的实现,其基本实现为:

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

这个置换方式和大多数人想象的一样,它涉及到三个对象的复制:
a复制到temp
b复制到a
temp复制到a
假设有种很大的数据类型

class WidgetImpl{
public:
    ...
private:
    int a,b,c;              
    std::vector<double> d;
    ...
};

标准方法涉及到了三次复制,数据很多,复制时间很长,效率显然不给力,有没有更给力的方式呢?

有,指针交换

class Widget  {    // 这个class使用pimpl手法  
public:  
    Widget( const Widget& rhs );  
    Widget& operator=( const Widget& rhs )    // 复制Widget时  
    {                                         //令它复制其WidgetImpl对象
        ...  
        *pImpl = *(rhs.pImpl);
        ...  
    }  
    ...  
private:  
    WidgetImpl* pImpl;                        //指针,指向对象内含的widget数据
};  

注解:

PIMPL(Private Implementation 或 Pointer to Implementation)是通过一个私有的成员指针,将指针所指向的类的内部实现数据进行隐藏。
1)降低模块的耦合。因为隐藏了类的实现,被隐藏的类相当于原类不可见,对隐藏的类进行修改,不需要重新编译原类。
2)降低编译依赖,提高编译速度。指针的大小为(32位)或8(64位),x.h发生变化,指针大小却不会改变,文件c.h(包含x.h)也不需要重编译。
3)接口与实现分离,提高接口的稳定性。

但是显然不能直接用标准swap交换PIMPL后的类Widget ,这样它会复制三个Widget +WidgetImpl,会更低效。我们只需要置换Widget中的指针而已。

那我们在Widget中写一个置换的函数:

class Widget  {    // 这个class使用pimpl手法  
public:  
    Widget( const Widget& rhs );  
    Widget& operator=( const Widget& rhs )    // 复制Widget时  
    {                                         //令它复制其WidgetImpl对象
        ...  
        *pImpl = *(rhs.pImpl);
        ...  
    }
    void swap(Widget& other)
    {
        using std::swap;
        swap(pImpl, other.pImpl);
    }  
    ...  
private:  
    WidgetImpl* pImpl;                        //指针,指向对象内含的widget数据
};  

再写 一个std中swap的重载版本:

namespace std  {  
    template<typename T>  
    void swap(Widget<T>& a,Widget<T>& b)  
    {  a.swap(b);  }  
}  

注意:我们并不能让这个函数模板篇特化,如以下代码

namespace std  {  
    template<typename T>    //std::swap重载版本  
    void swap<Widget>( Widget& a,Widget& b )  
    {  
        a.swap(b);  
    }  
}  

这个看起来合情合理,但是不合法。

因为我们企图偏特化一个function template(std::swap),但C++只允许对class templates进行偏特化,在function templates身上偏特化是行不通的。

这段代码不该通过编译(虽然有些编译器错误地接受了它)。

当你打算偏特化一个function templates时,惯常的做法是简单地为它添加一个重载模板。

所以整个代码如下:

class WidgetImpl{
public:
    ...
private:
    int a,b,c;              
    std::vector<double> d;
    ...
};

class Widget  {    // 这个class使用pimpl手法  
public:  
    Widget( const Widget& rhs );  
    Widget& operator=( const Widget& rhs )    // 复制Widget时  
    {                                         //令它复制其WidgetImpl对象
        ...  
        *pImpl = *(rhs.pImpl);
        ...  
    }  
    ...  
private:  
    WidgetImpl* pImpl;                        //指针,指向对象内含的widget数据
};  

namespace std  {  
    template<typename T>     
    void swap( Widget& a,Widget& b )  
    {  
        a.swap(b);  
    }  
}  

最后的测试成功的代码:

#include <iostream>
#include "vector"

class WidgetImpl{
public:
    WidgetImpl(int a, int b, int c):a(a),b(b),c(c)
    {}

    void look(){
        std::cout << a << b << c << std::endl;
    }
private:
    int a,b,c;
    std::vector<double> d;
};

class Widget  {    // 这个class使用pimpl手法
public:
    Widget(int a,int b, int c):pImpl(new WidgetImpl(a,b,c))
    {}
    Widget( const Widget& rhs );
    Widget& operator=( const Widget& rhs )    // 复制Widget时
    {                                         //令它复制其WidgetImpl对象
        *pImpl = *(rhs.pImpl);
    }
    void look(){pImpl->look();}
    void swap(Widget& other)
    {
        using std::swap;
        swap(pImpl, other.pImpl);
    }
private:
    WidgetImpl* pImpl;                        //指针,指向对象内含的widget数据
};

namespace std  {
    template<>    //std::swap重载版本
    void swap(Widget& a,Widget& b )
    {
        a.swap(b);
    }
}

int main(){
    Widget  a(1,2,3),b(4,5,6);
    std::swap(a, b);
    a.look();
    b.look();
}

输出:

456
123

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值