Существует два вида манипуляторов: с аргументами и без аргументов. Манипуляторы без аргументов пишутся просто. Вам требуется только написать функцию, которая принимает в качестве параметра поток, выполнить с ним какие-то действия (установить флаги или изменить установочные параметры) и возвратить его. Сложнее написать манипулятор, который имеет один или несколько аргументов, потому что потребуется создавать дополнительные классы и функции, которые работают «за кулисами». Поскольку манипуляторы без аргументов более простые, начнем с них.
Прочитав рецепт 10.1, вероятно, вы поняли, что существует три формата вывода чисел с плавающей точкой и только два манипулятора для выбора формата. Для используемого по умолчанию формата не предусмотрен манипулятор; вам придется соответствующим образом установить флаг для потока, чтобы вернуться к стандартному формату:
myiostr.setf(0, ios_base::float field);
Но для удобства вы можете добавить свой собственный манипулятор, делающий то же самое. Именно это сделано в примере 10.4. Манипулятор
floatnormal
устанавливает соответствующий флаг потока для вывода чисел с плавающей точкой в стандартном формате.
Компилятор знает, что делать с вашей новой функцией, потому что в стандартной библиотеке определен следующий оператор для
— это указатель функции, которая принимает в аргументе ссылку на
basic_ostream
и возвращает ссылку на
basic_ostream
. Этот оператор просто обеспечивает вызов вашей функции, которая принимает в качестве аргумента текущий поток.
Манипуляторы с аргументами более сложные. Чтобы понять причину этого, рассмотрим работу манипулятора без аргументов. Пусть используется, например, следующий манипулятор
myostream << myManip << "foo";
Вы задаете его без скобок, поэтому его имя в действительности заменяется адресом функции вашего манипулятора. В действительности
operator<<
вызывает функцию манипулятора и передает ей поток, чтобы манипулятор мог выполнить свою работу.
Для сравнения представим, что у вас имеется манипулятор, который принимает числовой аргумент, так что в идеале вы могли бы его использовать следующим образом.
myostream << myFancyManip(17) << "apple";
Как это будет работать? Если вы считаете, что
myFancyManip
является функцией, принимающей целочисленный аргумент, то возникает проблема: как передать поток в функцию без включения его в параметры и явного его использования? Вы могли бы написать так.
Но это выглядит непривлекательно и избыточно. Одним из удобств манипуляторов является то, что их можно просто добавлять в строку с группой операторов
operator<<
, и они хорошо воспринимаются и используются.
Решение состоит в том, чтобы заставить компилятор пойти окольным путем. Вместо того чтобы
operator<<
вызывал функцию вашего манипулятора для потока, вам надо просто создать временный объект, который возвращает нечто такое, что может использовать
operator<<
.
Во-первых, вам необходимо определить временный класс, который делал бы всю работу. Для простоты предположим, что вам требуется написать манипулятор с именем
setWidth
, который делает то же самое, что и
setw
. Временная структура, которую вам необходимо построить, будет выглядеть примерно так.
Этот класс содержит простую функцию. Предусмотрите в ней целочисленный аргумент и, когда
operator
вызывается с аргументом потока, установите ширину для потока в значение, в какое она была установлена при инициализации этого объекта. В результате мы получим
WidthSetter
, сконструированный одной функцией и используемый другой. Ваш манипулятор конструирует эту функцию, и это будет выглядеть следующим образом.
WidthSetter setWidth(int n) {
return(WidthSetter(n)); // Возвращает инициализированный объект
}
Эта функция всего лишь возвращает объект
WidthSetter
, инициализированный целым значением. Этот манипулятор вы будете использовать в строке операторов
operator<<
следующим образом.
myostream << setWidth(20) << "banana";
Но этого недостаточно, потому что
setWidth
просто возвращает объект
WidthSetter
;
operator<<
не будет знать, что с ним делать. Вам придется перегрузить
Это решает проблему, но не в общем виде. Вам не захочется писать класс типа
WidthSetter
для каждого вашего манипулятора, принимающего аргумент (возможно, вы это и делаете, но были бы не против поступить по-другому), поэтому лучше использовать шаблоны и указатели функций для получения привлекательной, обобщенной инфраструктуры, на базе которой вы можете создавать любое количество манипуляторов. Пример 10.5 содержит класс
ManipInfra
и версию
operator<<
, использующую аргументы шаблона для работы с различными типами символов, которые может содержать поток, и с различными типами аргументов, которые могут быть использованы манипулятором потока.
Пример 10.5. Инфраструктура манипуляторов
#include <iostream>
#include <string>
using namespace std;
// ManipInfra - это небольшой промежуточный класс, который помогает
// создавать специальные манипуляторы с аргументами. Вызывайте его
// конструктор с предоставлением указателя функции и значения из основной