C++. Сборник рецептов.
10.3. Написание своих собственных манипуляторов потока.
Проблема.
Требуется иметь манипулятор потока, который делает что-нибудь такое, что не могут делать стандартные манипуляторы. Или вам нужен такой один манипулятор, который устанавливает несколько флагов потока, и вам не приходится вызывать несколько манипуляторов всякий раз, когда необходимо установить конкретный формат вывода.
Решение.
Чтобы создать манипулятор, который не имеет аргументов (типа left), напишите функцию, которая принимает в качестве параметра ios_base и устанавливает для него флаги потока. Если вам нужен манипулятор с аргументом, см. приводимое ниже обсуждение. Пример 10.4 показывает возможный вид манипулятора без аргументов.
Пример 10.4. Простой манипулятор потока.
#include <iostream>
#include <iomanip>
#include <string>
Using namespace std;
// вывести числа с плавающей точкой в обычном виде.
Inline ios_base& floatnormal(ios_base& io) {
io.setf(0, ios_base::floatfield);
return(io);
}
Int main() {
ios_base::fmtflags flags = // Сохранить старые флаги.
cout.flags();
double pi = 22.0/7.0;
cout << pi = " << scientific // Научный режим.
<< pi * 1000 << '\n';
cout << "pi = " << floatnormal << pi << '\n';
cout.flags(flags);
}
Обсуждение.
Существует два вида манипуляторов: с аргументами и без аргументов. Манипуляторы без аргументов пишутся просто. Вам требуется только написать функцию, которая принимает в качестве параметра поток, выполнить с ним какие-то действия (установить флаги или изменить установочные параметры) и возвратить его. Сложнее написать манипулятор, который имеет один или несколько аргументов, потому что потребуется создавать дополнительные классы и функции, которые работают «за кулисами». Поскольку манипуляторы без аргументов более простые, начнем с них.
Прочитав рецепт 10.1, вероятно, вы поняли, что существует три формата вывода чисел с плавающей точкой и только два манипулятора для выбора формата. Для используемого по умолчанию формата не предусмотрен манипулятор; вам придется соответствующим образом установить флаг для потока, чтобы вернуться к стандартному формату:
Myiostr.setf(0, ios_base::float field);
Но для удобства вы можете добавить свой собственный манипулятор, делающий то же самое. Именно это сделано в примере 10.4. Манипулятор floatnormal устанавливает соответствующий флаг потока для вывода чисел с плавающей точкой в стандартном формате.
Компилятор знает, что делать с вашей новой функцией, потому что в стандартной библиотеке определен следующий оператор для basic_ostream ( basic_ostream — имя шаблона класса, инстанцируемого в классах ostream и wostream).
Basic_ostream<charT, traits>& operator<<
(basic_ostream<charT, traits>& (* pf)basic_ostream<charT, traits>&)).
Здесь pf — это указатель функции, которая принимает в аргументе ссылку на basic_ostream и возвращает ссылку на basic_ostream. Этот оператор просто обеспечивает вызов вашей функции, которая принимает в качестве аргумента текущий поток.
Манипуляторы с аргументами более сложные. Чтобы понять причину этого, рассмотрим работу манипулятора без аргументов. Пусть используется, например, следующий манипулятор.
Myostream << myManip << "foo";
Вы задаете его без скобок, поэтому его имя в действительности заменяется адресом функции вашего манипулятора. В действительности operator<< вызывает функцию манипулятора и передает ей поток, чтобы манипулятор мог выполнить свою работу.
Для сравнения представим, что у вас имеется манипулятор, который принимает числовой аргумент, так что в идеале вы могли бы его использовать следующим образом.
Myostream << myFancyManip(17) << "apple";
Как это будет работать? Если вы считаете, что myFancyManip является функцией, принимающей целочисленный аргумент, то возникает проблема: как передать поток в функцию без включения его в параметры и явного его использования? Вы могли бы написать так.
Myostream << myFancyManip(17, myostream) << "apple";
Но это выглядит непривлекательно и избыточно. Одним из удобств манипуляторов является то, что их можно просто добавлять в строку с группой операторов operator<<, и они хорошо воспринимаются и используются.
Решение состоит в том, чтобы заставить компилятор пойти окольным путем. Вместо того чтобы operator<< вызывал функцию вашего манипулятора для потока, вам надо просто создать временный объект, который возвращает нечто такое, что может использовать operator<<.
Во-первых, вам необходимо определить временный класс, который делал бы всю работу. Для простоты предположим, что вам требуется написать манипулятор с именем setWidth, который делает то же самое, что и setw. Временная структура, которую вам необходимо построить, будет выглядеть примерно так.
Class WidthSetter {
Public:
WidthSetter(int n) : width_(n) {}.
void operator()(ostream& os) const {os.width(width_);}.
Private:
int width_;
};
Этот класс содержит простую функцию. Предусмотрите в ней целочисленный аргумент и, когда operator() вызывается с аргументом потока, установите ширину для потока в значение, в какое она была установлена при инициализации этого объекта. В результате мы получим WidthSetter, сконструированный одной функцией и используемый другой. Ваш манипулятор конструирует эту функцию, и это будет выглядеть следующим образом.
WidthSetter setWidth(int n) {
return(WidthSetter(n)); // Возвращает инициализированный объект.
}
Эта функция всего лишь возвращает объект WidthSetter, инициализированный целым значением. Этот манипулятор вы будете использовать в строке операторов operator<< следующим образом.
Myostream << setWidth(20) << "banana";
Но этого недостаточно, потому что setWidth просто возвращает объект WidthSetter; operator<< не будет знать, что с ним делать. Вам придется перегрузить operator<<, чтобы он знал, как управлять объектом WidthSetter:
Ostream& operator<<(ostream& os, const WidthSetter& ws) {
ws(os); // Передать поток объекту ws.
return(os); // для выполнения реальной работы.
}
Это решает проблему, но не в общем виде. Вам не захочется писать класс типа WidthSetter для каждого вашего манипулятора, принимающего аргумент (возможно, вы это и делаете, но были бы не против поступить по-другому), поэтому лучше использовать шаблоны и указатели функций для получения привлекательной, обобщенной инфраструктуры, на базе которой вы можете создавать любое количество манипуляторов. Пример 10.5 содержит класс ManipInfra и версию operator<<, использующую аргументы шаблона для работы с различными типами символов, которые может содержать поток, и с различными типами аргументов, которые могут быть использованы манипулятором потока.
Пример 10.5. Инфраструктура манипуляторов.
#include <iostream>
#include <string>
Using namespace std;
// ManipInfra - это небольшой промежуточный класс, который помогает.
// создавать специальные манипуляторы с аргументами. Вызывайте его.
// конструктор с предоставлением указателя функции и значения из основной.
// функции манипулятора.
// Указатель функции должен ссылаться на вспомогательную функцию, которая.
// делает реальную работу. См. примеры ниже.
Template<typename T, typename C>
Class ManipInfra {
Public:
ManipInfra(basic_ostream<C>& (*pFun) (basic_ostream<C>&, T), T val) :
manipFun_(pFun), val_(val) {}.
void operator()(basic_ostream<C>& os) const {
manipFun_(os, val_);
} // Вызовите функцию, задавая ее указатель и.
Private: // передавая ей поток и значение.
T val_;
basic_оstrеаm<С>& (*manipFun_) (basic_ostream<C>&, T);
};
Template<typename T, typename C>
Basic_ostream<C>& operator<<(basic_ostream<C>& os,
const ManipInfra<T, C>& manip) {
manip(os);
return(os);
}
// Вспомогательная функция, которая вызывается в итоге в классе ManipInfra.
Ostream& setTheWidth(ostream& os, int n) {
os.width(n);
return(os);
}
// Собственно функция манипулятора. Именно она используется в клиентском.
// программном коде.
ManipInfra<int, char> setWidth(int n) {
return(ManipInfra<int, char>(setTheWidth, n));
}
// Ещё одна вспомогательная функция, которая принимает аргумент типа char.
Ostream& setTheFillChar(ostream& os, char с) {
os.fill(c);
return(os);
}
ManipInfra<char, char> setFill(char c) {
return(ManipInfra<char, char>(setTheFillChar, c));
}
Int main() {
cout << setFill('-').
<< setWidth(10) << right << "Proust\n";
}
Если последовательность событий при работе этого класса все же остается неясной, я советую прогнать пример 10.5 через отладчик. Увидев его реальную работу, вы все поймете.
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
- 58.
- 59.
- 60.
- 61.
- 62.
- 63.
- 64.
- 65.
- 66.
- 67.
- 68.
- 69.
- 70.
- 71.
- 72.
- 73.
- 74.
- 75.
- 76.
- 77.
- 78.
- 79.
- 80.
- 81.
- 82.
- 83.
- 84.
- 85.
- 86.
- 87.
- 88.
- 89.
- 90.
- 91.
- 92.
- 93.
- 94.
- 95.
- 96.
- 97.
- 98.
- 99.
- 100.
- 101.
- 102.
- 103.
- 104.
- 105.
- 106.
- 107.
- 108.
- 109.
- 110.
- 111.
- 112.
- 113.
- 114.
- 115.
- 116.
- 117.
- 118.
- 119.
- 120.
- 121.
- 122.
- 123.
- 124.
- 125.
- 126.
- 127.
- 128.
- 129.
- 130.
- 131.
- 132.
- 133.
- 134.
- 135.
- 136.
- 137.
- 138.
- 139.
- 140.
- 141.
- 142.
- 143.
- 144.
- 145.
- 146.
- 147.
- 148.
- 149.
- 150.
- 151.
- 152.
- 153.
- 154.
- 155.
- 156.
- 157.
- 158.
- 159.
- 160.
- 161.
- 162.
- 163.
- 164.
- 165.
- 166.
- 167.
- 168.
- 169.
- 170.
- 171.
- 172.
- 173.
- 174.
- 175.
- 176.
- 177.
- 178.
- 179.
- 180.
- 181.
- 182.
- 183.
- 184.
- 185.
- 186.
- 187.
- 188.
- 189.
- 190.
- 191.
- 192.
- 193.
- 194.
- 195.
- 196.
- 197.
- 198.
- 199.
- 200.
- 201.
- 202.
- 203.
- 204.