Идиомы и стили С++ | страница 16



", апрель 2000 года.

Желая продолжить изыскания в области ограничения конструирования, зададим вопрос: А можно ли совсем запретить конструирование экземпляров класса, даже для друзей и для статических функций? Ответ: Да. Можно. Нужно сделать как минимум одну функцию чистой виртуальной (pure virtual). Для этого есть специальный синтаксис:

>virtual void f(void)=0;

В этом случае компилятор не может создать для класса виртуальную таблицу, и соответственно не может создать экземпляр.

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

>CClass* cc1 = CClass::factory(void);

>CClass* cc2 = cc1-›factory(void); // Вызов производящей функции


>// Не знаю, откуда мы его берем, но это стековый экземпляр

>CClass cc3;

>CClass* cc4 = cc3.factory(void); // Еще один вызов производящей функции

Тут-то и делается самый прикол. Мы делаем виртуальный конструктор: виртуальную производящую функцию:

>CClass {

>public:

> // Теперь виртуальная, а не статическая.

> virtual CClass* factory (void);

> // Конструктор делаем для простоты открытым,

> // поскольку все-таки нам нужен

> // базовый способ получения экземпляров

> CClass () {}

>};


>CClass* CClass::factory(void) { return new CClass(); }


>// Где-то в коде

>CClass* cc = new CClass();


>// Виртуальное конструирование!!!

>CClass* cc1 = cc-›factory(void);

Думаю, что на этом следует закончить этот шаг. К конструированию объектов мы будем возвращаться еще не раз… но не сегодня.

Примером производящих функций являются макросы DECLARE_SERIAL, IMPLEMENT_SERIAL, DECLARE_DYNCREATE, IMPLEMENT_DYNCREATE в MFC. Они конечно сложнее и делают много чего еще, но в конечном итоге это замазанные макросом производящие функции.

Шаг 12 - Двухэтапная инициализация.

Когда мы создаем нестековый экземпляр, то пишем такой код:

>CClass* cc = new CClass();

Попробуем поразбираться. new - это глобальный оператор с определением:

>void* operator new (size_t bytes);

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

1. Выделение памяти;

2. Конструирование.

Оба действия могут кончиться неудачей. Либо память не выделится, тогда негде будет инициализировать объект, либо память выделится, но инициализация будет неудачной. С 1998 года стандарт