流类被设计为可扩展的,包括存储附加信息的能力:流对象(实际上是共同的基类std::ios_base)提供了一些管理与流相关数据的函数:
1. `iword()`接受一个整数作为键并产生一个`int&`,初始值为`0`。
2. `pword()`接受一个整数作为键并产生一个`void*&`,初始值为`0`。
3. `xalloc()` 是一个静态函数,每次调用都会产生一个不同的`int`以“分配”唯一的键(这些键无法释放)。
4. `register_callback()` 用于注册一个函数,当流被销毁时调用该函数,在调用`copyfmt()`或新的`std::locale`进行`imbue()`时也会调用。
对于像`String`示例中存储简单格式信息的情况,只需分配一个`int`并在其中存储适当的值即可。
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:
delete static_cast<std::string*>(s.pword(idx));
s.pword(idx) = 0;
break;
case std::ios_base::copyfmt_event:
s.pword(idx) = new std::string(*static_cast<std::string*>(s.pword(idx)));
break;
default:
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()
中。
关键是回调函数:它在三种情况下被调用:
1. 当流
s
被销毁或调用
s.copyfmt(other)
时,每个注册的回调函数都会被调用,
s
作为
std::ios_base&
参数,事件为
std::ios_base::erase_event
。使用此标志的目的是释放任何资源。为避免数据的意外重复释放,在删除
std::string
后将
pword()
设置为
0
。
2. 当调用
s.copyfmt(other)
时,使用事件
std::ios_base::copyfmt_event
调用回调函数,在所有回调函数和内容被复制之后。
pword()
将只包含原始数据的浅拷贝,因此回调函数需要进行深度复制。由于在之前使用了
std::ios_base::erase_event
调用了回调函数,因此不需要清理任何内容(此时它将被覆盖)。
3. 在调用
s.imbue()
后,使用
std::ios_base::imbue_event
调用回调函数。这个调用的主要用途是更新可能为流缓存的
std::locale
特定值。对于前缀维护,将忽略这些调用。
上述代码应该是描述如何将数据与流相关联的概要。此方法允许存储任意数据和多个独立数据项。值得注意的是,
xalloc()
仅返回唯一整数序列。如果使用
iword()
或
pword()
的用户没有使用
xalloc()
,则索引可能冲突。因此,重要的是使用
xalloc()
使不同的代码可以很好地协作。
这里 有一个实时示例。