Шаг 509.
Библиотека STL. Ввод-вывод с использованием потоковых классов. Операторы ввода-вывода для пользовательских типов. Пользовательские форматные флаги

    На этом шаге мы рассмотрим задание и использование пользовательских форматных флагов.

    При определении пользовательских операторов ввода-вывода часто бывает удобно определить для этих операторов специальные форматные флаги (вероятно, устанавливаемые при помощи соответствующих манипуляторов). Например, было бы неплохо, если бы на предыдущих шагах оператор вывода дробей можно было настроить иа отделение пробелами числителя и знаменателя от символа /.

    Объекты потоков данных предоставляют такую возможность - в них предусмотрен механизм связывания данных с потоком. Он позволяет задать нужные значения (например, при помощи манипулятора) и прочитать их позднее. В классе ios_base определены две функции iword() и pword(), которые при вызове получают индекс типа int и возвращают по нему соответствующее значение long& и vold*&. Предполагается, что iword() и pword() обращаются к объектам long или void* в массиве произвольного размера, хранящемся в объекте потока данных. Форматные флаги, сохраняемые для потока, располагаются по одному и тому же индексу для всех потоков. Статическая функция xalloc() класса ios_base используется для получения индекса, который еще не применялся для этой цели.

    В исходном состоянии объекты, доступ к которым осуществляется функциями iword() и pword(), равны 0. Это значение может интерпретироваться и как представление форматирования по умолчанию, и как признак того, что данные еще не инициализировались. Пример:

// Получение индекса для новых данных ostream
static const int iword_index = std::ios_base::xalloc();

// Определение манипулятора для модификации этих данных
std::ostream& fraction_spaces (std::ostream& strm)
{
    strm.iword(iword_index) = true;
    return strm;
}

std::ostream& operator<< (std::ostream& strm, const Fraction& f)
{
    // Запросить данные у ostream
    // - true: использовать пробелы между числителем и знаменателем
    // - false: выводить без пробелов

    if (stem.iword(iword_index)) {
        strm << f.numerator() << " / " << f.denominator();
    }
    else {
        strm << f.numerator() << "/" << f.denominator();
    }
    return strm;
}

    В этом примере используется упрощенный подход к реализации оператора вывода, поскольку его основная цель - демонстрация функции iword(). Форматный флаг считается логическим признаком, определяющим необходимость вывода пробелов между числителем и знаменателем.

    В первой строке функция ios_base::xalloc() возвращает индекс, который может использоваться для хранения форматного флага. Результат вызова сохраняется в константе, так как это значение не модифицируется. Функция fraction_spaces() представляет собой манипулятор для установки значения int, хранящегося по индексу iword_index в целочисленном массиве, связанном с потоком данных strm. Оператор вывода получает это значение и выводит дробь в соответствии с состоянием флага. Если флаг равен false, по умолчанию дробь выводится без пробелов, а если нет - символ / окружается двумя пробелами.

    Функции iword() и pword() возвращают ссылки на int или void*. Эти ссылки остаются действительными только до следующего вызова iword() или pword() с соответствующим объектом потока данных нли до уничтожения объекта потока. Обычно результаты iword() и pword() сохраняться не должны. Предполагается, что доступ происходит достаточно быстро, хотя представление данных в виде массива не гарантировано.

    Функция copyfmt() копирует всю форматирующую информацию (смотри 485 шаг), в том числе и массивы, с которыми работают функции iword() и pword(). Это может вызвать проблемы для объектов, сохраняемых в контексте потока данных при помощи pword(). Например, если значение представляет собой адрес объекта, то вместо объекта будет скопирован только адрес. Как следствие, смена формата в одном потоке данных будет распространяться на другие потоки. Также может быть желательно, чтобы объект, ассоциированный с потоком данных функцией pword(), уничтожался при уничтожении потока. Следовательно, для таких объектов желательно реализовать "глубокое" копирование вместо "поверхностного".

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

namespace std {
  class ios_base {
    public:
      // Разновидности событий обратного вызова
      enum event { erase_event, imbue_event, copyfmt_event };
      // Тип функции обратного вызова
      typedef void (*event_callback) (event e, ios_base& strm,
                                      int arg);
      // Регистрация функций обратного вызова
      void register_callback (event_callback cb, int arg);
      ...
    };
}

    Функция reglster_callback() получает два аргумента: указатель на функцию и число int. Аргумент int передается в третьем аргументе при вызове зарегистрированной функции. Например, он может использоваться для идентификации индекса pword(), то есть определять обрабатываемый элемент массива. Аргумент strm, передаваемый функции обратного вызова, содержит объект ios_base, обратившийся к этой функции. Аргумент е определяет причину вызова, его допустимые значения перечислены в таблице 1.

Таблица 1. Причины обратного вызова
Событие Причина
ios_base::imbue_event Задание локального контекста функцией imbue()
ios_base::erase_event Уничтожение потока или использование copyfmt()
ios_base::copy_event Использование функции copyfmt()

    При вызове для объекта функции copyfmt() функции обратного вызова вызываются дважды. Еще до начала копирования они вызываются с аргументом erase_event для выполнения необходимой зачистки (например, удаления объектов, хранящихся в массиве pword()). После копирования данных форматирования функции обратного вызова вызываются снова, на этот раз - с аргументом сору_event. Например, этот проход может использоваться для организации глубокого копирования объектов, хранящихся в массиве pword(). Обратите внимание: вместе с данными форматирования копируются и функции обратного вызова, а исходный список зарегистрированных функций удаляется. Следовательно, при втором проходе будут вызваны только что скопированные функции.

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

    На следующем шаге мы рассмотрим правила построения пользовательских итераторов ввода-вывода.




Предыдущий шаг Содержание Следующий шаг