【问题标题】:Polymorphism with Distribution in C++11C++11 中具有分布的多态性
【发布时间】:2019-09-25 18:50:23
【问题描述】:

我有一个问题,我想要一个对象来表示 c++11 中随机库中使用的任何分布类(多态性)。据我所知,由于发行版没有通用的基类,因此我正在使用工厂模式来解决我的问题。有更好的方法吗?我正在添加下面有一些错误的代码

#include <iostream>
#include<random>

using namespace std;

class Distribution 
{
public:
    void *distribution;
    virtual void get_distribution() = 0;
};

class normal_dist: public Distribution
{
public:
    //normal_distribution<double> *distribution;
    void get_distribution()
    {
        distribution = new normal_distribution<double>(5.0,2.0); 
    }
};

class uniform_real_dist: public Distribution
{
public:
    //uniform_real_distribution<double> *distribution;
    void get_distribution()
    {
        distribution = new uniform_real_distribution<double>(0.0,1.0);
    }
};

class Helper
{
public:
    Distribution *dist;
    default_random_engine generator;

Helper(string dist_type) 
{
    if(dist_type == "normal")
    {
        dist = new normal_dist;
        dist->get_distribution();
    }
}
};


int main() {

Helper *help = new Helper("normal");
cout<<(*(help->dist->distribution))(help->generator);


// your code goes here
return 0;
}

我的问题是三个方面

1) 有没有分布的基类

2) 如果没有,有没有办法创建多态性

3) 上面的代码是否是正确的方法,什么是错误以及如何解决这个问题。如果有人可以帮助我,我将非常感激

【问题讨论】:

  • 如果我必须从您的代码中猜测,我会说您来自 java 或 C# 背景。在 C++ 中使用 new 几乎总是一个错误、错误或反模式。使用void* 是另一个巨大的危险信号。
  • 谢谢@bolov 的回答。它真的帮了很多忙。您能否告诉我一些标准书籍以外的资源如何提高我的设计和编码技能。非常感谢

标签: c++ c++11 random


【解决方案1】:

1) 有没有分布的基类

不。 C++ - 与 Java 或 C# 不同 - 不是 OOP 语言。它是一种多范式语言。 C++ 尽可能地使用元编程,而不是所有东西的基类。例如,并非所有容器都有一个基类。想要接受作为参数容器的函数,没有基类作为参数。相反,您编写一个能够处理任何范围的模板函数。

2) 如果没有,有没有办法创建多态性

是的,当然,但它并不像乍看起来那么简单。

3) 上面的代码是否是正确的方法,什么是错误以及如何解决这个问题。如果有人可以帮助我,我将非常感激

首先,不要在 C++ 中使用显式 new。并且不要使用原始指针来建模所有权。在 C++ 中,我们使用 RAII 原则,因此当需要拥有资源的指针时,我们使用智能指针。

其次,不要使用void*。如果您确实需要类型擦除,请使用 std::any,虽然目前使用起来很麻烦,但它提供了类型安全的类型擦除。


因此,我没有尝试解决您的编译问题(这不会解决您的设计),而是创建了一个多态性的实现版本。

当我们着手这项任务时,我们遇到的第一个问题是:分布生成器函数中的不同类型(operator())。这一次,不同的发行版可以获得不同的类型(intfloat 等)。然后,我们需要一个生成器——即模板——来调用它。这些使得我们不可能简单地创建一个我们可以覆盖的虚函数。

要返回不同的类型,您需要std::variant。因为实现已经很困难了,我会在第二个例子中这样做。

对于生成器,有一些方法可以摆脱更简单的设计。我选择在派生发行版中存储指向生成器的指针。这对分布中生成器的类型进行了编码,但在许多情况下是可以接受的。另一种更长的方法是创建生成器的多态版本。

所以这是第一个使用多态 int 分布向量的示例。在示例中,向量存储均匀分布、二项分布和泊松分布,以查看工作中的多态性:

#include <iostream>
#include <random>
#include <memory>
#include <vector>

class Base_int_distribution
{
public:
    virtual int generate() = 0;
    virtual ~Base_int_distribution() = default;
};

template <class Eng, class D>
class Int_distribution : public Base_int_distribution
{
    std::shared_ptr<Eng> engine_;
    D distribution_;

public:
    template <class... Args>
    Int_distribution(std::shared_ptr<Eng> engine, Args... args)
        : engine_{std::move(engine)},
          distribution_{args...} {}

    Int_distribution(const Int_distribution&) = default;
    Int_distribution(Int_distribution&&) = default;

    Int_distribution& operator=(const Int_distribution&) = default;
    Int_distribution& operator=(Int_distribution&&) = default;

    int generate() override { return distribution_(*engine_); }
};

// some alias helpers, because oh boy, does it get crazy with the types

template <class Gen>
using Uniform_int_dist_poly = Int_distribution<Gen, std::uniform_int_distribution<int>>;

template <class Gen>
using Binomial_int_dist_poly = Int_distribution<Gen, std::binomial_distribution<int>>;

template <class Gen>
using Poisson_int_dist_poly = Int_distribution<Gen, std::poisson_distribution<int>>;
int main()
{
    std::random_device rd;  
    auto eng = std::make_shared<std::mt19937>(rd());

    std::vector<std::unique_ptr<Base_int_distribution>> distributions{};
    // nope, we can't use initializer list, don't get me started

    distributions.push_back(std::make_unique<Uniform_int_dist_poly<std::mt19937>>(eng, -50, 50));
    distributions.push_back(std::make_unique<Binomial_int_dist_poly<std::mt19937>>(eng, 4, .05));
    distributions.push_back(std::make_unique<Poisson_int_dist_poly<std::mt19937>>(eng, 4.));

    // for each distribution
    for (auto& dist : distributions)
    {
        // get 10 numbers
        for (int i = 0; i < 10; ++i)
        {
            std::cout << dist->generate() << " ";
        }
        std::cout << std::endl;
    }

    return 0;
}

样本输出:

21 0 -43 2 -37 -38 39 42 -37 46 
0 0 0 0 0 0 0 1 0 0 
5 4 7 6 5 6 6 4 1 8

接下来,我修改了它以使用标准算术类型,并在示例中使用了long longdoubleint,以及之前的所有不同分布:

#include <iostream>
#include <random>
#include <memory>
#include <vector>
#include <variant>

using DistTs = std::variant<
    signed char, unsigned char, char,
    short, int, long, long long,
    unsigned short, unsigned int, unsigned long, unsigned long long,
    float, double, long double>;

class Base_distribution
{
public:
    virtual DistTs generate() = 0;
    virtual ~Base_distribution() = default;
};

template <class Eng, class D>
class Distribution : public Base_distribution
{
    std::shared_ptr<Eng> engine_;
    D distribution_;

public:
    template <class... Args>
    Distribution(std::shared_ptr<Eng> engine, Args... args)
        : engine_{std::move(engine)},
          distribution_{args...} {}

    Distribution(const Distribution&) = default;
    Distribution(Distribution&&) = default;

    Distribution& operator=(const Distribution&) = default;
    Distribution& operator=(Distribution&&) = default;

    DistTs generate() override { return distribution_(*engine_); }
};

// some alias helpers, because oh boy, does it get crazy with the types

template <class Gen, class T = int>
using Uniform_int_dist_poly = Distribution<Gen, std::uniform_int_distribution<T>>;

template <class Gen, class T = double>
using Uniform_real_dist_poly = Distribution<Gen, std::uniform_real_distribution<T>>;

template <class Gen, class T = int>
using Binomial_dist_poly = Distribution<Gen, std::binomial_distribution<T>>;

template <class Gen, class T = int>
using Poisson_int_dist_poly = Distribution<Gen, std::poisson_distribution<T>>;
int main()
{
    std::random_device rd;  
    auto eng = std::make_shared<std::mt19937>(rd());

    std::vector<std::unique_ptr<Base_distribution>> distributions{};
    // nope, we can't use initializer list, don't get me started

    distributions.push_back(std::make_unique<Uniform_int_dist_poly<std::mt19937, long long>>(eng, -50, 50));
    distributions.push_back(std::make_unique<Uniform_real_dist_poly<std::mt19937, double>>(eng, -2.0, 2.0));
    distributions.push_back(std::make_unique<Binomial_dist_poly<std::mt19937>>(eng, 4, .05));
    distributions.push_back(std::make_unique<Poisson_int_dist_poly<std::mt19937>>(eng, 4.));

    // for each distribution
    for (auto& dist : distributions)
    {
        // get 10 numbers
        for (int i = 0; i < 10; ++i)
        {
            std::visit([](auto e) { std::cout << e << " "; }, dist->generate());
        }
        std::cout << std::endl;
    }

    return 0;
}

样本输出:

8 -25 29 14 -17 -6 -16 18 32 16 
0.683707 0.2806 -1.00301 1.89474 1.19426 0.069856 -0.354233 1.1193 0.0319062 -1.69658 
0 0 1 1 0 0 0 0 0 0 
3 4 5 5 2 8 3 8 1 5

如果您需要工厂模式,可以在此基础上轻松制作。


如您所见,... 假设... 使分布具有多态性并非易事。而且你需要对你的设计做出一些选择,无论你选择哪一个,你都会做出妥协。

所以你真的需要分析它是否真的值得。只有你能回答这个问题。至少现在你有了一个起点。

【讨论】:

    【解决方案2】:

    如果您知道哪种发行版适用于哪种场景,那么使用模板是更好的方法。这是相同的示例代码:

    template<class C, template <class> class M>
    M<C> getDistributionObject(const C& arg1, const C& arg2)
    {
       return M<C>(arg1,arg2);
    }
    int main() {
    
    auto normalDistribution = getDistributionObject<double,normal_distribution>(5.0,2.0); 
    return 0;
    }
    

    【讨论】:

    • 永远不要使用显式的new 并且永远不要使用原始指针来指向模型所有权(至少不在用户代码中)。
    • 我必须承认我印象深刻。好工作!我已经删除了反对票。但是,我不能赞成你的答案,因为我没有看到使用智能指针的意义,因为你没有使用多态性。如果您可以在帖子中解释使用智能指针而不是值对象的目的是什么。话又说回来,OP 没有解释他/她需要什么,所以......
    • @bolov 你是对的,因为我没有使用多态性所以使用指针没有任何意义,所以再次更新我的答案没有指针
    猜你喜欢
    • 1970-01-01
    • 2019-12-18
    • 2017-11-02
    • 1970-01-01
    • 2021-06-21
    • 2017-03-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多