我尝试创建一些I / O操纵器,以允许用户修改自定义类型的输出格式。
假设我有一个Foo
对象:我可能想要以一种漂亮的,人类可读的格式输出它(漂亮的打印),或者我可能想要以浓缩的形式打印它以在序列化时节省空间
所以,拥有像condensed
和pretty
之类的自定义I / O操纵器来修改方面的内部标志会很好,所以我可以这样说:
Foo f;
...
std::cout << pretty << f; // output human-readable format
std::cout << condensed << f; // output condensed format
我经常遇到的问题是,一旦创建了一个构面对象,它只能在以后使用std::use_facet
检索,它会返回一个 const 引用。这意味着我以后无法修改任何内部方面标志。
考虑一个简单的方面:
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操纵器都必须重新创建构面对象,因此先前的标志集会丢失。原因是无法访问现有构面的非const 引用。
我想说:
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
,这是荒谬的。
所以,我感觉到我做错了。似乎没有任何方法可以获得对现有方面的非const引用,所以我认为我以错误的方式处理这一切。
那么,这种事情通常是如何实现的?如何编写可以链接在一起设置不同标志的I / O操纵器,例如:
std::cout << pretty << indent(3) << etc ...
答案 0 :(得分:2)
存储自定义格式化状态的可接受方式是使用std::ios_base::xalloc
分配的内存。例如(删节,live demo with full code here):
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);