更多 C++ 慣用法/複製並交換
建立過載賦值運算子的異常安全實現。
建立臨時物件並交換
異常安全性是使用異常來指示“異常”情況的可靠 C++ 軟體的非常重要的基石。至少有 3 種異常安全性級別:基本、強和無丟擲。基本異常安全性應該始終提供,因為它通常很容易實現。保證強異常安全性可能並非在所有情況下都可行。複製並交換慣用法允許以強異常安全性優雅地實現賦值運算子。
建立臨時物件並交換慣用法在放棄當前資源之前獲取新的資源。為了獲取新的資源,它使用 RAII 慣用法。如果成功獲取新的資源,它將使用無丟擲交換慣用法交換資源。最後,舊資源作為第一步使用 RAII 的副作用而被釋放。
class String
{
char * str;
public:
String & operator=(const String & s)
{
String temp(s); // Copy-constructor -- RAII
temp.swap(*this); // Non-throwing swap
return *this;
}// Old resources released when destructor of temp is called.
void swap(String & s) noexcept // Also see the non-throwing swap idiom
{
std::swap(this->str, s.str);
}
};
上述實現也可能存在一些變體。對自賦值的檢查並不是嚴格必要的,但在(很少發生的)自賦值情況下可以提高一些效能。
class String
{
char * str;
public:
String & operator=(const String & s)
{
if (this != &s)
{
String(s).swap(*this); //Copy-constructor and non-throwing swap
}
// Old resources are released with the destruction of the temporary above
return *this;
}
void swap(String & s) noexcept // Also see non-throwing swap idiom
{
std::swap(this->str, s.str);
}
};
複製省略和複製並交換慣用法
嚴格來說,在賦值運算子中顯式建立臨時物件是不必要的。賦值運算子的引數(右側)可以按值傳遞給函式。引數本身用作臨時物件。
String & operator = (String s) // the pass-by-value parameter serves as a temporary
{
s.swap (*this); // Non-throwing swap
return *this;
}// Old resources released when destructor of s is called.
這不僅僅是方便的問題,事實上是一種最佳化。如果引數 (s) 繫結到一個左值(另一個非 const 物件),在建立引數 (s) 時會自動建立一個物件的副本。但是,當 s 繫結到一個右值(臨時物件,字面量)時,複製通常會被省略,這可以節省對複製建構函式和解構函式的呼叫。在賦值運算子的早期版本中,引數以 const 引用形式接受,當引用繫結到右值時,複製省略不會發生。這會導致建立和銷燬一個額外的物件。
在 C++11 中,這樣的賦值運算子被稱為統一賦值運算子,因為它消除了編寫兩種不同賦值運算子的需要:複製賦值和移動賦值。只要一個類具有移動建構函式,C++11 編譯器就會始終使用它來最佳化從另一個臨時物件(右值)建立副本。複製省略是與非 C++11 編譯器中的類似最佳化,以實現相同的效果。
String createString(); // a function that returns a String object.
String s;
s = createString();
// right hand side is a rvalue. Pass-by-value style assignment operator
// could be more efficient than pass-by-const-reference style assignment
// operator.
並非每個類都從這種賦值運算子中受益。考慮一個 String 賦值運算子,它只在現有記憶體不足以容納右側 String 物件的副本時才釋放舊記憶體並分配新記憶體。為了實現這種最佳化,必須編寫一個自定義賦值運算子。由於新的 String 副本將使記憶體分配最佳化失效,因此此自定義賦值運算子必須避免將引數複製到任何臨時 String,特別是需要按 const 引用形式接受引數。