Чтение онлайн

ЖАНРЫ

C++. Сборник рецептов

Когсуэлл Джефф

Шрифт:

#include <typeinfo>

using namespace std;

class Base {

public:

 virtual ~Base {} // Делаем класс полиморфным

};

class Derived : public Base {

public:

 virtual ~Derived {}

};

int main {

 Derived d;

 // Запрашиваем тип отношений

 if (dynamic_cast<Base*>(&d)) {

cout << "Derived
является классом, производным от Base" << endl;

 } else {

cout << "Derived HE является классом, производным от Base" << endl;

 }

}

Обсуждение

Для запроса отношений между двумя типами используйте оператор

dynamic_cast
.
dynamic_cast
принимает указатель или ссылку на некий тип и пытается преобразовать его к указателю или ссылке на производный класс, т.е. выполняя преобразование типа вниз по иерархии классов. Если есть
Base*
, который указывает на объект
Derived
, то
dynamic_cast<Base*>(&d)
возвращает указатель типа
Derived
только в том случае, если
d
— это объект типа, производного от
Base
. Если преобразование невозможно (из-за того, что
Derived
не является подклассом — явным или косвенным — класса
Base
), то преобразование завершается неудачей и, если в
dynamic_cast
был передан указатель на производный класс, возвращается
NULL
. Если в него была передана ссылка, то выбрасывается стандартное исключение
bad_cast
. Также базовый класс должен наследоваться как
public
и это наследование не должно быть двусмысленным. Результат говорит о том, является ли один класс наследником другого класса. Вот что я сделал в примере 8.7.

if (dynamic_cast<Base*>(&d)) {

Здесь возвращается нe-

NULL
– указатель, так как
d
— это объект класса, производного от
Base
. Эту возможность можно использовать для определения отношения любых двух классов. Единственным требованием является то, что аргумент объекта должен быть полиморфным типом, что означает, что он должен иметь по крайней мере одну виртуальную функцию. Если это не будет соблюдено, то такой код не скомпилируется. Однако обычно это не вызывает особых проблем, так как иерархия классов без виртуальных функций встречается крайне редко.

Если этот синтаксис кажется вам слишком запутанным, используйте макрос, скрывающий некоторые подробности.

#define IS_DERIVED_FROM(BaseClass, x) (dynamic_cast<baseClass*>(&(x)))

//...

if (IS_DERIVED_FROM(Base, l)){//...

Но помните, что такая информация о типах не бесплатна, так как

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

Смотри также

Рецепт 8.6.

8.8. Присвоение каждому экземпляру класса уникального идентификатора

Проблема

Требуется, чтобы каждый объект класса имел уникальный идентификатор.

Решение

Для отслеживания следующего доступного для использования идентификатора используйте статическую переменную-член. В конструкторе присвойте текущему объекту очередное доступное значение, а затем инкрементируйте статическую переменную. Чтобы понять, как это работает, посмотрите на пример 8.8.

Пример 8.8. Присвоение уникальных идентификаторов

#include <iostream>

class UniqueID {

protected:

 static int nextID;

public:

 int id;

 UniqueID;

 UniqueID(const UniqueID& orig);

 UniqueID& operator=(const UniqueID& orig);

};

int UniqueID::nextID = 0;

UniqueID::UniqueID {

 id = ++nextID;

}

UniqueID::UniqueID(const UniqueID& orig) {

 id = orig.id;

}

UniqueID& UniqueID::operator=(const UniqueID& orig) {

 id = orig.id;

 return(*this);

}

int main {

 UniqueID a;

 std::cout << a.id << std::endl;

 UniqueID b;

 std::cout << b.id << std::endl;

 UniqueID c;

 std::cout << c.id << std::endl;

}

Обсуждение

Для

отслеживания следующего доступного для использования идентификатора используйте статическую переменную. В примере 8.8 используется
static int
, но вместо нее можно использовать все, что угодно, при условии, что имеется функция, которая может генерировать уникальные значения.

В данном случае идентификаторы не используются повторно до тех пор, пока не будет достигнуто максимально возможное для целого числа значение. При удалении объекта его уникальное значение пропадает либо до перезапуска программы, либо до переполнения значения идентификатора. Эта уникальность в программе может иметь несколько интересных преимуществ. Например, при работе с библиотекой управления памятью, которая перемещает блоки памяти и обновляет значения указателей, можно быть уверенным, что для каждого объекта будет сохранено его первоначальное уникальное значение. При использовании уникальных значений в сочетании с рецептом 8.4, но применении

map
вместо
list
можно легко найти объект с заданным уникальным номером. Чтобы сделать это, просто отобразите уникальные ID на экземпляры объектов, как здесь.

static map<int, MyClass*> instmap;

Таким образом любой код, который отслеживает идентификаторы объектов, всегда сможет найти его без необходимости хранить ссылку на него.

Но это еще не все. Рассмотрим случай, когда один из этих объектов требуется добавить в стандартный контейнер (

vector
,
list
,
set
и т.п.). Стандартные контейнеры хранят копии объектов, добавляемых в них, а не ссылки или указатели на эти объекты (конечно, при условии, что это не контейнер указателей). Таким образом, стандартные контейнеры ожидают, что объекты, которые в них содержатся, ведут себя как объекты значений, что означает, что при присвоении с помощью оператора присвоения или копировании с помощью конструктора копирования создается новая версия, полностью эквивалентная оригинальной версии.

Это означает, что требуется решить, как должны себя вести уникальные объекты. При создании объекта с уникальным идентификатором и добавлении его в контейнер у вас появятся два объекта с одним и тем же идентификатором при условии, что вы не переопределили оператор присвоения. В операторе присвоения и конструкторе копирования требуется выполнить те действия с уникальным значением, которые имеют смысл для вашего случая. Имеет ли смысл то, что объект в контейнере будет равен оригинальному объекту? Если да, то вполне подойдет стандартный конструктор копирования и оператор присвоения, но вы должны указать это явно, чтобы пользователи вашего класса знали, что вы делаете это намеренно, а не просто забыли, как работают контейнеры. Например, чтобы использовать одно и то же значение идентификатора, конструктор копирования и оператор присвоения должны выглядеть вот так.

Поделиться с друзьями: