【问题标题】:Encapsulating boost::random for ease of usage to replace rand()封装 boost::random 以便于替换 rand()
【发布时间】:2010-09-01 11:03:39
【问题描述】:

对于我的程序,我需要具有不同范围的伪随机整数。 到目前为止,我一直使用 rand() 函数,但它有其局限性。

我发现 boost::random 库是一个更好的替代品,但我不想到处创建随机生成器。
(我在许多类中都需要随机整数,因为它是一个压力测试软件,可以伪随机地做出每个决定(-> 测试运行必须通过设置相同的起始种子来重复)。

这就是为什么我将 boost::random 封装在自己的课堂上。

这背后的想法是简化使用,使其几乎与 C++ rand() 方法

#include "boost/shared_ptr.hpp"
#include "boost/random.hpp"

class Random{
public:
   typedef boost::shared_ptr< Random > randomPtr;
   typedef boost::mt19937 randomGeneratorType;

   static randomPtr Get(){
      static randomPtr randomGen( new RandomGenerator() );
      return randomGen;
   }

   void SetSeed(int seed){
      randomGenerator.seed( seed );
   }

   int Random( int lowerLimit, int upperLimit ){
   boost::uniform_int<> distribution( lowerLimit, upperLimit );
   boost::variate_generator< randomGeneratorType&, boost::uniform_int<> >
   LimitedInt( randomGenerator , distribution );
   return LimitedInt();
   }

private:
   // prevent creation of more than one object of the LogManager class
   // use the Get() method to get a shared_ptr to the object
  Random():
    randomGenerator() //initialize randomGenerator with default constructor
  {}

  RandomGenerator( const RandomGenerator& orig ){};

  randomGeneratorType randomGenerator;
};

在给定范围内生成一个随机数现在就像

#include "Random.h"
  Random::Get()->SetSeed( 123123 );  // If you want to make the run repeatable
  int dice = Random::Get()->Random(1,6);

问题:
这种生成随机数的方式有什么问题吗?
我不认识的大开销?
纯粹的邪恶还是过时的编程技术?

(我还是 C++ 新手,想提高自己的技能,我发现堆栈溢出是 获得高质量建议的最佳场所)

【问题讨论】:

标签: c++ random boost-random


【解决方案1】:

Joe Gauterin 演示了这个问题,但它没有提供任何解决方案 :)

共享状态的问题是没有重入:即,执行两次相同的方法不会提供相同的结果。这在多线程情况下尤其重要,因为全局状态可能并不总是在程序中的同一点发生变化,从而导致从一次运行到另一次运行的结果不一致。

解决方案是每个模拟都应该有自己的“状态”,然后你会避免共享状态。

这可以通过多种方式实现:例如,您仍然可以使用“全局”状态,但将其设置为线程的本地状态,这样线程就不会互相踩到对方的脚趾。

然而,更简洁的版本在于将此状态存储在某处,更简单的方法是拥有某种Context 类,每次模拟实例化一次,它是模拟状态的聚合(用于模拟- 宽状态)。

考虑到这一点:

class Context
{
public:
  typedef boost::mt19937 RandomGeneratorType;

  void SetSeed(int seed){
     rg.seed( seed );
  }

  int Next( int lowerLimit, int upperLimit ) {
    boost::uniform_int<> distribution( lowerLimit, upperLimit );
    boost::variate_generator< randomGeneratorType&, boost::uniform_int<> >
    LimitedInt( rg, distribution );
    return LimitedInt();
  }

private:
  RandomGeneratorType rg;
};

然后,在您的模拟中传递Context 实例,您可以并行运行任意数量的实例。

【讨论】:

  • 这是有道理的。谢谢你的澄清。
【解决方案2】:

您实际上已经将您的生成器包装在singleton 中,引入了单例和全局变量带来的所有问题。例如,您很难让多个压力测试并行运行,因为您的实现不是线程安全的。

但我看到的主要问题是你的包装器并不比只使用没有包装器的 boost::random 简单。

【讨论】:

  • 我理解它,因此我必须为每个需要随机数的类编写 Random() 方法中列出的所有内容,此外我还必须创建 boost::mt19937 的实例(或其他)每个班级?
【解决方案3】:

你或许可以避开Get()。对我来说,这纯粹是主观的。我更喜欢Random::Seed()Random::Next()Random::Next(min,max) 这样的调用机制。 Random 中没有太多的函数,因此您可以将它们全部设为静态函数。

这是一个简单的实现。但请记住,这是考虑您在单线程环境中使用它。对于多线程环境,最好不要将其作为单例。

class Random
{
public:
    typedef boost::mt19937 RandomGeneratorType;

    static void Seed(int seed)
    {
        s_randGen.seed(seed);
    }

    static int NextInt(int min_val, int max_val)
    {
        boost::uniform_int<> distribution(min_val, max_val);boost::variate_generator< randomGeneratorType&, boost::uniform_int<> >
        return LimitedInt( s_randGen , distribution );;
    }
private:
    static RandomGeneratorType s_randGen;
};

Random::RandomGeneratorType Random::s_randGen;

【讨论】:

  • 答案为 0,使用 static 成员并不比使用本地 static 好:它基本上只是同一个 Global 的另一种实现。
  • @Matthieu:它确实有好处。避免了一些函数调用(比如从 boost::shared_ptr 和静态 Get() 获取指针)。就像我说的,这是一个主观问题。对你来说,这似乎很糟糕,而对我来说,这很有意义。
  • @Vite:它主要有缺点。不使用延迟初始化意味着可能使用未初始化的对象(未定义行为)并且不使用shared_ptr 来包装全局意味着可能使用已破坏的对象(未定义行为)。为了几个内联函数调用?请阅读Initialization Order FiascoDestruction Order Fiasco
  • @Matthieu:您的评论确实有道理,但在这种情况下,我真的没有看到任何 boost::mt19937 依赖于在它获取之前被初始化。
    清除“初始化订单惨败”=In short, suppose you have two static objects x and y which exist in separate source files, say x.cpp and y.cpp. Suppose further that the initialization for the y object (typically the y object's constructor) calls some method on the x object.。对我来说,你想做的只是发表一个花哨的评论来证明别人错了,而不是在上下文中。
  • @Matthieu:..(续)。你的论点在一般实践方面是好的,但在这种情况下不是一个缺点。
【解决方案4】:

下面是我的封装版本:

#include <boost/random.hpp>
#include <ctime>  


int getRandomIntValue(int min, int max)
{
    static boost::minstd_rand gen((unsigned int)std::time(NULL));
    boost::uniform_int<int> dist(min, max);
    boost::variate_generator<
        boost::minstd_rand&,
        boost::uniform_int<int>> combgen(gen, dist);

    return combgen();
}

【讨论】:

  • 快速连续多次调用(在一秒钟内多次调用,并不罕见)将一遍又一遍地给出相同的号码。出于大多数实际目的,需要更改播种(添加种子参数,包装在每次调用后更新种子的类中,等等)。否则是一个非常干净的解决方案。
【解决方案5】:

我会说这看起来不错 - 这样您可以在需要时轻松替换随机生成算法。

【讨论】:

    【解决方案6】:

    您也可以尝试在容器中创建实体并随机打乱它们。

    void
    GenerateRandomString(vector<string>& container,
                         int size_of_string,
                         unsigned long long num_of_records,
                         int thread_id)
    {
      srandom(time(0));
      random();
      for(unsigned long long int i=0; i < num_of_records; ++i)
      {
        stringstream str_stream;
        str_stream.clear();
        str_stream << left << setfill('x') << setw(size_of_string-4);
        str_stream << num_of_records+i+1 << "-" << thread_id;
        container.push_back(str_stream.str());
      }
      random_shuffle(container.begin(), container.end());
    }
    

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-21
    • 1970-01-01
    • 1970-01-01
    • 2013-06-03
    • 2018-04-27
    • 1970-01-01
    相关资源
    最近更新 更多