Программирование >>  Синтаксис инициирования исключений 

1 ... 50 51 52 [ 53 ] 54 55 56 ... 82


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

class Number { public:

static Number* make(); Конструирует число по умолчанию static Number* make(int); static Number* make(double);

static Number* make(string); Например, -1.23

В файле .cpp

class Integer : public Number { ... }; class Real : public Number { ... };

class ArbitraryPrecisionNumber : public Number { ... }; Number* Number::make()

return new Integer(0);

Number* Number::make(int x)

return new Integer(x);

Number* Number::make(doub1e x)

return new Rea1(x);

Number* Number::make(string s)

Псевдокод

if ( малое целое)

return new Integer(atoi(s)); if ( большое целое)

return new ArbitraryPrecisionNumber(s); if ( вещественное)

И т.д.

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

Локализованное использование производящих функций

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

class Window { public:



static Window* make();

Далее следует гомоморфный интерфейс

В window.cpp для ОС Windows

class MS Window : public Window { ... };

Window* Window::make()

return MS Window();

или в window.cpp для Mac OS

class Mac Window : public Window { ... };

Window* Window::make()

return Mac Window*();

Чтобы переключиться с одной платформы на другую, достаточно перекомпилировать и перекомпоновать файл .cpp. Все клиенты класса Window ничего не будут знать о произошедшей локализации (предполагается, что вы собираетесь создать действительно универсальное, гомоморфное представление окна в графическом интерфейсе - задача, перед которой дрогнет даже самый отчаянный проектировщик).

Уничтожающие функции

Уничтожающие функции (junkyard functions) уничтожают экземпляры классов. Идея заключается в том, чтобы сделать деструктор закрытым или защищенным, а затем предоставить функцию, в которой инкапсулируется вызов оператора delete.

class Grandpa { protected:

virtual ~Grandpa(); public:

static Grandpa make();

static void destroy(Grandpa*);

В файле grandpa.cpp

void Grandpa::destroy(Grandpa* g)

delete g;

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

Снова о двойной передаче: промежуточные базовые классы

Приняв на вооружение производящие функции, мы легко повысим инкапсуляцию двойной передачи. В файле grandpa.h

class Grandpa { public:

Производящие функции и гомоморфный интерфейс



В файле grandpa.cpp

class RealGrandpa : public Grandpa {

Промежуточный гомоморфный базовый класс

protected:

Функции двойной передачи

class Dad : public RealGrandpa { ... }; class AuntieEm : public RealGrandpa { ... };

Наличие производящих функций означает, что производные классы можно скрыть. Добавляя промежуточный базовый класс RealGrandpa, мы полностью прячем все жуткие подробности двойной передачи в файле .cpp. Никаких защищенных функций в файле .h!

Нет - конструкторам копий и оператору =!

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

Раз уж мы заговорили на эту тему, после непродолжительных размышлений становится ясно, что клиент гомоморфного базового класса не должен использовать конструктор копий или оператор =. Если кто-нибудь захочет подублировать экземпляр, создайте специальную версию make-функции для копирования this.

class Grandpa { public:

virtual Grandpa* makeClone() = 0;

Эта функция не объявляется статической, поскольку в каждом производном классе она должна решать специализированную задачу. С присвоением дело обстоит сложнее. Если переопределить оператор = для левого операнда, непонятно, что же тогда делать с правым операндом, тип которого неизвестен. Первое практическое решение - полностью запретить присваивание в таких ситуациях и сделать оператор = закрытым. Второе - использовать вариацию на тему двойной передачи: сделать оператор = виртуальным и в каждом производном классе вызывать виртуальную функцию AssignTo(), перегружаемую для кажого производного класса. Смотрится уродливо, но работает.

Объекты классов

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

class GrandpaClass { Объект класса для Grandpa public:

Grandpa* make(); Создает экземпляры Grandpa

class Grandpa { ... };

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



1 ... 50 51 52 [ 53 ] 54 55 56 ... 82

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