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 se preocupe por enlazar un fichero de implementación.

Debería fijarse 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 debería recuperarse- en lugar de simplemente abortar el programa. Las condiciones que maneja require.h, sin embargo, son algunas que impiden que el programa continúe, como por ejemplo que el usuario no introdujo 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*, 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 está ejecutado 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 con cada función. Esto es porque algunos compiladores en el momento de escribir este libro incluyen incorrectamente las funciones de la librería C estándar en el espacio de nombres std, así que la cualificación explícita podría causar 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 requite.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 a 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 que, de nuevo, una macro parece una función pero se comporta diferente; realmente se está creando un objeto in cuyo alcance 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 no abusar de las macros del preprocesador siempre que pueda.