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> #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 ///:~
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.