Чаще всего этот прием я видел в отношении оператора 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; }