Introduction
C++ developers usually try to find errors/exceptions by putting logging statements (cout
, etc.) in their code to find what is going on. Some people choose to use software like Dr.Watson to get the callstack and find the exception location with some effort. As you know, the only way to catch exceptions is to use try
/catch
blocks. But putting these blocks makes the code less readable, and perhaps more error-prone because of the statements in the catch
block.
I present here some easily usable macros which put these try
/catch
blocks in the code. The macros will let you catch exceptions and log the exception location in a file. If you use the macros in your code, whenever an exception occurs, you will be able to see a callstack.
It is also possible to disable exception throwing so that your program will live without exceptions.
Using the Code
Using the macros is very easy. Assume you have a function named myMethod
:
void myMethod()
{
EXLOG_START
...
...
EXLOG_END
}
EXLOG_START
is a simple a try
expression. On the other hand, EXLOG_END
is the catch
block with the error logging mechanism.
When an exception occurs in the myMethod
function, it will be caught and logged in a file. After that, if exception throwing is not disabled (will be told), the exception will be thrown to the caller function of myMethod
. If you have put the EXLOG
statements in the functions, the exception will be logged up to the main
function.
You can disable EXLOG's exception throwing so that the exception is caught, logged, and the program will try to continue running. You can set it in any part of your code using:
EXLOG::setIsThrowExceptions(false);
Some functions may return a value instead of void
. For these functions, you should use another macro EXLOG_END_WITH_RETURN
to allow these functions to return a value when an exception occurs:
int myMethodWithReturn()
{
EXLOG_START
...
...
EXLOG_END_WITH_RETURN(-1)
}
So if there is an exception, myMethodWithReturn
will return -1 (which is assumed the default value for this function) and the caller gets -1, and the program will go on running.
Along with the macros, the ExLogger
class is also important. It is the class that logs the info to the file. It uses boost's date time classes to get a unique name for the files/logs. So you should add boost's libraries to your project.
You can also use ExLogger
to log anything to the file. It is a Singleton class that has overloaded <<
operators which let you write easy and readable code as if you are writing to cout
. You can get an instance by using the macro EXLOG
:
EXLOG << myInt << "sth" << myValue << EXLOG_ENDL("");
EXLOG_ENDL
is similar to std::endl
which causes the log stream to be flushed into the file.
Points of Interest
In the code, the EXLOG
macros are changeable with a define: EXLOG_ENABLED
. I have put this define in the header, but you can put it in your preprocessor section so that you can enable/disable EXLOG
related code with just one define.
If EXLOG_ENABLED
is not enabled, the macros will be replaced with the //
comment expression. So, the expressions will be commented out. There is only a small problem with this usage: if you want to disable the EXLOG
statements with this define, you should put the EXLOG
statements in a single line.
So, for example:
EXLOG << myInt << "sth" << myValue << EXLOG_ENDL("");
is safe. But:
EXLOG << myInt <<
"sth" << myValue
<< EXLOG_ENDL("");
will cause compilation errors if you don't define EXLOG_ENABLED
.
But I think this approach is more readable and maintainable than using:
#ifdef _DEBUG
Exlogger::instance() << myInt << "sth" << myValue << EXLOG_ENDL("");
#endif
Notes
- With the default project settings, hardware related exceptions like division by zero, dangling pointer usage, etc., cannot be caught with the standard
try
/catch
blocks. You should change your project settings to enable this by following the path:
Project Properties >> C/C++ >> Code Generation >> Enable C++ Exceptions
and setting the value to Yes With SEH Exceptions(/EHa).
Another setting is about STL classes. These classes contain many assertions. As you know, assertions cannot be caught. To avoid this, you should also define these values in the preprocessor settings of the project:
_SECURE_SCL=0 ve _HAS_ITERATOR_DEBUGGING=0
But remember to set these preprocessor settings in all the imported projects! So this last setting is optional. If you do not define these preprocessor values, the STL exceptions may not be caught/logged, but will be informed to you by assertions.
Discussion
You may have thought that putting try
/catch
may affect performance. But you can be comfortable when using try
/catch
blocks. I have searched many forums and have also made performance tests. There is only an insignificant difference with try
/catch
blocks and without them.
Have a secure development. :)