C ++: type_info для различения типов

Я знаю, что компиляторы имеют большую свободу в реализации поведения std::type_info функций.

Я подумываю использовать его для сравнения типов объектов, поэтому хочу быть уверенным, что:

  1. std::type_info::name должен возвращать две разные строки для двух разных типов.

  2. std::type_info::before должен указать, что Type1 перед Type2 исключающее или Type2 перед Type1.

    // like this:
    typeid(T1).before( typeid(T2) ) != typeid(T2).before( typeid(T1) )
    
  3. Две разные специализации одного и того же шаблонного класса считаются разными типами.

  4. Два разных typedef-инициатора одного типа относятся к одному и тому же типу.

И наконец:

  • Поскольку std::type_info нельзя копировать, как я могу хранить где-нибудь type_info (например, в std::map)? Единственный способ иметь std::type_info всегда где-нибудь (например, в стеке или статической / глобальной переменной) и использовать указатель на него?

  • Насколько быстры operator==, operator!= и before на наиболее распространенных компиляторах? Я думаю, им следует сравнивать только стоимость. А насколько быстро typeid?

  • У меня есть класс A с virtual bool operator==( const A& ) const. Поскольку A имеет много подклассов (некоторые из которых неизвестны во время компиляции), я бы перегрузил этот виртуальный оператор в любом подклассе B следующим образом:

    virtual bool operator==( const A &other ) const {
      if( typeid(*this) != typeid(other) ) return false;
      // bool B::operator==( const B &other ) const // is defined for any class B
      return operator==( static_cast<B&>( other ) );
    }
    

    Является ли это приемлемым (и стандартным) способом реализации такого оператора?


person peoro    schedule 16.11.2010    source источник
comment
Примечание по части 2: определенно нет, если typeid (Type1) == typeid (Type2), т.е. они не отдельные типы ;-)   -  person Steve Jessop    schedule 16.11.2010


Ответы (5)


Бегло взглянув на документацию, я бы сказал, что:

  1. std :: type_info :: name всегда возвращает две разные строки для двух разных типов, в противном случае это означает, что компилятор потерял себя при разрешении типов, и вам больше не следует его использовать.

  2. Ссылка сообщает: «before возвращает истину, если тип предшествует типу rhs в порядке сопоставления. Порядок сопоставления - это просто внутренний порядок, поддерживаемый конкретной реализацией и не обязательно связанный с отношениями наследования или порядком объявления». Таким образом, у вас есть гарантия, что ни один из типов не будет иметь одинаковый ранг в порядке сортировки.

  3. Каждый экземпляр класса шаблона относится к разному типу. Специализация не делает исключений.

  4. Я не совсем понимаю, что ты имеешь в виду. Если вы имеете в виду что-то вроде того, что typedef foo bar; в двух отдельных единицах компиляции, и эта полоса одинакова в обоих, это работает именно так. Если вы имеете в виду typedef foo bar; typedef int bar;, это не работает (кроме случаев, когда foo имеет значение int).

По поводу других вопросов:

  • Вы должны хранить ссылки на std :: type_info, или как-то обернуть его.
  • Совершенно не знаю о производительности, я предполагаю, что операторы сравнения имеют постоянное время, несмотря на сложность типа. Раньше должен иметь линейную сложность в зависимости от количества различных типов, используемых в вашем коде.
  • Это действительно странно, имхо. Вам следует перегрузить свой operator== вместо того, чтобы делать его виртуальным и отменять его.
person Opera    schedule 16.11.2010
comment
Примерно 4. Я имел в виду: typedef a b; typedef a c;, typeid(b)==typeid(c)? Но да, я был уверен, что еще до публикации ответ был положительным. - О before: Я думаю, это могло быть постоянное время; это могло быть реализовано так: return &typeid(a) < &typeid(b);. - По поводу последнего пункта: упс! Я, конечно, имел в виду перегрузку, а не отмену. - person peoro; 16.11.2010
comment
Примерно 4, если, конечно, вы наберете ‹code› typedef a b; typedef a c; ‹/code›, a и c одного типа. Что касается ранее, это зависит от того, создается ли std :: type_info во время компиляции или во время выполнения. - person Opera; 17.11.2010
comment
Существует соответствующая реализация before, которая возвращает в постоянное время: берем адреса объектов type_info и сравниваем их как целые числа. Это может дать разные результаты при разных запусках одной и той же программы, но это все равно будет соответствовать. - person Martin v. Löwis; 25.11.2010
comment
Соответствующая реализация может возвращать одно и то же имя для разных типов. - person bames53; 03.01.2013

Стандарт 18.5.1 (информация о типе класса):

Класс type_info описывает информацию о типе, сгенерированную реализацией. Объекты этого класса эффективно хранят указатель на имя типа и закодированное значение, подходящее для сравнения двух типов на равенство или порядок сортировки. Имена, правила кодирования и последовательность сортировки для типов не указаны и могут различаться в разных программах.

Насколько я понимаю:

  1. У вас нет этой гарантии в отношении std:type_info::name. В стандарте только указано, что name возвращает NTBS, определяемую реализацией, и я считаю, что соответствующая реализация вполне может вернуть одну и ту же строку для каждого типа.
  2. Я не знаю, и стандарт на этот счет не ясен, поэтому я бы не стал полагаться на такое поведение.
  3. Это должно быть для меня определенным да.
  4. Это должно быть для меня определенным да.

По поводу второго блока вопросов:

  • Нет, вы не можете хранить type_info. Андрей Александреску предлагает TypeInfo оболочку в своей книге Современный дизайн C ++. Обратите внимание, что объекты, возвращаемые typeid, имеют статическое хранилище, поэтому вы можете безопасно хранить указатели, не беспокоясь о времени жизни объекта.
  • Я полагаю, вы можете предположить, что type_info сравнения чрезвычайно эффективны (сравнивать особо нечего).
person icecrime    schedule 16.11.2010
comment
Для (1) формально достаточно строки "". Он завершается нулем, а тип - const char[1]. - person MSalters; 16.11.2010

Вы можете хранить это так.

class my_type_info
{
public:
     my_type_info(const std::type_info& info) : info_(&info){}
     std::type_info get() const { return *info_;}
private:
     const std::type_info* info_;
};

РЕДАКТИРОВАТЬ:

Стандарт C ++ 5.2.8.

Результатом выражения typeid является lvalue статического типа const std :: type_info ...

Это означает, что вы можете использовать его вот так.

my_type_info(typeid(my_type));

Функция typeid возвращает lvalue (это не временное значение), и поэтому адрес возвращаемого type_info всегда действителен.

person ronag    schedule 16.11.2010
comment
Да, но где мне хранить значение, на которое указывает my_type_info::info_? Разве его не нужно всегда выделять (например, в стеке или в статических / глобальных переменных)? - person peoro; 16.11.2010
comment
Хорошо, если он статичен, можно хранить указатель или ссылку на type_info, возвращаемый typeid, даже если такой type_info удаляется из стека, спасибо. - person peoro; 16.11.2010
comment
Его нельзя удалить из стека, так как он никогда не находится в стеке. - person ronag; 16.11.2010
comment
Гарантия, которую вы используете, - это время жизни объекта, на который указывает lvalue, до конца программы. Тот факт, что выражение lvalue имеет статический тип const std::type_info, ничего не говорит вам о времени жизни объекта и, в частности, не означает, что object является статическим. Статический тип определен в 1.3.11. - person Steve Jessop; 16.11.2010
comment
В стандарте указано, что это lvalue статического типа. - person ronag; 16.11.2010
comment
@ronag. Нет, это не так. В нем говорится, что это lvalue статического типа const std::type_info. Вы удалили половину существительной фразы и в значительной степени придумали собственное определение статического типа, которое отличается от определения в 1.3.11. Время жизни объекта почти полностью не связано с его типом или статическим типом выражения, включающего этот объект. - person Steve Jessop; 16.11.2010
comment
@ronag, вы правы, поскольку typeid возвращает ссылку на константу (я думал, она вернула const type_info, а не type_info const &. Тогда вы, должно быть, правы :) - person peoro; 16.11.2010
comment
@ Стив Джессоп. Возможно, ты прав. Я не совсем понимаю. Придется об этом подумать. В любом случае, спасибо, что указали на это. - person ronag; 16.11.2010
comment
Если это поможет понять, первую часть этого предложения можно перефразировать: результатом выражения typeid является lvalue. Статический тип выражения lvalue - const std::type_info. Наиболее производным типом объекта, на который ссылается lvalue, является const std::type_info или const name ... - person Steve Jessop; 16.11.2010
comment
Спасибо. Это сделало меня понятным. - person ronag; 16.11.2010

Текущие ответы на вопросы 1 и 2 совершенно правильны, и по сути они просто детали для класса type_info - нет смысла повторять эти ответы.

Что касается вопросов 3 и 4, важно понимать, что именно является типом в C ++ и как они соотносятся с именами. Во-первых, существует множество предопределенных типов, и у них есть имена: int, float, double. Далее, есть несколько сконструированных типов, которые не имеют собственных имен: const int, int*, const int*, int* const. Существуют типы функций int (int) и типы указателей функций int (*)(int).

Иногда бывает полезно дать имя безымянному типу, что можно сделать с помощью typedef. Например, typedef int* pint или typedef int (*pf)(int);. Это вводит имя, а не новый тип.

Далее следуют определяемые пользователем типы: структуры, классы, объединения. Есть хорошая традиция давать им имена, но это не обязательно. Не добавляйте такое имя с typedef, вы можете сделать это напрямую: struct Foo { }; вместо typedef struct {} Foo;. Обычно определения классов находятся в заголовках, которые заканчиваются \ в нескольких единицах перевода. Это означает, что класс определен более одного раза. Это все того же типа, и поэтому вам не разрешается использовать макросы для изменения определений членов класса.

Класс шаблона - это не тип, это рецепт типов. Два экземпляра одного шаблона класса являются разными типами, если аргументы шаблона имеют разные типы (или значения). Это работает рекурсивно: учитывая template <typename T> struct Foo{};, Foo<Foo<int> > имеет тот же тип, что и Foo<Foo<Bar> >, тогда и только тогда, когда Bar - другое имя для типа int.

person MSalters    schedule 16.11.2010

Type_info - это реализация, определенная, поэтому я бы не стал на нее полагаться. Однако, основываясь на моем опыте использования g ++ и MSVC, предположения 1,3 и 4 верны ... не совсем уверен насчет №2.

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

template<typename T, typename U>
struct is_same       { static bool const result = false; };

template<typename T>
struct is_same<T, T> { static bool const result = true;  };

template<typename S, typename T>
bool IsSame(const S& s, const T& t) {   return is_same<S,T>::result; }
person Nathan Pitman    schedule 16.11.2010
comment
Да ладно, я должен был упомянуть, что это не работает для полиморфизма. Это довольно веская причина не использовать его: D - person Nathan Pitman; 16.11.2010