While writing any decently big software, you will in most cases dump messages at various points in the software to capture important events or issue warnings and errors. You typically do this by adding printfs, cout's, cerr's etc. and all of this works pretty fine. But, what if tomorrow you want to dump all messages in a log file instead of pushing them on stdout or you want to do some processing over all the messages like adding a message code in the header or replacing all the end-of-line characters with just a space etc. Well, you can say that I will go over all the places where the messages are being dumped and easily adapt them - works, but can be immensely cumbersome and error-prone. Why not do something that's elegant, maintainable and less error-prone? Why not use the power unleashed by C++? Why not create a logger class that unveils a convenient and flexible interface to dump messages. Following is an example of such a class:
#include <iostream>
#include <fstream>
#include <string>
class MyLogger
{
public:
// 2nd bit tells that you need to push to a file too
enum LoggingMode { NORMAL=0, FILE_ONLY=2, TEE_MODE=3 };
MyLogger(bool is_err=false) {
m_Mode = NORMAL;
m_fLogErrorStream = is_err;
}
~MyLogger() {
if ((int)m_Mode & 2) m_OutFile.close();
}
void initFile(const std::string& outfile) {
m_Mode = FILE_ONLY; // set the m_Mode to file mode
m_OutFile.open(outfile.c_str(), std::ofstream::out | std::ofstream::app);
}
void setMode(const LoggingMode& mode)
{
m_Mode = mode;
}
template <typename T>
MyLogger& operator << (const T& val) {
out() << val;
if ((int)m_Mode & 2) fout() << val;
return *this;
}
private:
LoggingMode m_Mode;
bool m_fLogErrorStream;
std::ofstream m_OutFile;
std::ostream& fout() {
return m_OutFile;
}
std::ostream& out() {
if (m_fLogErrorStream) return std::cerr;
return std::cout;
}
};
To keep the interface of the logger C++like, operator << is overloaded. The operator is implemented as a function template whose specialized versions would be generated by the C++ compiler based on the types of the argument it is applied to. Based on the LoggingMode, the MyLogger class decides whether to log to usual streams (i.e. stdout or stderr) or to a file only or to both. The enum LoggingMode can be extended to support other modes. Following is a simple test for this class and it works:
MyLogger mlog;
int x = 100; float y = 6.67;
mlog.initFile("test.log");
mlog.setMode(MyLogger::TEE_MODE);
mlog << "Hello World!!\n";
mlog << "Value of x and y is: " << x << ", " << y << "\n";
In this test, the arguments to operator << were the usual types viz. const char*, int and float. Let's try other things that are typically given to the cout/cerr objects like 'std::endl', 'std::flush', 'std::hex' etc. popularly known as manipulators. All these manipulators are basically functions (or function pointers) and can also match to the operator << function template - hence, no further extensions should be needed. But, that's not entirely true - for example, consider the std::endl function that has the following prototypes:
ostream& endl (ostream& os); template <class charT, class traits> basic_ostream<charT,traits>& endl (basic_ostream<charT,traits>& os);Now when you have a statement like 'mlog << std::endl', the compiler will be left confused when trying to resolve the type 'T' for std::endl as it will have 2 ambiguous choices.Hence, it will bail out giving the errors as shown below (using MSVC 2010 compiler):
error C2914: 'MyLogger::operator <<' : cannot deduce template arg as function arg is ambiguous error C2676: binary '<<' : 'MyLogger' does not define this operator or a conversion to a type acceptable to the predefined operatorSo, how do we fix this? We know that we need to choose the first prototype as that corresponds to 'ostream', so let's add a specialization for operator <<, that will help the compiler zero-in on the first prototype:
MyLogger& operator << (std::ostream& (*manip)(std::ostream&)) {
#if DEBUG
if (manip == std::endl) {
std::cout << "endl found!\n";
}
#endif
manip(out());
if ((int)m_Mode & 2) manip(fout());
return *this;
}
With this specialized implementation of operator << added, the compiler is now able to resolve the ambiguity and we get the desired result!
No comments:
Post a Comment