views:

109

answers:

3

I'm currently writing a program that has debug output strewn throughout it. This is all well and good, but I'd like to be able to advance the tab position for things in different scopes, for instance, this is what I have right now:

#ifndef NDEBUG
printf("Updating player\n");
#endif
player.Update();
#ifndef NDEBUG
printf("Done updating player\n");
#endif

I'd like to have so that all the output called between these two blocks is advanced by one tab position; however, simply adding tabs to the beginning of Player::Update() output is incredibly clunky AND difficult to maintain.

Anybody got any help? (Note: I have no problem with using cout instead; I was just recently lectured about the overhead and insecurity with cout)

+6  A: 

Instead of using printf directly, create a class to handle console output and have a member function such as SetTabLevel() -- and perhaps also IncrementTabLevel() and DecrementTabLevel() -- which tells the class how many tabs to add at the beginning of each subsequent line.

Adrian Lopez
Bonus point: use RAII to manage the tab level, such that `IncrementTabLevel` returns an object which upon destruction will call `DecrementTabLevel` automagically. This way you won't forget ;)
Matthieu M.
+3  A: 

You could have a class that essentially maintained a "tab count" and had a print_line function: when called, it would output tab-count tabs, and then print the line. While you could have a increment_indent function, you could create a sister object TabIndent using RAII: When it is created, increment the tab, when it is destructed, decrement the indent:

some_function()
{
    TabIndent ti(global_debug_outputter);

    global_debug_outputted.print_line("foo bar baz");
    // ti's d-tor calls global_debug_outputted.unindent()
}

class TabIndent
{
public:
    TabIndent(Outputter &o) : m_outputter(o)
    {
        o.indent();
    }
    ~TabIndent()
    {
        o.unindent();
    }
};

class Outputter
{
// functions indent, unindent, print_line...
};

Good use of inlines will let the compiler optimize them out when their bodies are empty. Use #ifndef in the Outputter class, and let the compiler optimize the rest out.

Thanatos
A: 

The good news is that C++ iostreams are very customizable. The bad news is that the interface is a bit weird.

#include <iostream>

class scoped_streambuf : public std::streambuf {
    std::streambuf *sb;
    size_t tabs;
    bool at_nl;

    virtual int_type overflow( int_type c = traits_type::eof() ) {
        if ( at_nl ) for ( size_t t = 0; t < tabs; ++ t ) {
            int_type r = sb->sputc( '\t' );
            if ( r == traits_type::eof() ) return r;
        }
        int_type r = sb->sputc( c );
        if ( r == traits_type::eof() ) return r;
        at_nl = c == '\n';
        return c;
    }

    virtual int sync() { return sb->pubsync(); }

    static void uninstall( std::ios_base::event what, std::ios_base &ios, int ) {
        if ( what != std::ios_base::erase_event ) return;

        std::ostream &os = dynamic_cast< std::ostream & >( ios );
        scoped_streambuf *this_ = static_cast< scoped_streambuf * >( os.rdbuf() );
        os.rdbuf( this_->sb );
        delete this_;
    }

public:
    scoped_streambuf( std::ostream &inos )
        : sb( inos.rdbuf( this ) ), tabs(), at_nl() {
        inos.register_callback( &uninstall, 0 );
    }

    friend std::ostream &indent( std::ostream &os ) {
        ++ dynamic_cast< scoped_streambuf & >( * os.rdbuf() ).tabs;
        return os;
    }

    friend std::ostream &outdent( std::ostream &os ) {
        -- dynamic_cast< scoped_streambuf & >( * os.rdbuf() ).tabs;
        return os;
    }
};
std::ostream &indent( std::ostream & );
std::ostream &outdent( std::ostream & );

struct indent_scope {
    std::ostream &os;
    indent_scope( std::ostream &inos ) : os( inos ) { os << indent; }
    ~indent_scope() { os << outdent; }
};

int main() {
    new scoped_streambuf( std::cout );

    std::cout << "hello\n";
    {
        indent_scope s( std::cout );
        std::cout << "world" << std::endl;
    }
    std::cout << "!\n";
}

I checked that the scoped_streambuf indeed deletes itself when the associated stream is destroyed, but apparently std::cout itself is never destroyed on GCC.

Level-up iostream fu, whee!

Potatoswatter