更多 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] 有兩種可能的解決方案,兩者都不完美,可以解決此問題
- 符合標準的解決方案。利用 Koenig 查詢,在與正在交換的類相同的名稱空間中定義一個過載的交換函式模板。並非所有編譯器都可能正確支援它,但此解決方案符合標準。[2]
- Fingers-crossed 解決方案。部分特化 std::swap 並忽略從技術上講這是未定義的行為,希望不會發生任何事情,並等待下一個語言標準中的修復。
所有 boost 智慧指標(例如,boost::shared_ptr)
- ↑ "名稱空間問題,專門交換". comp.lang.c++.moderated (Usenet 新聞組). 2000 年 3 月 12 日。
- ↑ Sutter,Herb; Alexandrescu,Andrei (2004年10月25日). C++ 編碼規範:101 條規則、指南和最佳實踐. C++ 深入淺出. Addison Wesley Professional. ISBN 0-321-11358-6. 專案 66:不要專門化函式模板。