|
|
|
|
Improved error checking
The
require.h functions have
been used up to this point without defining them (although
assert( ) has also been used to help detect
programmer errors where it’s appropriate). Now it’s time to define
this header file. Inline
functions are convenient here because they allow everything to be placed in a
header file, which simplifies the process of using the package. You just include
the header file and you don’t need to worry about linking an
implementation file.
You should note that exceptions
(presented in detail in Volume 2 of this book) provide a much more effective way
of handling many kinds of errors – especially those that you’d like
to recover from – instead of just halting the program. The conditions that
require.h handles, however, are ones which prevent the continuation of
the program, such as if the user doesn’t provide enough command-line
arguments or if a file cannot be opened. Thus, it’s acceptable that they
call the Standard C Library function
exit( ).
The following header file is placed in
the book’s root directory so it’s easily accessed from all
chapters.
//: :require.h
// Test for error conditions in programs
// Local "using namespace std" for old compilers
#ifndef REQUIRE_H
#define REQUIRE_H
#include <cstdio>
#include <cstdlib>
#include <fstream>
#include <string>
inline void require(bool requirement,
const std::string& msg = "Requirement failed"){
using namespace std;
if (!requirement) {
fputs(msg.c_str(), stderr);
fputs("\n", stderr);
exit(1);
}
}
inline void requireArgs(int argc, int args,
const std::string& msg =
"Must use %d arguments") {
using namespace std;
if (argc != args + 1) {
fprintf(stderr, msg.c_str(), args);
fputs("\n", stderr);
exit(1);
}
}
inline void requireMinArgs(int argc, int minArgs,
const std::string& msg =
"Must use at least %d arguments") {
using namespace std;
if(argc < minArgs + 1) {
fprintf(stderr, msg.c_str(), minArgs);
fputs("\n", stderr);
exit(1);
}
}
inline void assure(std::ifstream& in,
const std::string& filename = "") {
using namespace std;
if(!in) {
fprintf(stderr, "Could not open file %s\n",
filename.c_str());
exit(1);
}
}
inline void assure(std::ofstream& out,
const std::string& filename = "") {
using namespace std;
if(!out) {
fprintf(stderr, "Could not open file %s\n",
filename.c_str());
exit(1);
}
}
#endif // REQUIRE_H ///:~
The default values provide reasonable
messages that can be changed if necessary.
You’ll notice that instead of using
char* arguments, const string& arguments are used. This allows
both char* and strings as arguments to these functions, and thus
is more generally useful (you may want to follow this form in your own
coding).
In the definitions for
requireArgs( ) and requireMinArgs( ), one is added to
the number of arguments you need on the command line because argc always
includes the name of the program being executed as argument zero, and so always
has a value that is one more than the number of actual arguments on the command
line.
Note the use of local “using
namespace std” declarations within each function. This is because some
compilers at the time of this writing incorrectly did not include the C standard
library functions in namespace std, so explicit
qualification would cause a compile-time error. The local declaration allows
require.h to work with both correct and incorrect libraries without
opening up the namespace std for anyone who includes this header
file.
Here’s a simple program to test
require.h:
//: C09:ErrTest.cpp
//{T} ErrTest.cpp
// Testing require.h
#include "../require.h"
#include <fstream>
using namespace std;
int main(int argc, char* argv[]) {
int i = 1;
require(i, "value must be nonzero");
requireArgs(argc, 1);
requireMinArgs(argc, 1);
ifstream in(argv[1]);
assure(in, argv[1]); // Use the file name
ifstream nofile("nofile.xxx");
// Fails:
//! assure(nofile); // The default argument
ofstream out("tmp.txt");
assure(out);
} ///:~
You
might be tempted to go one step further for opening files and add a macro to
require.h:
#define IFOPEN(VAR, NAME) \
ifstream VAR(NAME); \
assure(VAR, NAME);
Which could then be used like
this:
IFOPEN(in, argv[1])
At first, this might seem appealing since
it means there’s less to type. It’s not terribly unsafe, but
it’s a road best avoided. Note that, once again, a macro looks like a
function but behaves differently; it’s actually creating an object
(in) whose scope persists beyond the macro. You may understand this, but
for new programmers and code maintainers it’s just one more thing they
have to puzzle out. C++ is complicated enough without adding to the confusion,
so try to talk yourself out of using preprocessor macros whenever you
can.
|
|
|