C++模板背后的黑箱操作:編譯器
一、編譯器如何處理模板
1.模板代碼的處理
為了理解模板的復雜性,你需要了解編譯器是如何處理模板代碼的。當編譯器遇到模板方法定義時,它會進行語法檢查,但實際上不會編譯模板。編譯器不能編譯模板定義,因為它不知道這些模板將用于哪些類型。編譯器不可能為像 x = y 這樣的代碼生成代碼,而不知道 x 和 y 的類型。
當編譯器遇到模板的實例化,例如 Grid<int>,它會通過將類模板定義中的每個 T 替換為 int 來為 int 版本的 Grid 模板編寫代碼。當編譯器遇到模板的不同實例化,例如 Grid<SpreadsheetCell>,它會為 SpreadsheetCell 編寫另一個版本的 Grid 類。編譯器只是寫出了如果沒有模板支持,你需要為每種元素類型編寫單獨類時的代碼。這里沒有魔法;模板只是自動化了一個煩人的過程。如果你在程序中沒有為任何類型實例化類模板,那么類方法定義就永遠不會被編譯。
這種實例化過程解釋了為什么你需要在定義的各個地方使用 Grid<T> 語法。當編譯器為特定類型(如 int)實例化模板時,它會將 T 替換為 int,使 Grid<int> 成為該類型。
2.選擇性實例化
對于隱式類模板實例化,如以下示例:
Grid<int> myIntGrid;
編譯器總是為類模板的所有虛擬方法生成代碼。然而,對于非虛擬方法,編譯器只為你實際調用的那些非虛擬方法生成代碼。例如,給定前面的 Grid 類模板,假設你在 main() 中寫了這樣的代碼(僅此代碼):
Grid<int> myIntGrid;
myIntGrid.at(0, 0) = 10;
編譯器僅為 int 版本的 Grid 生成無參數構造函數、析構函數和非 const 的 at() 方法。它不會生成其他方法,如拷貝構造函數、賦值運算符或 getHeight()。這被稱為選擇性實例化。
存在的風險是,某些類模板方法中的編譯錯誤可能會被忽略。未使用的類模板方法可能包含語法錯誤,因為這些不會被編譯。這使得測試所有代碼的語法錯誤變得困難。
你可以通過使用顯式模板實例化來強制編譯器為所有方法(虛擬和非虛擬)生成代碼。以下是一個示例:
template class Grid<int>;
注意:顯式模板實例化有助于發現錯誤,因為它強制編譯器編譯所有即使未使用的類模板方法。使用顯式模板實例化時,不要只嘗試使用基本類型(如 int)實例化類模板,還要嘗試使用更復雜的類型(如 string)。
二、模板對類型的要求
1.類型獨立的代碼編寫
當你編寫與類型無關的代碼時,必須對這些類型做出某些假設。例如,在 Grid 類模板中,你假設元素類型(由 T 表示)是可銷毀的、可拷貝/移動構造的,以及可拷貝/移動賦值的。
當編譯器嘗試用不支持類模板方法所使用的所有操作的類型來實例化模板時,代碼將無法編譯,且錯誤消息通常相當晦澀難懂。
然而,即使你想使用的類型不支持類模板的所有方法所需的操作,你也可以利用選擇性實例化來使用某些方法而不是其他方法。
2.C++20 引入的概念(Concepts)
C++20 引入了概念(concepts),允許你為模板參數編寫編譯器可以解釋和驗證的要求。如果傳遞給模板實例化的模板參數不滿足這些要求,編譯器可以生成更易讀的錯誤消息。后面將討論概念。
概念為模板編程增加了額外的類型安全性,它通過為模板參數提供一個明確的接口合約來實現。這種方式不僅可以防止類型不匹配的問題,還可以改善模板錯誤消息的可讀性,從而使模板代碼更容易維護和理解。
三、類模板代碼的文件
在類模板中,類模板定義和方法定義必須對任何使用它們的源文件可用。有幾種機制可以實現這一點:
1.方法定義與類模板定義在同一文件
你可以將方法定義直接放在定義類模板本身的模塊接口文件中。當你在另一個源文件中導入這個模塊以使用模板時,編譯器將能夠訪問它所需的所有代碼。這種機制用于之前的 Grid 實現。
2.方法定義在單獨的文件
或者,你可以將類模板方法定義放在一個單獨的模塊接口分區文件中。然后,你還需要將類模板定義放在自己的分區中。例如,Grid 類模板的主模塊接口文件可能如下所示:
export module grid;
export import :definition;
export import :implementation;
這導入并導出了兩個模塊分區:定義(definition)和實現(implementation)。類模板定義在定義分區中定義:
export module grid:definition;
import <vector>;
import <optional>;
export template <typename T> class Grid { ... };
方法的實現位于實現分區中,該分區還需要導入定義分區,因為它需要 Grid 類模板定義:
export module grid:implementation;
import :definition;
import <vector>;
...
export template <typename T> Grid<T>::Grid(size_t width, size_t height)
: m_width { width }, m_height { height } { ... }