effective-c++系列:深入探析swap

swap介绍

  • swap作为STL的一部分,在算法、容器、迭代器中被广泛使用。
    c++为用户定义了算法的默认形式如下:
    1
    2
    3
    4
    5
    6
    7
    8
    namespace std{
    template<typename T>
    void swap(T& x, T& y){
    T tmp(x);
    x = y;
    y = tmp;
    }
    }
    如果类型T支持拷贝构造和拷贝赋值就可以完成交换。

内含指针的类型交换

  • 从上面可以看出,默认版本需要执行一次拷贝构造,两次拷贝赋值才能完成交换,如果类型内部像下面这种pimpl(内含指针,指向数据)的形式,显然只需要交换指针即可,如果采用默认版本就回降低效率。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    class A_impl{
    int val;
    };
    class A{
    A_impl* p;
    public:
    A(){};
    inline bool operator=(const A& rhs){
    //do something
    *p = *(rhs.p);
    //do something
    }
    inline A(const A& rhs);
    };
    • 很自然地,我们可以通过函数重载来在类内自定义swap函数:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    class A_impl{
    public:
    int val;
    A_impl(int val):val(val){};
    };
    class A{
    A_impl* p;
    public:
    A(){};
    inline bool operator=(const A& rhs);
    inline A(const A& rhs);
    inline A(A_impl* p):p(p){};
    void swap(A& rhs){
    std::swap(p, rhs.p);
    }
    inline int getVal(){
    return p -> val;
    }
    };
    namespace std{
    template<>
    void swap<A>(A& lhs, A& rhs){
    lhs.swap(rhs);
    }
    }
    A* d1 = new A(new A_impl(0));
    A* d2 = new A(new A_impl(3));
    cout << d1->getVal() << "--" << endl; //0
    cout << d2->getVal() << "--" << endl; //3
    swap(d1, d2);
    cout << d1->getVal() << "--" << endl; //3
    cout << d2->getVal() << "--" << endl; //0

swap函数重载和特化

  • 对于模板类,如何设计swap
    假如我们现在有个模板类
1
2
3
4
5
6
7
8
9
10
11
12
template<typename T>
class A{
T val;
public:
void swap(A& rhs){
T tmp = val;
val = rhs.val;
rhs.val = tmp;
};
A(int x):val(x){}
inline int getVal(){return val;}
};

我们希望写出它的特化版本,但是很不幸会报错

1
2
3
4
5
6
7
namespace std{
template<typename T>
void swap<A<T>>(A<T>& lhs, A<T>& rhs){
lhs.swap(rhs);
}
//error: non-class, non-variable partial specialization 'swap<A<T> >' is not allowed
}

通常情况下我们是偏特化一个函数模板:

1
2
3
4
5
6
7
8
9
10
11
12
13
namespace std{
template<typename T>
void swap(A<T>& lhs, A<T>& rhs){
lhs.swap(rhs);
}
}
A<int> d1(0);
A<int> d2(3);
cout << d1.getVal() << "--" << endl; //0
cout << d2.getVal() << "--" << endl; //3
swap(d1, d2);
cout << d1.getVal() << "--" << endl; //3
cout << d2.getVal() << "--" << endl; //0

但是c++的std命名空间很特殊,如果不必要,尽量不要在其中添加自定义的操作。那么我们该怎么让程序调用我们自己的版本呢?很简单,定义一个非成员swap,并置于某个命名空间中:

1
2
3
4
5
6
namespace hqin{
template<typename T>
void swap(A<T>& lhs, A<T>& rhs){
lhs.swap(rhs);
}
}

下面看看我们在调用下面这个函数时会发生肾么事

1
2
3
4
5
template<typename T>
void test(T& lhs, T& rhs){
using std::swap;
swap(lhs, rhs);
}

执行到swap后编译器有很多不同版本可以选择:

  • std一般化版本
  • std特化版本
  • 某个命名空间中的T专属版本

事实情况是编译器会优先匹配global空间或T所在命名空间中的所有T专属的版本,如果不存在就使用std特化版本,最后使用一般版本。

swap异常

  • 成员版的swap绝不可抛出异常。因为swap的一个最好的应用就是帮助class提供强烈的一场安全性保障。
  • 非成员版本可以抛出异常。