(C ++) Контейнер шаблонных классов - PullRequest
1 голос
/ 18 марта 2020

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