class 非靜態成員初始化順序

C/Cpp Sep 4, 2021

the initialization order of class non-static data member is the order of declaration in the class.
這一篇其實是把自己之前遇到的問題記錄下來,雖然現在看來有點蠢,但當時還是花了一些時間才找出來

經歷

在檢測單元測試時,發現有一項測試,在 CI/CD 中會隨機出錯,一開始想說這種隨機錯誤通常是沒有初始化,或者一些陣列操作超出範圍,導致記憶體被意外修改了。在 linux 又很難重現,只有在 github action MSVC 中出錯,又當時沒有手邊沒有 MSVC 能夠直接使用,導致一開始方向並沒有很明確,檢查了初始化跟陣列操作都也正確,最後才發現某一項初始化在第一項測試不一定能夠歸零,但第二項之後都可以正確歸零,所以才開始懷疑初始化的順序,然後在查了一些資料後確認 class non-static data (非靜態成員) 初始化順序是根據宣告順序,而非直接使用 constructor 的順序

參考資料 cppreference 中的 Initialization order 的第三項 Then, non-static data member are initialized in order of declaration in the class definition.

完整的也貼在這邊方便參考

The order of member initializers in the list is irrelevant: the actual order of initialization is as follows:

  1. If the constructor is for the most-derived class, virtual bases are initialized in the order in which they appear in depth-first left-to-right traversal of the base class declarations (left-to-right refers to the appearance in base-specifier lists)
  2. Then, direct bases are initialized in left-to-right order as they appear in this class's base-specifier list
  3. Then, non-static data member are initialized in order of declaration in the class definition.
  4. Finally, the body of the constructor is executed

例子

#include <iostream>

class test {
public:
    test(int val) : b(val), a(b) {}
// many codes
    int a;
    int b;
};

int main()
{
    test k(3);
    std::cout << k.a << " " << k.b << std::endl;
    return 0;
}

以上是一個簡單的例子,在 constructor 後面 member initializers 是使用 b(val), a(b),當 class 中間又有更多函式時,在看 constructor 時,是會看不到宣告順序的,所以我當時就直覺認為會是 b 先被給定 val 然後 a 再被給定 b 也就是 val,然而這是錯的。
想像輸出會是 3 3

實際情形

在前面有提過他是根據宣告順序,而非 member initializers 怎麼寫 (constructor 後面 {} 之前),所以這邊實際的運作會是,a 被給定 b (未定) , b 再被給定 val,因此 a 的值會根據 b 一開始為初始化的值而決定。
因此實際輸出會是 * 3

而在這邊 * 在 linux 為初始化地很常會是零 (個人經驗),又剛好我的初始化是要歸零,導致在 linux 的部分很難遇到這樣的問題,幸好在 MSVC 有指出這樣的問題。

檢查

後來有分享這件事給同實驗室的,他們也給出了一項編譯器選項,可以指出這項問題 -Wreorder,就會指出例子中的 test::b 會在 test::a 之後初始化

可以很迅速地發現這項問題,也可以透過此連結來看這個例子

結語

class non-static data member 在 member initializers 中初始化順序是根據宣告順序來定,可以用 -Wreorder 來檢查,以上是之前遇到的問題分享。

Tags

Great! You've successfully subscribed.
Great! Next, complete checkout for full access.
Welcome back! You've successfully signed in.
Success! Your account is fully activated, you now have access to all content.