Бьерн Страуструп - Язык программирования С++. Главы 11-13
Страница 49. Класс Type_info



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(), которая преобразует
указатель на виртуальный базовый класс в указатель на его производные
классы.

 
« Предыдущая статья   Следующая статья »