粘性自定义流操纵器

时间:2012-11-11 20:36:15

标签: c++

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

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

1 个答案:

答案 0 :(得分:17)

做一切事情有点牵扯。为了使其易于理解,我将从基本的东西开始:为用户定义的类型使用自定义格式标记。整数的自定义格式如下所示。

IOStream类[间接]来自std::ios_base,它为数据提供了两个存储:std::ios_base::iword()std::ios_base::pword()分别用于intvoid*。维护与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不是用户定义类型,并且已经为这些类型定义了输出运算符。幸运的是,您可以使用facet自定义整数的格式,更具体地说,使用std::num_put<char>。现在,为此,您需要执行许多步骤:

  1. std::num_put<char>派生一个班级,并覆盖您希望为其提供专门行为的do_put()个成员。
  2. 使用新创建的构面创建std::locale对象。
  3. std::ios_base::imbue()包含新std::locale
  4. 的信息流

    为了让用户更好,您可能想要在使用操纵器时使用合适的std::locale方面来构建新的std::num_put<char>。但是,在这样做之前,让我们从创建一个合适的方面开始:

    #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操纵器是必要的。要摆脱这种情况,我们可以确保自定义构面安装在使用过的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一起正常使用,但这只是一个练习。