Шаг 11 - Нетривиальное конструирование объектов

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

CClass
{
	protected:
	CClass (){};
};

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

CClass
{
	private:
	CClass (){};
};

Но как же тогда их вообще создавать, если их конструкторы недоступны? Да ясно как, ведь сам вопрос неверен: конструкторы не недоступны, они доступны, да только не для всех. Мы же как-то уже замечали, что класс по определению имеет несколько интерфейсов для разных клиентов, и помним, что самый полный, самый неограниченный интерфейс класс имеет для себя и для своих друзей. Следовательно, производящая функция-член класса или дружественная функция может свободно штамповать экземпляры класса и размещать где угодно, кроме стека; функция-член класса должна быть кроме того статической (то есть независимой от экземпляров), иначе в ней нет смысла.

// Вариант 1: производящая функция-член.
CClass
{
public:
	static CClass* factory (void);
private:
	CClass (){};
};

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

// Где-то в коде
CClass* cc = CClass::factory(void);

// Вариант 2. Дружественная функция.
CClass
{
	friend CClass* factory (void);
	private:
	CClass (){};
};

// Дружественная Функция, создающая экземпляры класса.
CClass* factory (void)
{
	return new CClass;
}
// Где-то в коде
CClass* cc = factory(void);

Вы видите, что разницы между двумя вариантами практически нет? Единственно, что дружественная функция лежит вне области видимости класса. Но она фактически является элементом его интерфейса! Именно это наблюдение позволило Мейерсу сделать несколько неожиданный вывод: дружественные функции могут улучшать инкапсуляцию класса! Не знаю, как для Вас, но мне пришлось прочитать его статью дважды, а потом еще найти перевод на русский язык, потому как сразу это не в голове не уложилось. Подробности читайте в "С++ Journal", апрель 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. Они конечно сложнее и делают много чего еще, но в конечном итоге это замазанные макросом производящие функции.


Предыдущий Шаг | Следующий Шаг | Оглавление
Автор Albert Makhmutov - 13.04.2001