9.7. Comprobación de errores mejorada

Las funciones de require.h se han usado antes de este punto sin haberlas definido (aunque assert() se ha usado también para ayudar a detectar errores del programador donde es apropiado). Ahora es el momento de definir este fichero de cabecera. Las funciones inline son convenientes aquí porque permiten colocar todo en el fichero de cabecera, lo que simplifica el proceso para usar el paquete. Simplemente incluya el fichero de cabecera y no tendrá que preocuparse por enlazar un fichero de implementación.

Debería fijarse en que las excepciones (presentadas en detalle en el Volumen 2 de este libro) proporcionan una forma mucho más efectiva de manejar muchos tipos de errores -especialmente aquellos de los que le gustaría recuperarse- en lugar de simplemente abortar el programa. Las condiciones que maneja require.h, sin embargo, son de las que impiden que el programa continúe, como por ejemplo que el usuario no introduzca suficientes argumentos en la línea de comandos o que un fichero no se puede abrir. De modo que es aceptable que usen la función exit() de la librería C estándar.

El siguiente fichero de cabecera está en el directorio raíz del libro, así que es fácilmente accesible desde todos los capítulos.

//: :require.h
// From Thinking in C++, 2nd Edition
// Available at http://www.BruceEckel.com
// (c) Bruce Eckel 2000
// Copyright notice in Copyright.txt
// Test for error conditions in programs
// Local "using namespace std" for old compilers
#ifndef REQUIRE_H
#define REQUIRE_H
#include <cstdio>&#13;
#include <cstdlib>&#13;
#include <fstream>&#13;
#include <string>&#13;
&#13;
inline void require(bool requirement, &#13;
  const std::string& msg = "Requirement failed"){&#13;
  using namespace std;&#13;
  if (!requirement) {&#13;
    fputs(msg.c_str(), stderr);&#13;
    fputs("\n", stderr);&#13;
    exit(1);&#13;
  }&#13;
}&#13;
&#13;
inline void requireArgs(int argc, int args, &#13;
  const std::string& msg = &#13;
    "Must use %d arguments") {&#13;
  using namespace std;&#13;
   if (argc != args + 1) {&#13;
     fprintf(stderr, msg.c_str(), args);&#13;
     fputs("\n", stderr);&#13;
     exit(1);&#13;
   }&#13;
}&#13;
&#13;
inline void requireMinArgs(int argc, int minArgs,&#13;
  const std::string& msg =&#13;
    "Must use at least %d arguments") {&#13;
  using namespace std;&#13;
  if(argc < minArgs + 1) {&#13;
    fprintf(stderr, msg.c_str(), minArgs);&#13;
    fputs("\n", stderr);&#13;
    exit(1);&#13;
  }&#13;
}&#13;
  &#13;
inline void assure(std::ifstream& in, &#13;
  const std::string& filename = "") {&#13;
  using namespace std;&#13;
  if(!in) {&#13;
    fprintf(stderr, "Could not open file %s\n",&#13;
      filename.c_str());&#13;
    exit(1);&#13;
  }&#13;
}&#13;
&#13;
inline void assure(std::ofstream& out, &#13;
  const std::string& filename = "") {&#13;
  using namespace std;&#13;
  if(!out) {&#13;
    fprintf(stderr, "Could not open file %s\n", &#13;
      filename.c_str());&#13;
    exit(1);&#13;
  }&#13;
}&#13;
#endif // REQUIRE_H ///:~&#13;

Los valores por defecto proporcionan mensajes razonables que se pueden cambiar si es necesario.

Fíjese en que en lugar de usar argumentos char* se utiliza const string&. Esto permite tanto char* como cadenas string como argumentos para estas funciones, y así es más general (quizá quiera utilizar esta forma en su propio código).

En las definiciones para requireArgs() y requireMinArgs(), se añade uno al número de argumentos que necesita en la línea de comandos porque argc siempre incluye el nombre del programa que se está ejecutando como argumento cero, y por eso siempre tiene un valor que excede en uno al número real de argumentos de la línea de comandos.

Fíjese en el uso de declaraciones locales using namespace std dentro de cada función. Esto es porque algunos compiladores, en el momento de escribir este libro, no incluían (incorrectamente) las funciones de la librería C estándar en el espacio de nombres std, así que la calificación explícita causaría un error en tiempo de compilación. Las declaraciones locales permiten que require.h funcione tanto con librerías correctas como con incorrectas sin abrir el espacio de nombres std para cualquiera que incluya este fichero de cabecera.

Aquí hay un programa simple para probar 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);
} ///:~

Listado 9.16. C09/ErrTest.cpp


Podría estar tentado de ir un paso más allá para manejar la apertura de ficheros y añadir una macro a require.h:

#define IFOPEN(VAR, NAME) \
      ifstream VAR(NAME); \
      assure(VAR, NAME);

Que podría usarse entonces así:

IFOPEN(in, argv[1])

En principio, esto podría parecer atractivo porque significa que hay que escribir menos. No es terriblemente inseguro, pero es un camino que es mejor evitar. Fíjese en que, de nuevo, una macro parece una función pero se comporta de forma diferente; realmente se está creando un objeto in cuyo ámbito persiste más allá de la macro. Quizá lo entienda, pero para programadores nuevos y mantenedores de código sólo es una cosa más que ellos deben resolver. C++ es suficientemente complicado sin añadir confusión, así que intente evitar las macros del preprocesador siempre que pueda.