初始化會為實體提供第一個值,或建立參照繫結。指派會修改已經存在的物件。由於建構函式只參與初始化,而指派運算子只會在生命週期開始後參與,因此兩者的區別會影響多載解析、不變量和效能。
C 背景 #
C 宣告包含宣告子,每個宣告子後面都可以接著 = initializer。初始化式可以是指派運算式或大括號初始化式清單;巢狀大括號用於初始化巢狀聚合體。
struct point { int x; int y; } points[] = {
[0] = {.x = 1},
[2] = {.y = 4},
};C 聚合體初始化會將省略的元素視為具有靜態儲存期來初始化:算術值會變成零,指標值會變成 null 指標,巢狀聚合體則會遞迴初始化。相反地,沒有初始化式的自動儲存期純量具有不定值,在儲存某個值之前不得讀取。
C++ 形式與語法 #
C++ 允許宣告子攜帶等號初始化式、大括號初始化式或圓括號初始化式。因此,以下宣告屬於不同形式:
T copy = expression; // 複製初始化
T direct(expression); // 直接初始化
T list{expression}; // 直接清單初始化
T copy_list = {expression}; // 複製清單初始化
braced-init-list 不是運算式,也沒有型別。目的型別和初始化形式決定多載解析、聚合體規則及縮窄檢查。在宣告中,T object() 是函式宣告,而不是空物件;若要宣告物件,請使用 T object{};。
基本類別 #
零初始化 #
零初始化會將純量值設定為相應的零值,將指標值設定為 null 指標;陣列則逐一處理元素。零初始化會作為靜態儲存期和執行緒儲存期初始化的一部分發生,也會出現在某些值初始化路徑中。它不是參照的獨立寫法,因為每個參照都必須繫結至物件或函式。
static int requests; // 在動態初始化之前進行零初始化。
thread_local void* cache; // 在動態初始化之前為 null 指標。
預設初始化 #
不帶初始化式的宣告會對其物件進行預設初始化。類別會呼叫所選的預設建構函式;陣列會對每個元素進行預設初始化。具有自動儲存期或動態儲存期的純量不會獲得初始化,因此在指派之前不能安全地讀取。
Widget widget; // 呼叫 Widget::Widget()。
int count; // 沒有初始值。
值初始化 #
值初始化會在 T{} 或 T() 等情境中使用空初始化式。純量會變成零。對於類別型別,會選取預設建構函式;如果該建構函式不是使用者提供的,則會在建構函式主體之前進行零初始化。陣列會對每個元素進行值初始化。
int count{};
std::array<int, 4> flags{};複製初始化、直接初始化和清單初始化 #
複製初始化允許隱式轉換,但不會選取 explicit 建構函式。直接初始化可以選取 explicit 建構函式。直接清單初始化同樣可以選取它,而如果複製清單初始化所選的建構函式是 explicit,則程式不合語法。
struct Port {
explicit Port(int);
};
Port a(443); // 直接初始化。
Port b{443}; // 直接清單初始化。
// Port c = 443; // 錯誤:排除 explicit 建構函式。
清單初始化會拒絕縮窄轉換。它會優先考慮可行的 std::initializer_list 建構函式,因此可能有意產生與圓括號不同的結果。
std::vector<int> repeated(3, 7); // 7, 7, 7
std::vector<int> elements{3, 7}; // 3, 7
// int truncated{3.5}; // 錯誤:縮窄轉換。
當初始化式清單的優先順序不會造成語意不符時,請使用大括號來檢查縮窄轉換。當型別有意區分數量/值建構函式與值清單時,請使用圓括號。
聚合體、陣列和儲存期 #
陣列是聚合體。大括號聚合體初始化會依宣告順序進行;省略的元素會根據適用的空初始化式規則進行初始化。
int values[5]{1, 2}; // 其餘元素為零。
struct Endpoint {
std::string host;
int port;
};
Endpoint local{.host = "localhost", .port = 443}; // C++20 指定初始化。
C++20 也允許使用圓括號進行聚合體初始化。與大括號聚合體初始化不同,圓括號形式允許縮窄轉換,且不會延長繫結至參照元素的暫時物件生命週期。C++ 指定初始化式比 C 的限制更多:它們必須依宣告順序指定直接的非靜態資料成員;C++ 沒有陣列索引指定子。
具有靜態儲存期或執行緒儲存期的物件,在宣告符合條件時會進行常數初始化;否則靜態初始化會將其進行零初始化。所有靜態初始化都會在動態初始化之前完成。constinit 要求靜態初始化,但不會使物件成為 const;當其他要求都滿足時,constexpr 宣告一個可用於常數求值的值。這些是儲存和求值保證,而不是一般物件初始化的替代方案。
參照與生命週期 #
參照初始化會繫結一個別名;它不會建立第二個物件。繫結形式和值類別決定暫時物件的生命週期是否會延長。
int value = 7;
int& writable = value;
const int& extended = 42; // 暫時物件會存續到與 `extended` 相同的時間。
透過 writable 進行指派會修改 value,但不會重新繫結參照。應將初始化視為物件不變量開始生效的時點,並將指派視為另外指定的狀態轉換。
評論
在 GitHub 上閱讀或評論(發表評論需要登入)