Динамическая информация о типе




Иногда бывает полезно знать истинный тип объекта до его использованияв каких-либо операциях. Рассмотрим функцию my(set&) из $$13.3. void my_set(set& s) { for (T* p = s.first(); p; p = s.next()) { // мой код } //... } Она хороша в общем случае, но представим,- стало известно,что многие параметры множества представляют собой объекты типаslist. Возможно также стал известен алгоритм перебора элементов, которыйзначительно эффективнее для списков, чем для произвольныхмножеств. В результате эксперимента удалось выяснить, что именноэтот перебор является узким местом в системе. Тогда, конечно, имеетсмысл учесть в программе отдельно вариант с slist. Допустив возможностьопределения истинного типа параметра, задающего множество, функциюmy(set&) можно записать так: void my(set& s) { if (ref_type_info(s) == static_type_info(slist_set)) { // сравнение двух представлений типа // s типа slist slist& sl = (slist&)s; for (T* p = sl.first(); p; p = sl.next()) { // эффективный вариант в расчете на list } } else { for (T* p = s.first(); p; p = s.next()) { // обычный вариант для произвольного множества } } //... } Как только стал известен конкретный тип slist, сталидоступны определенные операции со списками, и даже стала возможнареализация основных операций подстановкой. Приведенный вариант функции действует отлично, посколькуslist - это конкретный класс, и действительно имеет смысл отдельноразбирать вариант, когда параметр является slist_set. Рассмотримтеперь такую ситуацию, когда желательно отдельно разбирать вариант какдля класса, так и для всех его производных классов. Допустим, мыимеем класс dialog_box из $$13.4 и хотим узнать, является ли онклассом dbox_w_str. Поскольку может существовать много производныхклассов от dbox_w_str, простую проверку на совпадение с нимнельзя считать хорошим решением. Действительно, производные классымогут представлять самые разные варианты запроса строки. Например,один производный от dbox_w_str класс может предлагать пользователюварианты строк на выбор, другой может обеспечить поиск в каталогеи т.д. Значит, нужно проверять и на совпадение со всеми производнымиот dbox_w_str классами. Это так же типично для узловых классов, какпроверка на вполне определенный тип типична для абстрактных классов,реализуемых конкретными типами. void f(dialog_box& db) { dbox_w_str* dbws = ptr_cast(dbox_w_str, &db); if (dbws) { // dbox_w_str // здесь можно использовать dbox_w_str::get_string() } else { // ``обычный'' dialog_box } //... } Здесь "операция" приведения ptr_cast() свой второй параметр(указатель) приводит к своему первому параметру (типу) при условии, чтоуказатель настроен на объект тип, которого совпадает с заданным(или является производным классом от заданного типа). Для проверкитипа dialog_box используется указатель, чтобы после приведения егоможно было сравнить с нулем. Возможно альтернативное решение с помощью ссылки на dialog_box: void g(dialog_box& db) { try { dbox_w_str& dbws = ref_cast(dialog_box,db); // здесь можно использовать dbox_w_str::get_string() } catch (Bad_cast) { // ``обычный'' dialog_box } //... } Поскольку нет приемлемого представления нулевой ссылки, с которойможно сравнивать, используется особая ситуация, обозначающая ошибкуприведения (т.е. случай, когда тип не есть dbox_w_str). Иногдалучше избегать сравнения с результатом приведения. Различие функций ref_cast() и ptr_cast() служит хорошейиллюстрацией различий между ссылками и указателями: ссылка обязательноссылается на объект, тогда как указатель может и не ссылаться,поэтому для указателя часто нужна проверка.

Информация о типе

В С++ нет иного стандартного средства получения динамической информациио типе, кроме вызовов виртуальных функцийЬ. Ь Хотя было сделано несколько предложений по расширению С++ в этомнаправлении. Смоделировать такое средство довольно просто и в большинствебольших библиотек есть возможности динамических запросов о типе.Здесь предлагается решение, обладающее тем полезным свойством,что объем информации о типе можно произвольно расширять. Его можнореализовать с помощью вызовов виртуальных функций, и оно можетвходить в расширенные реализации С++. Достаточно удобный интерфейс с любым средством, поставляющиминформацию о типе, можно задать с помощью следующих операций: typeid static_type_info(type) // получить typeid для имени типа typeid ptr_type_info(pointer) // получить typeid для указателя typeid ref_type_info(reference) // получить typeid для ссылки pointer ptr_cast(type,pointer) // преобразование указателя reference ref_cast(type,reference) // преобразование ссылки Пользователь класса может обойтись этими операциями, а создателькласса должен предусмотреть в описаниях классов определенные"приспособления", чтобы согласовать операции с реализациейбиблиотеки. Большинство пользователей, которым вообще нужна динамическаяидентификация типа, может ограничиться операциями приведенияptr_cast() и ref_cast(). Таким образом пользователь отстраняется отдальнейших сложностей, связанных с динамической идентификациейтипа. Кроме того, ограниченное использование динамической информациио типе меньше всего чревато ошибками. Если недостаточно знать, что операция приведения прошла успешно,а нужен истинный тип (например, объектно-ориентированныйввод-вывод), то можно использовать операции динамических запросов о типе:static_type_info(), ptr_type_info() и ref_type_info(). Эти операциивозвращают объект класса typeid. Как было показано в примере сset и slist_set, объекты класса typeid можно сравнивать. Длябольшинства задач этих сведений о классе typeid достаточно. Но длязадач, которым нужна более полная информация о типе, в классеtypeid есть функция get_type_info(): class typeid { friend class Type_info; private: const Type_info* id; public: typeid(const Type_info* p): id(p) { } const Type_info* get_type_info() const { return id; } int operator==(typeid i) const; }; Функция get_type_info() возвращает указатель на неменяющийся (const)объект класса Type_info из typeid. Существенно, что объектне меняется: это должно гарантировать, что динамическая информацияо типе отражает статические типы исходной программы. Плохо, еслипри выполнении программы некоторый тип может изменяться. С помощью указателя на объект класса Type_info пользовательполучает доступ к информации о типе из typeid и, теперь егопрограмма начинает зависеть от конкретной системы динамическихзапросов о типе и от структуры динамической информации о нем.Но эти средства не входят в стандарт языка, а задать их с помощьюхорошо продуманных макроопределений непросто.

Класс Type_info

В классе Type_info есть минимальный объем информации для реализацииоперации ptr_cast(); его можно определить следующим образом: class Type_info { const char* n; // имя const Type_info** b; // список базовых классов public: Type_info(const char* name, const Type_info* base[]); const char* name() const; Base_iterator bases(int direct=0) const; int same(const Type_info* p) const; int has_base(const Type_info*, int direct=0) const; int can_cast(const Type_info* p) const; static const Type_info info_obj; virtual typeid get_info() const; static typeid info(); }; Две последние функции должны быть определены в каждом производномот Type_info классе. Пользователь не должен заботиться о структуре объекта Type_info, иона приведена здесь только для полноты изложения. Строка, содержащаяимя типа, введена для того, чтобы дать возможность поиска информациив таблицах имен, например, в таблице отладчика. С помощью нее а такжеинформации из объекта Type_info можно выдавать более осмысленныедиагностические сообщения. Кроме того, если возникнет потребностьиметь несколько объектов типа Type_info, то имя может служить уникальнымключом этих объектов. const char* Type_info::name() const { return n; } int Type_info::same(const Type_info* p) const { return this==p || strcmp(n,p->n)==0; } int Type_info::can_cast(const Type_info* p) const { return same(p) || p->has_base(this); } Доступ к информации о базовых классах обеспечивается функциямиbases() и has_base(). Функция bases() возвращает итератор, которыйпорождает указатели на базовые классы объектов Type_info, а спомощью функции has_base() можно определить является ли заданный классбазовым для другого класса. Эти функции имеют необязательный параметрdirect, который показывает, следует ли рассматривать все базовые классы(direct=0), или только прямые базовые классы (direct=1). Наконец,как описано ниже, с помощью функций get_info() и info() можнополучить динамическую информацию о типе для самого класса Type_info. Здесь средство динамических запросов о типе сознательнореализуется с помощью совсем простых классов. Так можно избежатьпривязки к определенной библиотеке. Реализация в расчете наконкретную библиотеку может быть иной. Можно, как всегда, посоветоватьпользователям избегать излишней зависимости от деталей реализации. Функция has_base() ищет базовые классы с помощью имеющегося вType_info списка базовых классов. Хранить информацию о том, являетсяли базовый класс частным или виртуальным, не нужно, посколькувсе ошибки, связанные с ограничениями доступа или неоднозначностью,будут выявлены при трансляции. class base_iterator { short i; short alloc; const Type_info* b; public: const Type_info* operator() (); void reset() { i = 0; } base_iterator(const Type_info* bb, int direct=0); ~base_iterator() { if (alloc) delete[] (Type_info*)b; } }; В следующем примере используется необязательный параметр для указания,следует ли рассматривать все базовые классы (direct==0) или только прямыебазовые классы (direct==1). base_iterator::base_iterator(const Type_info* bb, int direct) { i = 0; if (direct) { // использование списка прямых базовых классов b = bb; alloc = 0; return; } // создание списка прямых базовых классов: // int n = число базовых b = new const Type_info*[n+1]; // занести базовые классы в b alloc = 1; return; } const Type_info* base_iterator::operator() () { const Type_info* p = &b[i]; if (p) i++; return p; } Теперь можно задать операции запросов о типе с помощью макроопределений: #define static_type_info(T) T::info() #define ptr_type_info(p) ((p)->get_info()) #define ref_type_info(r) ((r).get_info()) #define ptr_cast(T,p) \ (T::info()->can_cast((p)->get_info())? (T*)(p): 0) #define ref_cast(T,r) \ (T::info()->can_cast((r).get_info()) \? 0: throw Bad_cast(T::info()->name()), (T&)(r)) Предполагается, что тип особой ситуации Bad_cast (Ошибка_приведения)описан так: class Bad_cast { const char* tn; //... public: Bad_cast(const char* p): tn(p) { } const char* cast_to() { return tn; } //... }; В разделе $$4.7 было сказано, что появление макроопределенийслужит сигналом возникших проблем. Здесь проблема в том, что толькотранслятор имеет непосредственный доступ к литеральным типам,а макроопределения скрывают специфику реализации. По сути для храненияинформации для динамических запросов о типах предназначена таблицавиртуальных функций. Если реализация непосредственно поддерживаетдинамическую идентификацию типа, то рассматриваемые операции можнореализовать более естественно, эффективно и элегантно. В частности,очень просто реализовать функцию ptr_cast(), которая преобразуетуказатель на виртуальный базовый класс в указатель на его производныеклассы.


Поделиться:




Поиск по сайту

©2015-2024 poisk-ru.ru
Все права принадлежать их авторам. Данный сайт не претендует на авторства, а предоставляет бесплатное использование.
Дата создания страницы: 2016-02-16 Нарушение авторских прав и Нарушение персональных данных


Поиск по сайту: