零法則/五法則(rule of zero / rule of five)
這些不是編譯器強制的語言規則——它們是久經考驗的指引,告訴你如何一致地處理特殊成員函式,好讓你的型別正確地複製、移動與清理。最重要的建議是零法則:把你的類別設計成根本不必寫任何那些函式。若每個成員本身都是行為良好的 RAII 型別(std::vector、std::string、std::unique_ptr),編譯器產生的複製、移動與解構就已經做對了事,而最安全的程式碼正是你從未寫下的那段。
當你真的必須親自管理一個裸資源——檔案描述符、C 函式庫指標、一塊記憶體——五法則就登場了:只要你必須寫解構子、複製建構子、複製賦值、移動建構子、移動賦值其中任何一個,你幾乎一定需要把五個全都想清楚,因為它們共同構成一個關於所有權的連貫故事。較舊的三法則是 C++11 之前的版本,只涵蓋解構子與兩個複製操作;後來加上移動那一對才湊成五個。道理很簡單:一旦你寫了個會呼叫 free() 的自訂解構子,預設的逐成員複製就變錯了(它會複製指標並重複釋放),所以你必須定義相符的複製與移動。
為何重要:這是無洩漏、無崩潰的資源程式碼,與經典的重複釋放/懸置指標臭蟲之間的分界。現代最佳實務是把五法則往下推進小型的單一資源包裝類別(或乾脆用標準函式庫的),讓其上的一切都遵循零法則。誠實的細節:寫了五者中的任何一個都會抑制編譯器產生其他某些——所以五法則部分是防禦性的,確保沒有任何操作無聲地缺席,或無聲地把移動退化成複製。
class Good { std::unique_ptr<int[]> data; std::string name; }; // 零法則:沒寫任何特殊成員,預設一切正確
因為每個成員都是 RAII,複製/移動/解構都被免費且正確地產生——理想的現代設計。
還有所謂三法則加一半、以及把預設建構子算進去的「六法則」;名稱本身不如原則重要。真正的法則是:資源的所有權,在建構、複製、移動與解構之間,必須有唯一且連貫的答案。