結論
C++のメンバ関数の第一引数は、そのインスタンスのアドレスです。
static関数はメンバ変数を使用する必要がないため、例外です。
以下は解説と茶番です。
C++のメンバ関数をスレッド実行したかった
std::thread、_beginthread、CreateThreadと色々あるが、今日は気分でCreateThreadをチョイス。
書いてみたら何やらエラーが。エラー内容は以下
型 "DWORD (__stdcall hoge::*)(LPVOID pParam)" の引数は型 "LPTHREAD_START_ROUTINE" のパラメータと互換性がありません
「なるほど、スレッド化したい関数と型が違うとな。ちゃんと定義を確認しに行こう」
typedef DWORD (WINAPI *PTHREAD_START_ROUTINE)(
LPVOID lpThreadParameter
);
typedef PTHREAD_START_ROUTINE LPTHREAD_START_ROUTINE;
戻り値 | 呼び出し規約 | 引数 |
---|---|---|
DWORD | WINAPI | LPVOID一つ |
「合っとるやんけ。何で使えないんや。」
メンバ変数の第一引数はインスタンスのアドレスなのです
論より証拠ということで、実際のソースコードと生成されたアセンブラを見てみましょう。
int main()
{
hoge instance;
instance.hugaThread(NULL);
return 0;
}
hoge instance;
00007FF7B6CA19AD lea rcx,[instance]
00007FF7B6CA19B1 call hoge::hoge (07FF7B6CA100Fh)
00007FF7B6CA19B6 nop
instance.hugaThread(NULL);
00007FF7B6CA19B7 xor edx,edx
00007FF7B6CA19B9 lea rcx,[instance]
00007FF7B6CA19BD call hoge::hugaThread (07FF7B6CA137Fh)
Windows10 VisualStudio2022 x64でビルドしたものです。
このアセンブラから、以下の処理が確認できます。
-
instance
の宣言をしたところで、コンストラクタを呼び出している - edx(rdx)レジスタを0クリア、つまりNULLを格納している
-
instance
のアドレスをrcxレジスタに格納している
MSのx64環境では一般的に第一引数から順にrcx、rdx、r8、r9に格納されます。詳細はこちらを御覧ください。
x64 での呼び出し規則
よって、スレッド化しようとしたメンバ変数の方は本当は以下のようになっているのです。
DWORD WINAPI hoge::hugaThread(hoge* myself, PVOID pParam);
内部的にこうしてくれているおかげで、メンバ関数内で、インスタンスのメンバ変数にアクセスできているわけですね。
C言語では構造体を作って各関数に渡してステータスを変更/参照するといったことになりますが、内部的にはそれをやっているのと大して変わりはないわけです。
これの仕組みが理解できれば、冒頭で書いた通り、staticのメンバ関数であればCreateThreadに渡す事ができる理屈を察する事ができると思います。
C++のメンバ関数をスレッド実行する
本記事ではメインの話題ではありませんが、こういった所で躓く人もいらっしゃると思うので、他の方の記事のリンクを貼り付けておきます。
https://kakashibata.hatenablog.jp/entry/2019/09/29/210359
https://qiita.com/tsuru3/items/c3e706ed77912f809cd2
https://stackoverflow.com/questions/1372967/how-do-you-use-createthread-for-functions-which-are-class-members
おわり
以上、お読みいただきありがとうございました。
全然遊戯王ネタ入れるところなかった。