Я уверен, что этот вопрос часто возникает, но у моего вопроса есть небольшая разница, на которую я не обращал внимания (возможно, это связано с плохим дизайном с моей стороны: я довольно плохо знаком с C ++).
Например, скажем, у меня есть базовый класс как таковой:
class Var {
public:
Var(std::string id, std::string description) : _id(id), _description(description) {}
private:
std::string _id, _description;
public:
std::string id() {
return this->_id;
}
std::string description() {
this->_description;
}
};
И некоторые реализации, подобные этой:
template<
typename N,
typename = typename std::enable_if<std::is_arithmetic<N>::value, N>::type
>
class VarNumber : public Var {
public:
VarNumber(std::string id, std::string desc, N value, N min, N max) : Var(id, desc), _value(value), _min(min), _max(max) {}
VarNumber(std::string id, N value, N min, N max) : VarNumber(id, "", value, min, max) {}
private:
N _value;
N _min, _max;
public:
N value() {
return this->_value;
}
void set(N value) {
this->_value = std::clamp(value, this->_min, this->_max);
}
N min_value() {
return this->_min;
}
N max_value() {
return this->_max;
}
};
class VarBool : public Var {
public:
VarBool(std::string id, std::string desc, bool value) : Var(id, desc), _value(value) {}
VarBool(std::string id, bool value) : VarBool(id, "", value) {}
private:
bool _value;
public:
bool value() {
return this->_value;
}
bool set_value(bool value) {
this->_value = value;
}
bool toggle() {
this->set_value(!this->_value);
}
};
Скажем, я хотел контейнер держать все реализации Var в одном месте. Теперь мне сказали, что злоупотреблять наследованием, чтобы сделать это, не путь к go, то есть:
std::vector<Var*> vars;
vars.push_back(new VarBool(...));
vars.push_back(new VarNumber<float>(...));
Тем более что приведенное выше потребовало бы приведения для доступа к методам, указанным c для каждого Var
реализация:
(static_cast<VarBool*>(vars.at(0)))->toggle();
float max = (static_cast<VarNumber<float>*>(vars.at(1)))->max_value());
Я также попытался определить контейнер следующим образом:
std::vector<std::variant<VarBool, VarNumber<int>, VarNumber<float>, ... >>
Это сработало бы, если бы для каждого Var было конечное число типов шаблонов реализация, но это не работает для моих целей. У меня есть другие Var
типы, которые созданы для перечислений, которые не могут быть определены во время компиляции:
template<
typename E,
typename = typename std::enable_if<std::is_enum<E>::value>::type
>
class VarMOption : public Var {
...
};
Какой лучший способ go по этому поводу? Я пытался реализовать шаблон стирания типа, но он работает только в том случае, если у каждого «потомка» есть одинаковые методы.
Я хотел бы спроектировать это таким образом, чтобы при сохранении всех типов в одном В контейнере нет представленных бессмысленных состояний, то есть метод max_value()
для VarBool
или метод toggle()
для VarNumber<float>
.
Является ли это результатом плохого дизайна? Какой лучший подход для этого?
Спасибо.
РЕДАКТИРОВАТЬ: Пример относительно комментария @ ALX23z
enum Target {
FOO,
BAR,
QAZ
}
std::unique_ptr<Var> enum_var = std::make_unique(Var("test enum", "description"));
enum_var->variant = EnumVar<Target>(Target::FOO); //EnumVar doesn't exist, since we would have to somehow define it in the variant
// But this would work:
std::unique_ptr<Var> num_var = std::make_unique(Var("test num", "desc"));
num_var->variant = NumberVar<float>(3.f, 0.f, 10.f);
// In Var class:
class Var {
// constructor here... (2 strs)
std::variant<NumberVar<float>, NumberVar<int> .. etc> variant;
}
РЕДАКТИРОВАТЬ 2 Вот то, что я придумал. Это очень некрасиво.
class VarBool {
public:
VarBool(bool value_) : value_(value_) {}
private:
bool value_;
public:
bool& value() {
return this->value_;
}
void set(const bool& other) {
this->value_ = other;
}
};
template<
typename N,
typename = typename std::enable_if<std::is_arithmetic<N>::value, N>::type
>
class VarNumber {
public:
VarNumber(N value_, N min_, N max_) : value_(value_), min_(min_), max_(max_) {}
private:
N value_, min_, max_;
public:
N& value() {
return this->value_;
}
void set(const N& other) {
this->value_ = std::clamp(other, this->min_, this->max_);
}
N minimum() {
return this->min_;
}
N maximum() {
return this->max_;
}
};
class VarWrapper {
using V = std::variant<VarBool, VarNumber<int>, VarNumber<float>>;
public:
VarWrapper(std::string id_, std::string desc_, V inner_) : id_(id_), desc_(desc_), inner_(inner_) {}
VarWrapper(std::string id_, V inner_) : VarWrapper(id_, "", inner_) {}
private:
std::string id_, desc_;
V inner_;
public:
std::string id() {
return this->id_;
}
std::string desc() {
return this->desc_;
}
template<typename T>
T& unwrap() {
if (std::holds_alternative<T>(this->inner_))
return std::get<T>(this->inner_);
}
};
Использование:
std::shared_ptr<VarWrapper> w_var = std::make_shared<VarWrapper>(VarWrapper("name", "description", VarNumber<float>(1.5f, 0.f, 10.f)));
float& unwrapped_var_val = w_var->unwrap<VarNumber<float>>().value();
std::cout << unwrapped_var_val << "\n"; //prints 1.5f
w_var->unwrap<VarNumber<float>>().set(200.f);
std::cout << unwrapped_var_val << "\n"; //prints 10.f (clamped)