目次
はじめに
C++の std::thread
を使って新しいスレッドを起動する際、そのスレッドで実行される関数に、メインスレッドから何らかのデータを渡したい場合がほとんどです。
std::thread
のコンストラクタは、2番目以降の引数として、スレッドで実行する関数に渡す引数を取ることができます。しかし、引数の渡し方には注意が必要で、主に以下の3つの方法があります。
- 値渡し(コピー): デフォルトの動作。
- 参照渡し:
std::ref
を使う。 - ムーブ渡し:
std::move
を使う。
この記事では、これら3つの引数の渡し方について、それぞれの違いと使い方を解説します。
スレッドに引数を渡すサンプルコード
このコードは、worker_task
という関数を3つの異なるスレッドで起動し、それぞれ「値渡し」「参照渡し」「ムーブ渡し」で引数を渡します。
完成コード
#include <iostream>
#include <string>
#include <thread>
#include <vector>
#include <memory> // unique_ptr
using namespace std;
// スレッドで実行されるタスク
// 1. int: 値渡し, 2. int&: 参照渡し, 3. unique_ptr<int>: ムーブ渡し
void worker_task(int val, int& ref_val, unique_ptr<int> p_val) {
cout << "--- スレッド開始 ---" << endl;
val++; // コピーなので、元の変数は変わらない
ref_val++; // 参照なので、元の変数が変わる
cout << "値渡しの値 (スレッド内): " << val << endl;
cout << "参照渡しの値 (スレッド内): " << ref_val << endl;
if (p_val) {
cout << "ムーブ渡しの値 (スレッド内): " << *p_val << endl;
}
cout << "--- スレッド終了 ---" << endl;
}
int main() {
int value_arg = 10;
int ref_arg = 20;
auto ptr_arg = make_unique<int>(30);
// 1. 値渡し: value_arg のコピーが渡される
thread th1(worker_task, value_arg, ref(ref_arg), move(ptr_arg));
th1.join();
cout << "\n--- スレッド終了後の main 関数の値 ---" << endl;
cout << "値渡しの元の変数 (value_arg): " << value_arg << endl;
cout << "参照渡しの元の変数 (ref_arg): " << ref_arg << endl;
if (!ptr_arg) {
cout << "ムーブ渡しの元のポインタ (ptr_arg): 所有権が移動し、空になりました。" << endl;
}
return 0;
}
コードの解説
1. 値渡し(デフォルト)
thread th1(worker_task, value_arg, ...);
thread
のコンストラクタは、デフォルトで全ての引数をコピーして、新しいスレッドの内部記憶域に格納します。worker_task
内でval++
としても、それはコピーされた値に対する変更なので、main
関数内の元の変数value_arg
の値(10
)は変わりません。
2. 参照渡し (std::ref
)
thread th1(..., ref(ref_arg), ...);
- スレッド内の関数で、メインスレッドの変数を直接変更したい場合は、引数を
std::ref
でラップする必要があります。 ref(ref_arg)
とすることで、ref_arg
のコピーではなく、ref_arg
への参照がスレッドに渡されます。- その結果、
worker_task
内でref_val++
とすると、main
関数内の元の変数ref_arg
の値が21
に変わります。
3. ムーブ渡し (std::move
)
thread th1(..., move(ptr_arg));
std::unique_ptr
のように、コピーできないがムーブ(所有権の移動)は可能なオブジェクトを渡す場合は、std::move
を使います。move(ptr_arg)
とすることで、ptr_arg
が管理していたメモリ領域の所有権が、スレッド内の引数p_val
に移動します。- 実行後、
main
関数内のptr_arg
は所有権を失い、空のポインタ(nullptr
)になります。
まとめ
今回は、C++のスレッドに関数を渡す際の3つの主要な方法を解説しました。
- 値渡し(デフォルト): 安全ですが、元の変数は変更できません。
- 参照渡し (
std::ref
): スレッドから元の変数を変更したい場合に使います。 - ムーブ渡し (
std::move
):unique_ptr
など、所有権を移動させる必要がある場合に使います。
thread
のコンストラクタが引数をコピーする、という基本動作を理解し、参照やムーブが必要な場合は ref
や move
を明示的に使う、という点を押さえておくことが重要です。