I've very new to C++ and have been working through a tutorial and got as far as std::rand()
. I was immediately horrified and rushed out to see if I could implement this as a service. This is my (pretty basic) non-cryptographically-secure implementation in the style of .Net's System.Random
.
There are a number of things I'm unhappy about:
Anemic object. TBH there isn't much implementation here. Am I just (wrongly) trying to crowbar c++ into my .Net way of thinking? Is it a redundant layer of abstraction on top of the more powerful
<random>
library. However, my goal is to pass around one service for random numbers (to absolutely avoid hard coding things dependencies from<random>
in my classes) and I'm not sure this can be achieved using<random>
directly.Lifetime of the distributions. I'm not really sure what this should be. I've gone for the lifetime of the Random object where possible, but in the methods where I take a max-min value for each call this has not been possible. I'm pretty sure
std::mt19937
should have the same lifetime as myRandom
object.Hard-coded uniform distributions. Preferably I would like some kind of IOC, it would be nice to pass in whether I'm using a uniform, poisson etc. I've looked inside the class and there a lot of templates, could I somehow wrap this into factory class that has methods to return int-returning-distributions, double-returning-distributions etc (would that even be a good idea?)? Given that System.Random has a hardcoded unif dist, arguably this may to too much IOC.
I think I've found an implementation that uses templates but I'm not really sure it's solving the IOC problem.
Interface: IRandom.h (Has no corresponding .cpp file)
#pragma once
class IRandom
{
public:
virtual int32_t Next() = 0;
virtual int32_t Next(int32_t maxValue) = 0;
virtual int32_t Next(int32_t minValue, int32_t maxValue) = 0;
virtual double_t NextDouble() = 0;
virtual double_t NextDouble(double_t minValue, double_t maxValue) = 0;
virtual void NextBytes(std::vector<uint8_t>& buffer) = 0;
};
Random.h
#pragma once
#include <random>
#include "IRandom.h"
class Random : public IRandom
{
std::mt19937 _randomNumberGenerator;
std::uniform_real_distribution<double_t> _realDistribution;
std::uniform_int_distribution<int32_t> _bytedistribution;
public:
Random(uint_least32_t seed);
Random() : Random(std::_Random_device()) {};
~Random();
int32_t Next();
int32_t Next(int32_t maxValue);
int32_t Next(int32_t minValue, int32_t maxValue);
double_t NextDouble();
double_t NextDouble(double_t minValue, double_t maxValue);
void NextBytes(std::vector<uint8_t>& buffer);
};
Random.cpp
#include "stdafx.h"
#include "Random.h"
#include <random>
Random::Random(uint_least32_t seed)
{
this->_randomNumberGenerator = std::mt19937(seed);
this->_realDistribution = std::uniform_real_distribution<double_t>();
this->_bytedistribution = std::uniform_int_distribution<int32_t>(0, 256);
}
int32_t Random::Next()
{
return this->Next(0, INT32_MAX);
}
int32_t Random::Next(int32_t maxValue)
{
return this->Next(0, maxValue);
}
int32_t Random::Next(int32_t minValue, int32_t maxValue)
{
std::uniform_int_distribution<int32_t> distribution(minValue, maxValue);
return distribution(this->_randomNumberGenerator);
}
double_t Random::NextDouble()
{
return this->_realDistribution(this->_randomNumberGenerator);
}
double_t Random::NextDouble(double_t minValue, double_t maxValue)
{
std::uniform_real_distribution<double_t> distribution(minValue, minValue);
return this->_realDistribution(this->_randomNumberGenerator);
}
void Random::NextBytes(std::vector<uint8_t>& buffer)
{
for (auto &i : buffer)
{
i = static_cast<uint8_t>(_bytedistribution(this->_randomNumberGenerator));
}
}
Example Usage
unique_ptr<IRandom> randomService = make_unique<Random>();
cout << "NUMBERS" << endl;
for (int i = 0; i < 10; i++)
{
cout << randomService->Next(0,10) << endl;
}
auto buffer = vector<uint8_t>(100);
randomService->NextBytes(buffer);
cout << "BUFFER" << endl;
for (auto &i : buffer)
{
cout << i << endl;
}
<random>
library. The "engine" selection was template based. Being honest with you, I don't see much use for this today besides enforcing your own C#-ish style, though there might be some legitimate cases where you might need to select the random engine at runtime. That would be the only justification for the abstract interface, IMHO. \$\endgroup\$rand()
and the new C++ libraries, I think this article might be of interest: eternallyconfuzzled.com/arts/jsw_art_rand.aspx \$\endgroup\$