跳轉到內容

更多 C++ 慣用法/複製並交換

來自 Wikibooks,開放世界中的開放書籍

複製並交換

[編輯 | 編輯原始碼]

建立過載賦值運算子的異常安全實現。

也稱為

[編輯 | 編輯原始碼]

建立臨時物件並交換

異常安全性是使用異常來指示“異常”情況的可靠 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 引用形式接受引數。

已知用法

[編輯 | 編輯原始碼]
[編輯 | 編輯原始碼]

參考文獻

[編輯 | 編輯原始碼]
華夏公益教科書