Язык программирования C++ от Страуструпа

Правильное и неправильное использование динамической информации о типе


Динамическая информация о типе может использоваться во многих ситуациях, в том числе для: объектного ввода-вывода, объектно-ориентированных баз данных,  отладки. В тоже время велика вероятность ошибочного использования такой информации. Известно,что в языке Симула использование таких средств, как правило, приводит к ошибкам. Поэтому эти средства не были включены в С++. Слишком велик соблазн воспользоваться динамической информацией о типе, тогда как правильнее вызвать виртуальную функцию. Рассмотрим в качестве примера класс Shape из $$1.2.5. Функцию rotate можно было задать так:

void rotate(const Shape& s)

  // неправильное использование динамической

  // информации о типе

{

  if (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 класса, то она сработает неверно (действительно, вращать круг (Circle) нет смысла, но для объекта, представляющего производный класс, это может потребоваться). Опыт показывает, что программистам, воспитанным на таких языках как С или Паскаль, трудно избежать этой ловушки. Стиль программирования этих языков требует меньше предусмотрительности, а при создании библиотеки такой стиль можно просто считать небрежностью.

Может возникнуть вопрос, почему в интерфейс с системой динамической информации о типе включена условная операция приведения ptr_cast(), а не операция is_base(), которая непосредственно определяется с помощью операции has_base() из класса Type_info. Рассмотрим такой пример:


void f(dialog_box& db)

{

  if (is_base(&db,dbox_w_str)) {   // является ли db базовым

                                   // для  dbox_w-str?

     dbox_w_str* dbws = (dbox_w_str*) &db;

     // ...

  }

  // ...

}

Решение с помощью ptr_cast ($$13.5) более короткое, к тому же здесь явная и безусловная операция приведения отделена от проверки в операторе if, значит появляется возможность ошибки, неэффективности и даже неверного результата. Неверный результат может возникнуть в тех редких случаях, когда система динамической идентификации типа распознает, что один тип является производным от другого, но транслятору этот факт неизвестен, например:

class D;

class B;

void g(B* pb)

{

  if (is_base(pb,D)) {

     D* pb = (D*)pb;

     // ...

  }

  // ...

}

Если транслятору пока неизвестно следующее описание класса D:

class D : public A, public B {

  // ...

};

то возникает ошибка, т.к. правильное приведение указателя pb к D* требует изменения значения указателя. Решение с операцией ptr_cast() не сталкивается с этой трудностью, поскольку эта операция применима только при условии, что в области видимости находятся описания обеих ее параметров. Приведенный пример показывает, что операция приведения для неописанных классов по сути своей ненадежна, но запрещение ее существенно ухудшает совместимость с языком С.


Содержание раздела