跳轉到內容

更多 C++ 慣用法/非丟擲交換

來自華夏公益教科書

非丟擲交換

[編輯 | 編輯原始碼]
  • 實現一個異常安全的、高效的交換操作。
  • 為其提供統一的介面以促進泛型程式設計。

也稱為

[編輯 | 編輯原始碼]
  • 異常安全的交換

交換的典型實現如下所示

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

這存在兩個問題

效能
由於為中間臨時物件獲取和釋放資源,交換兩個大型、複雜且型別相同的物件效率低下。
異常安全性
如果資源不可用,此泛型交換實現可能會丟擲異常。(這種行為沒有意義,因為實際上不應該請求任何新的資源。)因此,此實現不能用於複製並交換 慣用法。

解決方案和示例程式碼

[編輯 | 編輯原始碼]

非丟擲交換慣用法使用控制代碼主體 慣用法來實現所需的效果。正在考慮的抽象被拆分為兩個實現類。一個是控制代碼,另一個是主體。控制代碼儲存指向主體物件的指標。交換被實現為指標的簡單交換,保證不會丟擲異常,並且非常高效,因為不會獲取或釋放任何新資源。

namespace Orange {
class String 
{
    char * str;
  public:
    void swap (String &s) // noexcept
    {
      std::swap (this->str, s.str);
    }
};
}

儘管可以實現高效且異常安全的交換函式(如上所示)作為成員函式,但非丟擲交換慣用法更進一步,以實現簡單性、一致性和促進泛型程式設計。應該在 std 名稱空間以及類的名稱空間中新增 std::swap 的顯式特化。

namespace Orange { // namespace of String
  void swap (String & s1, String & s2) // noexcept
  {
    s1.swap (s2);
  }
}
namespace std {
  template <>
  void swap (Orange::String & s1, Orange::String & s2) // noexcept
  {
    s1.swap (s2);
  }
}

在兩個地方新增它將處理兩種不同的常見交換使用方式(1)非限定交換(2)完全限定交換(例如,std::swap)。當使用非限定交換時,使用 Koenig 查詢查詢正確的交換(如果已經定義)。如果使用完全限定交換,則會抑制 Koenig 查詢,而是使用 std 名稱空間中的交換。這是一種非常常見的做法。這裡剩餘的討論只使用完全限定的交換。它提供了一種統一的外觀和感覺,因為 C++ 程式設計師通常以慣用的方式使用交換函式,將其完全限定為std::,如下所示。

template <class T>
void zoo (T t1, T t2) {
//...
int i1, i2;
std::swap(i1,i2); // note uniformity
std::swap(t1,t2); // Ditto here
}

在這種情況下,當 zoo 使用前面定義的 String 類例項化時,會選擇正確的、有效的交換實現。否則,將例項化預設的 std::swap 函式模板——完全違背了定義成員交換和名稱空間範圍交換函式的目的。在泛型程式設計中,這種在 std 名稱空間中定義交換的顯式特化的慣用法特別有用。

使用非丟擲交換慣用法的這種統一性會導致其級聯使用,如以下示例所示。

class UserDefined 
{
    String str;
  public:
    void swap (UserDefined & u) // noexcept
    { 
      std::swap (str, u.str); 
    }
};
namespace std
{
  // Full specializations of the templates in std namespace can be added in std namespace.
  template <>
  void swap (UserDefined & u1, UserDefined & u2) // noexcept
  {
    u1.swap (u2);
  }
}
class Myclass
{
    UserDefined u;
    char * name;
  public:
    void swap (Myclass & m) // noexcept
    {
      std::swap (u, m.u);       // cascading use of the idiom due to uniformity
      std::swap (name, m.name); // Ditto here
    }   
}
namespace std
{
   // Full specializations of the templates in std namespace can be added in std namespace.
   template <> 
   void swap (Myclass & m1, Myclass & m2) // noexcept
   {
     m1.swap (m2);
   }
};

因此,為適合異常安全、高效交換實現的型別定義 std::swap 的特化是一個好主意。當前的 C++ 標準不允許我們在 std 名稱空間中新增新的模板,但允許我們對該名稱空間中的模板(例如 std::swap)進行特化,並將它們添加回其中。

注意事項

[編輯 | 編輯原始碼]

對模板類(例如,Matrix<T>)使用非丟擲交換慣用法可能會是一個微妙的問題。根據 C++98 標準,只有 std::swap 的完全特化才允許在 std 名稱空間中為使用者定義的型別定義。語言不允許部分特化或函式過載。嘗試對模板類(例如,Matrix<T>)實現類似的效果會導致 std 名稱空間中 std::swap 的過載,從技術上講這是未定義的行為。正如 comp.lang.c++.moderated 新聞組中一些人在一段異常長的討論執行緒中指出的那樣,這並不一定是理想的狀態。[1] 有兩種可能的解決方案,兩者都不完美,可以解決此問題

  1. 符合標準的解決方案。利用 Koenig 查詢,在與正在交換的類相同的名稱空間中定義一個過載的交換函式模板。並非所有編譯器都可能正確支援它,但此解決方案符合標準。[2]
  2. Fingers-crossed 解決方案。部分特化 std::swap 並忽略從技術上講這是未定義的行為,希望不會發生任何事情,並等待下一個語言標準中的修復。

已知用途

[編輯 | 編輯原始碼]

所有 boost 智慧指標(例如,boost::shared_ptr)

[編輯 | 編輯原始碼]
  1. "名稱空間問題,專門交換". comp.lang.c++.moderated (Usenet 新聞組). 2000 年 3 月 12 日。
  2. Sutter,HerbAlexandrescu,Andrei (2004年10月25日). C++ 編碼規範:101 條規則、指南和最佳實踐. C++ 深入淺出. Addison Wesley Professional. ISBN 0-321-11358-6. 專案 66:不要專門化函式模板。
華夏公益教科書