如何向流运算符添加缩进



在我们的项目中,我们使用对象模型中的 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 个空格缩进。我们可以向类添加一个成员来指定缩进,但这似乎不是一个优雅的解决方案。

当然,这不是一个很大的问题,但如果这可行,我们的日志记录会更好。

谢谢

最简单的解决方案是在 ostream和实际的流。 像这样:

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 );
        }
    }
};

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

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

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

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

这可以使用自定义流操纵器来完成,该操作器将所需的缩进级别存储在流的内部可扩展数组的单词中。您可以使用函数请求这样的单词 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
}

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

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

然后根据需要进行设置。

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

最新更新