Как обычно реализован dynamic_cast?

Является ли проверка типа простым целочисленным сравнением? Или имеет смысл иметь GetTypeId виртуальная функция для различения, которая сделает это целочисленным сравнением?

(Просто не хочу, чтобы вещи сравнивались в именах классов)

РЕДАКТИРОВАТЬ: Я имею в виду, что если я часто ожидаю неправильный тип, имеет ли смысл использовать что-то вроде:

struct Token
{
    enum {
        AND,
        OR,
        IF
    };
    virtual std::size_t GetTokenId() = 0;
};

struct AndToken : public Token
{
    std::size_t GetTokenId() { return AND; }
};

И использовать GetTokenId член вместо того, чтобы полагаться на dynamic_cast,

4 ответа

Решение

Функциональность dynamic_cast выходит далеко за рамки простой проверки типов. Если бы это была просто проверка типов, ее было бы очень легко реализовать (что-то вроде того, что вы имели в своем первоначальном посте).

В дополнение к проверке типа, dynamic_cast может выполнять приведение к void * и иерархические перекрестные броски. Эти типы приведений концептуально требуют некоторой способности проходить иерархию классов в обоих направлениях (вверх и вниз). Структуры данных, необходимые для поддержки таких приведений, являются более сложными, чем простой скалярный идентификатор типа. Информация о dynamic_cast использует является частью RTTI.

Попытка описать это здесь будет контрпродуктивной. Раньше у меня была хорошая ссылка, описывающая одну из возможных реализаций RTTI... постараюсь ее найти.

В некоторых оригинальных компиляторах вы правы, они использовали сравнение строк.

В результате dynamic_cast<> был очень медленным (условно говоря), так как иерархия классов обходилась, каждый шаг вверх / вниз по цепочке иерархии требовал сравнения строк с именем класса.

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

Но это тоже древняя история.

Я не уверен, как это делается сейчас, но это определенно не предполагает сравнения строк. Делать это самостоятельно - тоже плохая идея (никогда не делайте работу, которую уже выполняет компилятор). Любая ваша попытка не будет такой же быстрой и точной, как у компилятора, помните, что годы разработки были направлены на то, чтобы сделать код компилятора настолько быстрым, насколько это возможно (и это всегда будет правильно).

Я не знаю точную реализацию, но вот идея, как бы я это сделал:

Кастинг от Derived* в Base* может быть сделано во время компиляции. Приведение между двумя не связанными полиморфными типами может быть выполнено и во время компиляции (просто верните NULL).

Кастинг от Base* в Derived* должно быть сделано во время выполнения, потому что возможно несколько производных классов. Идентификация динамического типа может быть выполнена с использованием таблицы виртуальных методов, привязанной к объекту (поэтому для этого требуются полиморфные классы).

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

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

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

Другие вопросы по тегам