Программирование >>  Поддержка объектно-ориентированного программирования 

1 ... 52 53 54 [ 55 ] 56 57 58 ... 120


6.7 Свободная память

Если определить функции operator new() и operator delete(), управление памятью для класса можно взять в свои руки. Это также можно, (а часто и более полезно), сделать для класса, служащего базовым для многих производных классов. Допустим, нам потребовались свои функции размещения и освобождения памяти для класса employee ($$6.2.5) и всех его производных классов:

class employee {

...

public:

void* operator new(size t); void operator delete(void*, size t);

void* employee::operator new(size t s)

отвести память в s байтов и возвратить указатель на нее

void employee::operator delete(void* p, size t s)

p должно указывать на память в s байтов, отведенную функцией employee::operator new(); освободить эту память для повторного использования

Назначение до сей поры загадочного параметра типа size t становится очевидным. Это - размер освобождаемого объекта. При удалении простого служащего этот параметр получает значение sizeof(employee), а при удалении управляющего - sizeof(manager). Поэтому собственные функции классы для размещения могут не хранить размер каждого размещаемого объекта. Конечно, они могут хранить эти размеры (подобно функциям размещения общего назначения) и игнорировать параметр size t в вызове operator delete(), но тогда вряд ли они будут лучше, чем функции размещения и освобождения общего назначения.

Как транслятор определяет нужный размер, который надо передать функции operator delete()? Пока тип, указанный в operator delete(), соответствует истинному типу объекта, все просто; но рассмотрим такой пример:

class manager : public employee { int level; ...

void f()

employee* p = new manager; проблема delete p;

В этом случае транслятор не сможет правильно определить размер. Как и в случае удаления массива, нужна помощь программиста. Он должен определить виртуальный деструктор в базовом классе employee:

class employee {

...

public:

...

void* operator new(size t);



void operator delete(void*, size t); virtual ~employee();

Даже пустой деструктор решит нашу проблему:

employee::~employee() { }

Теперь освобождение памяти будет происходить в деструкторе (а в нем размер известен), а любой производный от employee класс также будет вынужден определять свой деструктор (тем самым будет установлен нужный размер), если только пользователь сам не определит его. Теперь следующий пример пройдет правильно:

void f()

employee* p = new manager; теперь без проблем delete p;

Размещение происходит с помощью (созданного транслятором) вызова

employee::operator new(sizeof(manager)) а освобождение с помощью вызова

employee::operator delete(p,sizeof(manager))

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

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

class X {

...

public:

...

virtual void f();

virtual ~X();

в X есть виртуальная функция, поэтому определяем виртуальный деструктор

6.7.1 Виртуальные конструкторы

Узнав о виртуальных деструкторах, естественно спросить: Могут ли конструкторы то же быть виртуальными? Если ответить коротко - нет. Можно дать более длинный ответ: Нет, но можно легко получить требуемый эффект .

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

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



inline void copy(expr* s); создать копию объекта, на который смотрит this virtual expr* clone(int deep = 0);

Параметр deep показывает различие между копированием собственно объекта (поверхностное копирование) и копированием всего поддерева, корнем которого служит объект (глубокое копирование). Стандартное значение 0 означает поверхностное копирование.

Функцию clone() можно использовать, например, так:

void fct(expr* root)

expr* c1 = root->clone(1); глубокое копирование expr* c2 = root->clone(); поверхностное копирование ...

Являясь виртуальной, функция clone() способна размножать объекты любого производного от expr

1 76

операции, нужно копировать только один раз. Тогда нам потребуется виртуальная функция размножения для узла выражения.

Как правило виртуальные конструкторы являются стандартными конструкторами без параметров или конструкторами копирования, параметром которых служит тип результата:

class expr {

...

public:

expr(); стандартный конструктор

virtual expr* new expr() { return new expr(); }

Виртуальная функция new expr() просто возвращает стандартно инициализированный объект типа expr, размещенный в свободной памяти. В производном классе можно переопределить функцию new expr() так, чтобы она возвращала объект этого класса:

class conditional : public expr {

...

public:

conditional(); стандартный конструктор expr* new expr() { return new conditional(); }

Это означает, что, имея объект класса expr, пользователь может создать объект в точности такого же типа :

void user(expr* p1, expr* p2)

expr* p3 = p1->new expr(); expr* p4 = p2->new expr(); ...

Переменным p3 и p4 присваиваются указатели неизвестного, но подходящего типа.

Тем же способом можно определить виртуальный конструктор копирования, называемый операцией размножения, но надо подойти более тщательно к специфике операции копирования:

class expr {

...

expr* left; expr* right; public: ...

копировать s в this



1 ... 52 53 54 [ 55 ] 56 57 58 ... 120

© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки.
Яндекс.Метрика