C++. Сборник рецептов - Д. Стефенс
Шрифт:
Интервал:
Закладка:
protected:
static int count;
После этого требуется определить эту переменную в исходном файле. При этом для нее будет выделена память. Это делается с помощью указания полного имени переменной и присвоения ей значения, как здесь.
int OneStatic::count = 0;
В примере 8.5 я поместил это определение в файл Static.cpp. Именно так вы и должны делать — не помещайте определение в заголовочный файл. Если это сделать, память будет выделена в каждом файле реализации, включающем этот заголовочный файл, и либо возникнут ошибки компиляции, либо, что хуже, в памяти появятся несколько экземпляров этой переменной. Это не то, что требуется при использовании переменной-члена static.
В главном файле StaticMain.cpp вы можете видеть то, что происходит. Создается несколько экземпляров класса OneStatic, и каждый раз конструктор по умолчанию OneStatic инкрементирует статическую переменную. В результате вывод main из StaticMain.cpp имеет вид:
3
3
3
Каждый вызов getCount возвращает одно и то же целое значение, даже несмотря на то, что он делается для различных экземпляров класса.
8.6. Определение типа объекта во время выполнения
ПроблемаВо время выполнения требуется динамически узнавать тип определенного класса.
РешениеДля запроса, на объект какого типа указывает адрес объекта, используйте идентификацию типов во время выполнения (обычно называемую просто RTTI — runtime type identification). Пример 8.6 показывает, как это делается.
Пример 8.6. Использование идентификации типов во время выполнения
#include <iostream>
#include <typeinfo>
using namespace std;
class Base {};
class Derived : public Base {};
int main() {
Base b, bb;
Derived d;
// Используем typeid для проверки равенства типов
if (typeid(b) == typeid(d)) { // No
cout << "b и d имеют один и тот же тип.n";
}
if (typeid(b) == typeid(bb)) { // Yes
cout << "b и bb имеют один и тот же тип.n";
}
it (typeid(a) == typeid(Derived)) { // Yes
cout << "d имеет тип Derived.n";
}
}
ОбсуждениеПример 8.6 показывает, как использовать оператор typeid для определения и сравнения типов объектов, typeid принимает выражение или тип и возвращает ссылку на объект типа type_info или его подкласс (что зависит от реализации). Возвращенное значение можно использовать для проверки на равенство или получить строковое представление имени типа. Например, сравнить типы двух объектов можно так.
if (typeid(b) == typeid(d)) {
Это выражение возвращает истину, если возвращаемые объекты type_info равны. Это работает благодаря тому, что typeid возвращает ссылку на статический объект, так что при его вызове для двух объектов одного и того же типа будут получены две ссылки на один и тот же объект и сравнение вернет истину.
typeid также можно использовать непосредственно с типом, как здесь.
if (typeid(d) == typeid(Derived)) {
Это позволяет явно проверять определенный тип.
Вероятно, наиболее часто typeid используется для отладки. Для записи имени типа используйте type_info::name, как здесь.
std::cout << typeid(d).name() << std::endl;
При передаче объектов различных типов это может быть очень полезно. Строка, завершающаяся нулем, возвращаемая name, зависит от реализации, но вы можете ожидать (но не полагаться на это), что она будет равна имени типа. Это также работает и для встроенных типов.
Не злоупотребляйте этой методикой, основывая на информации о типе логику программы, если это не абсолютно необходимо. В общем случае наличие логики, которая выполняет что-то похожее на следующее, расценивается как плохой дизайн.
Если obj имеет тип X, сделать что-то одно, а если obj имеет тип Y, сделать что-то другое.
Это плохой дизайн, потому что клиентский код теперь содержит избыточные зависимости от типов используемых объектов. Это также приводит к большой каше из if/then кода, который то и дело повторяется, если для объектов типов X или Y требуется различное поведение. Объектно-ориентированное программирование и полиморфизм существуют в большой степени для того, чтобы избавить нас от написания подобного рода логики. Если для какого-либо семейства связанных классов требуется зависящее от типа поведение, то они все должны наследоваться от какого-то базового класса и использовать виртуальные функции, динамически вызывая различное поведение в зависимости от типа.
RTTI приводит к накладным расходам, так что компиляторы обычно по умолчанию его отключают. Скорее всего ваш компилятор имеет параметр командной строки для включения RTTI. Также это не единственный способ, которым можно получить информацию о типе. Другая методика приведена в рецепте 8.7.
Смотри такжеРецепт 8.7.
8.7. Определение, является ли класс объекта подклассом другого класса
ПроблемаИмеется два объекта и требуется узнать, имеют ли их классы отношения на уровне базовый класс/производный класс, или они не связаны друг с другом.
РешениеИспользуйте оператор dynamic_cast, который пытается выполнить преобразование одного типа в другой. Результат скажет, имеется ли связь между классами. Пример 8.7 представляет код, который это делает.
Пример 8.7. Определение отношений классов
#include <iostream>
#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. Присвоение каждому экземпляру класса уникального идентификатора
ПроблемаТребуется, чтобы каждый объект класса имел уникальный идентификатор.