Классы потоков были разработаны с возможностью расширения, включая возможность хранения дополнительной информации: объекты потока (на самом деле общий базовый класс std::ios_base
) предоставляют пару функций, управляющих данными, связанными с потоком:
iword()
, который принимает int
в качестве ключа и дает int&
, который начинается как 0
.
pword()
, который принимает int
в качестве ключа и дает void*&
, который начинается как 0
.
xalloc()
функция static
, которая выдает разные int
при каждом вызове для «выделения» уникального ключа (эти ключи не могут быть освобождены).
register_callback()
для регистрации функции, которая вызывается при уничтожении потока, вызывается copyfmt()
или новым std::locale
является imbue()
d.
Для хранения простой информации о форматировании, как в примере String
, достаточно выделить int
и сохранить подходящее значение в iword()
:
int stringFormatIndex() {
static int rc = std::ios_base::xalloc();
return rc;
}
std::ostream& squote(std::ostream& out) {
out.iword(stringFormatIndex()) = '\'';
return out;
}
std::ostream& dquote(std::ostream& out) {
out.iword(stringFormatIndex()) = '"';
return out;
}
std::ostream& operator<< (std::ostream& out, String const& str) {
char quote(out.iword(stringFormatIndex()));
return quote? out << quote << str.c_str() << quote: out << str.c_str();
}
Реализация использует функцию stringFormatIndex()
, чтобы убедиться, что выделяется ровно один индекс, поскольку rc
инициализируется при первом вызове функции. Поскольку iword()
возвращает 0
, когда значение еще не задано для потока, это значение используется для форматирования по умолчанию (в данном случае без использования кавычек). Если необходимо использовать котировку, значение котировки char
просто сохраняется в файле iword()
.
Использование iword()
довольно прямолинейно, потому что нет необходимости в управлении ресурсами. Для примера предположим, что String
также должно быть напечатано со строковым префиксом: длина префикса не должна быть ограничена, т. е. он не будет помещаться в int
. Установка префикса уже немного сложнее, так как соответствующий манипулятор должен быть типом класса:
class prefix {
std::string value;
public:
prefix(std::string value): value(value) {}
std::string const& str() const { return this->value; }
static void callback(std::ios_base::event ev, std::ios_base& s, int idx) {
switch (ev) {
case std::ios_base::erase_event: // clean up
delete static_cast<std::string*>(s.pword(idx));
s.pword(idx) = 0;
break;
case std::ios_base::copyfmt_event: // turn shallow copy into a deep copy!
s.pword(idx) = new std::string(*static_cast<std::string*>(s.pword(idx)));
break;
default: // there is nothing to do on imbue_event
break;
}
}
};
std::ostream& operator<< (std::ostream& out, prefix const& p) {
void*& pword(out.pword(stringFormatIndex()));
if (pword) {
*static_cast<std::string*>(pword) = p.str();
}
else {
out.register_callback(&prefix::callback, stringFormatIndex());
pword = new std::string(p.str());
}
return out;
}
Чтобы создать манипулятор с аргументом, создается объект, который захватывает std::string
, который будет использоваться в качестве префикса, и реализуется «оператор вывода», чтобы фактически установить префикс в pword()
. Поскольку может храниться только void*
, необходимо выделить память и сохранить потенциально существующую память: если что-то уже сохранено, это должно быть std::string
, и он заменяется новым префиксом. В противном случае регистрируется обратный вызов, который используется для поддержки содержимого pword()
, и после регистрации обратного вызова новый std::string
выделяется и сохраняется в pword()
.
Хитрое дело — обратный вызов: он вызывается при трех условиях:
- Когда поток
s
уничтожается или вызывается s.copyfmt(other)
, каждый зарегистрированный обратный вызов вызывается с s
в качестве аргумента std::ios_base&
и с событием std::ios_base::erase_event
. Целью с этим флагом является освобождение любых ресурсов. Чтобы избежать случайного двойного сброса данных, pword()
устанавливается на 0
после удаления std::string
.
- При вызове
s.copyfmt(other)
обратные вызовы вызываются с событием std::ios_base::copyfmt_event
после копирования всех обратных вызовов и содержимого. Однако pword()
будет содержать только поверхностную копию оригинала, т. е. обратный вызов должен сделать глубокую копию pword()
. Поскольку обратный вызов был вызван с std::ios_base::erase_event
до того, как нет необходимости что-либо очищать (в любом случае он будет перезаписан в этот момент).
- После вызова
s.imbue()
обратные вызовы вызываются с std::ios_base::imbue_event
. Основное использование этого вызова — обновление определенных значений std::locale
, которые могут кэшироваться для потока. При обслуживании префикса эти вызовы будут игнорироваться.
Приведенный выше код должен быть схемой, описывающей, как данные могут быть связаны с потоком. Подход позволяет хранить произвольные данные и несколько независимых элементов данных. Стоит отметить, что xalloc()
просто возвращает последовательность уникальных целых чисел. Если есть пользователь iword()
или pword()
, который не использует xalloc()
, есть шанс, что индексы столкнутся. Таким образом, важно использовать xalloc()
, чтобы разные коды хорошо сочетались друг с другом.
Вот живой пример.
person
Dietmar Kühl
schedule
26.12.2013