Если контейнер содержит экземпляры Ring
, а не указатели на них, у вас не может быть реального OneRing
, а только нарезанный кусочек, вставленный в Ring
.В этом случае вы не можете точно знать, что это был за объект до того, как его вставили в контейнер, поскольку теперь это обычный Ring
для каждой практической цели.
Если вместо этого контейнер хранит указатели /ссылки на элементы (как я полагаю), вы можете просто попробовать dynamic_cast
из Ring
указателя / ссылки, которую вы получаете на тип OneRing
;если dynamic_cast
завершается успешно, указатель / ссылка правильно приводятся к его «реальному» типу OneRing
, в противном случае вы знаете, что это не OneRing
, а просто обычный Ring
(или какой-либо другой производный класс, который не 't OneRing
).
Очевидно, dynamic_cast
требует, чтобы RTTI работал правильно (если RTTI отключен, информация о типах не включается в исполняемый файл, поэтому ничего не известно о типах во время выполнения), поэтому не забудьте включить егоесли ваш компилятор отключает его по умолчанию.
Если вы не можете включить его, вы не можете использовать dynamic_cast
или typeid
;в языке нет встроенного «альтернативного» механизма, потому что он будет избыточным, и для правильной работы RTTI потребуется та же информация, что и для RTTI.
Единственное, что вы можете сделать, - это заново изобрести RTTI в своем классе.иерархия, обеспечивающая переопределенный метод virtual
для возврата разных значений для каждого подкласса;таким образом, вы могли бы проверить из Ring
указателя, что это за Ring
, на самом деле, тогда вы могли бы жестоко разыграть его.Обратите внимание, что это громоздко, сложно поддерживать и небезопасно (приведение жестких указателей может не работать в сложных сценариях наследования), поэтому я настоятельно рекомендую не использовать этот метод.Просто используйте RTTI.
Кстати, имейте в виду, что во многих случаях, если вам нужен dynamic_cast
, что-то не так в вашей иерархии классов: хорошо спроектированные иерархии классов стремятся достичь своих целей без динамического приведения,используя правильно virtual
методы.
Мой вопрос касается простого отлова проблем приведения во время выполнения в C ++.Я понимаю, что C ++ не предоставляет «RTTI» в большинстве случаев (скажем так, я не могу изменить настройки своего компилятора и включить его, ради аргумента.).
Стандарт C ++ предоставляет RTTI,Вы можете отключить его в компиляторах , если он вам не нужен , чтобы сохранить некоторое исполняемое пространство, которое было бы потрачено впустую для ненужной информации о типе.Если вам вместо этого нужен RTTI, непонятно, почему вы хотите его отключить.
Слишком много Pepsi gen, чтобы понять, что C ++ не способен ответить на простой вопрос типа «это кольцо также OneRing?'или поймать исключения, возникающие из-за принятия Кольца за OneRing
Именно в этом и заключается цель dynamic_cast
.Примените его к указателю, и если он не может быть преобразован, вы получите NULL
, примените его к ссылке, и если он не может быть преобразован, выдается std::bad_cast
.
Приложение
C ++ имеет RTTI только для указателей на классы.Вы не можете динамически разыграть void *, и если вы запросите информацию о типе, это будет void *.RTTI находится «на» указатель.Я понимаю, почему вы не «приводите» член (с безумной точки зрения C ++, в языках без членов вам не нужно делать такое искусственное различие), и, возможно, эти факты связаны по какой-то архаичной причине эффективности / сложности компилятора.
Подождите, подождите, это не работает так, информация RTTI не указатель, это как-то на объекте.Но чтобы понять, почему это такой беспорядок, я думаю, вам нужно немного информации о том, как этот материал работает под прикрытием.
Уведомление : всето, что следует, зависит от реализации, стандарт не требует какого-либо конкретного способа реализации виртуальных функций и RTTI, просто это обычно делается так.
В C ++ неполиморфный класс - это просто нормальная структура, содержащая его поля. Различие между частным и общедоступным вводится во время компиляции, а методы - это обычные функции, которые вызываются со скрытым параметром this
, который указывает на экземпляр класса, для которого они вызываются. Каждый вызов метода разрешается во время компиляции. Наследование - это просто вставка полей базового класса перед добавленными полями. Все просто, все счастливы.
Если статический тип всегда совпадает с «реальным» типом объекта, все работает нормально. Проблемы начинают возникать, когда вы хотите иметь полиморфное поведение: если вы сохраняете Derived *
в переменной-указателе, статический тип которой равен Base *
, «статический» и «настоящий» типы больше не совпадают. Видя вызов, выполненный для такого указателя, компилятор не имеет ни малейшего представления о необходимости вызова метода Derived
, а просто вызывает метод Base
, поскольку вся информация, которую он имеет, является статическим типом такого указатель.
Чтобы решить эту проблему, были изобретены virtual
вызовов и vtables. Когда вы объявляете метод класса как virtual
, ваш класс становится полиморфным классом, то есть он допускает полиморфное поведение методов, объявленных как virtual
.
Это работает так. Для каждого класса компилятор создает таблицу указателей на функции (так называемый «vtable»), которая помещается где-то в исполняемый образ; есть один «ряд» для каждого virtual
метода. В vtable базового класса будут содержаться указатели на реализацию Base
виртуальных методов, в то время как производные классы будут иметь свои vtable, содержащие указатели на их версии методов. Таблицы производных классов также могут быть больше из-за дополнительных методов, но методы, присутствующие также в базовом классе, находятся в начале, и индексы для общих методов в Derived
vtable совпадают с индексами Base
.
Теперь у каждого объекта полиморфного класса есть дополнительный член - указатель виртуальной таблицы (обычно называемый vptr, обычно помещаемый в начало класса). Этот элемент автоматически инициализируется непосредственно перед запуском каждого конструктора, чтобы указать на правильный vtable для объекта. Обратите внимание, что именно по этой причине при полиморфном типе запускается конструктор базового класса, виртуальные методы не работают «правильно» (т. Е. Они работают так, как если бы класс был типа Base
): производный конструктор hasn ' пока не запущен, поэтому vptr по-прежнему указывает на Base
vtable.
Когда объект полностью построен, его «реальный» тип теперь гораздо более определен, чем для неполиморфного объекта: если у вас есть Derived *
, сохраненный в Base *
, компилятор теперь сможет вызывать правильные версии виртуальных методов: каждый виртуальный вызов реализован как поиск в виртуальной таблице (к которой можно получить доступ, поскольку vptr присутствует как в Base
, так и в Derived
в той же позиции), после чего следует вызов функция, указатель которой хранится в правильном месте виртуальной таблицы (индекс для конкретной виртуальной функции одинаков в виртуальной таблице каждого производного класса). Поскольку вызываемая функция «знает», что она работает с экземпляром Derived
, она может получить доступ ко всем членам класса Derived
.
Это более или менее то, как виртуальные функции работают для одиночного наследования в «простой» полиморфной иерархии классов. При множественном наследовании или при смешивании полиморфных классов с неполиморфными все становится запутанным (у вас может быть несколько vptr, макет класса должен соответствовать некоторым ограничениям, ...), но это не относится к нам.
Теперь у нас работают виртуальные функции. typeid
это всего лишь маленький шаг отсюда.
RTTI необходимо отслеживать некоторую информацию, связанную с «реальным» типом каждого объекта. Итак, поскольку у нас уже есть vtables, разумная идея - поместить такую информацию (возможно, как указатель на большую структуру) где-нибудь в vtable - например, в начале или в конце. Таким образом, вам не нужно добавлять еще один скрытый указатель внутри каждого объекта - одного из vtable достаточно как для виртуальных вызовов, так и для информации о типе.
Каждый полиморфный класс будет иметь связанную структуру, содержащую информацию о его типе, которая может включать имя, некоторый уникальный идентификатор (может не потребоваться, если имя гарантировано будет уникальным) и, вероятно, указатели на информацию о типе базовых классов. (они понадобятся для dynamic_cast
).
Когда вы вызываете typeid
для класса, вы получаете экземпляр type_info
, непрозрачный класс, который встраивает небольшую часть этой информации, а именно: имя класса и скрытый уникальный идентификатор (на самом деле, часто это будет содержать только одно из них: если компилятор достаточно дружественен для предоставления имени, он, вероятно, удостоверится, что он уникален, чтобы повторно использовать его также в качестве уникального идентификатора).
Элемент name
практически бесполезен для целей без отладки. Настоящая полезность type_info
- это operator==
, которую он предоставляет; на самом деле, основная цель type_info
состоит в том, что он может быть по сравнению , и два type_info
объекта оцениваются равными тогда и только тогда, когда они являются результатом typeid
для идентичных типов. Таким образом, основное использование typeid
состоит в том, чтобы проверить, является ли реальный тип указателя / ссылки на объект точно таким же, как другой (или тип, фиксированный во время компиляции). Сравнение обычно выполняется путем сравнения уникального идентификатора (возможно, указателя на структуру RTTI в памяти) или уникального имени.
dynamic_cast
- гораздо более сложный зверь. Тривиальный случай (приведение от указателя к производному классу к указателю на базовый класс), ну, в общем, тривиальный, без проверки во время выполнения, и это единственный случай, когда он отлично работает на неполиморфных классах (у которых нет RTTI) .
Второй самый простой случай - когда вы пытаетесь разыграть Base *
, чей «реальный» тип - от Derived *
до Derived *
. Это просто вопрос сравнения указателя RTTI Derived *
с указателем, связанным с объектом, который будет приведен; если они совпадают, приведение выполнено успешно.
Общий случай, напротив, гораздо сложнее. dynamic_cast
должен проверить иерархию классов указанного объекта, следуя некоторым правилам, которые гарантируют, что никакие бессмысленные преобразования не выполняются (эти правила определены в стандарте C ++, §5.2.7 ¶8). Эта проверка во время выполнения выполняется с использованием информации о типе, описанной выше. Если приведение выполнено успешно, вы получите правильно приведенный указатель / ссылку, в противном случае вы получите исключение NULL
(если это указатель) или bad_cast
(если это указание).
А как насчет неполиморфных типов? Что ж, они более или менее исключены из RTTI: typeid
будет возвращать информацию о статическом типе (которая обычно бесполезна), dynamic_cast
будет разрешать только апкастинг (который может быть проверен во время компиляции), при компиляции даункастинг будет неудачным -время.
Смысл этого решения, вероятно, заключался не в создании "сложных" типов, которые должны быть простыми: расширение RTTI до каждого типа с использованием скрытого указателя сделало бы невозможным использование типов POD ( «Обычные старые данные», т.е. вещи, аналогичные традиционным C struct
s).
Кроме того, в конце концов, если у вас нет virtual
методов, вам, вероятно, не нужны dynamic_cast
и type_id
: если у вас есть иерархии объектов, которыми манипулирует указатель на базовые классы, вы должен иметь как минимум деструктор virtual
, иначе могут произойти неприятные вещи (если вы delete
a Derived
ссылаетесь на него через Base *
, а деструктор не является virtual
, вы вызовете только деструктор Base
, но не тот, который определен в Derived
- определенно не очень).
Теперь на другие ваши комментарии можно легко ответить:
Если вы получите информацию о типе Ring * для OneRing, это будет Ring *.Почему они решили не размещать информацию на занятиях, я понятия не имею.Это делает typeid () практически бесполезным - он даже возвращает закрытый класс с единственным членом, к которому у вас есть доступ, именем char *!Это как 11/10 по шкале недружелюбия и бесполезности - они сделали это нарочно?
Вы получаете Ring *
, только если Ring
не является полиморфным.Вы должны сделать по крайней мере один член Ring
полиморфным для работы RTTI (и, как указано, вероятно, по крайней мере, деструктор Ring
должен быть виртуальным).Полезность типа, возвращаемого typeid
, заключается в том, что его можно сравнить в равенстве с другим type_info
- элемент name
полезен только для целей отладки (фактически, если говорить о стандарте, он всегда может вернуть(пустая строка).
Вы можете безопасно свернуть дерево наследования с помощью динамического приведения, но опять же вы не сможете сделать это, если у вас нет общего предка наследования для всех полученных данных и вы не знаете его.
Это необходимо, потому что, если у вас есть универсальный void *
, компилятор не может знать, где находится vptr - черт, он даже не знает, есть ли у этой вещи вптр, это может быть просто int *
!:-)
Другими словами, компилятор должен иметь некоторую минимальную информацию о статическом типе, чтобы иметь возможность определить, есть ли какая-либо информация о типе, доступная во время выполнения, для выполнения решения (если класс не полиморфен или даже не являетсяclass
приведение завершается неудачно во время компиляции ) и где находится эта информация (т.е. где vptr находится в объекте - информация, которая может быть выведена из статического типа любого предка).
Если вы выходите за рамки вышеописанного сценария или у вас нет RTTI по какой-либо причине, вы попадаете в пресловутый ручей, потому что нет способа отловить исключение или сгенерировать ошибку в результате неправильного приведения.если вы сами не внедрили какую-либо форму RTTI.
Вот так работает язык;другие варианты реализации RTTI (например, отслеживание типа каждого указателя) были бы намного более сложными (если это возможно) и мало дополнительной полезности.Если вам нужны широкие возможности отражения, C ++ - не тот путь, вам нужно использовать управляемые языки.
Небольшой пример для подведения итогов:
#include <iostream>
#include <typeinfo>
using namespace std;
class Base_NoPolymorphic
{
public:
void Name()
{
cout<<"Base_NoPolymorphic"<<endl;
}
};
class Derived_NoPolymorphic : public Base_NoPolymorphic
{
public:
void Name()
{
cout<<"Derived_NoPolymorphic"<<endl;
}
};
class Base_Polymorphic
{
public:
virtual void Name()
{
cout<<"Base_Polymorphic"<<endl;
}
};
class Derived_Polymorphic : public Base_Polymorphic
{
public:
virtual void Name()
{
cout<<"Derived_Polymorphic"<<endl;
}
};
int main()
{
Base_NoPolymorphic bnp;
Derived_NoPolymorphic dnp;
Base_Polymorphic bp;
Derived_Polymorphic dp;
// Non-polymorphic stuff: nothing works as expected
Base_NoPolymorphic *bnpp=&bnp, *dnpp=&dnp;
// These will always behave as Base_NoPolymorphic
cout<<typeid(bnpp).name()<<" - ";
bnpp->Name();
cout<<typeid(dnpp).name()<<" - ";
dnpp->Name();
// This does not compile, because you're trying a cast down the class hierachy
// on a non-polymorphic type
// cout<<dynamic_cast<Derived_NoPolymorphic *>(dnpp)<<endl;
//Polymorphic stuff: everything works fine
Base_Polymorphic *bpp=&bp, *dpp=&dp;
// These will behave correctly depending on their "real" type
cout<<typeid(bpp).name()<<" - ";
bpp->Name();
cout<<typeid(dpp).name()<<" - ";
dpp->Name();
// This will succeed (output != 0)
cout<<dynamic_cast<Derived_Polymorphic *>(dpp)<<endl;
// Notice that also with polymorphic stuff a void * remains a black box
void * ptr=&dp;
cout<<typeid(ptr).name()<<endl;
// This does not compile, because the source static type is a non-class type
// cout<<dynamic_cast<Derived_Polymorphic *>(ptr)<<endl;
return 0;
}
Вывод на моей машине (g ++ 4.5):
P18Base_NoPolymorphic - Base_NoPolymorphic
P18Base_NoPolymorphic - Base_NoPolymorphic
P16Base_Polymorphic - Base_Polymorphic
P16Base_Polymorphic - Derived_Polymorphic
0x7fffd7ad6890
Pv
, как и ожидалось: typeid
только для неполиморфных типов возвращаетстатическая информация о типе (Pv
- это «имя» RTTI g ++ для void *
), в то время как отлично работает с полиморфными типами;то же самое верно для virtual
/ невиртуальных вызовов методов через указатель базового класса.dynamic_cast
успешно работает с полиморфными типами, и, если вы раскомментируете другие dynamic_cast
, вы увидите, что он не компилируется.