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.
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.
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.
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;
}
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; }
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.
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";
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With