|
Программирование >> Разработка устойчивых систем
linclude <vector> linclude ../purge.h using namespace std; class Shape { Shape* s; Запрет конструирования копий и присваивания Shape(Shape&); Shape operator=(Shape&); protected; ShapeO { s = 0; } public: virtual void drawO { s->draw(); } virtual void eraseO { s->erase(); } virtual void testO { s->test(): } virtual -ShapeO { cout -Shape endl: if(s) { cout Making virtual call: : s->erase(): Виртуальный вызов cout delete s: ; delete s; Полиморфное удаление (выражение delete О допустимо, это пустая операция) class BadShapeCreation : public logic error { public: BadShapeCreation(string type) : logic error( Cannot create type + type) {} Shape(string type) throw(BadShapeCreation); class Circle : public Shape { Circle(Circle&): Circle operator-(Circle&): CircleO {} Private constructor friend class Shape; public: void drawO { cout Circle: :draw endl; } void eraseO { cout Circle; :erase endl: } void testO { drawO: } -CircleO { cout Circle: :~Circle endl; } class Square : public Shape { Square(Square&): Square operator=(Square&); SquareO {} friend class Shape; public: void drawO { cout Square:: draw endl; } void eraseO { cout Square::erase endl; } void testO { drawO: } -SquareO { cout Square::-Square endl; } Shape::Shape(string type) throw(Shape::BadShapeCreation) { if(type == Circle ) s new Circle: else if(type -= Square ) s - new Square: else throw BaclShapeCreation(type): drawO: Виртуальный вызов в конструкторе char* sl[] = { Circle . Square . Square . Circle . Circle . Circle . Square }: int mainO { vector<Shape*> shapes; cout virtual constructor calls: endl; try { for(size t i = 0; i < sizeof si / sizeof sl[0]: i++) shapes.push back(new Shape(sl[i])); } catch(Shape::BadShapeCreation e) { cout e.whatО endl; purge(shapes): return EXITJAILURE: for(size t i = 0; i < shapes.sizeO; i++) { shapes[i]->draw(); cout test endl; shapes[i]->test(); cout end test endl; shapes[i]->erase(); Shape c( Circle ); Создание в стеке cout destructor calls: endl: purge(shapes); } /:- Базовый класс Shape хранит указатель на объект типа Shape в своей единственной переменной (при моделировании виртуального конструктора следует особенно внимательно следить за тем, чтобы этот указатель всегда инициализировался существующим объектом). Фактически базовый класс является посредником, поскольку он единственный, что видит и с чем взаимодействует клиентский код. При каждом порождении нового производного типа от Shape необходимо включить процедуру создания этого типа в одном месте, внутри виртуального конструктора в базовом классе Shape. Сделать это несложно, но при этом возникает нежелательная зависимость между классом Shape и всеми классами, производными от него. В этом примере информация о создаваемом типе передается виртуальному конструктору в виде строки с именем типа. Тем не менее, в ващей схеме может использоваться другой способ, например в парсере виртуальному конструктору может передаваться результат работы лексического сканера, и на основании этой информации парсер рещает, какую лексему следует создать. Виртуальный конструктор Shape(type) может определяться лищь после объявления всех производных классов. Конструктор по умолчанию может быть определен в классе Shape, но его следует объявить защищенным, чтобы предотвратить создание временных объектов Shape. Конструктор по умолчанию вызывается только конструкторами объектов производных классов. Он должен определяться явно, потому что компилятор автоматически генерирует конструктор по умолчанию только при отсутствии определенных конструкторов. Так как мы должны определить Shape(type), также придется определить Shape(). Конструктор по умолчанию в этой схеме должен решать по крайней мере одну важную задачу - он должен обнулить указатель s. На первый взгляд это кажется странным, по вспомните, что конструктор по умолчанию вызывается как часть конструирования фактического объекта - в терминологии Коплина письма , а не конверта . Но письмо является производным от конверта , поэтому оно также наследует переменную s. В конверте указатель s играет важную роль, потому что он ссылается на реальный объект, но в письме он просто является лишним грузом. Но даже лишний груз необходимо инициализировать, и если s не обнуляется вызовом конструктора по умолчанию для письма , это кончится плохо (как вы вскоре увидите). Виртуальный конструктор получает в своем аргументе информацию, полностью определяющую тип объекта. Но обратите внимание на то, что эта информация не читается и не обрабатывается до стадии выполнения, тогда как компилятор в обычном случае должен знать точный тип на стадии компиляции (еще одна причина, позволяющая говорить об успешной имитации виртуальных конструкторов). Виртуальный конструктор использует свой аргумент для выбора реального конструируемого объекта ( письма ), указатель на который затем присваивается переменной конверта . В этой точке конструирование письма уже завершено, поэтому любые виртуальные вызовы будут должным образом перенаправлены. В качестве примера рассмотрим вызов draw() в виртуальном конструкторе. Если трассировать этот вызов (вручную или с помощью отладчика), вы увидите, что он начинается с функции draw() базового класса Shape. Эта функция вызывает draw() для хранящегося в конверте указателя s на письмо . Все типы, производные от Shape, обладают одинаковым интерфейсом, поэтому виртуальный вызов будет правильно выполнен, хотя он вроде бы находится в конструкторе (в действительности конструктор письма уже был завершен). Пока виртуальные вызовы в базовом классе просто вызывают идентичные виртуальные функции через указатель на письмо , система будет работать правильно. Чтобы разобраться в происходящем, рассмотрим код main(). При заполнении vector shapes используются вызовы виртуального конструетора Shape. Обычно в подобных ситуациях конструктор вызывается для фактического типа, и в объекте устанавливается VPTR для этого типа. Но здесь во всех случаях будет использоваться VPTR для Shape, а не для специализированных классов Circle, Square и Triangle. В цикле for, где для каждого объекта Shape вызываются функции draw() и erase(), вызов виртуальной функции через VPTR направляется соответствующему типу. Тем не менее, во всех случаях это будет тип Shape. Может возникнуть вопрос: зачем же функции draw() и erase() объявлены виртуальными? Ответ на него проясняется на следующем шаге: версия draw() базового класса через указатель s на письмо вызывает виртуальную функцию draw() для письма . На этот раз вызов направляется фактическому типу объекта, а не базовому классу Shape. Таким образом, за применение виртуальных деструкторов приходится расплачиваться дополнительным уровнем косвенности при каждом вызове виртуальной функции. Чтобы создать любую переопределяемую функцию (такую как draw(), erase() или test()), вы должны перенаправить все вызовы через указатель s в реализации базового класса, как было показано ранее. Дело в том, что вызов функции конвер-
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |