Правила программирования на Си и Си++

       

Опасайтесь приведения типов (спорные вопросы Си++)


Приведение типов в Си рассмотрено ранее, но и в Си++ приведение вызывает проблемы. В Си++ у вас также существует проблема нисходящего приведения —

приведения указателя или ссылки на базовый класс к производному классу. Эта проблема обычно появляется при замещениях виртуальных функций, потому что сигнатуры функций производного класса должны точно совпадать с сигнатурами базового класса. Рассмотрим этот код:

class base

{

public:

   virtual int

operator==( const base r ) = 0;

};

class derived

{

   char *key;

public:

   virtual int



operator==( const base r )

   {

      return strcmp(key, ((const derived )r).key ) == 0;

   }

};

К несчастью, здесь нет гарантии, что передаваемый аргумент r

действительно ссылается на объект производного класса. Он не может ссылаться на объект базового класса из-за того, что функция чисто виртуальная: вы не можете создать экземпляр объекта base. Тем не менее, r мог бы быть ссылкой на объект некоего другого класса, унаследованного от

base, но не являющегося классом derived. С учетом предыдущего определения следующий код не работает:

class other_derived : public base

{

   int key;

   // ...

};

f()

{

   derived       dobj;

   other_derived other;

   if( derived == other_derived )

      id_be_shocked();

}

Комитет ISO/ANSI по Си++ рекомендовал механизм преобразования типов во время выполнения, который решает эту проблему, но на момент написания этой книги многие компиляторы его не поддерживают. Предложенный синтаксис выглядит подобным образом:

class derived : public base

{

   char *key;

public:

   virtual int operator==( const base r )

   {

      derived *p = dynamic_castderived *( r );

      return !p ? 0 : strcmp(key, ((const derived )r).key )==0;

   }

};

Шаблон функции dynamic_castt

возвращает 0, если операнд не может быть безопасно преобразован в тип t, иначе он выполняет преобразование.

Это правило является также хорошей демонстрацией того, почему вы не хотите, чтобы все классы в вашей иерархии происходили от общего класса object. Почти невозможно использовать аргументы класса object

непосредственно, потому что сам по себе класс object

почти лишен функциональности. Вы поймаете себя на том, что постоянно приводите указатели на object к тому типу, который на самом деле имеет переданный аргумент. Это приведение может быть опасным без использования преобразования типов во время выполнения, потому что вы можете преобразовать в неверный тип. Приведение уродливо даже в виде преобразования во время выполнения, добавляя ненужный беспорядок в программу.



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