straustrup2 (852740), страница 94
Текст из файла (страница 94)
Таким образом пользователь отстраняетсяот дальнейших сложностей, связанных с динамической идентификацией типа. Кроме того,ограниченное использование динамической информации о типе меньше всего чревато ошибками.Если недостаточно знать, что операция приведения прошла успешно, а нужен истинный тип (например,объектно-ориентированный ввод-вывод), то можно использовать операции динамических запросов отипе: 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. Существенно, что объект не меняется: это должно гарантировать, что динамическаяинформация о типе отражает статические типы исходной программы.
Плохо, если при выполнениипрограммы некоторый тип может изменяться.346Бьерн Страуструп.Язык программирования С++С помощью указателя на объект класса Type_info пользователь получает доступ к информации о типеиз typeid и, теперь его программа начинает зависеть от конкретной системы динамических запросов отипе и от структуры динамической информации о нем. Но эти средства не входят в стандарт языка, азадать их с помощью хорошо продуманных макроопределений непросто.13.5.2 Класс 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.Здесь средство динамических запросов о типе сознательно реализуется с помощью совсем простыхклассов. Так можно избежать привязки к определенной библиотеке.
Реализация в расчете наконкретную библиотеку может быть иной. Можно, как всегда, посоветовать пользователям избегатьизлишней зависимости от деталей реализации.347Бьерн Страуструп.Язык программирования С++Функция 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];// занести базовые классы в balloc = 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; }// ...348Бьерн Страуструп.Язык программирования С++};В разделе $$4.7 было сказано, что появление макроопределений служит сигналом возникших проблем.Здесь проблема в том, что только транслятор имеет непосредственный доступ к литеральным типам, амакроопределения скрывают специфику реализации.
По сути для хранения информации длядинамических запросов о типах предназначена таблица виртуальных функций. Если реализациянепосредственно поддерживает динамическую идентификацию типа, то рассматриваемые операцииможно реализовать более естественно, эффективно и элегантно. В частности, очень простореализовать функцию ptr_cast(), которая преобразует указатель на виртуальный базовый класс вуказатель на его производные классы.13.5.3 Как создать систему динамических запросов о типеЗдесь показано, как можно прямо реализовать динамические запросы о типе, когда в трансляторе такихвозможностей нет. Это достаточно утомительная задача и можно пропустить этот раздел, так как в неместь только детали конкретного решения.Классы set и slist_set из $$13.3 следует изменить так, чтобы с ними могли работать операции запросов отипе.
Прежде всего, в базовый класс set нужно ввести функции-члены, которые используют операциизапросов о типе:class set {public:static const Type_info info_obj;virtual typeid get_info() const;static typeid info();// ...};При выполнении программы единственным представителем объекта типа set является set::info_obj,который определяется так:const Type_info set::info_obj("set",0);С учетом этого определения функции тривиальны:typeidtypeidtypeidtypeidset::get_info() const { return &info_obj; }set::info() { return &info_obj; }slist_set::get_info() const { return &info_obj; }slist_set::info() { return &info_obj; }Виртуальная функция get_info() будет предоставлять операции ref_type_info() и ptr_type_info(), астатическая функция info() - операцию static_type_info().При таком построении системы запросов о типе основная трудность на практике состоит в том, чтобыдля каждого класса объект типа Type_info и две функции, возвращающие указатель на этот объект,определялись только один раз.Нужно несколько изменить класс slist_set:class slist_set : public set, private slist {// ...public:static const Type_info info_obj;virtual typeid get_info() const;static typeid info();// ...};static const Type_info* slist_set_b[]= { &set::info_obj, &slist::info_obj, 0 };const Type_info slist_set::info_obj("slist_set",slist_set_b);typeid slist_set::get_info() const { return &info_obj; }typeid slist_set::info() { return &info_obj; }349Бьерн Страуструп.Язык программирования С++13.5.4 Расширенная динамическая информация о типеВ классе Type_info содержится только минимум информации, необходимой для идентификации типа ибезопасных операций приведения.
Но поскольку в самом классе Type_info есть функции-члены info() иget_info(), можно построить производные от него классы, чтобы в динамике определять, какие объектыType_info возвращают эти функции. Таким образом, не меняя класса Type_info, пользователь можетполучать больше информации о типе с помощью объектов, возвращаемых функциями dynamic_type() иstatic_type(). Во многих случаях дополнительная информация должна содержать таблицу членовобъекта:struct Member_info {char* name;Type_info* tp;int offset;};class Map_info : public Type_info {Member_info** mi;public:static const Type_info info_obj;virtual typeid get_info() const;static typeid info();// функции доступа};Класс Type_info вполне подходит для стандартной библиотеки.
Это базовый класс с минимумомнеобходимой информации, из которого можно получать производные классы, предоставляющиебольше информации. Эти производные классы могут определять или сами пользователи, или какие-тослужебные программы, работающие с текстом на С++, или сами трансляторы языка.13.5.5 Правильное и неправильное использование динамическойинформации о типеДинамическая информация о типе может использоваться во многих ситуациях, в том числе для:объектного ввода-вывода, объектно-ориентированных баз данных, отладки.
В тоже время великавероятность ошибочного использования такой информации. Известно,что в языке Симулаиспользование таких средств, как правило, приводит к ошибкам. Поэтому эти средства не быливключены в С++. Слишком велик соблазн воспользоваться динамической информацией о типе, тогдакак правильнее вызвать виртуальную функцию. Рассмотрим в качестве примера класс Shape из $$1.2.5.Функцию rotate можно было задать так:void////{ifrotate(const Shape& s)неправильное использование динамическойинформации о типе(ref_type_info(s)==static_type_info(Circle)) {// для этой фигуры ничего не надо}else if (ref_type_info(s)==static_type_info(Triangle)) {// вращение треугольника}else if (ref_type_info(s)==static_type_info(Square)) {// вращение квадрата}// ...}Если для переключателя по типу поля мы используем динамическую информацию о типе, то тем самымнарушаем в программе принцип модульности и отрицаем сами цели объектно-ориентированногопрограммирования.
К тому же это решение чревато ошибками: если в качестве параметра функциибудет передан объект производного от Circle класса, то она сработает неверно (действительно,350Бьерн Страуструп.Язык программирования С++вращать круг (Circle) нет смысла, но для объекта, представляющего производный класс, это можетпотребоваться).