【问题标题】:Sticky custom stream manipulator粘性自定义流操纵器
【发布时间】:2012-11-11 20:36:14
【问题描述】:

如何实现我自己的自定义流操纵器以使其具有粘性。例如,我想将整数转换为二进制,这样:

cout << "decimal of 4: " <<  4 
     << "\ndecimal of 4: " << 4 
     << binary << "\nbinary of 4: " << 4 
     << "\nbinary of 4: " << 4 
     << nobinary << "\ndecimal of 4: " << 4 
     << "\ndecimal of 4: " << 4 << endl;

会返回:

decimal of 4: 4
decimal of 4: 4
binary of 4: 100
binary of 4: 100
decimal of 4: 4
decimal of 4: 4

【问题讨论】:

  • 我会扼杀nobinary 的概念,让他们使用std::dec 等。
  • 您是否希望它在语句结束后仍保持不变?
  • 顺便说一句,don't use std::endl.
  • @VaughnCato:这有什么关系?
  • @DietmarKühl:您可以使用一些模板技巧使其持续到表达式结束。

标签: c++


【解决方案1】:

做整个事情有点复杂。为了使其易于理解,我将从基本内容开始:对用户定义的类型使用自定义格式标志。整数的自定义格式如下所示。

IOStream 类 [间接] 派生自 std::ios_base,它提供两个数据存储:std::ios_base::iword()std::ios_base::pword() 分别用于 ints 和 void*。维护使用std::ios_base::pword() 存储的分配内存并非易事,幸运的是,对于这个相对简单的用例,不需要。要使用这些同时返回对相应类型的非const 引用的函数,通常在程序中使用std::ios_base::xalloc() 分配一个索引,并在需要访问自定义格式标志时使用它。当您使用iword()pword() 访问一个值时,最初它将被初始化为零。总而言之,这里有一个小程序演示了这一点:

#include <iostream>

static int const index = std::ios_base::xalloc();

std::ostream& custom(std::ostream& stream) {
    stream.iword(index) = 1;
    return stream;
}

std::ostream& nocustom(std::ostream& stream) {
    stream.iword(index) = 0;
    return stream;
}

struct mytype {};
std::ostream& operator<< (std::ostream& out, mytype const&) {
    return out << "custom-flag=" << out.iword(index);
}

int main()
{
    std::cout << mytype() << '\n';
    std::cout << custom;
    std::cout << mytype()  << '\n';
    std::cout << nocustom;
    std::cout << mytype() << '\n';
}

现在,int 之类的 4 不是用户定义类型,并且已经为这些类型定义了输出运算符。幸运的是,您可以使用构面自定义整数格式化的方式,更具体地说,使用std::num_put&lt;char&gt;。现在,要做到这一点,您需要执行多个步骤:

  1. std::num_put&lt;char&gt; 派生一个类并覆盖您要为其赋予特殊行为的 do_put() 成员。
  2. 使用新创建的构面创建std::locale 对象。
  3. std::ios_base::imbue() 带有新 std::locale 的流。

为了让用户感觉更好,您可能想在使用操纵器时使用合适的std::num_put&lt;char&gt; 构想出一个新的std::locale。但是,在这样做之前,让我们先创建一个合适的方面:

#include <bitset>
#include <iostream>
#include <limits>
#include <locale>

static int const index = std::ios_base::xalloc();

class num_put
    : public std::num_put<char>
{
protected:
    iter_type do_put(iter_type to,
                     std::ios_base& fmt,
                     char_type fill,
                     long v) const
    {
        if (!fmt.iword(index)) {
            return std::num_put<char>::do_put(to, fmt, fill, v);
        }
        else {
            std::bitset<std::numeric_limits<long>::digits> bits(v);
            size_t i(bits.size());
            while (1u < i && !bits[i - 1]) {
                --i;
            }
            for (; 0u < i; --i, ++to) {
                *to = bits[i - 1]? '1': '0';
            }
            return to;
        }
    }
#if 0
    // These might need to be added, too:
    iter_type do_put(iter_type, std::ios_base&, char_type,
                     long long) const;
    iter_type do_put(iter_type, std::ios_base&, char_type,
                     unsigned long) const;
    iter_type do_put(iter_type, std::ios_base&, char_type,
                     unsigned long long) const;
#endif
};

std::ostream& custom(std::ostream& stream) {
    stream.iword(index) = 1;
    return stream;
}

std::ostream& nocustom(std::ostream& stream) {
    stream.iword(index) = 0;
    return stream;
}

int main()
{
    std::locale loc(std::locale(), new num_put);
    std::cout.imbue(loc);
    std::cout << 13 << '\n';
    std::cout << custom;
    std::cout << 13  << '\n';
    std::cout << nocustom;
    std::cout << 13 << '\n';
}

有点难看的是需要imbue()自定义std::locale才能使用custom操纵器。为了摆脱这种情况,我们可以确保自定义 facet 安装在使用的 std::locale 中,如果没有,则在设置标志时安装它:

std::ostream& custom(std::ostream& stream) {
    if (!stream.iword(index)
        && 0 == dynamic_cast<num_put const*>(
                &std::use_facet<std::num_put<char> >(stream.getloc()))) {
        stream.imbue(std::locale(stream.getloc(), new num_put));
    }
    stream.iword(index) = 1;
    return stream;
}

现在剩下的就是覆盖不同的do_put() 成员,以便与各种unsigned 类型和long long 一起正常工作,但这留作练习。

【讨论】:

  • 我没有得到的是dynamic_cast。那是为了什么?
  • @0x499602D2: 所有的dynamic_cast 都会验证自定义std::locale 是否需要imbue()ed: 如果已经安装了合适的方面,则不需要imbue()一个新的方面。查看编辑,实际上似乎较早的编辑将代码调试为先前正确的代码! dynamic_cast 尝试测试自定义方面 num_put,而不是 std::num_put&lt;char&gt;
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-10-11
  • 2018-07-07
  • 2014-05-15
  • 2015-06-02
  • 1970-01-01
  • 2010-10-22
  • 2010-10-06
相关资源
最近更新 更多