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
Если вам нужен заводской шаблон, это легко можно сделать поверх этого.
Как вы можете видеть, ... скажем ... нетривиально сделать распределение полиморфным.И вам нужно сделать выбор относительно вашего дизайна, и какой бы выбор вы ни выбрали, вы идете на компромисс.
Так что вам действительно нужно проанализировать, действительно ли это того стоит.Только вы можете ответить на этот вопрос.По крайней мере, теперь у вас есть отправная точка.