最終更新日時:
が更新

履歴 編集

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): 擬似乱数生成器 mt19937 を用いるため使用を推奨しない。詳細は備考欄を参照
  • 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++) では、random_deviceクラスは擬似乱数生成器であるmt19937で実装されている。その環境のデフォルトでは固定の乱数列が生成されてしまうので注意すること。コンストラクタの引数としてシード値を文字列化して渡せばmt19937のシードとして扱われるが、非決定論的な乱数として振る舞わないことは変わらない。この環境ではrandom_deviceの使用は推奨しない

代替
Workaround

ワークアラウンドとして次のコードが利用できる。rand_sではなくCryptGenRandomを用いているのは、rand_sを利用するにはWindows.hをincludeする前に_CRT_RAND_Sのdefineが必要でworkaroundには向かないため。

#include <random>
#if defined(__MINGW32__) && defined(__GNUC__) && !defined(__clang__)
#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)
    {
      using std::system_category;
      using std::system_error;
      const auto er = ::CryptAcquireContext(&this->prov_, container, provider, prov_type, flags);
      if(!er) {
        throw system_error(
          std::error_code(::GetLastError(), system_category()),
          "CryptAcquireContext:(" + std::to_string(er) + ')'
        );
      }
    }
    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()()
  {
    using std::system_category;
    using std::system_error;
    result_type re;
    const auto er = ::CryptGenRandom(this->prov_.get(), sizeof(re), reinterpret_cast<BYTE*>(&re));
    if(!er) {
      throw system_error(
        std::error_code(::GetLastError(), system_category()),
        "CryptGenRandom:(" + std::to_string(er) + ')'
      );
    }
    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 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 <vector>
#include <functional>
#include <algorithm>
using seed_v_t = std::vector<cpprefjp::random_device::result_type>;
seed_v_t create_seed_v()
{
  cpprefjp::random_device rnd;
  seed_v_t sed_v(9);
  std::generate(sed_v.begin(), sed_v.end(), std::ref(rnd));
  return sed_v;
}
int main()
{
  const auto sed_v = create_seed_v();
  std::seed_seq seq(sed_v.begin(), sed_v.end());
  std::mt19937 engine(seq);
  std::uniform_int_distribution<int> dist(1, 32);
  for(int i = 0; i < 10; ++i) std::cout << dist(engine) << std::endl;
}

参照