Как избежать dynamic_cast/RTTI

Недавно я работал над фрагментом кода C++ для стороннего проекта (библиотека cpp-markdown для любопытный) и столкнулся с вопросом кодирования, по которому я хотел бы услышать некоторые мнения.

cpp-markdown имеет базовый класс Token, у которого есть несколько подклассов. Два основных подкласса — это Container (который содержит наборы других Token) и TextHolder (используется в качестве базового класса для Token, которые, конечно же, содержат текст).

Большая часть обработки выполняется с помощью виртуальных функций, но некоторые из них лучше обрабатывать в одной функции. Для этого я использовал dynamic_cast для понижения указателя Token* на один из его подклассов, чтобы я мог вызывать функции, специфичные для подкласса и его дочерних классов. Нет никаких шансов, что приведение будет неудачным, потому что код может сказать, когда это необходимо, с помощью виртуальных функций (таких как isUnmatchedOpenMarker).

Есть два других способа справиться с этим:

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

  2. Создайте виртуальную функцию в Token, которая будет возвращать правильно типизированный указатель на this, когда он вызывается для определенных подтипов, и нулевой указатель, если вызывается что-то еще. По сути, это расширение системы виртуальных функций, которую я там уже использую.

Второй способ мне кажется лучше как существующего, так и первого. Но я хотел бы узнать мнение других опытных разработчиков C++ по этому поводу. Или я слишком много беспокоюсь о пустяках. :-)


person Head Geek    schedule 24.02.2009    source источник


Ответы (6)


# 1 загрязняет пространство имен классов и виртуальную таблицу для объектов, которым это не нужно. Хорошо, когда у вас есть несколько методов, которые обычно реализуются, но просто уродливы, когда нужны только для одного производного класса.

№2 — просто dynamic_cast<> в платье в горошек и с помадой. Не упрощает клиентский код и запутывает всю иерархию, требуя, чтобы базовый и каждый производный класс были частично осведомлены о каждом другом производном классе.

Просто используйте dynamic_cast<>. Вот для чего он нужен.

person Shog9    schedule 24.02.2009

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

  • Создайте базовый TokenVisitor класс, содержащий пустые виртуальные visit(SpecificToken*) методы.
  • Добавьте один виртуальный метод accept(TokenVisitor*) в Token, который вызывает правильно типизированный метод для переданного TokenVisitor.
  • Получите от TokenVisitor различные вещи, которые вам нужно будет делать разными способами для всех токенов.

Для шаблона полного посетителя, полезного для древовидных структур, методы accept по умолчанию выполняют итерацию по дочерним элементам, вызывая token->accept(this); для каждого.

person Jeffrey Hantin    schedule 24.02.2009

Если вы знаете, что преобразование не может быть недействительным, просто используйте static_cast.

person Hans Passant    schedule 24.02.2009
comment
Согласен, если вы уже проверяете тип виртуальной функцией, то dynamic_cast бесполезен. Оба ваших других решения - просто белизна, что ваша иерархия нарушена. Если вы собираетесь взломать, по крайней мере, используйте более быстрый взлом. - person BigSandwich; 24.02.2009
comment
static_cast не будет работать, если производный класс использует виртуальное наследование для наследования от Token. dynamic_cast в этом случае не был бы бесполезен. - person bk1e; 24.02.2009

Почему вы хотите избежать использования dynamic_cast? Вызывает ли это неприемлемое узкое место в вашем приложении? Если нет, возможно, сейчас не стоит ничего делать с кодом.

Если вы не возражаете против того, чтобы в вашей конкретной ситуации пожертвовать безопасностью ради небольшой скорости, вы должны быть в порядке, выполняя static_cast; тем не менее, это укрепляет ваше предположение о том, что вы знаете тип объекта и нет никаких шансов, что бросок будет плохим. Если позже ваше предположение окажется неверным, вы можете столкнуться с некоторыми загадочными ошибками сбоя в своем коде. Возвращаясь к моему первоначальному вопросу, вы действительно уверены, что компромисс того стоит?

Что касается вариантов, которые вы перечислили: первый на самом деле звучит не как решение, а как хак в последнюю минуту, который я ожидаю увидеть, когда кто-то пишет код в 3 часа ночи. Функциональность, просачивающаяся к основанию иерархии классов, является одним из наиболее распространенных антипаттернов, с которыми сталкиваются новички в ООП. Не делай этого.

Для второго варианта, который вы указали, любой подобный вариант на самом деле просто переопределяет dynamic_cast - если вы работаете на платформе, где доступны только дерьмовые компиляторы (я слышал истории о том, что компилятор Gamecube занимает четверть доступной оперативной памяти системы с информацией RTTI) это может быть полезно, но, скорее всего, вы просто тратите свое время. Вы действительно уверены, что об этом стоит беспокоиться?

person Andrew Khosravian    schedule 24.02.2009
comment
Нет, я не уверен, что это стоит беспокойства. Вот почему я спросил. :-) - person Head Geek; 24.02.2009
comment
Как упоминалось выше: static_cast не работает при использовании виртуального наследования. - person mmmmmmmm; 24.02.2009

Истинный чистый способ предотвратить dynamic_cast — это иметь указатели нужного типа в нужном месте. Абстракция должна позаботиться обо всем остальном.

ИМХО, причина, по которой dynamic_cast имеет такую ​​репутацию, заключается в том, что его производительность немного снижается каждый раз, когда вы добавляете еще один подтип вниз в иерархию классов. Если у вас 4-5 классов в иерархии, то беспокоиться не о чем.

person jturcotte    schedule 24.02.2009

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

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

person MSN    schedule 24.02.2009