【问题标题】:How to store formatting settings with an IOStream?如何使用 IOStream 存储格式设置?
【发布时间】:2014-01-14 12:55:23
【问题描述】:

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

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();
}

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

【问题讨论】:

  • 当我第一次读到这个问题时,我很困惑,因为我知道你已经知道答案了。大声笑:)
  • @0x499602D2:FAQ 鼓励提出问题并直接回答(甚至界面也支持),我认为这是一个问题的答案对其他人有用的问题。

标签: c++ iostream istream ostream


【解决方案1】:

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

  1. iword()int 作为键并生成以 0 开头的 int&amp;
  2. pword()int 为键并生成以 0 开头的 void*&amp;
  3. xalloc() 一个 static 函数,它在每次调用“分配”唯一键时产生不同的 int(它们的键不能被释放)。
  4. register_callback() 注册一个函数,该函数在流被销毁时调用,copyfmt() 被调用,或者新的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();
}

该实现使用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() 中。

棘手的事情是回调:它在三种情况下被调用:

  1. 当流s 被销毁或s.copyfmt(other) 被调用时,每个注册的回调都被调用s 作为std::ios_base&amp; 参数和事件std::ios_base::erase_event。此标志的目标是释放任何资源。为避免意外重复释放数据,删除std::string后将pword()设置为0
  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() 使不同的代码可以很好地协同工作很重要。

Here 是一个活生生的例子。

【讨论】:

  • @0x499602D2:谢谢!尝试编译代码结果发现包含几个额外的错误,我现在已修复。
  • +1 一个 SSCCE(最好作为一个活生生的例子)来看看图书馆这些黑暗但有趣的角落在行动,将不胜感激!
猜你喜欢
  • 2010-12-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多