|
Программирование >> Поддержка объектно-ориентированного программирования
класса. Настоящее копирование можно определить так: void expr::copy(expression* s, int deep) if (deep == 0) { копируем только члены *this = *s; else { пройдемся по указателям: left = s->clone(1); right = s->clone(1); ... Функция expr::clone() будет вызываться только для объектов типа expr (но не для производных от expr классов), поэтому можно просто разместить в ней и возвратить из нее объект типа expr, являющийся собственной копией: expr* expr::clone(int deep) expr* r = new expr(); строим стандартное выражение r->copy(this,deep); копируем *this в r return r; Такую функцию clone() можно использовать для производных от expr классов, если в них не появляются члены-данные (а это как раз типичный случай): class arithmetic : public expr { ... новых членов- данных нет => можно использовать уже определенную функцию clone С другой стороны, если добавлены члены-данные, то нужно определять собственную функцию clone(): class conditional : public expression { expr* cond; public: inline void copy(cond* s, int deep = 0); expr* clone(int deep = 0); ... Функции copy() и clone() определяются подобно своим двойникам из expression: expr* conditional::clone(int deep) conditional* r = new conditional(); r->copy(this,deep); return r; void conditional::copy(expr* s, int deep) if (deep == 0) { *this = *s; else { expr::copy(s,1); копируем часть expr cond = s->cond->clone(1); Определение последней функции показывает отличие настоящего копирования в expr::copy() от полного размножения в expr::clone() (т.е. создания нового объекта и копирования в него). Простое копирование оказывается полезным для определения более сложных операций копирования и размножения. Различие между copy() и clone() эквивалентно различию между операцией присваивания и конструктором копирования ($$1 .4.2) и эквивалентно различию между функциями draw() и draw() ($$6.5.3). Отметим, что функция copy() не является виртуальной. Ей и не надо быть таковой, поскольку виртуальна вызывающая ее функция clone(). Очевидно, что простые операции копирования можно также определять как функции-подстановки. 6.7.2 Указание размещения По умолчанию операция new создает указанный ей объект в свободной памяти. Как быть, если надо разместить объект в определенном месте? Этого можно добиться переопределением операции размещения. Рассмотрим простой класс: class X { ... public: X(int); Объект можно разместить в любом месте, если ввести в функцию размещения дополнительные параметры: операция размещения в указанном месте: void* operator new(size t, void* p) { return p; } и задав эти параметры для операции new следующим образом: char buffer[sizeof(X)]; void f(int i) X* p = new(buffer) X(i); разместить X в buffer ... Функция operator new(), используемая операцией new, выбирается согласно правилам сопоставления параметров ($$R.13.2). Все функции operator new() должны иметь первым параметром size t. Задаваемый этим параметром размер неявно передается операцией new. Определенная нами функция operator new() с задаваемым размещением является самой простой из функций подобного рода. Можно привести другой пример функции размещения, выделяющей память из некоторой заданной области: class Arena { ... virtual void* alloc(size t) = 0; virtual void free(void*) = 0; void operator new(size t sz. Arena* a) return a.alloc(sz); Теперь можно отводить память для объектов произвольных типов из различных областей (Arena): extern Arena* Persistent; постоянная память extern Arena* Shared; разделяемая память void g(int i) X* p = new(Persistent) X(i); X в постоянной памяти X* q = new(Shared) X(i); Если мы помещаем объект в область памяти, которая непосредственно не управляется стандартными функциями распределения свободной памяти, то надо позаботиться о правильном уничтожении объекта. Основным средством здесь является явный вызов деструктора: void h(X* p) p->~X(); Persistent->free(p); вызов деструктора освобождение памяти Заметим, что явных вызовов деструкторов, как и глобальных функций размещения специального назначения, следует, по возможности, избегать. Бывают случаи, когда обойтись без них трудно, но новичок должен трижды подумать, прежде чем использовать явный вызов деструктора, и должен сначала посоветоваться с более опытным коллегой. 6.8 Упражнения 1 . (*1 ) Пусть есть класс class base { public: virtual void iam() { cout << base\n ; } Определите два производных от base класса и в каждом определите функцию iam(), выдающую имя своего класса. Создайте объекты этих классов и вызовите iam() для них. Присвойте адреса объектов производных классов указателю типа base* и вызовите iam() с помощью этих указателей. 2. (*2) Реализуйте примитивы управления экраном ($$6.4.1) разумным для вашей системы образом. 3. (*2) Определите классы triangle (треугольник) и circle (окружность). 4. (*2) Определите функцию, рисующую отрезок прямой, соединяющий две фигуры. Вначале надо найти самые ближайшие точки фигур, а затем соединить их. 5. (*2) Измените пример с классом shape так, чтобы line было производным классом от rectangle, или наоборот. 6. (*2) Пусть есть класс class char vec { int sz; char element [1]; public: static new char vec(int s); char& operator[] (int i) { return element[i]; } Определите функцию new char vec() для отведения непрерывного участка памяти для объектов char vec так, чтобы элементы можно было индексировать как массив element[]. В каком случае эта функция вызовет серьезные трудности? 7. (*1 ) Опишите структуры данных, которые нужны для примера с классом shape из $$6.4, и объясните, как может выполняться виртуальный вызов. 8. (*1 .5) Опишите структуры данных, которые нужны для примера с классом satellite из $$6.5, и объясните, как может выполняться виртуальный вызов. 9. (*2) Опишите структуры данных, которые нужны для примера с классом window из $$6.5.3, и объясните, как может выполняться виртуальный вызов. X в разделяемой памяти
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |