C Избегает понижения

#c #inheritance #polymorphism #subclass #downcast

#c #наследование #полиморфизм #подкласс #понижение

Вопрос:

Мне нужно проанализировать исходный код. Я определил 3 разных типа токенов: символы (операторы, ключевые слова), символы (целые числа, строки и т. Д.) И идентификаторы.

У меня уже есть следующий дизайн с базовым классом, который отслеживает тип подкласса, чтобы его можно было понизить с помощью указателя базового класса :

 class Token
{
    type_e type; // E_SYMBOL, E_LITTERAL, E_TOKEN
};

class Symbol : public Token
{
    const symbol_e symbol;
};

class Litteral : public Token
{
    const Value value;
};

class Identifier : public Token
{
    const std::string name;
};
 

Мне нужно, чтобы эти классы хранились в одном массиве токенов, поэтому мне нужно, чтобы у них был общий базовый класс. Затем я использую их следующим образом :

 if( cur->type == E_SYMBOL amp;amp; static_cast< const Symbol * >( cur )->symbol == E_LPARENT )
{
    // ...
}
 

Я мог бы создать виртуальные функции isSymbol, isLitteral, isIdentifer, которые каждый подкласс переопределял бы, но мне все равно пришлось бы понизить указатель базового класса до указателя подкласса, чтобы я мог получить доступ к конкретным данным подкласса.

Люди говорят, что даункастинг означает, что интерфейс, скорее всего, имеет недостатки, и это делает синтаксис очень тяжелым, поэтому я хотел бы найти другой способ, но не могу. Некоторые люди предложили шаблон visitor, но я боюсь, что это бесполезно усложнило бы код, и я даже не понимаю, как я мог бы использовать шаблон visitor для решения этой проблемы.

Кто-нибудь может помочь? Спасибо 🙂

Комментарии:

1. Token boost::variant Может быть, сделать?

2. Вам не нужен общий базовый класс для хранения разных типов в одном массиве. Например, тип варианта, упомянутый выше.

3. @Virus721: Boost — чистейший из чистейших C . У вас не может быть более чистого C , чем boost. Ну, серьезно, с одной стороны, boost — это своего рода промежуточная область для стандартной библиотеки (точно так же, как STL был много лет назад). А для другого вы можете легко выбрать определенные биты из boost; они в основном независимы.

4. @Virus721: изобретать дискриминационное объединение было бы такой же пустой тратой времени.

5. @Virus721 Нет, потому что вы не предоставили никакой полезной информации о своей проблеме.

Ответ №1:

У вас есть три варианта. Каждое решение имеет свои преимущества и недостатки.

  • Поместите логику в классы токенов, чтобы вызывающему коду не нужно было знать, с каким типом токена он имеет дело.

    Это было бы «самым чистым объектно-ориентированным» решением. Недостатком является то, что логика имеет тенденцию распространяться между базовым классом и подклассами, что затрудняет ее понимание. Это также может привести к тому, что классы станут довольно большими. Но у компиляторов / интерпретаторов обычно не так много действий, чтобы это стало проблемой.

  • Используйте шаблон посетителя.

    То есть имеет интерфейс TokenVisitor с visit перегруженным методом для подтипов токенов и accept(TokenVisitoramp;) метод, для Token которого каждый подкласс переопределял бы для вызова соответствующей перегрузки visit .

    Теперь вам нужно знать полный набор типов токенов в интерфейсе, но это позволяет сохранить классы достаточно маленькими и сгруппированными по действиям, логике которых обычно легче следовать.

  • Используйте различимый союз, например Boost.Variant.

    Это вообще не объектно-ориентированный. Это приведет к повсеместному переключению типа и, вероятно, будет выглядеть некрасиво. Но поскольку логика состоит из всех элементов, ей часто легче следовать, особенно для тех, кто не понимает идею, лежащую в основе кода.