std::facet 对象上的可变标志

Mutable flags on a std::facet object

本文关键字:标志 facet 对象 std      更新时间:2023-10-16

我正在尝试创建一些I/O操纵器以允许用户修改自定义类型的输出格式。

假设我有一个Foo对象:我可能希望以漂亮的、人类可读的格式(漂亮的打印)输出它,或者我可能希望以精简形式打印它以节省序列化时的空间。

因此,最好有自定义的 I/O 操纵器(如 condensedpretty)来修改分面的内部标志,所以我可以这样说:

Foo f;
...
std::cout << pretty << f; // output human-readable format
std::cout << condensed << f; // output condensed format

我经常遇到的问题是,一旦创建了一个分面对象,以后只能通过使用返回常量引用的 std::use_facet 来检索它。 这意味着我以后无法修改任何内部分面标志。

考虑一个简单的方面:

class my_facet : public std::locale::facet
{
    public:
    my_facet() : m_pretty(false), m_condensed(false)
    { }
    void set_pretty(bool b) { m_pretty = b; }
    void set_condensed(bool b) { m_condensed = b; }
    static std::locale::id id;
    private:
    bool m_pretty;
    bool m_condensed;
};

然后,我可以创建 I/O 操纵器,如下所示:

template <class CharT, class Traits>
inline std::basic_ostream<CharT, Traits>& pretty(std::basic_ostream<CharT, Traits>& os)
{
    my_facet<CharT>* facet = new my_facet();
    facet->set_pretty(true);
    facet->set_condensed(false);
    std::locale loc = std::locale(os.getloc(), facet);
    os.imbue(loc);
    return os;
}

这很好用...但是,如果我想允许用户指定其他标志或格式选项,例如允许用户指定要缩进的空格数量的indentation选项,如下所示:

std::cout << pretty << indent(4) << f;

问题是每个 I/O 操纵器都必须重新创建分面对象,因此先前设置的标志将丢失。 原因是无法访问对现有方面的非常量引用。

我想说:

template <class CharT, class Traits>
inline std::basic_ostream<CharT, Traits>& operator << (std::basic_ostream<CharT, Traits>& os,
    const indent& ind)
{
    const my_facet<CharT>& facet = std::use_facet<my_facet>(os.getloc()); 
    facet.set_indentation(ind.value()); // Error: facet is const!
    return os;
}

。但是,当然,这是行不通的,因为facet const. 我能看到的唯一方法是使所有内部标志mutable,这是荒谬的。

所以,我感觉到我只是做错了。 似乎没有任何方法可以获取对现有方面的非常量引用,所以我认为我以错误的方式处理整个事情。

那么,这种事情通常是如何实现的呢? 如何编写可以链接在一起以设置不同标志的 I/O 操纵器,例如:

std::cout << pretty << indent(3) << etc ...

存储自定义格式状态的公认方法是使用由 std::ios_base::xalloc 分配的内存。例如(此处包含完整代码的删节版现场演示):

class MyFancyManipulator
{
    static int myidx;
    int st;
  public:
    MyFancyManipulator(int st) : st(st) {};
    template <typename Ch, typename Tr> friend
        std::basic_ostream<Ch, Tr>& operator<< (std::basic_ostream<Ch, Tr>& str,
                const MyFancyManipulator& man) {
            //
            // Transfer state from the manipulator to the stream
            //
            str.iword(MyFancyManipulator::myidx) = man.st;
            return str;
        }
};
// Allocate index for the state variable
// This is thread safe per C++14 standard
int MyFancyManipulator::myidx = std::ios_base::xalloc();

// ... In some custom operator<<
   int state = str.iword(MyFancyManipulator::myidx);