Writing a Factory method for STL random number generators

北战南征 提交于 2019-12-06 09:57:47

I've tried to keep the boilerplate to a bare minimum. Assumptions:

  • You know the type of your generator in advance (that's easily switchable if you need your generators to be dynamic as well)

  • All of the distributions generate doubles (that's pretty much baked in since the API has to return something concrete to be decently usable)

  • All of the distributions are constructible from double parameters (that's tweakable as well with a proxy object, but depending on your actual JSON library the work may already be done there)

  • I've used a GCC preprocessor extension to handle the zero-parameter case, but the macro can certainly be rewritten to not need it.

using Generator = std::mt19937;
using Distribution = std::function<double(Generator &)>;
using Json = std::map<std::string, std::string>;

template <class DistributionType, class... Parameters>
Distribution make_distribution_impl(Json const &json, Parameters... parameters) {
    return DistributionType{std::stod(json.at(parameters))...};
}

Distribution make_distribution(Json const &json) {
    auto const &distributionName = json.at("dist");

    #define generate_distribution_factory(name_, ...) \
        if(distributionName == #name_) \
            return make_distribution_impl<std::name_<double>>(json, ## __VA_ARGS__)

    generate_distribution_factory(uniform_real_distribution, "a", "b");
    generate_distribution_factory(normal_distribution, "mean", "stddev");
    // ...

    #undef generate_distribution_factory

    throw std::runtime_error{"Unknown distribution " + distributionName};
}

See it live on Wandbox

You described a pretty standard way to deal with this situation - to have abstract RandomGenerator class with just one virtual method gen().

Then, it will have implementations like NormalDistributionGenerator, UniformDistributionGenerator etc. with constructors accepting appropriate distribution parameter set and initializing STL stuff as members. These concrete classes will be used directly only in generator creation routine, and used in other places as abstract RandomGenerator.

So creation routine will look something like this

std::unique_ptr<RandomGenerator> CreateRandomGenerator(const Info& info) {
    switch (info.type) {
    case Type::Normal:
        return std::make_unique<NormalDistributionGenerator>(info.mean(), info.stddev());
    case Type::Uniform:
        return std::make_unique<UniformDistributionGenerator>(info.a(), info.b());
    // ...
    }
}

Info - is a class which holds distribution info (some JSON wrapper, of map/hash_table - whatever works best in your case).

So you will definitely need to write some boilerplate code to make it work, but it will make a usage of your RandomGenerator simple and clear, and adding new types of distributions will be easy enough and require code modification in single place only - factory method.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!