最終更新日時: 2025年07月12日 02時16分44秒 (JST)
Koichi Murase が更新

履歴 編集

class template
<random>

# std::random_device(C++11)

    namespace std {
      class random_device;
    }

## 概要

`random_device`クラスは、非決定論的な乱数生成エンジンである。予測不能な乱数を生成することから、擬似乱数生成エンジンのシード初期化や、暗号化といった用途に使用できる。

`random_device`の実装は処理系定義だが、Windows環境では`CryptGenRandom()`関数のラッパーとして、UNIX系環境では`/dev/random`や`/dev/urandom`から値を読み取る形で定義される場合がある。 実装の制限によって予測不能な乱数生成器を定義できない場合、このクラスは擬似乱数生成器で定義される可能性があるため、特にクロスプラットフォームなコードを書く場合は注意すること。

予測不能な乱数はソフトウェアでは実装できないため、これらはハードウェアのノイズやマウスの動きといった環境ノイズをエントロピープールとして乱数を生成する。 非決定論的な乱数生成器のパフォーマンスは擬似乱数生成器よりも悪く、特にエントロピープールが枯渇すると著しく悪化する。 再現性が必要なく、速度が遅くても問題ない状況で使用すること。

## 実装

- Windows
  - Visual C++: 外部デバイスを用いており、暗号学的に安全で非決定論的 (`rand_s`)
  - Clang: 暗号論的な乱数である `rand_s` を使用する
  - GCC (MinGW): GCC 9.1までは擬似乱数生成器 `mt19937` を用いるため使用を推奨しない。詳細は備考欄を参照。GCC 9.2からは暗号論的な乱数である `rand_s` を使用する。
- UNIX 系
  - Clang (libc++): `/dev/urandom` (デフォルト) または `/dev/random` から値を読み取る
  - GCC (libstdc++): CPU の `RDRAND` 命令を使う (デフォルト) か、`/dev/urandom` (`RDRAND` が使用できないときのデフォルト) または `/dev/random` から値を読み取る

## メンバ関数

### 構築

名前 | 説明 | 対応バージョン
--- | --- | ---
`(constructor)` | コンストラクタ | C++11
`~random_device() = default;` | デストラクタ | C++11
`void operator=(const random_device& ) = delete;` | 代入演算子。代入不可 | C++11

### 生成

名前 | 説明 | 対応バージョン
--- | --- | ---
`operator()` | 乱数を生成する | C++11

### エンジンの特性

名前 | 説明 | 対応バージョン
--- | --- | ---
`entropy` | エントロピーを取得する | C++11

## 静的メンバ関数

### 生成の特徴

名前 | 説明 | 対応バージョン
--- | --- | ---
`min` | 生成する範囲の最小値を取得する | C++11
`max` | 生成する範囲の最大値を取得する | C++11

## メンバ型

型 | 説明 | 対応バージョン
--- | --- | ---
`result_type` | 乱数生成結果の符号なし整数型 `unsigned int`。 | C++11

## 例

### 基本的な使い方

    #include <iostream>
    #include <random>

    int main()
    {
      std::random_device rng;

      for (int i = 0; i < 10; ++i) {
        // 予測不能な乱数を生成する
        unsigned int result = rng();

        std::cout << result << std::endl;
      }
    }

#### 出力例

    770203506
    3783995753
    458506084
    4033712415
    4182902552
    940753559
    327526966
    3226755811
    4026482080
    1445600541

### パスワードを生成する

    #include <iostream>
    #include <cassert>
    #include <string>
    #include <random>

    // candidate_chars : パスワードに含める文字の集合
    // length : パスワードの長さ
    std::string create_password(const std::string& candidate_chars, std::size_t length)
    {
      assert(!candidate_chars.empty());

      // 非決定論的な乱数生成器を使用する
      std::random_device engine;

      // パスワード候補となる文字集合の範囲を一様分布させる
      std::uniform_int_distribution<std::size_t> dist(0, candidate_chars.size() - 1);

      std::string password;
      for (std::size_t i = 0; i < length; ++i) {
        // パスワード候補の文字集合から、ランダムな一文字を選択する
        std::size_t random_index = dist(engine);
        char random_char = candidate_chars[random_index];

        // 選択した文字を、パスワード文字列に追加する
        password += random_char;
      }
      return password;
    }

    int main()
    {
      std::string candidate_chars = "abcdefghijklmnopqrstuvwxyz";
      std::size_t length = 8;

      std::string password = create_password(candidate_chars, length);
      std::cout << password << std::endl;
    }

#### 出力例

    jyiasder

## バージョン

### 言語

- C++11

### 処理系

- Clang: 3.2 ✅
- GCC: 4.7.2 ✅
- ICC: ??
- Visual C++: 2010 ✅, 2012 ✅, 2013 ✅, 2015 ✅, 2017 ✅

## 備考

### MinGW GCC(libstdc++)

Windows版のGCC (MinGW, libstdc++) 9.1まででは、`random_device`クラスは擬似乱数生成器である`mt19937`で実装されている。その環境のデフォルトでは固定の乱数列が生成されてしまうので注意すること。コンストラクタの引数としてシード値を文字列化して渡せば`mt19937`のシードとして扱われるが、非決定論的な乱数として振る舞わないことは変わらない。この環境では`random_device`の使用は推奨しない

GCC (MinGW, libstdc++) 9.2からは、この問題は解決されている。PR libstdc++/85494 use rdseed and rand_s in std::random_deviceにより、暗号学的に安全で非決定論的な乱数を生成する`rand_s`関数での実装になるためである。

##### 代替

- クロスプラットフォーム
  - CPU が提供する `RDRAND`, `RDSEED` 命令
- Windows
  - `rand_s` (`CryptGenRandom` のラッパー)
  - `CryptGenRandom` 関数(Windows XP/Windows Server 2003以降。非推奨)
  - `BCryptGenRandom` 関数(Windows Vista/Windows Server 2008以降)

##### Workaround

ワークアラウンドとして次のコードが利用できる。`rand_s`ではなく`CryptGenRandom`を用いているのは、`rand_s`を利用するには`Windows.h`をincludeする前に`_CRT_RAND_S`のdefineが必要でworkaroundには向かないため。また`BCryptGenRandom`を用いていないのは、対象としている環境では`CryptGenRandom`は利用可能であり、`BCryptGenRandom`より使える環境が広いためである。

    //! @file random_device.hpp
    #pragma once

    #include <random>

    #if defined(__MINGW32__) && defined(__GNUC__) && !defined(__clang__) && (__GNUC__ < 9 || (__GNUC__ == 9 && __GNUC_MINOR__ <2))

    #include <system_error>
    #include <limits>
    #include <string>
    #include <Windows.h>
    #include <wincrypt.h>

    namespace workaround_mingw_gcc {
    class random_device {
    private:
      class crypt_context {
      private:
        HCRYPTPROV prov_;
      public:
        crypt_context(DWORD prov_type, LPCTSTR container = nullptr, LPCTSTR provider = nullptr, DWORD flags = 0) {
          const auto success = ::CryptAcquireContext(&this->prov_, container, provider, prov_type, flags);
          if (!success) {
            throw std::system_error(
              std::error_code(::GetLastError(), std::system_category()),
              "CryptAcquireContext:(" + std::to_string(success) + ')'
            );
          }
        }
        crypt_context(const crypt_context&) = delete;
        void operator=(const crypt_context&) = delete;
        ~crypt_context() noexcept {
          ::CryptReleaseContext(this->prov_, 0);
        }
        //HCRYPTPROV& get() noexcept { return this->prov_; }
        const HCRYPTPROV& get() const noexcept { return this->prov_; }
      };
      crypt_context prov_;

    public:
      using result_type = unsigned int;
      explicit random_device(const std::string& /*token*/ = "workaround_mingw_gcc ")
      : prov_(PROV_RSA_FULL)
      {}
      random_device(const random_device&) = delete;
      void operator=(const random_device&) = delete;
      //~random_device() = default;
      double entropy() const noexcept { return 0.0; }
      result_type operator()() {
        result_type re;
        const auto success = ::CryptGenRandom(this->prov_.get(), sizeof(re), reinterpret_cast<BYTE*>(&re));
        if (!success) {
          throw std::system_error(
            std::error_code(::GetLastError(), std::system_category()),
            "CryptGenRandom:(" + std::to_string(success) + ')'
          );
        }
        return re;
      }
      static constexpr result_type min() { return std::numeric_limits<result_type>::min(); }
      static constexpr result_type max() { return std::numeric_limits<result_type>::max(); }
    };
    } // namespace workaround_mingw_gcc

    namespace cpprefjp {
    using workaround_mingw_gcc::random_device;
    }

    #else //defined(__MINGW32__) && defined(__GNUC__) && !defined(__clang__)

    namespace cpprefjp {
    using std::random_device;
    }

    #endif //defined(__MINGW32__) && defined(__GNUC__) && !defined(__clang__)

使用例は次の通り。上記コードを`random_device.hpp`というファイル名で保存していると仮定する。`cpprefjp::random_device`が`std::random_device`のworkaroundで、C++11標準規格の要求を満たしたクラス。

    #include "random_device.hpp"
    #include <iostream>
    #include <array>
    #include <functional>
    #include <algorithm>
    using seed_v_t = std::array<cpprefjp::random_device::result_type, sizeof(std::mt19937)/sizeof(cpprefjp::random_device::result_type)>;
    seed_v_t create_seed_v()
    {
      cpprefjp::random_device rnd;
      seed_v_t sed_v;
      std::generate(sed_v.begin(), sed_v.end(), std::ref(rnd));
      return sed_v;
    }
    std::mt19937 create_random_engine()
    {
      const auto sed_v = create_seed_v();
      std::seed_seq seq(sed_v.begin(), sed_v.end());
      return std::mt19937(seq);
    }
    std::mt19937& random_engine()
    {
      static thread_local std::mt19937 engine = create_random_engine();
      return engine;
    }
    int main()
    {
      std::mt19937& engine = random_engine();
      std::uniform_int_distribution<int> dist(1, 32);
      for(int i = 0; i < 10; ++i) std::cout << dist(engine) << std::endl;
    }

## 参照

- GCC: Implementation Status 26.5.6 [rand.device]
- Microdoft Visual Studio 2017: random_device Class
- /dev/random - Wikipedia
- Man page of RANDOM
- CryptGenRandom function (wincrypt.h) - Win32 apps | Microsoft Docs
- random_deviceの実装（再訪） - 煙人計画
- Replacing `/dev/urandom` May 4, 2016 - Security
- gccをwindowsで使うならstd::random_deviceを使ってはいけない - Qiita
- MSC30-C. 擬似乱数の生成に rand() 関数を使用しない
- `CryptGenRandom`のエントロピー源(2005年時点): Cryptographically Secure Random number on Windows without using CryptoAPI – Michael Howard's Web Log