如何将缩进添加到流操作符

时间:2012-03-07 10:34:53

标签: c++ stream operator-overloading indentation

在我们的项目中,我们在对象模型中使用c ++流运算符(<<<)来打印出易于读取的数据格式。一个简单的例子就是:

std::ostream& operator<<(std::ostream & oStream, const OwnClass& iOwnClass) {
    oStream << "[SomeMember1: " << iOwnClass._ownMember1 << "]\n";
    oStream << "[SomeMember2: " << iOwnClass._ownMember2 << "]\n";
}

在日志记录中导致此问题:

[SomeMember1: foo]
[SomeMember2: bar]

我们现在想要的是能够缩进该运算符的结果。某些调用类可能不希望得到这样的结果,但是想要在每行之前添加2个空格。我们可以在我们的类中添加一个指定缩进的成员,但这似乎不是一个优雅的解决方案。

当然这不是一个非常大的问题,但是如果这样做的话我们的日志会更好。

由于

4 个答案:

答案 0 :(得分:24)

最简单的解决方案是在过滤器之间滑动过滤streambuf ostream和实际的streambuf。类似的东西:

class IndentingOStreambuf : public std::streambuf
{
    std::streambuf*     myDest;
    bool                myIsAtStartOfLine;
    std::string         myIndent;
    std::ostream*       myOwner;
protected:
    virtual int         overflow( int ch )
    {
        if ( myIsAtStartOfLine && ch != '\n' ) {
            myDest->sputn( myIndent.data(), myIndent.size() );
        }
        myIsAtStartOfLine = ch == '\n';
        return myDest->sputc( ch );
    }
public:
    explicit            IndentingOStreambuf( 
                            std::streambuf* dest, int indent = 4 )
        : myDest( dest )
        , myIsAtStartOfLine( true )
        , myIndent( indent, ' ' )
        , myOwner( NULL )
    {
    }
    explicit            IndentingOStreambuf(
                            std::ostream& dest, int indent = 4 )
        : myDest( dest.rdbuf() )
        , myIsAtStartOfLine( true )
        , myIndent( indent, ' ' )
        , myOwner( &dest )
    {
        myOwner->rdbuf( this );
    }
    virtual             ~IndentingOStreambuf()
    {
        if ( myOwner != NULL ) {
            myOwner->rdbuf( myDest );
        }
    }
};

要插入,只需创建streambuf的实例:

IndentingOStreambuf indent( std::cout );
//  Indented output...

indent超出范围时,一切都恢复正常。

(对于日志记录,我有一个更复杂的: LoggingOStreambuf__FILE____LINE__作为参数设置 myIndent到带有这些参数的格式化字符串,加上一个时间 stamp,在每次输出后将其重置为缩进字符串,收集 std::ostringstream中的所有输出,并以原子方式输出 析构函数中的myDest。)

答案 1 :(得分:1)

不太好的方法是添加一个全局变量,它告诉缩进。像这样:

std::string OwnClassIndentation;
std::ostream& operator<<(std::ostream & oStream, const OwnClass& iOwnClass) {
    oStream << "[SomeMember1:" << OwnClassIndentation << iOwnClass._ownMember1 << "]\n";
    oStream << "[SomeMember2:" << OwnClassIndentation << iOwnClass._ownMember2 << "]\n";
}

然后根据需要进行设置。

答案 2 :(得分:1)

这可以使用自定义流操纵器来完成,该操纵器将所需的缩进级别存储在流的内部可扩展数组的单词中。您可以使用函数ios_base::xalloc请求这样的单词。此功能将为您提供单词的索引。您可以使用ios_base::iword访问它。实现这一目标的一种方法是:

struct indent {
    indent(int level) : level(level) {}
private:
    friend std::ostream& operator<<(std::ostream& stream, const indent& val);

    int level;
};

std::ostream& operator<<(std::ostream& stream, const indent& val) {
    for(int i = 0; i < val.level; i++) {
        stream << " ";
    }
    return stream;
}

std::ostream& operator<<(std::ostream & oStream, const OwnClass& iOwnClass) {
    oStream << indent(oStream.iword(index)) << "[SomeMember1: " << 
               iOwnClass._ownMember1 << "]\n";
    oStream << indent(oStream.iword(index)) << "[SomeMember2: " << 
               iOwnClass._ownMember2 << "]\n";
}

您必须找出存储index的位置。这有效地允许您向流添加自定义状态(请注意,这不是开箱即用的线程安全)。每个想要缩进的函数都应该将请求的缩进添加到流中,并在完成后再次减去它。您可以通过使用警卫来添加/减去所需的缩进来确保这一切(恕我直言,这比使用操纵器更优雅):

class indent_guard {
public:
    indent_guard(int level, std::ostream& stream, int index) 
    : level(level),
      stream(stream),
      index(index)
    {
        stream.iword(index) += level;
    }

    ~indent_guard() {
        stream.iword(index) -= level;
    }

 private:
     int level;
     std::ostream& stream;
     int index;
};

您可以像这样使用它:

void some_func() {
    indent_guard(2, std::cout, index);

    // all output inside this function will be indented by 2 spaces

    some_func(); // recursive call - output will be indented by 4 spaces

    // here it will be 2 spaces again
}

答案 3 :(得分:0)

您可以创建自己的具有缩进变量的流类,并覆盖该类的endl,插入缩进。