繁体   English   中英

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

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

我正在学习 C++ 并偶然发现了一个我不知道如何解决的问题。 我有一个概率分布的 class 层次结构。 我要求用户输入概率分布的名称(例如"uniform" )及其相关参数,我将其存储为表单的元组

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

然后我想调用对应概率分布class的构造函数,例如

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

但是,我不确定如何将 map 作为 C++ 字符串(元组的第一个元素)给出的分布名称传递给适当的构造函数。 我认为不可能直接将字符串转换为构造函数或成员 function。 我尝试使用if语句检查给定名称并调用相应的构造函数,但这遇到identifier "x" is undefined问题,显然是因为这将构造函数的 scope 限制为仅 if 语句。 这个问题有解决方案吗?

如果你真的想在这里使用动态 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;
    }
}

但是,我也会考虑不同的方法,例如使用不同分布的std::variant ,或者简单地使用std::function或 function 指针,假设分布只有一个虚拟 ZC1C424Z0768E68394F11C

我可能会使用unordered_map在分布的文字名称和能够使用该分布生成随机数的 object 之间进行映射。

在这种特殊情况下,我会避免运行时多态性——因为处理随机数生成的人通常痴迷于速度,而动态调度会带来一些开销。

以下是我会考虑的构建块:

  • 创建 function 以返回对种子 PRNG 的引用。
  • 创建简单的函数以返回对您想要支持的分布的引用。
  • 上述函数中的实例应该是static - 或thread_local如果这将是多线程的。
  • 创建一个 map,它返回一个 lambda 能够读取用户提供的参数,在适当的分布中设置这些参数,然后返回另一个 lambda 可用于调用分布。

首先是 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;
}

然后是 map。 它有点混乱,但遵循一个模式:

  • 将想要的参数打印给用户。 在此示例中,一个分布只需要 1 个参数,而另一个分布需要 2 个。
  • 从用户那里读取参数。
  • 调用所选分布 function 以获取对分布的引用并在该分布中设置参数。
  • 最后,返回一个 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()); };
         }},
    };

与用户的交互可能如下所示:

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";
        }
    }
}

示例 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