1. 程式人生 > >考慮寫一個不拋異常的swap函式

考慮寫一個不拋異常的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複製到b,但是對於某些情況,多餘的複製是沒有必要的:

考慮下面的例子:

class WidgetImpl
{
public:
    ...
private:
    int a, b, c;
    std::vector<double> v;
};
class Widget
{
    Widget(const Widget &rhs);
    Widget &operator =(const Widget &rhs);
private:
    WdigetImpl *pImpl;
};

        一旦要交換兩個Widget物件的值,我們僅僅需要的就是置換其pImpl指標,但預設的演算法不止複製三個Widgets,還複製三個WidgetImpl物件,非常低效。

      我們需要告訴預設的swap函式,只需要交換兩個指標就好,為了實現這個思路,考慮如下做法:

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

        這個做法叫做函式的特化https://mp.csdn.net/postedit/83651232

但是上述做法並不能通過編譯,因為pImpl是類的私有成員,我們無法在類外呼叫。

既然如此,那麼我們就讓特化的swap函式成為Widget類的類成員:

class Widget
{
public:
...
    Widget(const Widget &rhs);
    Widget &operator =(const Widget &rhs);
public:
    void swap(Widget &Other)
    {
        using std::swap;
        swap(pImpl, Other.pImpl);
    }
private:
    WdigetImpl *pImpl;
};
namespace std
{
    template <>
    void swap<Widget>(Widget &a, Widget &b)
    {
        a.swap(b);
    }
}

 這樣,就可以通過編譯。

上述做法是基於類都是class而不是template class,下面考慮一下如果類都是template class會發生什麼:

template <typename T>
class WidgetImpl{...};
template <typename T>
class Widget{...};

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

這種情況是不能通過編譯的,因為函式是不能夠被偏特化的,只有類才能偏特化。

一般情況下,偏特化一個函式的代替做法為過載函式:

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

但是這也是不合法的,因為名稱空間std內是禁止新的東西的,你可以全特化,但不可以過載。

新增一個新的名稱空間可以解決這種局面:

namespace WidgetStuff
{
    template <typename T>
    class WidgetImpl{...};
    template <typename T>
    class Widget{...};   //包括成員函式swap


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

這樣一來,c++的名稱查詢法則會找到WidgetStuff內的Widget專屬版本。

這個做法既適用於class也適用於template class,但你最好還是為swap函式在名稱空間std中實現一個特化的版本:

template <typename T>
void DoSomething(T &a, T &b)
{
    ...
    using std::swap;
    swap(a, b);
    ...
}

        如果這樣呼叫swap函式,那麼如果編譯器找不到針對T在某個名稱空間內的專屬版本,就會呼叫std內的預設版本,但是如果這樣呼叫:

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

那麼就會直接呼叫std中的預設版本, 所以為了使這種錯誤的呼叫方式也可以呼叫適當的swap函式,你可以在std中實現一個全特化的版本,這樣編譯器會首先呼叫全特化版本。

總結:

如果std版本的效率滿足你的要求,可以什麼都不做,否則:

1. 在類中提供一個public function高效的置換;

2. 在class或template class所在的名稱空間提供一個non-member函式去呼叫成員函式;

3. 如果你編寫的是class而不是template class,為你的class特化std::swap。

最後,注意函式的呼叫方式。

還有一點:成員版swap函式不應該丟擲異常。