如何使用IOStream存储格式设置?

Die*_*ühl 9 c++ iostream istream ostream

为用户定义的类型创建格式化输出时,通常需要定义自定义格式标记.例如,如果自定义字符串类可以选择在字符串周围添加引号,那将是很好的:

String str("example");
std::cout << str << ' ' << squotes << str << << ' ' << dquotes << str << '\n';
Run Code Online (Sandbox Code Playgroud)

应该产生

example 'example' "example"
Run Code Online (Sandbox Code Playgroud)

很容易创建操纵器来更改格式化标志本身:

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();
}
Run Code Online (Sandbox Code Playgroud)

...但是操纵器如何存储哪些引号应该与流一起使用,然后让输出操作符检索值?

Die*_*ühl 12

流类被设计为可扩展的,包括存储附加信息的能力:流对象(实际上是公共基类std::ios_base)提供了一些管理与流相关的数据的函数:

  1. iword()它取一个int关键并产生一个int&开头为0.
  2. pword()它取一个int关键并产生一个void*&开头为0.
  3. xalloc()在每次调用时static产生不同的函数int以"分配"唯一键(它们不能释放它们的键).
  4. register_callback()注册一个在流被销毁时调用的函数copyfmt(),或者一个new std::localeimbue()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();
}
Run Code Online (Sandbox Code Playgroud)

该实现使用该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;
}
Run Code Online (Sandbox Code Playgroud)

要创建带参数的操纵器,会创建一个对象,该对象捕获std::string将用作前缀的对象,并实现"输出操作符"以实际设置a中的前缀pword().由于只能void*存储,因此必须分配内存并维护可能存在的内存:如果已经存储了某些内存,则必须是a std::string并将其更改为新前缀.否则,将注册一个回调函数,用于维护其内容.pword()一旦注册了回调函数,std::string就会分配并存储新的回调函数pword().

棘手的业务是回调:它在三个条件下被调用:

  1. 当流s被销毁或被s.copyfmt(other)调用时,每个已注册的回调都将s作为std::ios_base&参数和事件进行调用std::ios_base::erase_event.这个标志的目标是释放任何资源.为了避免意外的数据双版本中,pword()被设为0std::string即被删除.
  2. s.copyfmt(other)被调用时,回调调用的事件std::ios_base::copyfmt_event 后,所有的回调和内容被复制.然而,pword()遗嘱只包含原始的浅拷贝,即回调需要制作一份深层拷贝pword().由于回调是std::ios_base::erase_event在之前调用的,所以不需要清理任何东西(无论如何都会覆盖它).
  3. s.imbue()调用之后调用回调std::ios_base::imbue_event.此调用的主要用途是更新std::locale可以为流缓存的特定值.对于前缀维护,将忽略这些调用.

上面的代码应该是描述数据如何与流相关联的大纲.该方法允许存储任意数据和多个独立数据项.值得注意的是,xalloc()仅返回一系列唯一整数.如果有用户iword()pword()不使用xalloc(),则索引可能会发生冲突.因此,重要的是使用xalloc()不同的代码很好地一起玩.

是一个实例.