Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

operator << - how to detect last argument

I'm writting a log class in c++. This class is an singleton. I want to add logs in such a way:

Log::GetInstance() << "Error: " << err_code << ", in class foo";

Ok, and inside a Log object, I want to save this whole line at the time when the last argument comes (", in class foo" in this example).

How to detect the last one << argument? << a << b << is_this_last << maybe_this_is << or_not.

I dont to use any end tags.

like image 207
asm Avatar asked Aug 16 '10 20:08

asm


6 Answers

You can solve this problem by not using a singleton. If you make a function like this:

Log log()
{
    return Log();
}

You can add a log almost the same way you did before:

log() << "Error: " << err_code << ", in class foo";

The difference is that the destructor of the Log object gets called after this line. So now you have a way to detect when the last argument has been processed.

like image 99
mtvec Avatar answered Nov 19 '22 02:11

mtvec


I would have your Log::GetInstance return a proxy object instead of the log object itself. The proxy object will save the data that's written to it, and then in its destructor, it'll actually write the accumulated data to the log.

like image 26
Jerry Coffin Avatar answered Nov 19 '22 02:11

Jerry Coffin


You make Log return a different object after the operator << .

template<typename T>
LogFindT operator<<(Log aLog, T const& data)
{
    // Put stuff in log.
    log.putStuffInLog(data);

    // now return the object to detect the end of the statement.
    return LogFindT(aLog);
}


struct LogFindT
{
    LogFindT(Log& aLog) : TheLog(aLog) {}
    Log& TheLog;
    ~LogFindT()
    {
        // Do stuff when this object is eventually destroyed
        // at the end of the expression.
    }
};

template<typename T>
LogFindT& operator<<(LogFindT& aLog, T const& data)
{
     aLog.TheLog.putStuffInLog(data);

     // Return a reference to the input so we can chain.
     // The object is thus not destroyed until the end of the stream.
     return aLog;
}
like image 29
Martin York Avatar answered Nov 19 '22 02:11

Martin York


I think Jerry and Martin have given the best suggestion, but for the sake of completeness, the first thing I thought of was std::endl.

If you implemented Log within the iostream system by a custom streambuf class, then you can simply add << endl or << flush at the end of the line. Since you're asking, I suppose you didn't.

But you can mimic the way endl works. Either add a manipulator handler

Log &operator<< ( Log &l, Log & (*manip)( Log & ) )
    { return manip( l ); } // generically call any manipulator

Log &flog( Log &l ) // define a manipulator "flush log"
    { l->flush(); return l; }

or add a dedicated operator<<

struct Flog {} flog;

Log &operator<< ( Log &l, Flog )
    { l->flush(); return l; }
like image 33
Potatoswatter Avatar answered Nov 19 '22 04:11

Potatoswatter


Don't get too clever with your operators. You should overload operators when it makes sense to do so. Here you don't should not. That just looks weird.

You should just have a static method that looks like this:

Log::Message( message_here );

which takes a std::string. Then clients have the head-ache of figuring out how to assemble the error string.

like image 1
C Johnson Avatar answered Nov 19 '22 04:11

C Johnson


Here is a solution based on @martin-york's answer. Slightly modified to use member operator in the structs.

#include<sstream>
#include<iostream>

struct log_t{
    void publish(const std::string &s){
        std::cout << s << std::endl;
    }
};

struct record_t{

    struct record_appender_t
    {
        record_appender_t(record_t& record_) : record(record_) {}
        record_t& record;
        ~record_appender_t()
        {
            // Do stuff when this object is eventually destroyed
            // at the end of the expression.
            record.flush();
        }

        template<typename T>
        record_appender_t& operator<<(T const& data)
        {
            record.stream() << data;
            // Return a reference to the input so we can chain.
            // The object is thus not destroyed until the end of the stream.
            return *this;
        }
    };

    std::ostringstream message;
    log_t log;
    void flush(){
        log.publish(message.str());
    }
    std::ostringstream& stream() {
        return message;
    }
    template<typename T>
    record_appender_t operator<<(T const& data)
    {
        // Put stuff in log.
        message << data;

        // now return the object to detect the end of the statement.
        return record_appender_t(*this);
    }
};

#define LOG \
    record_t()

int main(){
    LOG << 1 << 2 << "a";
}
like image 1
gyro Avatar answered Nov 19 '22 02:11

gyro