C++ 3d.Комментарии

7b6e9298

Манипуляторы, определяемые пользователем


Коль скоро с эффективностью потоков ввода-вывода мы уже разобрались, следует поговорить об удобстве. К сожалению, для сколько-нибудь сложного форматирования предоставляемые потоками средства не предназначены. Не в том смысле, что средств нет, а в том, что они чрезвычайно неудобны и легко выводят из себя привыкшего к элегантному формату ...printf() программиста. Не верите? Давайте попробуем вывести обыкновенную дату в формате dd.mm.yyyy: int day= 31, mon= 1, year=1974;

printf("%02d.%02d.%d\n", day, mon, year); // 31.01.1974

cout<<setfill('0')<<setw(2)<<day<<'.'<<setw(2)<<mon<<setfill(' ')<<'.' <<year<<"\n"; // тоже 31.01.1974

Думаю, что комментарии излишни.

За что же не любят потоки C и чем потоки C++ могут быть удобнее? У потоков C++ есть только одно существенное достоинство -- типобезопасность. Т.к. потоки C++ все же нужно использовать, я написал специальный манипулятор, который, оставаясь типобезопасным, позволяет использовать формат ...printf(). Он не вызывает существенных накладных расходов и с его помощью приведенный выше пример будет выглядеть следующим образом: cout<<c_form(day,"02")<<'.'<<c_form(mon,"02")<<'.'<<year<<'\n';

Вот исходный код заголовочного файла: #include <ostream>

/** личное пространство имен функции c_form, содержащее детали реализации */ namespace c_form_private {

typedef std::ios_base::fmtflags fmtflags; typedef std::ostream ostream; typedef std::ios_base ios;

/** * Вспомогательный класс для осуществления форматирования. */ class Formatter { /** флаги для установки */ fmtflags newFlags; /** ширина */ int width; /** точность */ int prec; /** символ-заполнитель */ char fill; /** сохраняемые флаги */ fmtflags oldFlags;

public: /** * Создает объект, использующий переданное форматирование. */ Formatter(const char* form, int arg1, int arg2);

/** * Устанавливает новое форматирование для переданного потока, сохраняя * старое. */ void setFormatting(ostream& os);


/** * Восстанавливает первоначальное форматирование, сохраненное в функции * setFormatting(). */ void restoreFormatting(ostream& os); };

/** * Вспомогательный класс. */ template <class T> class Helper { /** выводимое значение */ const T& val; /** объект для форматирования */ mutable Formatter fmtr;

public: /** * Создает объект по переданным параметрам. */ Helper(const T& val_, const char* form, int arg1, int arg2) : val(val_), fmtr(form, arg1, arg2) {}

/** * Функция для вывода в поток сохраненного значения в заданном формате. */ void putTo(ostream& os) const; };



template <class T> void Helper<T>::putTo(ostream& os) const { fmtr.setFormatting(os); os<<val; fmtr.restoreFormatting(os); }

/** * Оператор для вывода объектов Helper в поток. */ template <class T> inline ostream& operator<<(ostream& os, const Helper<T>& h) { h.putTo(os); return os; } }

/** * Функция-манипулятор, возвращающая объект вспомогательного класса, для * которого переопределен оператор вывода в ostream. Переопределенный оператор * вывода осуществляет форматирование при выводе значения. * @param val значение для вывода * @param form формат вывода: [-|0] [число|*] [.(число|*)] [e|f|g|o|x] * @param arg1 необязательный аргумент, задающий ширину или точность. * @param arg2 необязательный аргумент, задающий точность. * @throws std::invalid_argument если передан аргумент form некорректного * формата. */ template <class T> inline c_form_private::Helper<T> c_form(const T& val, const char* form, int arg1=0, int arg2=0) { return c_form_private::Helper<T>(val, form, arg1, arg2); }

и файла-реализации: #include "c_form.hpp" #include <stdexcept> #include <cctype>

namespace {

/** * Вспомогательная функция для чтения десятичного числа. */ int getval(const char*& iptr) { int ret=0; do ret=ret*10 + *iptr-'0'; while (std::isdigit(*++iptr));

return ret; }

}

c_form_private::Formatter::Formatter(const char* form, int arg1, int arg2) : newFlags(fmtflags()), width(0), prec(0), fill(0) { const char* iptr=form; // текущий символ строки формата



if (*iptr=='-') { // выравнивание влево newFlags|=ios::left; iptr++; } else if (*iptr=='0') { // добавляем '0'ли только если !left fill='0'; iptr++; }

if (*iptr=='*') { // читаем ширину, если есть width=arg1; iptr++;

arg1=arg2; // сдвигаем агрументы влево } else if (std::isdigit(*iptr)) width=getval(iptr);

if (*iptr=='.') { // есть точность if (*++iptr=='*') { prec=arg1; iptr++; } else if (std::isdigit(*iptr)) prec=getval(iptr); else throw std::invalid_argument("c_form"); }

switch (*iptr++) { case 0: return; // конец строки формата case 'e': newFlags|=ios::scientific; break; case 'f': newFlags|=ios::fixed; break; case 'g': break; case 'o': newFlags|=ios::oct; break; case 'x': newFlags|=ios::hex; break; default: throw std::invalid_argument("c_form"); }

if (*iptr) throw std::invalid_argument("c_form"); }

void c_form_private::Formatter::setFormatting(ostream& os) { oldFlags=os.flags(); // очищаем floatfield и устанавливаем свои флаги os.flags((oldFlags & ~ios::floatfield) | newFlags);

if (width) os.width(width); if (fill) fill=os.fill(fill); if (prec) prec=os.precision(prec); }

void c_form_private::Formatter::restoreFormatting(ostream& os) { os.flags(oldFlags);

if (fill) os.fill(fill); if (prec) os.precision(prec); }

Принцип его работы основан на следующей идее: функция c_form<>() возвращает объект класса c_form_private::Helper<>, для которого определена операция вывода в ostream.

Для удобства использования, c_form<>() является функцией, т.к. если бы мы сразу использовали конструктор некоторого класса-шаблона c_form<>, то нам пришлось бы явно задавать его параметры: cout<<c_form<int>(day,"02");

что, мягко говоря, неудобно. Далее. Мы, в принципе, могли бы не использовать нешаблонный класс Formatter, а поместить весь код прямо в Helper<>, но это привело бы к совершенно ненужной повторной генерации общего (не зависящего от параметров шаблона) кода.

Как можно видеть, реализацию манипулятора c_form вряд ли можно назвать тривиальной. Тем не менее, изучить ее стоит хотя бы из тех соображений, что в процессе разработки было использовано (неожиданно) большое количество полезных приемов программирования.


Содержание раздела