How to add indention to the stream operator

前端 未结 4 1225
予麋鹿
予麋鹿 2020-12-31 05:02

In our project we use the c++ stream operator (<<) in our object model to print out an easy readible format of the data. A simplified example is this:

         


        
相关标签:
4条回答
  • 2020-12-31 05:15

    You can make your own stream class that has an indentation variable and override the endl for that class, inserting the indentation.

    0 讨论(0)
  • 2020-12-31 05:21

    This can be done using a custom stream-manipulator that stores the desired indentation-level in a word of the internal extensible array of the stream. You can request such a word using the function ios_base::xalloc. This function will give you the index of your word. You can access it using ios_base::iword. One way to implement that would be this:

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

    You'd have to figure out where to store the index. This effectively allows you to add custom state to the stream (note that this would not be thread-safe out-of-the-box). Every function that wants indentation should add the requested indentation to the stream, and subtract it again when it is done. You could make sure this always happen by using a guard to add/subtract the desired indent (IMHO this is more elegant than using a manipulator):

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

    You could use it like this:

    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
    }
    
    0 讨论(0)
  • 2020-12-31 05:26

    The simplest solution is to slip a filtering streambuf between the ostream and the actual streambuf. Something like:

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

    To insert, just create an instance of the streambuf:

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

    When indent goes out of scope, everything returns to normal.

    (For logging, I have one that is a bit more complex: the LoggingOStreambuf takes __FILE__ and __LINE__ as arguments, sets myIndent to a formatted string with these arguments, plus a time stamp, resets it to an indentation string after each output, collects all of the output in an std::ostringstream, and outputs it atomically to myDest in the destructor.)

    0 讨论(0)
  • 2020-12-31 05:30

    Not so good way to do this is to add a global variable, which tells the indentation. Something like this :

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

    And then set it as appropriate.

    0 讨论(0)
提交回复
热议问题