Defining an Exception using
a Macro
by Curtis Krauskopf
I'm a lazy coder. I don't like writing more code than
I have to and I don't like to see redundancy.
Last week, I was working on a C++ class for a new library.
Because the class was going to be used by coders other
than myself, I paid particular attention to the ways
that the class was designed to recover from errors.
The constructor for the C++ class I was building was
particularly robust and there were several ways that
it could fail. A long time ago, I learned one of the
best ways to handle an error in a class (especially
in a constructor) is to throw an exception.
I created an exception
class for each failure -- and in the process, I ran
across a lot of redundancy. Figure 1 shows the exception
classes I coded for one class in the library.
...
class FileNotFoundError : public exception
{
public:
FileNotFoundError()
: exception("File not found")
{ }
};
class NoColumnLabelsError : public exception
{
public:
NoColumnLabelsError()
: exception("No column labels")
{ }
};
class ColumnNotFoundError : public exception
{
public:
ColumnNotFoundError()
: exception("Column not found")
{ }
};
...
|
Figure 1: Three exception
definitions |
In figure 1, I define three exceptions:
- FileNotFoundError
- NoColumnLabelsError
- ColumnNotFoundError
The exceptions all inherit from the exception
class. In the standard C++ library, the exception
class is available by #include-ing
<exception>, like
this:
#include <exception>
The definition of each exception is almost trivial.
An exception constructor
takes a char * message
parameter. The message helps to distinguish, in the
catch() block at runtime,
which exception was triggered.
The compiler I was using
was the Microsoft Visual C++ .NET compiler. Their implementation
of the exception class allows a const
char * to be passed to the class' constructor,
as shown in the constructor for the FileNotFoundError
class in figure 1:
FileNotFoundError()
: exception("Input file not found")
Overall, these three exceptions take 21 lines of code.
That's a lot to look at. It's also a nightmare for the
maintenance programmer because the first question he/she
will ask is "what's the same in these three exceptions
and is there anything special defined in any one of
them?". Even though the definition of each exception
is relatively simple, it's still a lot for a maintenance
programmer to digest. Another issue: this code needs
to be repeated for each exception and my design plans
on having dozens of exceptions throughout the library.
There must be a better solution.
I'm normally not in favor of macros in C++ programming.
Many books, magazine articles and web pages have influenced
my opinion on this. But in this case, a macro will certainly
simplify the coding. Overall, I dislike redundancy more
than I dislike macros.
The solution I'll use will turn the exceptions in figure
1 into the simple lines of code in figure 2.
DEFINE_EXCEPTION(FileNotFoundError, "File not found");
DEFINE_EXCEPTION(NoColumnLabelsError, "No column labels");
DEFINE_EXCEPTION(ColumnNotFoundError, "Column not found");
|
Figure 2: Three improved exception
definitions |
These exception definitions
are much easier to understand. Two side-effects of this
implementation are:
- The maintenance programmer knows that each exception
is defined the same way
- Changes to the DEFINE_EXCEPTION
macro will affect all of the exception definitions
The definition for the DEFINE_EXCEPTION
macro for a Microsoft Visual C++ implementation of the
exception class is in figure
3.
define_exception.h: (For
the MicroSoft Visual C++ .NET compiler) |
#ifndef DEFINE_EXCEPTION_H
#define DEFINE_EXCEPTION_H
#include <exception>
// DEFINE_EXCEPTION macro defines exceptions derived from the
// standard exception class.
//
// Copyright (c) 2006 by Curtis Krauskopf
// >>http://www.decompile.com
// |
// Permission to use, copy, modify, distribute and sell this
// software for any purpose is hereby granted without fee,
// provided that the above copyright notice appears in all
// copies of this code and in all modified versions of this
// code.
// The author makes no representations about the suitability of
// this software for any purpose. It is provided "as is"
// without express or implied warranty.
//
// Created: July 20, 2006 by Curtis Krauskopf (cdk)
//
// In the parameter list:
// ClassName is the exception being defined. It should not
// have been previously defined in the current namespace.
// Message is the message that will be returned by calling
// the .what() method.
// Message can be "".
// The exception's type is the ClassName.
//
#define DEFINE_EXCEPTION(ClassName,Message) \
class ClassName : public exception \
{ \
public: \
ClassName(const char *msg = Message) \
: exception(msg) \
{ } \
};
#endif
|
Figure 3: Exception definition macro |
As demonstrated by the example in figure
2, the DEFINE_EXCEPTION
macro takes two parameters:
Parameter |
Definition |
ClassName |
The name of the exception
class being defined |
Message |
A message that is available at runtime by calling
the .what() method
on the exception object |
As mentioned above, the
Microsoft Visual C++ .NET compiler's exception
class allows a const char *
to be passed to the class' constructor. Not all compilers
have this capability -- notably the Borland C++ Builder
compiler.
Figure 4 shows an implementation of the DEFINE_EXCEPTION
macro for compilers that don't have a const
char * exception constructor. It's also compatible
with the Microsoft Visual C++ .NET compiler because
of the way the local const char
* for the message pointer is handled.
define_exception.h: (Suitable
for all compilers)
Download define_exception.h
(2.5k) |
#ifndef DEFINE_EXCEPTION_H
#define DEFINE_EXCEPTION_H
#include <exception>
// DEFINE_EXCEPTION macro defines exceptions derived from the
// standard exception class.
//
// Copyright (c) 2006 by Curtis Krauskopf
// >>http://www.decompile.com
// |
// Permission to use, copy, modify, distribute and sell this
// software for any purpose is hereby granted without fee,
// provided that the above copyright notice appears in all
// copies of this code and in all modified versions of this
// code.
// The author makes no representations about the suitability of
// this software for any purpose. It is provided "as is"
// without express or implied warranty.
//
// Created: July 20, 2006 by Curtis Krauskopf (cdk)
// Update: July 24, 2006: consolidate MSVC solution
// with Borland C++ Builder and GNU solution.
//
//
// For the declaration:
// DEFINE_EXCEPTION(FileNotFoundError, "File not found");
//
// the macro expands to:
//
// class FileNotFoundError : public ExceptionHelper
// {
// public:
// FileNotFoundError(const char *msg = "File not found")
// : ExceptionHelper(msg)
// { }
// };
//
// In the parameter list:
// ClassName is the exception being defined. It should not
// have been previously defined in the current namespace.
// Message is the message that will be returned by calling
// the .what() method.
// Message can be "".
// The exception's type is the ClassName.
//
// Class heirarchy:
//
// std::exception
// ^
// |
// ExceptionHelper
// ^
// |
// ClassName (class being defined)
//
// ExceptionHelper augments the standard exception class by
// allowing a const char * parameter in the constructor.
class ExceptionHelper : public std::exception
{
public:
ExceptionHelper(const char *msg)
: std::exception(), msg_(msg)
{ }
virtual const char * what() const throw() { return msg_; }
private:
const char *msg_;
};
#define DEFINE_EXCEPTION(ClassName,Message) \
class ClassName : public ExceptionHelper \
{ \
public: \
ClassName(const char *msg = Message) \
: ExceptionHelper(msg) \
{ } \
};
#endif
|
Figure 4: DEFINE_EXCEPTION macro definition that
is suitable for all C++ compilers. |
In figure 4, the ExceptionHelper
class augments the std::exception
class by allowing a const char
* parameter in the constructor. At runtime, the
what() method allows access
to the message that was passed in the constructor.
The test program for the DEFINE_EXCEPTION
macro is the same program for both figures 3 and 4.
The source code for the test program is in figure 5.
test_define_exception.cpp: |
#include <iostream>
#include "exception_definition.h"
int main(int, char* )
{
std::cout << "testing..." << std::endl;
DEFINE_EXCEPTION(FileNotFoundError, "FileNotFoundError")
try {
throw FileNotFoundError();
}
catch(FileNotFoundError &e) {
std::cout << "Caught: " << e.what() << std::endl;
}
try {
throw FileNotFoundError("Custom Exception Text");
}
catch(FileNotFoundError &e) {
std::cout << "Caught: " << e.what() << std::endl;
}
return 0;
}
|
Figure 5: Test program that demonstrates the DEFINE_EXCEPTION
macro. |
The output for the test program is shown in figure
6.
|
|
|
|
testing...
Caught: FileNotFoundError Caught: Custom Exception Text |
|
|
|
|
|
Figure 6: Test program that demonstrates the DEFINE_EXCEPTION
macro. |
Conclusion:
Language features that are convenient to use will be
used more often. The implementation of the DEFINE_EXCEPTION
macro makes exception creation almost trivial.
Curtis Krauskopf is a software
engineer and the president of The Database Managers (www.decompile.com).
He has been writing code professionally for over 25 years. His prior projects
include multiple web e-commerce applications, decompilers
for the DataFlex language, aircraft simulators, an automated Y2K conversion
program for over 3,000,000 compiled DataFlex programs, and inventory control projects.
Curtis has spoken at many domestic and international DataFlex developer conferences
and has been published in FlexLines Online, JavaPro
Magazine, C/C++
Users Journal and C++ Builder Developer's Journal.
Copyright 2003-2010 The Database Managers, Inc.
Popular C++ topics at The Database Managers:
|