<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                # Item 25: 考慮支持不拋異常的 swap 作者:Scott Meyers 譯者:fatalerror99 (iTePub's Nirvana) 發布:http://blog.csdn.net/fatalerror99/ swap 是一個有趣的函數。最早作為 STL 的一部分被引入,后來它成為異常安全編程(exception-safe programming)的支柱(參見 Item 29)和壓制自賦值可能性的通用機制(參見 Item 11)。因為 swap 太有用了,所以正確地實現它非常重要,但是伴隨它的不同尋常的重要性而來的,是一系列不同尋常的復雜性。在本 Item 中,我們就來研究一下這些復雜性究竟是什么樣的以及如何對付它們。 交換兩個對象的值就是互相把自己的值送給對方。缺省情況下,通過標準的交換算法來實現交換是非常成熟的技術。典型的實現完全符合你的預期: ``` 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; } } ``` 只要你的類型支持拷貝(通過拷貝構造函數和拷貝賦值運算符),缺省的 swap 實現就能交換你的類型的對象,而不需要你做任何特別的支持工作 可是,缺省的 swap 實現可能不那么酷。它涉及三個對象的拷貝:從 a 到 temp,從 b 到 a,以及從 temp 到 b。對一些類型來說,這些副本全是不必要的。對于這樣的類型,缺省的 swap 就好像讓你坐著快車駛入小巷。 這樣的類型中最重要的就是那些主要由一個指針組成的類型,那個指針指向包含真正數據的另一種類型。這種設計方法的一種常見的表現形式是 "pimpl idiom"("pointer to implementation" ——參見 Item 31)。一個使用了這種設計的 Widget 類可能就像這樣: ``` 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 對象的值,我們實際要做的就是交換它們的 pImpl 指針,但是缺省的交換算法沒有辦法知道這些。它不僅要拷貝三個 Widgets,而且還有三個 WidgetImpl 對象,效率太低了。一點都不酷。 當交換 Widgets 的是時候,我們應該告訴 std::swap 我們打算做什么,執行交換的方法就是交換它們內部的 pImpl 指針。這種方法的正規說法是:針對 Widget 特化 std::swap(specialize std::swap for Widget)。下面是一個基本的想法,雖然在這種形式下它還不能通過編譯: ``` 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&lt;&gt;" 表明這是一個針對 std::swap 的完全模板特化(total template specialization)(某些書中稱為 "full template specialization" 或 "complete template specialization" ——譯者注),函數名后面的 "&lt;Widget&gt;" 表明特化是在 T 為 Widget 類型時發生的。換句話說,當通用的 swap 模板用于 Widgets 時,就應該使用這個實現。通常,我們改變 std namespace 中的內容是不被允許的,但允許為我們自己創建的類型(就像 Widget)完全特化標準模板(就像 swap)。這就是我們現在在這里做的事情。 可是,就像我說的,這個函數還不能編譯。那是因為它試圖訪問 a 和 b 內部的 pImpl 指針,而它們是 private 的。我們可以將我們的特化聲明為友元,但是慣例是不同的:讓 Widget 聲明一個名為 swap 的 public 成員函數去做實際的交換,然后特化 std::swap 去調用那個成員函數: ``` 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 容器保持一致,所有 STL 容器都既提供了 public swap 成員函數,又提供了 std::swap 的特化來調用這些成員函數。 可是,假設 Widget 和 WidgetImpl 是類模板,而不是類,或許因此我們可以參數化存儲在 WidgetImpl 中的數據類型: ``` template<typename T> class WidgetImpl { ... }; template<typename T> class Widget { ... }; ``` 在 Widget 中加入一個 swap 成員函數(如果我們需要,在 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)一個函數模板(std::swap),但是盡管 C++ 允許類模板的部分特化(partial specialization),但不允許函數模板這樣做。這樣的代碼不能編譯(盡管一些編譯器錯誤地接受了它)。 當我們想要“部分特化”一個函數模板時,通常做法是簡單地增加一個重載。看起來就像這樣: ``` 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 } ``` 通常,重載函數模板確實很不錯,但是 std 是一個特殊的 namespace,規則對它也有特殊的待遇。它認可完全特化 std 中的模板,但它不認可在 std 中增加新的模板(也包括類,函數,以及其它任何東西)。std 的內容由 C++ 標準化委員會單獨決定,并禁止我們對他們做出的決定進行增加。而且,禁止的方式使你無計可施。打破這條禁令的程序差不多的確可以編譯和運行,但它們的行為是未定義的。如果你希望你的軟件有可預期的行為,你就不應該向 std 中加入新的東西。 因此該怎么做呢?我們還是需要一個方法,既使其他人能調用 swap,又能讓我們得到更高效的模板特化版本。答案很簡單。我們還是聲明一個非成員 swap 來調用成員 swap,只是不再將那個非成員函數聲明為 std::swap 的特化或重載。例如,如果我們的 Widget 相關機能都在 namespace WidgetStuff 中,它看起來就像這個樣子: ``` namespace WidgetStuff { ... // templatized WidgetImpl, etc. template<typename T> // as before, including the swap class Widget { ... }; // member function ... template<typename T> // non-member swap function; void swap(Widget<T>& a, // not part of the std namespace Widget<T>& b) { a.swap(b); } } ``` 現在,如果某處有代碼使用兩個 Widget 對象調用 swap,C++ 的名字查找規則(以參數依賴查找(argument-dependent lookup)或 Koenig 查找(Koenig lookup)著稱的特定規則)將找到 WidgetStuff 中的 Widget 專用版本。而這正是我們想要的。 這個方法無論對于類模板還是對于類都能很好地工作,所以看起來我們應該總是使用它。不幸的是,此處還是存在一個需要為類特化 std::swap 的動機(過一會兒我會講到它),所以如果你希望你的 swap 的類專用版本在盡可能多的上下文中都能夠調用(而你也確實這樣做了),你就既要在你的類所在的 namespace 中寫一個非成員版本,又要提供一個 std::swap 的特化版本。 順便提一下,如果你不使用 namespaces,上面所講的一切依然適用(也就是說,你還是需要一個非成員 swap 來調用成員 swap),但是你為什么要把你的類,模板,函數,枚舉(此處作者連用了兩個詞(enum, enumerant),不知有何區別——譯者注)和 typedef 名字都堆在全局 namespace 中呢?你覺得合適嗎? 迄今為止我所寫的每一件事情都適用于 swap 的作成者,但是有一種狀況值得從客戶的觀點來看一看。假設你寫了一個函數模板來交換兩個對象的值: ``` template<typename T> void doSomething(T& obj1, T& obj2) { ... swap(obj1, obj2); ... } ``` 哪一個 swap 應該被調用呢?std 中的通用版本,你知道它必定存在;std 中的通用版本的特化,可能存在,也可能不存在;T 專用版本,可能存在,也可能不存在,可能在一個 namespace 中,也可能不在一個 namespace 中(但是肯定不在 std 中)。究竟該調用哪一個呢?如果 T 專用版本存在,你希望調用它,如果它不存在,就回過頭來調用 std 中的通用版本。如下這樣就可以符合你的希望: ``` template<typename T> void doSomething(T& obj1, T& obj2) { using std::swap; // make std::swap available in this function ... swap(obj1, obj2); // call the best swap for objects of type T ... } ``` 當編譯器看到這個 swap 調用,他會尋找正確的 swap 版本來調用。C++ 的名字查找規則確保能找到在全局 namespace 或者與 T 同一個 namespace 中的 T 專用的 swap。(例如,如果 T 是 namespace WidgetStuff 中的 Widget,編譯器會利用參數依賴查找(argument-dependent lookup)找到 WidgetStuff 中的 swap。)如果 T 專用 swap 不存在,編譯器將使用 std 中的 swap,這歸功于此函數中的 using declaration 使 std::swap 在此可見。盡管如此,相對于通用模板,編譯器還是更喜歡 T 專用的 std::swap 的特化,所以如果 std::swap 對 T 進行了特化,則特化的版本會被使用。 得到正確的 swap 調用是如此地容易。你需要小心的一件事是不要對調用加以限定,因為這將影響 C++ 確定該調用的函數,如果你這樣寫對 swap 的調用, ``` std::swap(obj1, obj2); // the wrong way to call swap ``` 這將強制編譯器只考慮 std 中的 swap(包括任何模板特化),因此排除了定義在別處的更為適用的 T 專用版本被調用的可能性。唉,一些被誤導的程序員就是用這種方法限定對 swap 的調用,這也就是為你的類完全地特化 std::swap 很重要的原因:它使得以這種被誤導的方式寫出的代碼可以用到類型專用的 swap 實現。(這樣的代碼還存在于現在的一些標準庫實現中,所以它將有利于你幫助這樣的代碼盡可能高效地工作。) 到此為止,我們討論了缺省的 swap,成員 swaps,非成員 swaps,std::swap 的特化版本,以及對 swap 的調用,所以讓我們總結一下目前的狀況。 首先,如果 swap 的缺省實現為你的類或類模板提供了可接受的性能,你不需要做任何事。任何試圖交換你的類型的對象的人都會得到缺省版本的支持,而且能工作得很好。 第二,如果 swap 的缺省實現效率不足(這幾乎總是意味著你的類或模板使用了某種 pimpl idiom 的變種),就按照以下步驟來做: 1\. 提供一個能高效地交換你的類型的兩個對象的值的 public 的 swap 成員函數。出于我過一會兒就要解釋的動機,這個函數應該永遠不會拋出異常。 2\. 在你的類或模板所在的同一個 namespace 中提供一個非成員的 swap。用它調用你的 swap 成員函數。 3\. 如果你寫了一個類(不是類模板),就為你的類特化 std::swap。用它也調用你的 swap 成員函數。 最后,如果你調用 swap,請確保在你的函數中包含一個 using declaration 使 std::swap 可見,然后在調用 swap 時不使用任何 namespace 限定條件。 唯一沒有解決的問題就是我的警告——絕不要讓 swap 的成員版本拋出異常。這是因為 swap 的非常重要的應用之一是為類(以及類模板)提供強大的異常安全(exception-safety)保證。Item 29 將提供所有的細節,但是這項技術基于 swap 的成員版本絕不會拋出異常的假設。這一強制約束僅僅應用在成員版本上!它不能夠應用在非成員版本上,因為 swap 的缺省版本基于拷貝構造和拷貝賦值,而在通常情況下,這兩個函數都允許拋出異常。如果你寫了一個 swap 的自定義版本,那么,典型情況下你是為了提供一個更有效率的交換值的方法,你也要保證這個方法不會拋出異常。作為一個一般規則,這兩種 swap 的特型將緊密地結合在一起,因為高效的交換幾乎總是基于內建類型(諸如在 pimpl idiom 之下的指針)的操作,而對內建類型的操作絕不會拋出異常。 Things to Remember * 如果 std::swap 對于你的類型來說是低效的,請提供一個 swap 成員函數。并確保你的 swap 不會拋出異常。 * 如果你提供一個成員 swap,請同時提供一個調用成員 swap 的非成員 swap。對于類(非模板),還要特化 std::swap。 * 調用 swap 時,請為 std::swap 使用一個 using declaration,然后在調用 swap 時不使用任何 namespace 限定條件。 * 為用戶定義類型完全地特化 std 模板沒有什么問題,但是絕不要試圖往 std 中加入任何全新的東西。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看