Полиморфизм дизайна, шаблон как абстрактный класс
Как вы проектируете полиморфизм, когда у вас есть член, тип которого зависит от некоторых ограничений.
Скажи, что у меня есть это:
template<typename T>
class Base
{
public:
Base() = default;
virtual ~Base() = default;
T member;
};
class DerivedA : public Base<int>
{
public:
DerivedA() {member = 5;}
};
class DerivedB : public Base<float>
{
public:
DerivedB() = default;
};
Я хочу иметь возможность создавать новый производный объект в зависимости от различных параметров, а именно:
Base *b;
if (something)
b = new DerivedA();
else
b = new DerivedB();
Очевидно, я не могу этого сделать, так как мне нужно предоставить параметры шаблона для объявления b
,
Это плохой дизайн? Как вы справляетесь с этим?
Я мог бы написать небольшую обертку:
class Wrapper() {};
template<typename T>
class Base : public Wrapper
{
// ...
};
Wrapper a, b;
a = new DerivedA;
b = new DerivedB;
Но тогда у меня не будет доступа напрямую к member
или другие методы, объявленные в Base
или же Derived
, Я должен был бы бросить: reinterpret_cast<DerivedA*>(a)->member
, делающий полиморфизм бесполезным.
Спасибо
2 ответа
Дизайн обертки должен быть именно тем, что вы ищете. Проблема в том, что C++ имеет статическую типизацию, поэтому вы не можете объявить член, не указав его тип. Обычный способ избежать этого - создать базовый класс, который поддерживает все необходимые функции, и реализовать определенное поведение в производных классах, как вы это сделали.
Возможно, проблема в том, что ваш класс не поддерживает всю необходимую вам функциональность. Старайтесь избегать непосредственного использования члена и оберните его использование в виртуальные методы. Переопределите эти методы в ваших производных классах.
Если это все еще не вариант, рассмотрите способы извлечения вашего члена. Может быть, виртуальные геттеры и сеттеры с соответствующими преобразованиями.
В крайнем случае рассмотрим boost:: option, boost::any. Но на самом деле они реализованы с использованием техники, аналогичной вашей обертке. Таким образом, вы получаете обертку для обертки.
Хорошо, если "доступ" зависит от параметра шаблона T
(такое чтение Base.member
), то вы должны как- то его поставить. Приведение к одному из производных классов - это один из способов сделать это, но вам не нужно reinterpret_cast
, Вам нужно начать использовать указатели / ссылки, чтобы избежать обрезки и позволить корректной работе замещения:
Wrapper *a, *b;
a = new DerivedA;
b = new DerivedB;
int a_member = static_cast<DerivedA*>(a)->member;
float b_member = static_cast<DerivedB*>(b)->member;
И если вы добавите виртуальный метод Wrapper
чтобы сделать его полиморфным, вы также можете выполнить динамическое приведение:
DerivedB* b_derived = dynamic_cast<DerivedB*>(b);
if (b_derived != nullptr) {
float b_member = b_derived->member;
// ...
}