gpt4 book ai didi

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

转载 作者:可可西里 更新时间:2023-11-01 16:35:33 26 4
gpt4 key购买 nike

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

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

...但是操纵器如何存储应该在流中使用哪些引号,然后让输出运算符检索值呢?

最佳答案

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

  • iword()int作为键,并生成一个int&,其开头为0
  • pword()int作为键,并产生一个void*&,其开头为0
  • xalloc()一个static函数,该函数在每次调用时产生不同的int,以“分配”唯一键(它们的键无法释放)。
  • 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()中。

    棘手的事情是回调:它在三种情况下被调用:
  • 当流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是一个实时示例。

    关于c++ - 如何使用IOStream存储格式设置?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20792101/

    26 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com