А теперь вспомним наш пример со сложением денег и выводом результатов на экран. Несмотря на то, что дружественные функции не могут быть виртуальными, мы все-таки можем обеспечить различное поведение операции вывода operator<< для разной валюты с помощью механизма виртуальных функций. Следующий прием является уже практически стандартным: в базовом классе объявляется чистая виртуальная функция-метод, а в производных классах реализуется ее определение. Внешней независимой функции вывода передается параметр базового класса, для которого и вызывается базовая виртуальная функция. Простейшая работающая схема приведена в листинге 9.12.
Листинг 9.12. «Виртуализация» внешних функций
class TControl { public:
virtual ostream& print (ostream &t) const * 0:
}:
class TButton: public TControl { public:
virtual ostream& print (ostream &t) const { return (t<<"TButton!"); }
}:
class TListBox: public TControl {.public:
virtual ostream& print (ostream &t) const { return (t<<"TListBox!"); }
};
inline // для снижения накладных расходов на виртуализацию
ostream& operator<<(ostream &t, const TControl &r) { return r.print(t): }
Обратите внимание: внешняя функция — единственная, и она даже не является дружественной! Опять задействован принцип подстановки: в определении внешней функции-операции второй параметр — ссылка на базовый тип, а вызывается операция с параметрами производных типов. Поэтому следующий фрагмент работает совершенно правильно:
TButton b: cout << b << endl:
TListBox t: cout << t << endl:
Вывод на экран выглядит так:
TButton! TListBox!
Показанный прием в сочетании со статическим вызовом виртуальной функции базового класса и позволяет нам виртуализировать вывод в наших денежных классах TCurrency, Roubles и Bucks. Однако мы не можем в классе TCurrency объявить чистую виртуальную функцию — это сделает класс абстрактным, и возникнут ошибки, связанные с объявлением объектов в методах (например, в методах инкремента). Чтобы сделать класс TCurrency абстрактным, необходима существенная переработка методов — мы этого делать не будем.
Определим в базовом классе обычную (не чистую) виртуальную функцию р г i n t (), которая будет выполнять общую для любой валюты работу — преобразование суммы в строку. Для этого добавим в раздел protected базового класса строковое поле. Наследники вызывают базовую функцию статически (листинг 9.13).
Листинг 9.13. «Виртуализация» функции вывода для валют
class TCurrency { protected:
string toStringO const:
string s:
public:
// возвращаем ссылку на поток
virtual ostream& print(ostream &t) { s = this->toString(): return t:
}
//...
};
// Рубли
// конструктор
// вызов базового конструктора
class Roubles: public TCurrency { public:
Roubles(const long double &r=0.0)
:TCurrency(r)
{ >:
// вызов базовой функции
virtual ostream& print(ostream &t) { TCurrency::print(t): return (t << s << "p."):
}
>:
// Доллары
class Bucks: public TCurrency { public:
Bucks(const long double &r=0.0):TCurrency(r) // конструктор { };
virtual ostream& print(ostream &t)
{ TCurrency::print(t): // вызов базовой функции
return (t <<'$'<< s);
}
};
// внешняя функция вывода inline
ostream& operator<<(ostream& t, TCurrency &r)
{ return r.print(t): // виртуальный вызов
}
int main()
{ Roubles t(15):
++t; t++: cout << t << endl: // виртуальный вызов
Bucks b(l0):
++b: b++: cout << b << endl: // виртуальный вызов
return 0:
}
Вывод выполняется совершенно правильно. |