Как создаются downcast и upcast? А как сравниваются типы? Как обычно хранится RTTI? - PullRequest
0 голосов
/ 02 мая 2020

Я прочитал о RTTI. Информация, написанная здесь, может быть неверной. Это то, что я понял.

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

2 - Также он знает, является ли операция понижением или повышением перед фактическим приведением? Другими словами, когда я преформирую dynamic_cast<SomeClass>, он пытается найти SomeClass во всем дереве иерархии? Или он знает, в каком направлении go (искать в родительском узле или искать в дочерних узлах)? И если это так, то как?

3 - Насколько я понял, тип каждого класса хранится в виде строки, и всякий раз, когда кто-то использует dynamic_cast, он сравнивает строки типов до тех пор, пока находит правильный класс. Если это правда, почему это сделано? Почему бы не дать каждому классу целочисленный идентификатор во время компиляции и сохранить этот идентификатор вместо имени строки. И всякий раз, когда происходит кастинг, просто сравните два числа. И пусть строка типа всех классов будет храниться где-нибудь в массиве (давайте назовем ее typesArr), и всякий раз, когда действительно нужно получить имя класса, просто ищите typesArr[ID]. Я думаю, что-то подобное более интуитивно понятно, и я что-то упускаю. Так как же на самом деле хранится RTTI? Я не имею в виду, как это работает. Я имею в виду, если как это представлено в памяти? Я знаю, что это зависит от реализации. Но как это обычно хранится в большинстве компиляторов? А как на самом деле сравниваются типы?

1 Ответ

1 голос
/ 02 мая 2020

(Обязательный отказ от ответственности: как уже отмечалось в вопросе, большая часть этого является спецификацией реализации c, а не общими правилами C ++.)

Я думаю, что вы можете использовать "upcast" и "downcast" в обратном порядке из того, к чему я привык.

Но в любом случае преобразование из указателя производного класса в указатель базового класса (или инициализация ссылки на базовый класс из glvalue производного класса) не должно включать RTTI "дерево классов" вообще. Компилятор знает все базовые классы и расположение подобъектов в производном классе. Для не виртуальной базы адрес базового подобъекта находится на фиксированном смещении от адреса производного объекта. Для виртуального базового класса смещение базового подобъекта зависит от самого производного типа объекта, поэтому преобразование включает поиск этого смещения в виртуальной таблице.

dynamic_cast определено просто для выполнения вышеуказанного производного приведение к основанию, если оно допустимо ( [expr.dynami c .cast] / 5 ). В противном случае, да, он ищет базовый класс во всем дереве классов, унаследованных полным типом объекта. Реализация этого поиска, вероятно, начнется с root: наиболее производного класса. Обратите внимание, что «от основания к основанию» и «от основания к производному» - не единственные случаи: dynamic_cast также может приводить «вбок», чтобы найти родного брата / кузена / et c. подобъект.

struct A { virtual ~A(); int m; };
struct B { virtual ~B(); int n; };

int f(const A& a) {
    // Valid, even though there's no inheritance relation between
    // A and B at all:
    auto& b = dynamic_cast<const B&>(a);
    return b.n;
}

struct C : public A, public B { int p; };

void g() {
    C c;
    c.n = 2;
    // The dynamic_cast in f will be a successful "sideways cast"
    // from the A base subobject of c to the B base subobject of c.
    assert(f(c) == 2);
}

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

Это не будет легко работать из-за отдельной модели компиляции, используемой C ++. Скажем, Алиса компилирует свой файл a. cpp, который определяет некоторые классы polymorphi c. Компилятору нужно будет выбрать несколько идентификаторов для этих классов. Тем временем Боб является разработчиком NiftyLib и добавляет новую функцию, означающую, что файл b. cpp в источнике NiftyLib содержит несколько новых классов polymorphi c. Его функция готова к выпуску, поэтому проект библиотеки компилирует b. cpp и другие источники в файлы библиотеки, которые доступны для разработчиков. Это будет означать выбор некоторых идентификаторов для этих классов. Алиса. cpp является частью программы, которая использует NiftyLib, поэтому она обновляется до более новой версии NiftyLib. Создание полной программы Алисы включает связывание предварительно скомпилированного файла. cpp и файла библиотеки NiftyLib. Но как эти компиляторы могли выбирать уникальные идентификаторы, чтобы ни у одного из классов из. cpp и b. cpp не было одного и того же идентификатора?

Так что я думаю, что некоторые реализации dynamic_cast делают сравните какое-нибудь искаженное имя типа, найденное через RTTI, возможно, те же самые данные строки C, возвращаемые std::type_info::name(). Но не все. В системах Itanium ABI (см. Ниже) компилятор и компоновщик могут настраивать параметры таким образом, чтобы гарантировать, что для каждого типа имеется только один объект данных RTTI (который также является std::type_info), даже если дублирующиеся объекты изначально исходили из разных модулей перевода , Затем, когда код запрашивает dynamic_cast<T*>(ptr), компилятор передает известный объект RTTI для T и объект RTTI, полученный через vptr в *ptr, во внутреннюю функцию поддержки, реализующую dynamic_cast. Когда эта функция ищет дерево связанных объектов RTTI, она может просто сравнить адреса объектов RTTI, а не проверять совпадение любого из их содержимого.

Для тонны технических деталей вы можете посмотреть на Itanium C ++ ABI , используется на Linux, Ma c и некоторых других платформах. В частности, раздел 2.9 посвящен RTTI: 2.9.4 определяет все содержимое объекта RTTI и 2.9.7 описывает, как эти данные используются для реализации dynamic_cast (в случае действительно динамического c). В последний раз, когда я смотрел на это, схема данных RTTI, используемая MSV C, была довольно похожа, просто отличалась деталями.

...