Полиморфизм с распределением в C ++ 11 - PullRequest
0 голосов
/ 25 сентября 2019

У меня есть проблема, когда я хочу, чтобы один объект представлял любой класс распределения (полиморфизм), используемый в случайной библиотеке в 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) Является ли приведенный выше код правильным подходом и в чем заключается ошибка и как ее решить.Я был бы очень благодарен, если бы кто-то мог помочь мне в этом

Ответы [ 2 ]

1 голос
/ 26 сентября 2019

1) Существует ли базовый класс распространения

Нет.C ++ - в отличие от Java или C # - не является языком ООП.Это мультипарадигмальный язык.Вместо базового класса для всего, C ++ использует метапрограммирование, когда это возможно.Например, не существует базового класса для всех контейнеров.Функции, желающие принять в качестве параметров контейнеры, не имеют базового класса в качестве аргумента.Вместо этого вы пишете шаблонные функции, которые способны обрабатывать любой диапазон.

2) если нет, есть ли способ создать полиморфизм

Да, конечно,но это не так просто, как может показаться на первый взгляд.

3) Является ли приведенный выше код правильным подходом, в чем заключается ошибка и как ее решить.Я был бы очень благодарен, если бы кто-то мог помочь мне в этом

Во-первых, не используйте явный new в C ++.И не используйте сырые указатели для моделирования собственности.В C ++ мы используем принцип RAII, и поэтому мы используем умные указатели, когда нужны указатели, которым принадлежат ресурсы.

Во-вторых, не используйте void*.Если вам действительно нужно удалить тип, используйте std::any, который, по общему признанию, является громоздким в данный момент, но он предлагает стирание типов безопасного типа.


Поэтому вместо того, чтобы пытаться исправить проблемы компиляции- который бы не исправил ваш дизайн, я вместо этого создал одну версию полиморфизма реализации.

Когда мы приступаем к выполнению этой задачи, первая проблема, с которой мы сталкиваемся: разные типы в функции генератора распределения (operator()).За один раз разные дистрибутивы могут получить разные типы (int, float и т. Д.).Затем нам нужен генератор - который является шаблоном - чтобы вызвать его.Это делает невозможным просто создать виртуальную функцию, которую мы можем переопределить.

Для возврата различных типов вам нужно 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 long, double и int в примере вместе со всеми предыдущими различными дистрибутивами:

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

Sampleвывод:

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

Если вам нужен заводской шаблон, это легко можно сделать поверх этого.


Как вы можете видеть, ... скажем ... нетривиально сделать распределение полиморфным.И вам нужно сделать выбор относительно вашего дизайна, и какой бы выбор вы ни выбрали, вы идете на компромисс.

Так что вам действительно нужно проанализировать, действительно ли это того стоит.Только вы можете ответить на этот вопрос.По крайней мере, теперь у вас есть отправная точка.

0 голосов
/ 25 сентября 2019

Если вы знаете, какой дистрибутив применим в каком сценарии, тогда лучше использовать шаблон.Вот пример кода для того же:

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