Бьерн Страуструп (Стpаустpуп - Книга о C++), страница 78
Описание файла
Документ из архива "Стpаустpуп - Книга о C++", который расположен в категории "". Всё это находится в предмете "информатика" из , которые можно найти в файловом архиве . Не смотря на прямую связь этого архива с , его также можно найти и в других разделах. Архив можно найти в разделе "книги и методические указания", в предмете "информатика" в общих файлах.
Онлайн просмотр документа "Бьерн Страуструп"
Текст 78 страницы из документа "Бьерн Страуструп"
// ...
}
Она хороша в общем случае, но представим,- стало известно,
что многие параметры множества представляют собой объекты типа
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() служит хорошей
иллюстрацией различий между ссылками и указателями: ссылка обязательно
ссылается на объект, тогда как указатель может и не ссылаться,
поэтому для указателя часто нужна проверка.
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. Существенно, что объект
не меняется: это должно гарантировать, что динамическая информация
о типе отражает статические типы исходной программы. Плохо, если
при выполнении программы некоторый тип может изменяться.
С помощью указателя на объект класса 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.
Здесь средство динамических запросов о типе сознательно
реализуется с помощью совсем простых классов. Так можно избежать
привязки к определенной библиотеке. Реализация в расчете на
конкретную библиотеку может быть иной. Можно, как всегда, посоветовать
пользователям избегать излишней зависимости от деталей реализации.
Функция 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(), которая преобразует
указатель на виртуальный базовый класс в указатель на его производные