天天看點

[翻譯] Effective C++, 3rd Edition, Item 25: 考慮支援一個 non-throwing swap(不抛異常的 swap)(上)

Item 25: 考慮支援一個 non-throwing swap(不抛異常的 swap)

作者:Scott Meyers

譯者:fatalerror99 (iTePub's Nirvana)

釋出:http://blog.csdn.net/fatalerror99/

swap 是一個有趣的函數。最早作為 STL 的構件被引入,後來它成為 exception-safe programming(異常安全程式設計)的支柱(參見 Item 29)和壓制自指派可能性的通用機制(參見 Item 11)。因為 swap 太有用了,是以适當地實作它非常重要,但是伴随它的不同尋常的重要性而來的,是一系列不同尋常的複雜性。在本 Item 中,我們就來研究一下這些複雜性究竟是什麼樣的以及如何對付它們。

swap(交換)兩個 objects 的值就是互相把自己的值送給對方。預設情況下,經由标準的 swap 算法來實作交換是非常成熟的技術。典型的實作完全符合你的預期:

namespace std {

  template<typename T>          // typical implementation of std::swap;

  void swap(T& a, T& b)         // swaps a's and b's values

  {

    T temp(a);

    a = b;

    b = temp;

  }

}

隻要你的類型支援拷貝(經由 copy constructor(拷貝構造函數)和 copy assignment operator(拷貝指派運算符)),預設的 swap 實作就能交換你的類型的 objects,而不需要你做任何特别的支援工作。

然而,預設的 swap 實作可能不那麼酷。它涉及三個 objects 的拷貝:從 a 到 temp,從 b 到 a,以及從 temp 到 b。對一些類型來說,這些副本全是不必要的。對于這樣的類型,預設的 swap 就好像讓你坐着快車駛入小巷。

這樣的類型中最重要的就是那些主要由一個指針組成的類型,那個指針指向包含真正資料的另一種類型。這種設計方法的一種常見的表現形式是 "pimpl idiom"("pointer to implementation" ——參見 Item 31)。一個使用了這種設計的 Widget class 可能就像這樣:

class WidgetImpl {                          // class for Widget data;

public:                                     // details are unimportant

  ...

private:

  int a, b, c;                              // possibly lots of data —

  std::vector<double> v;                    // expensive to copy!

  ...

};

class Widget {                              // class using the pimpl idiom

public:

  Widget(const Widget& rhs);

  Widget& operator=(const Widget& rhs)      // to copy a Widget, copy its

  {                                         // WidgetImpl object. For

   ...                                      // details on implementing

   *pImpl = *(rhs.pImpl);                   // operator= in general,

   ...                                      // see Items 10, 11, and 12.

  }

  ...

private:

  WidgetImpl *pImpl;                         // ptr to object with this

};                                           // Widget's data

為了交換這兩個 Widget objects 的值,我們實際要做的全部就是交換它們的 pImpl pointers(指針),但是預設的 swap 算法沒有辦法知道這些。它不僅要拷貝三個 Widgets,而且還有三個 WidgetImpl objects,效率太低了。一點都不酷。

我們想要做的就是告訴 std::swap 當交換 Widgets 的是時候,執行交換的方法就是 swap 它們内部的 pImpl pointers。這種方法的正規說法是:specialize std::swap for Widget(針對 Widget 特化 std::swap)。下面是一個基本的想法,雖然在這種形式下它還不能通過編譯:

namespace std {

  template<>                            // this is a specialized version

  void swap<Widget>(Widget& a,          // of std::swap for when T is

                    Widget& b)          // Widget; this won't compile

  {

    swap(a.pImpl, b.pImpl);             // to swap Widgets, just swap

  }                                     // their pImpl pointers

}

這個函數開頭的 "template<>" 表明這是一個針對 std::swap 的 total template specialization(完全模闆特化)(某些書中稱為 "full template specialization" 或 "complete template specialization" ——譯者注),函數名後面的 "<Widget>" 表明這個 specialization(特化)是在 T 為 Widget 時發生的。換句話說,當通用的 swap template(模闆)用于 Widgets 時,就應該使用這個實作。通常,我們不被允許改變 std namespace 中的内容的,但我們被允許為我們自己建立的類型(比如 Widget)完全地特化标準模闆(比如 swap)。這就是我們現在在這裡做的事情。

可是,就像我說的,這個函數還不能編譯。那是因為它試圖通路 a 和 b 内部的 pImpl pointers(指針),而它們是 private(私有)的。我們可以将我們的 specialization(特化)聲明為一個 friend(友元),但是慣例是不同的:它讓 Widget 聲明一個名為 swap 的 public member function(公有成員函數)去做實際的交換,然後特化 std::swap 去調用那個 member function(成員函數):

class Widget {                     // same as above, except for the

public:                            // addition of the swap mem func

  ...

  void swap(Widget& other)

  {

    using std::swap;               // the need for this declaration

                                   // is explained later in this Item

    swap(pImpl, other.pImpl);      // to swap Widgets, swap their

  }                                // pImpl pointers

  ...

};

namespace std {

  template<>                       // revised specialization of

  void swap<Widget>(Widget& a,     // std::swap

                    Widget& b)

  {

    a.swap(b);                     // to swap Widgets, call their

  }                                // swap member function

}

這個不僅能夠編譯,而且和 STL containers(容器)保持一緻,所有 STL containers(容器)都既提供了 public swap member functions(公有 swap 成員函數),又提供調用這些 member functions(成員函數)的 std::swap 的 specializations(特化)。

可是,假設 Widget 和 WidgetImpl 是 class templates(類模闆),而不是 classes(類),或許是以我們可以參數化存儲在 WidgetImpl 中的資料的類型:

template<typename T>

class WidgetImpl { ... };

template<typename T>

class Widget { ... };

在 Widget 中加入一個 swap member function(成員函數)(如果我們需要,在 WidgetImpl 中也加一個)就像以前一樣容易,但我們特化 std::swap 時會遇到麻煩。這就是我們要寫的代碼:

namespace std {

  template<typename T>

  void swap<Widget<T> >(Widget<T>& a,      // error! illegal code!

                        Widget<T>& b)

  { a.swap(b); }

}

這看上去非常合理,但它是非法的。我們試圖 partially specialize(部分特化)一個 function template(函數模闆)(std::swap),但是盡管 C++ 允許 class templates(類模闆)的 partial specialization(部分特化),但不允許 function templates(函數模闆)這樣做。這樣的代碼不能編譯(盡管一些編譯器錯誤地接受了它)。

當我們想要 "partially specialize"(“部分地特化”)一個 function templates(函數模闆)時,通常做法是簡單地增加一個 overload(重載)。看起來就像這樣:

namespace std {

  template<typename T>             // an overloading of std::swap

  void swap(Widget<T>& a,          // (note the lack of "<...>" after

            Widget<T>& b)          // "swap"), but see below for

  { a.swap(b); }                   // why this isn't valid code

}

通常,重載 function templates(函數模闆)确實很不錯,但是 std 是一個特殊的 namespace,而且管理它的規則也是特殊的。它認可完全地特化 std 中的 templates(模闆),但它不認可在 std 中增加 new templates(新的模闆)(或 classes,或函數,或其它任何東西)。std 的内容由 C++ 标準化委員會單獨決定,并禁止我們對他們做出的決定進行增加。而且,禁止的方式使你無計可施。打破這條禁令的程式差不多的确可以編譯和運作,但它們的行為是未定義的。如果你希望你的軟體有可預期的行為,你就不應該向 std 中加入新的東西。

(本篇未完,點選此處,接下篇)