为用户定义的类型创建格式化输出时,通常需要定义自定义格式标记。例如,如果自定义字符串类可以选择在字符串周围添加引号,那将是很好的:
String str("example");
std::cout << str << ' ' << squotes << str << << ' ' << dquotes << str << '\n';
应该产生
example 'example' "example"
创建操纵器以更改格式标志本身很容易:
std::ostream& squotes(std::ostream& out) {
// what magic goes here?
return out;
}
std::ostream& dquotes(std::ostream& out) {
// similar magic as above
return out;
}
std::ostream& operator<< (std::ostream& out, String const& str) {
char quote = ????;
return quote? out << quote << str.c_str() << quote: str.c_str();
}
...但是操纵器如何存储哪些引号应该与流一起使用,然后让输出操作符检索值?
答案 0 :(得分:12)
流类设计为可扩展的,包括存储附加信息的能力:流对象(实际上是公共基类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()
使不同的代码很好地协同工作非常重要。
Here是一个实例。