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 вряд ли можно назвать тривиальной. Тем не менее, изучить ее стоит хотя бы из тех соображений, что в процессе разработки было использовано (неожиданно) большое количество полезных приемов программирования.




"Programming Optimization". Не очень глубокий, а местами и откровенно "слабый", но, тем не менее, практически полезный обзор более высокоуровневых техник представлен в книге Steve Heller "Optimizing C++".





Макросы



В C++ макросы не нужны! До боли знакомое высказывание, не так ли? Я бы его немного уточнил: не нужны, если вы не хотите существенно облегчить себе жизнь.


Я полностью согласен с тем, что чрезмерное и необдуманное использование макросов может вызвать большие неприятности, особенно при повторном использовании кода. Вместе с тем, я не знаю ни одного средства C++, которое могло бы принести пользу при чрезмерном и необдуманном его использовании.


Итак, когда макросы могут принести пользу?



  1. Макрос как надъязыковое средство. Хороший примером является простой, но удивительно полезный отладочный макрос _VAL_, выводящий имя и значение переменной:
    #define _VAL_(var) #var "=" << var << " "

    Надъязыковой частью здесь является работа с переменной как с текстом, путем перевода имени переменной (оно существует только в исходном коде программы) в строковый литерал, реально существующий в коде бинарном. Данную возможность могут предоставить только макросы.



  2. Информация о текущем исходном файле и строке -- ее пользу при отладке трудно переоценить. Для этого я использую специальный макрос _ADD_. Например:
    	cout<<_ADD_("Ошибка чтения");

    выведет что-то вроде
    Ошибка чтения <file.cpp:34>

    А если нужен перевод строки, то стоит попробовать
    	cout<<"Ошибка чтения" _ADD_("") "\n";

    Такой метод работает, потому что макрос _ADD_ возвращает строковый литерал. Вроде бы эквивалентная функция
    	char* _ADD_(char*);

    вполне подошла бы для первого примера, но не для второго. Конечно, для вывода в cout это не имеет никакого значения, но в следующем пункте я покажу принципиальную важность подобного поведения.