最終更新日時(UTC):
が更新

履歴 編集

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

処理系

備考

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関数での実装になるためである。

代替
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_devicestd::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;
}

参照