Б. Страуструп - Язык программирования С++ (1119446), страница 97
Текст из файла (страница 97)
интерфейс с большим числом функций-членов)пользователям, чем базовые классы;[3]основывать в первую очередь (но не исключительно) свой общий интерфейс на виртуальныхфункциях;[4]зависеть от всех своих (прямых и косвенных) базовых классов;[5]иметь смысл только в контексте своих базовых классов;[6]служить базовым классом для построения производных классов;[7]воплощаться в объекте.Не все, но многие, узловые классы будут удовлетворять условиям 1, 2, 6 и 7. Класс, который неудовлетворяет условию 6, походит на конкретный тип и может быть назван конкретным узловымклассом. Класс, который не удовлетворяет условию 7, походит на абстрактный тип и может быть названабстрактным узловым классом. У многих узловых классов есть защищенные члены, чтобы предоставитьдля производных классов менее ограниченный интерфейс.Укажем на следствие условия 4: для трансляции своей программы пользователь узлового класса долженвключить описания всех его прямых и косвенных базовых классов, а также описания всех тех классов, откоторых, в свою очередь, зависят базовые классы.
В этом узловой класс опять представляет контраст сабстрактным типом. Пользователь абстрактного типа не зависит от всех классов, использующихся дляреализации типа и для трансляции своей программы не должен включать их описания.13.5 Динамическая информация о типеИногда бывает полезно знать истинный тип объекта до его использования в каких-либо операциях.Рассмотрим функцию my(set&) из $$13.3.void my_set(set& s){for ( T* p = s.first(); p; p = s.next()) {// мой код}// ...}Она хороша в общем случае, но представим,- стало известно, что многие параметры множествапредставляют собой объекты типа slist. Возможно также стал известен алгоритм перебора элементов,который значительно эффективнее для списков, чем для произвольных множеств. В результатеэксперимента удалось выяснить, что именно этот перебор является узким местом в системе.
Тогда,344Бьерн Страуструп.Язык программирования С++конечно, имеет смысл учесть в программе отдельно вариант с slist. Допустив возможность определенияистинного типа параметра, задающего множество, функцию my(set&) можно записать так:void my(set& s){if (ref_type_info(s) == static_type_info(slist_set)) {// сравнение двух представлений типа// s типа slistslist& 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) {345Бьерн Страуструп.Язык программирования С++// ``обычный'' dialog_box}// ...}Поскольку нет приемлемого представления нулевой ссылки, с которой можно сравнивать, используетсяособая ситуация, обозначающая ошибку приведения (т.е.
случай, когда тип не есть dbox_w_str). Иногдалучше избегать сравнения с результатом приведения.Различие функций ref_cast() и ptr_cast() служит хорошей иллюстрацией различий между ссылками иуказателями: ссылка обязательно ссылается на объект, тогда как указатель может и не ссылаться,поэтому для указателя часто нужна проверка.13.5.1 Информация о типеВ С++ нет иного стандартного средства получения динамической информации о типе, кроме вызововвиртуальных функций.
Хотя было сделано несколько предложений по расширению С++ в этомнаправлении.Смоделировать такое средство довольно просто и в большинстве больших библиотек естьвозможности динамических запросов о типе. Здесь предлагается решение, обладающее тем полезнымсвойством, что объем информации о типе можно произвольно расширять.
Его можно реализовать спомощью вызовов виртуальных функций, и оно может входить в расширенные реализации С++.Достаточно удобный интерфейс с любым средством, поставляющим информацию о типе, можно задатьс помощью следующих операций: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.
Существенно, что объект не меняется: это должно гарантировать, что динамическаяинформация о типе отражает статические типы исходной программы. Плохо, если при выполнениипрограммы некоторый тип может изменяться.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.Здесь средство динамических запросов о типе сознательно реализуется с помощью совсем простыхклассов. Так можно избежать привязки к определенной библиотеке. Реализация в расчете наконкретную библиотеку может быть иной.