Шаг 25 - Как сделать виртуальной свободную функцию

Чаще всего этот прием я видел в отношении оператора operator<<. Точнее, не чаще, а всегда. На нем и разберем. Пусть у нас есть иерархия классов, и мы хотим определить диагностическую функцию Dump(). Она должна вываливать диагностику в заданное что-то (CDestination). У нас есть два варианта: или сделать функцию виртуальной в иерархии классов:

class CBase
{
	virtual void Dump( CDestination& ds ) = 0;
};
class CFirst : public CBase
{
	void Dump (CDestination& ds);
};
class CSecond : public CBase
{
	void Dump (CDestination& ds);
};

Или перегружать ее для каждого класса иерархии или в классе, или в свободной функции:

CDestination
{
	void Dump ( CFirst& fs);
 	void Dump ( CSecond& sc);
};
void Dump (CDestination& ds, CThird& td); 
void Dump (CDestination& ds, CFourth& fr);

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

Но у второго варианта есть одно преимущество: функцию Dump() можно обозвать оператором operator<<, и это будет выглядеть весьма презентабельно:

// Это декларация
CDestination
{
	CDestination& operator<< ( CFirst& fs);
};
CDestination&  operator<< (CDestination& ds, CSecond& sc);

// А это применение
dStream << dObject;

Как сделать так, чтобы сохранить замечательное полиморфное поведение первого варианта, и применить эту радость идиота operator<< ? Легко: пусть operator<< вместо реальной работы просто вызывает виртуальную Dump(). Именно так сделано в MFC - объект afxDump вызывает виртуальную Dump() именно через operator<<. (Можно что угодно говорить про Microsoft, но факт есть факт - огромное число полезных и интересных приемов использовано в их продуктах и "... взять их у нее - наша задача!").

#include <iostream.h>
class CNoChange;
class CBase
{
public:
	virtual void passTo (CNoChange& _cb)
		{cout << "base passed" << endl; };
};
class CFirst: public CBase
{
public:
	void passTo (CNoChange& _cb)
		{ cout << "first passed" << endl; };
};
class CSecond: public CBase
{
public:
	void passTo (CNoChange& _cb)
		{ cout << "second passed" << endl; };
};
class CNoChange
{
public:
int a;
// Это вариант с оператором - членом класса.
	CNoChange& operator<< (CBase& _cb)
		{  _cb.passTo(*this); return *this;};
};
// а это - свободная функция.
//CNoChange& operator<< (CNoChange& _nc, CBase& _cb) 
//	{  _cb.passTo(_nc); return _nc;};
// проверить надо.
int main ()
{
	CNoChange nc;
	CFirst fs;
	CSecond sc;
	nc<<fs;
	nc<<sc;
	return 0;
}

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