@Prosciutto
Постигаю

Издержки полиморфизма или неправильный дизайн?

Я получаю строку из токенов двух типов: float и char.
Нужно, считывая их последовательно, все поместить в массив, чтобы реализовать полиморфное поведение. Значит нужно выделить абстрактный класс и создать массив указателей на него, в который мы будем помещать адреса объектов классов-наследников.
Окей.

Пример кода:
class Token     // выделяем абстрактный класс
{
public:
    virtual float getFloat() = 0;
    virtual char getChar() = 0;
}

class FloatToken : public Token
{
private:
    float floatVal;
public:
    FloatToken() : floatVal(0.f) {}
    float getFloat() override {
        return floatVal;
    }
    char getChar() override {    // вынужденная реализация-заглушка
        return 0;
    }
}

class CharToken : public Token
{
private:
    char charVal;
public:
    CharToken() : charVal(0) {}
    char getChar() override {
        return charVal;
    }
    float getFloat() override {    // вынужденная реализация-заглушка
        return 0.f;
    }
}


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

UPD: пример учебный, к реальным задачам не имеет отношения. Из учебника Лафоре, глава 11, задача 8.
Там создается стек из чисел и операций. Операции - это char, а числа - float.
  • Вопрос задан
  • 219 просмотров
Решения вопроса 1
1. Не нужно пихать getChar и getFloat в абстрактный класс.
Но если таки делаешь заглушки - кидай полноценную ошибку, чтобы гарантировать корректное использование.

2. Вместо этого можно было бы использовать паттерн visitor - по крайней мере его часто используют в ситуациях, которые похожи на твою

3. Вместо указателей на классы можно попробовать union - для этого есть std::variant. Это будет чуть более эффективно по памяти, хоть размер массива может вырасти, если вместо float будет double, а вместо char - какой-нибудь wchar или "руна", или если добавится ещё какой-нибудь тяжёлый вариант. А до этого такой Юнион можно уместить в 8 байт вместе с выравниванием.
Ответ написан
Пригласить эксперта
Ответы на вопрос 3
wataru
@wataru Куратор тега C++
Разработчик на С++, экс-олимпиадник.
Не совсем правильный дизайн. Смысл складывать float и int в одну кучу, если все, что вы с ними делаете - это берете int или float значение.

В хорошем дизайне у вас какая-нибудь функция print будет. Которая будет соответствующее число красиво, в соответствии с типом, выводить. Или рисовать на экране что-то, или считать что-то.

Если же вы действительно хотите брать вот такие совершенно разные по смыслу значения у разных наследников, то, да, в интерфейсе должны быть все функции. Можно в интерфейсе их определить с ассертами и переопределить только в нужных наследниках.
Ответ написан
@mvv-rus
Настоящий админ AD и ненастоящий программист
Простейший способ решить вашу проблему, не нарушая вашу архитектуру - сделать корневой класс иерархии "полуабстрактным": сделать его виртуальные методы не чистыми виртуальными, а реализовать как те самые заглушки, которые вы реализуете в каждом классе-потомке.

PS Но лично мне не нравится сама такая архитектура: в ней получается, что использующая иерархию этих классов программа должна знать все типы, поддерживаемые этой иерархией, а это может вызвать проблемы с расширяемостью. Впрочем, выбор архитектуры - это вопрос всегда конкретный, завязанный на решение конкретной задачи.
Ответ написан
Комментировать
@vanyamba-electronics
union
{
    char charVal;
    float floatVal;
} Value;

class Token
{
    Value m_value;
public:
    inline Value const* getValue() const {
        return &m_value;
    }
    inline void setValue(Value const* pVal) {
       ::memcpy(&m_value, pVal, sizeof(Value));
    }
};

class CharToken : public Token
{
public:
     CharToken() {}
     inline char getChar() const {
        return m_value.charVal;
     }
};

class FloatToken : public Token
{
public:
     FloatToken() {}
     inline float getFloatValue() const {
        return m_value.floatValue;
     }
};
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы