最終更新日時(UTC): 2025年07月09日 08時52分14秒
Akira Takahashi が更新

履歴 編集

function
<thread>

# std::jthread::コンストラクタ(C++20)

    jthread() noexcept;                      // (1) C++20

    template <class F, class... Args>
    explicit jthread(F&& f, Args&&... args); // (2) C++20

    jthread(const jthread&) = delete;        // (3) C++20

    jthread(jthread&&) noexcept;             // (4) C++20

## 概要

- (1) : デフォルトコンストラクタ。新しいスレッドを生成せず、空の状態にする。
- (2) : 新しいスレッドを生成し、そのスレッド上で引数`args...`を渡して、関数オブジェクト`f`を呼び出す。
- (3) : コピーコンストラクタ。コピー不可。
- (4) : ムーブコンストラクタ。スレッドの所有権を移動する。

## テンプレートパラメータ制約

- (2) :
  - `remove_cvref_t<F>`が`jthread`ではないこと
  - `decay_t<F>`および`decay_t<Args>`の各型がCpp17MoveConstructible要件を満たすこと

## 適格要件

- (2) : 以下の条件がすべて`true`であること
  - `is_constructible_v<decay_t<F>, F>`
  - `(is_constructible_v<decay_t<Args>, Args> && ...)`
  - `is_move_constructible_v<decay_t<F>>`
  - `(is_move_constructible_v<decay_t<Args>> && ...)`
  - `is_invocable_v<decay_t<F>, decay_t<Args>...> || is_invocable_v<decay_t<F>, stop_token, decay_t<Args>...>`

## 効果

- (2) :

  - メンバ変数として保持している`std::stop_source`型オブジェクトを初期化する

  - 以下の式が有効であればそれで新たなスレッドを生成して実行し、

        invoke(decay-copy(std::forward<F>(f)), get_stop_token(), decay-copy(std::forward<Args>(args))...)

  - そうでなければ以下の式でスレッドを生成して実行する

        invoke(decay-copy(std::forward<F>(f)), decay-copy(std::forward<Args>(args))...)

  - この呼び出しでの戻り値は無視される。この関数呼び出しが例外を送出する場合、呼び出し元スレッドで`std::terminate`が呼び出される

## 同期操作

- (2) : コンストラクタ呼び出しの完了は、`f`のコピーの呼び出し開始に対して同期する 新しいスレッドを生成し、`INVOKE(DECAY_COPY(std::forward<F>(f)), DECAY_COPY(std::forward<Args>(args))...)`を実行する。ただし`DECAY_COPY`は同コンストラクタを呼び出したスレッド上にて評価される。また`f`のコピーの戻り値は無視される。
  - `DECAY_COPY(x)`は `template <class T> typename std::decay<T>::type decay_copy(T&& v) { return std::forward<T>(v); }` と定義される。おおよそ、`x`が配列型なら先頭要素へのポインタ、`x`が関数型ならその関数ポインタ、`x`がコピーコンストラクト可能な型なら`x`からコピーされたオブジェクト、`x`がムーブコンストラクト可能な型なら`x`からムーブされたオブジェクトとなる。

## 同期操作

- (2) : 同コンストラクタの呼び出し完了は、fのコピーの呼び出し開始に対して同期する。つまり、「コンストラクタ呼び出し側スレッドT0でのコンストラクタ呼び出し完了」は、「新しいスレッド`T1`上での`f`のコピーの呼び出し開始」よりも前に発生する。

## 事後条件

- (1) :
  - `get_id() == id()`が`true`となること
  - `get_stop_source()`で取得される`std::stop_source`オブジェクトの`stop_possible()`が`false`であること
- (2) :
  - `get_id() != id()`が`true`となること
  - `get_stop_source()`で取得される`std::stop_source`オブジェクトの`stop_possible()`が`true`であること
  - `*this`は新しいスレッドと関連付けられること
- (4) :
  - `x.get_id() == get_id()`が`true`であること
  - `get_id()`がムーブ前の`x.get_id()`の値であること
  - `x.get_stop_source().stop_possible()`が`false`であること

## 例外

- (2) : 新しいスレッドの作成に失敗した場合、`system_error` 例外を投げる。その例外オブジェクトには、以下のエラー状態が設定されうる：

  - `resource_unavailable_try_again` : 新たなスレッドを作るためのリソースが不足している。もしくはシステムやプロセスが規定するスレッド数の上限を超過した。

## 例

    #include <iostream>
    #include <cstdint>
    #include <thread>

    std::uint64_t sum1 = 0;
    std::uint64_t sum2 = 0;

    void f1(std::stop_token stoken, std::uint64_t n)
    {
      sum1 = 0;
      for (std::uint64_t i = 1; i < n; ++i) {
        if (stoken.stop_requested()) {
          // 中断リクエストがきたのでスレッドを終了する
          break;
        }
        sum1 += i;
      }
    }

    void f2(std::uint64_t n)
    {
      sum2 = 0;
      for (std::uint64_t i = 1; i < n; ++i) {
        sum2 += i;
      }
    }

    int main()
    {
      {
        // 関数の第1引数がstd::stop_token型である場合、
        // スレッドに中断リクエストを送れるようになる
        std::jthread jt1 {f1, 1'000'000};
        std::this_thread::sleep_for(std::chrono::milliseconds{3});
        jt1.request_stop(); // スレッドの中断要求を発行

        // スレッド実行する関数がstd::stop_tokenを受け取らない場合、
        // 中断リクエストを使用せず、
        // デストラクタで自動的にjoinするスレッドオブジェクトとして使用する
        std::jthread jt2 {
          [] { f2(1'000'000); }
        };
      } // jthreadのデストラクタでは、中断要求を発行し、スレッドの終了を待機する

      std::cout << sum1 << std::endl; // 計算できたところまで表示
      std::cout << sum2 << std::endl;
    }

### 出力例

    48458670270
    499999500000

## バージョン

### 言語

- C++20

### 処理系

- Clang:
- GCC: 10.2.0 ✅
- Visual C++: ??