Материалы книги получены с http://www.itlibitum.ru/
Определение класса по объекту
Для существующего экземпляра довольно часто требуется определить его класс. Вроде бы ничего сложного, но в действительности это очень глубокая тема. Помните, что объекты могут создаваться в стеке или в куче, внедряться в другие объекты в виде переменных или базовых классов, а также создаваться производящими функциями. Ниже описано несколько основных решений.
Внедрение указателя на объект класса
Самое очевидное решение - внедрять указатель на Class в любой объект, вложенный или нет.
class Object { // Предок всех реальных классов
protected:
static ObjectClass s_my_class;
Class* my_class; // == &s_my_class;
public:
Object() : my_class(&s_my_class) {}
Class* My_Class() { return my_class; }
};
class Foo : public Object
protected:
static FooClass s_my_class;
public:
Foo() { my_class = &s_my_class; }
};
Все классы порождаются от общего предка Object, в котором определяется протокол для получения объекта Class. Вы имеете полное право использовать одни и те же имена членов на разных уровнях иерархии классов (как это сделано с s_my_class в нашем примере). Компилятор выбирает имя, находящееся в непосредственной области действия. Более того, конструкторы выполняются в порядке «базовый класс/переменные класса/ производные классы», поэтому последний конструктор оставит my_class правильное значение. Эта схема позволяет всегда получить объект Class независимо от
того, сколько выполнялось преобразований типа от производных к базовым классам.
Издержки составляют четыре байта, необходимые для хранения указателя. Виртуальные функции не требуются, поэтому нам не придется добавлять v-таблицу в класс, обходившийся без нее. На избыточное конструирование my_class будут потрачены дополнительные такты процессора, но для большинства приложений это несущественно. Пожалуй, основные издержки сводятся к дополнительному коду, находящемуся в конструкторах.
В более «чистом» варианте указатель на Class задается производящими функциями объекта Class:
class Object {
friend class Class;
private:
Class* my_class;
public:
Class* My_Class() { reutrn my_class; }
};
class Class {
protected:
void SetClass(Object& obj) { obj.my_class = this; }
};
class Foo : public Object { ... };
class FooClass : public Class {
public:
Foo* make()
{
Foo* f = new Foo;
this->SetClass(f);
return f;
}
};
Выглядит получше, поскольку производные от Object классы и не подозревают об этих фокусах… но так ли это? Недостаток этого подхода - в том, что он не работает для экземпляров Foo, объявленных в стеке или вложенных в другие классы в виде структур данных. Перед вами одна из ситуаций, в которых приходится принимать трудное решение: то ли ограничить класс только динамическими экземплярами, то ли искать более сложное решение и без того сложной проблемы.
Существует еще один вариант - управлять выделением памяти и хранить адеса объекта класса прямо над самим объектом в памяти вместо того, чтобы делать его переменной класса предка. Для этого нам понадобятся приемы управления памятью, описанные в части 4.
Внешние структуры данных
Как упоминалось выше, вы также можете создать глобальную коллекцию с парами экземпляр/Class. Все не так скверно, как выглядит на первый взгляд, особенно если информация Class нужна только в процессе отладки и будет исключена в рабочем режиме. Если соблюдать осторожность в реализации, решение также распространяется и на такие вложенные объекты, как стековые переменные или экземпляры, хотя для этого вам понадобится соответствующая поддержка со стороны конструктора и деструктора основного класса.
Нестандартные пространства памяти
Другое решение, рассматриваемое в главах 15 и 16 - физическое разделение объектов по различным пространствам памяти в соответствии с их классом. Оно отличается повышенной сложностью и попросту не работает для вложенных объектов, но зато обладает впечатляющим быстродействием и малым расходом памяти.
Назад Содержание Далее
|