How does QDebug() << stuff; add a newline automatically?
Asked Answered
E

4

18

I'm trying to implement my own qDebug() style debug-output stream, this is basically what I have so far:

struct debug
{
#if defined(DEBUG)
    template<typename T>
    std::ostream& operator<<(T const& a) const
    {
        std::cout << a;
        return std::cout;
    }
#else
    template<typename T>
    debug const& operator<<(T const&) const
    {
        return *this;
    }

    /* must handle manipulators (endl) separately:
     * manipulators are functions that take a stream& as argument and return a
     * stream&
     */
    debug const& operator<<(std::ostream& (*manip)(std::ostream&)) const
    {
        // do nothing with the manipulator
        return *this;
    }
#endif
};

Typical usage:

debug() << "stuff" << "more stuff" << std::endl;

But I'd like not to have to add std::endl;

My question is basically, how can I tell when the return type of operator<< isn't going to be used by another operator<< (and so append endl)?

The only way I can think of to achieve anything like this would be to create a list of things to print with associated with each temporary object created by qDebug(), then to print everything, along with trailing newline (and I could do clever things like inserting spaces) in ~debug(), but obviously this is not ideal since I don't have a guarantee that the temporary object is going to be destroyed until the end of the scope (or do I?).

Exclude answered 1/2, 2010 at 19:56 Comment(1)
qDebug() just creates temporary objects. Follow the symbol and you will find something like this: QDebug qDebug() { return QDebug(QtDebugMsg); }.Blackfellow
K
11

Qt uses a method similar to @Evan. See a version of qdebug.h for the implementation details, but they stream everything to an underlying text stream, and then flush the stream and an end-line on destruction of the temporary QDebug object returned by qDebug().

Knurled answered 1/2, 2010 at 21:0 Comment(3)
Cool thanks, that's what I wondered, and confirms that the temporary gets promptly destructed most of the time :)Exclude
@Autopulated: I forget the precise verbiage, but I believe that the standard mandates that an unnamed object gets destructed immediately after the expression that it was created in.Zenaidazenana
Aha! found it: 12.2/4 says "There are two contexts in which temporaries are destroyed at a different point than the end of the full expression." Which basically means that aside from the two exceptions about to be listed, temporary objects will be destroyed immediately at the end of the expression.Zenaidazenana
Z
19

Something like this will do:

struct debug {
    debug() {
    }

    ~debug() {
        std::cerr << m_SS.str() << std::endl;
    }

public:
    // accepts just about anything
    template<class T>
    debug &operator<<(const T &x) {
        m_SS << x;
        return *this;
    }
private:
    std::ostringstream m_SS;
};

Which should let you do things like this:

debug() << "hello world";

I've used a pattern like this combined with a lock to provide a stream like logging system which can guarantee that log entries are written atomically.

NOTE: untested code, but should work :-)

Zenaidazenana answered 1/2, 2010 at 20:22 Comment(2)
You have a mistake: the destructor should be called "debug". Also, why use the intermediate ostringstream? Can't you just output things to cerr as they arrive in your operator<<?Roadrunner
I fixed the destructor name (copy/paste error). but the stringstream has a purpose. It makes it so the output is not written out until the destructor is called (making it possible to prevent threading issues).Zenaidazenana
K
11

Qt uses a method similar to @Evan. See a version of qdebug.h for the implementation details, but they stream everything to an underlying text stream, and then flush the stream and an end-line on destruction of the temporary QDebug object returned by qDebug().

Knurled answered 1/2, 2010 at 21:0 Comment(3)
Cool thanks, that's what I wondered, and confirms that the temporary gets promptly destructed most of the time :)Exclude
@Autopulated: I forget the precise verbiage, but I believe that the standard mandates that an unnamed object gets destructed immediately after the expression that it was created in.Zenaidazenana
Aha! found it: 12.2/4 says "There are two contexts in which temporaries are destroyed at a different point than the end of the full expression." Which basically means that aside from the two exceptions about to be listed, temporary objects will be destroyed immediately at the end of the expression.Zenaidazenana
J
5

When you write that this is the typical usage:

debug() << "stuff" << "more stuff" << std::endl;

are you definitely planning to construct a debug object each time you use it? If so, you should be able to get the behavior you want by having the debug destructor add the newline:

~debug()
{
    *this << std::endl;

    ... the rest of your destructor ...
}

That does mean you cannot do something like this:

// this won't output "line1" and "line2" on separate lines
debug d;
d << "line1";
d << "line2";
Juvenal answered 1/2, 2010 at 20:17 Comment(4)
@last part: Will work if you surround it with braces. When the debug object goes out of scope, its destructor is called.Tadeas
@jmucchiello: The destructor will be called at the end of the statement, which is exactly when you want the newline to be inserted.Cecillececily
@Tadeas - what I mean by "won't work" is that line1 and line2 will not be on separate lines like they would have been if you wrote debug() << "line1"; debug() << "line2()";Juvenal
Yes, I'm definitely planning to do that, I will probably disable the copy / copy assignment constructors to somewhat enforce it.Exclude
S
0

The stream insertion (<<) and extraction (>>) are supposed to be non-members.

My question is basically, how can I tell when the return type of operator<< isn't going to be used by another operator<< (and so append endl)?

You cannot. Create a member function to specially append this or append an endl once those chained calls are done with. Document your class well so that the clients know how to use it. That's your best bet.

Stoker answered 1/2, 2010 at 20:1 Comment(2)
I'm going to be the main user of this; I'm trying to save writing std::endl over and over and over again.Exclude
endl does two things -- 1) add a newline -- which is something you cannot control since strings can have embedded newlines and 2) flushes the output stream -- which you can control without needing to put them in the dtor. Your question wasn't very clear what you are trying to achieve.Stoker

© 2022 - 2024 — McMap. All rights reserved.