简体   繁体   English

如何根据用户输入声明 C++ 构造函数?

[英]How to declare a C++ constructor depending on user input?

I'm learning C++ and stumbled upon an issue I don't know how to solve.我正在学习 C++ 并偶然发现了一个我不知道如何解决的问题。 I have a class hierarchy of probability distributions.我有一个概率分布的 class 层次结构。 I'm asking the user to input the name of a probability distribution (eg "uniform" ) and its associated parameters, which I'm storing as a tuple of the form我要求用户输入概率分布的名称(例如"uniform" )及其相关参数,我将其存储为表单的元组

std::tuple<std::string, double, double>

I would then like to call the constructor of the corresponding probability distribution class, for example然后我想调用对应概率分布class的构造函数,例如

continuous_distribution *random_uniform = new uniform{0.0,1.0};

However, I'm not sure how to map the name of the distribution given as a C++ string (1st element of the tuple) to the appropriate constructor.但是,我不确定如何将 map 作为 C++ 字符串(元组的第一个元素)给出的分布名称传递给适当的构造函数。 I don't think it's possible to directly convert a string to a constructor or member function.我认为不可能直接将字符串转换为构造函数或成员 function。 I tried using if statements to check the given name and call the corresponding constructor, but this runs into identifier "x" is undefined problems, apparently because this limits the scope of the constructor to the if statement only.我尝试使用if语句检查给定名称并调用相应的构造函数,但这遇到identifier "x" is undefined问题,显然是因为这将构造函数的 scope 限制为仅 if 语句。 Is there a solution to this problem?这个问题有解决方案吗?

If you really wanted to use dynamic class hierarchies here, a simple factory function would do the trick.如果你真的想在这里使用动态 class 层次结构,一个简单的工厂 function 就可以了。

#include <string_view>
#include <memory>

struct distribution {
    virtual float generate() = 0;
    virtual ~distribution() = default;
};

struct uniform : distribution {
    uniform(float, float);
    float generate() final;
};

struct normal : distribution {
    normal(float, float);
    float generate() final;
};

std::unique_ptr<distribution> make_distribution(std::string_view name, 
                                                float x,
                                                float y)
{
    // for a large amount of different names, using a
    // std::unordered_map<std::string, distribution*(*)(float,float)>
    // may be more efficient than an if/else chain
    
    if (name == "uniform") {
        return std::make_unique<uniform>(x, y);
    }
    else if (name == "normal") {
        return std::make_unique<normal>(x, y);
    }
    else {
        return nullptr;
    }
}

However, I would also consider different approaches, such as using a std::variant of different distributions, or simply a std::function or function pointer, assuming that the distributions only have a single virtual function.但是,我也会考虑不同的方法,例如使用不同分布的std::variant ,或者简单地使用std::function或 function 指针,假设分布只有一个虚拟 ZC1C424Z0768E68394F11C

I would probably use an unordered_map to do the mapping between the literal name of the distribution and an object capable of generating random numbers using that distribution.我可能会使用unordered_map在分布的文字名称和能够使用该分布生成随机数的 object 之间进行映射。

I would avoid runtime polymorphism in this particular case - because people dealing with random number generation are often obsessed with speed and dynamic dispatch comes with some overhead.在这种特殊情况下,我会避免运行时多态性——因为处理随机数生成的人通常痴迷于速度,而动态调度会带来一些开销。

Here are the building blocks I'd consider:以下是我会考虑的构建块:

  • Create a function to return a reference to a seeded PRNG.创建 function 以返回对种子 PRNG 的引用。
  • Create simple functions to return references to the distributions you want to support.创建简单的函数以返回对您想要支持的分布的引用。
  • The instances in the above functions should be static - or thread_local if this is ever going to be multithreaded.上述函数中的实例应该是static - 或thread_local如果这将是多线程的。
  • Create a map that returns a lambda capable of reading user supplied parameters, set those parameters in the appropriate distribution and then return another lambda that can be used to call the PRNG using the distribution.创建一个 map,它返回一个 lambda 能够读取用户提供的参数,在适当的分布中设置这些参数,然后返回另一个 lambda 可用于调用分布。

First the prng() function and some examples of distributions:首先是 prng prng() function 和一些分布示例:

#include <algorithm>
#include <functional>
#include <iostream>
#include <random>
#include <type_traits>
#include <unordered_map>

auto& prng() {
    thread_local std::mt19937 instance(std::random_device{}());
    return instance;
}
auto& uniform() {
    thread_local std::uniform_real_distribution<double> instance;
    return instance;
}
auto& normal() {
    thread_local std::normal_distribution<double> instance;
    return instance;
}
auto& exponential() {
    thread_local std::exponential_distribution<double> instance;
    return instance;
}

Then the map.然后是 map。 It's a little cluttered but follows a pattern:它有点混乱,但遵循一个模式:

  • Print the wanted parameters to the user.将想要的参数打印给用户。 In this example, one distribution only requires 1 parameter and the other requires 2.在此示例中,一个分布只需要 1 个参数,而另一个分布需要 2 个。
  • Read the parameter(s) from the user.从用户那里读取参数。
  • Call the selected distribution function to get a reference to the distribution and set the parameters in that distribution.调用所选分布 function 以获取对分布的引用并在该分布中设置参数。
  • Finally, return a lambda that will act as a generator.最后,返回一个 lambda 作为生成器。
using gen_sig = double (*)(); // the signature of the returned generators

// A map from clear text name to a lambda that reads distribution parameters,
// sets them in the selected distribution and returns another lambda, wrapping
// a call to distribution(prng)
std::unordered_map<std::string, gen_sig (*)(std::ostream&, std::istream&)>
    gen_factories{
        {"uniform", [](std::ostream& os, std::istream& init) -> gen_sig {
             using ptype = std::remove_reference_t<decltype(uniform())>::param_type;
             os << "min max> ";
             double min, max;
             init >> min >> max;
             uniform().param(ptype(min, max)); // set params
             return [] { return uniform()(prng()); };
         }},
        {"normal", [](std::ostream& os, std::istream& init) -> gen_sig {
             using ptype = std::remove_reference_t<decltype(normal())>::param_type;
             os << "mean stddev> ";
             double mean, stddev;
             init >> mean >> stddev;
             normal().param(ptype(mean, stddev)); // set params
             return [] { return normal()(prng()); };
         }},
        {"exponential", [](std::ostream& os, std::istream& init) -> gen_sig {
             using ptype = std::remove_reference_t<decltype(exponential())>::param_type;
             os << "lambda> ";
             double lambda;
             init >> lambda;
             exponential().param(ptype(lambda)); // set params
             return [] { return exponential()(prng()); };
         }},
    };

The interaction with the user could then look like this:与用户的交互可能如下所示:

int main() {
    std::string dist;

    while(std::cout << "Select distribution> " && std::cin >> dist) {
        if(auto it = gen_factories.find(dist); it != gen_factories.end()) {

            // get a generator
            auto gen = it->second(std::cout, std::cin);

            // print 10 numbers using the selected distribution
            for(int i = 0; i < 10; ++i) std::cout << gen() << '\n';

        } else {
            std::cout << "ERROR: Distribution \"" << dist << "\" not implemented.\n";
        }
    }
}

Example session:示例 session:

Select distribution> exponential
lambda> 0.5
0.261241
0.0869284
1.73573
5.00439
1.68901
2.96613
0.583754
0.223562
0.2828
0.146842
Select distribution> uniform
min max> 10 20
17.177
19.587
18.9658
14.5269
11.6994
13.7626
13.8706
19.0378
17.0001
17.4791

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM