MFC Programmer's SourceBook : Thinking in C++
Bruce Eckel's Thinking in C++, 2nd Ed Contents | Prev | Next

Improved error checking

It’s convenient to improve the error checking for the rest of the book; with inline functions you can simply include the file and not worry about what to link. Up until now, the assert( ) macro has been used for “error checking,” but it’s really for debugging and should be replaced with something that provides useful information at run-time. In addition, exceptions (presented in Chapter 16) 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 described in this section, however, are ones which prevent the continuation of the program, such as if the user doesn’t provide enough command-line arguments or a file cannot be opened.

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.

The following header file will be 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>

inline void require(bool requirement, 
  const char* msg = "Requirement failed") {
  using namespace std;
  if (!requirement) {
    fprintf(stderr, "%s", msg);
    exit(1);
  }
}

inline void requireArgs(int argc, int args, 
  const char* msg = "Must use %d arguments") {
  using namespace std;
   if (argc != args + 1) {
     fprintf(stderr, msg, args);
     exit(1);
   }
}

inline void requireMinArgs(int argc, int minArgs,
  const char* msg = 
    "Must use at least %d arguments") {
  using namespace std;
  if(argc < minArgs + 1) {
    fprintf(stderr, msg, minArgs);
    exit(1);
  }
}
  
inline void assure(std::ifstream& in, 
  const char* filename = "") {
  using namespace std;
  if(!in) {
    fprintf(stderr,
      "Could not open file %s", filename);
    exit(1);
  }
}

inline void assure(std::ofstream& in, 
  const char* filename = "") {
  using namespace std;
  if(!in) {
    fprintf(stderr,
      "Could not open file %s", filename);
    exit(1);
  }
}
#endif // REQUIRE_H ///:~ 

The default values provide reasonable messages that can be changed if necessary.

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 the zeroth argument, 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.

Here’s a simple program to test require.h:

//: C09: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");
  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 macros whenever you can.

Contents | Prev | Next


Go to CodeGuru.com
Contact: webmaster@codeguru.com
© Copyright 1997-1999 CodeGuru