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

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

8 голосов
/ 24 февраля 2009

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

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

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

Есть еще два способа справиться с этим:

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

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

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

Ответы [ 6 ]

20 голосов
/ 24 февраля 2009

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

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

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

5 голосов
/ 24 февраля 2009

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

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

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

4 голосов
/ 24 февраля 2009

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

2 голосов
/ 24 февраля 2009

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

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

2 голосов
/ 24 февраля 2009

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

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

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

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

1 голос
/ 24 февраля 2009

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...