1
Pensar en C++ (Volumen 2) 1
Traducción (INACABADA) del libro Thinking in C++, 2
Volumen 2 3
4
Tabla de contenidos 5
6
1: Introducción 7
Tabla de contenidos 8
1.1. Objetivos 9
1.2. Capítulos 10
1.3. Ejercicios 11
1.4. Código fuente 12
1.5. Compiladores 13
1.6. Estándares del lenguaje 14
1.7. Seminarios, CD-ROMs y consultoría 15
1.8. Errores 16
1.9. Sobre la portada 17
1.10. Agradecimientos 18
En el volumen 1 de este libro, aprendió los fundamentos de C y C++. En 19
este volumen, veremos características más avanzadas, con miras hacia 20
técnicas e ideas para realizar programas robustos en C++. 21
Asumimos que está familiarizado con el material presentado en el Volumen 22
1. 23
1.1. Objetivos 24
Nuestros objetivos en este libros son: 25
1. Presentar el material como un sencillo paso cada vez, de este modo el 26
lector puede asimilar cada concepto antes de seguir adelante. 27
2. Enseñar técnicas de "programación práctica" que puede usar en la base 28
del día a día. 29
3. Darle lo que pensamos que es importante para que entienda el lenguaje, 30
más bien que todo lo que sabemos. Creemos que hay una "jerarquía de 31
2
importancia de la información", y hay algunos hechos que el 95% de los 32
programadores nunca necesitarán conocer, sino que sólo confundiría a la 33
gente y añade complejidad a su percepción del lenguaje. Tomando un 34
ejemplo de C, si memoriza la tabla de prioridad de operadores (nosotros 35
nunca lo hicimos) puede escribir código ingenioso. Pero si debe pensarlo, 36
confundirá al lector/mantenedor de ese código. De modo que olvide las 37
precedencias y use paréntesis cuando las cosas no estén claras. 38
4. Manténgase suficientemente centrado en cada sección de modo que el 39
tiempo de lectura -y el tiempo entre ejercicios- sea pequeño. Esto no sólo 40
hace mantener las mentes de los lectores más activas e involucradas 41
durante un seminario práctico sino que da al lector un mayor sentido de éxito. 42
Hemos procurado no usar ninguna versión de un vendedor particular de 43
C++. Hemos probado el código sobre todas las implementaciones que 44
pudimos (descriptas posteriormente en esta introducción), y cuando una 45
implementación no funciona en absoluto porque no cumple el Estándar C++, 46
lo hemos señalado en el ejemplo(verá las marcas en el código fuente) para 47
excluirlo del proceso de construcción. 48
6. Automatizar la compilación y las pruebas del código en el libro. Hemos 49
descubierto que el código que no está compilado y probado probablemente 50
no funcione correctamente, de este modo en este volumen hemos provisto a 51
los ejemplos con código de pruebas. Además, el código que puede 52
descargar desde http://www.MindView.net ha sido extraído directamente del 53
texto del libro usando programas que automáticamente crean makefiles para 54
compilar y ejecutar las pruebas. De esta forma sabemos que el código en el 55
libro es correcto. 56
1.2. Capítulos 57
Aquí está una breve descripción de los capítulos que contiene este libro: 58
Parte 1: Construcción de sistemas estables 59
1. Manejar excepciones. Manejar errores ha sido siempre un problema en 60
programación. Incluso si obedientemente devuelve información del error o 61
pone una bandera, la función que llama puede simplemente ignorarlo. 62
Manejar excepciones es una cualidad primordial en C++ que soluciona este 63
3
problema permitiéndole "lanzar" un objeto fuera de su función cuando ocurre 64
un error crítico. Tire diferentes tipos de objetos para diferentes errores, y la 65
función que llama "coge" estos objetos en rutinas separadas de gestión de 66
errores. Si lanza una excepción, no puede ser ignorada, de modo que puede 67
garantizar que algo ocurrirá en respuesta a su error. La decisión de usar 68
excepciones afecta al diseño del código positivamente, de modo 69
fundamental. 70
2. Programación defensiva. Muchos problemas de software pueden ser 71
prevenidos. Programar de forma defensiva es realizar cuidadosamente 72
código de tal modo que los bugs son encontrados y arreglados pronto antes 73
que puedan dañar en el campo. Usar aserciones es la única y más 74
importante forma para validar su código durante el desarrollo, dejando al 75
mismo tiempo seguimiento ejecutable de la documentación en su código que 76
muestra sus pensamientos mientras escribe el código en primer lugar. 77
Pruebe rigurosamente su código antes de darlo a otros. Un marco de trabajo 78
de pruebas unitario automatizado es una herramienta indispensable para el 79
éxito, en el desarrollo diario de software. 80
Parte 2: La biblioteca estándar de C++ 81
3. Cadenas en profundidad. La actividad más común de programación es 82
el procesamiento de texto. La clase string de C++ libera al programador de 83
los temas de gestión de memoria, mientras al mismo tiempo proporciona una 84
fuente de recursos para el procesamiento de texto. C++ también facilita el 85
uso de una gran variedad de caracteres y locales para las aplicaciones de 86
internacionalización. 87
4. Iostreams. Una de las bibliotecas original de C++-la que proporciona la 88
facilidad básica de I/O-es llamada iostreams. Iostreams está destinado a 89
reemplazar stdio.h de C con una biblioteca I/O que es más fácil de usar, más 90
flexible, y extensible- que puede adaptarla para trabajar con sus nuevas 91
clases. Este capítulo le enseña cómo hacer el mejor uso de la biblioteca 92
actual I/O, fichero I/O, y formateo en memoria. 93
5. Plantillas en profundidad. La distintiva cualidad del "C++ moderno" es el 94
extenso poder de las plantillas. Las plantillas hacen algo más que sólo crear 95
contenedores genéricos. Sostienen el desarrollo de bibliotecas robustas, 96
genéricas y de alto rendimiento.Hay mucho por saber sobre plantillas-97
4
constituyen, como fue, un sub-lenguaje dentro del lenguaje C++, y da al 98
programador un grado impresionante de control sobre el proceso de 99
compilación. No es una exageración decir que las plantillas han 100
revolucionado la programación de C++. 101
6. Algoritmos genéricos. Los algoritmos son el corazón de la informática, y 102
C++, por medio de la facilidad de las plantillas, facilita un impresionante 103
entorno de poder, eficiencia, y facilidad de uso de algoritmos genéricos. Los 104
algoritmos estándar son también personalizables a través de objetos de 105
función. Este capítulo examina cada algoritmo de la biblioteca. (Capítulos 6 y 106
7 abarcan esa parte de la biblioteca Estándar de C++ comúnmente conocida 107
como Biblioteca de Plantilla Estándar, o STL.) 108
7. Contenedores genéricos e Iteradores. C++ proporciona todas las 109
estructuras comunes de datos de modo de tipado fuerte. Nunca necesita 110
preocuparse sobre qué tiene tal contenedor. La homogenidad de sus objetos 111
está garantizada. Separar la #FIXME traversing de un contenedor del propio 112
contenedor, otra realización de plantillas, se hace posible por medio de los 113
iteradores. Este ingenioso arreglo permite una aplicación flexible de 114
algoritmos a contenedores usando el más sencillo de los diseños. 115
Parte 3: Temas especiales 116
8. Identificación de tipo en tiempo de ejecución. La identificación de tipo en 117
tiempo de ejecución (RTTI) encuentra el tipo exacto de un objeto cuando sólo 118
tiene un puntero o referencia al tipo base. Normalmente, tendrá que ignorar a 119
propósito el tipo exacto de un objeto y permitir al mecanismo de función 120
virtual implementar el comportamiento correcto para ese tipo. Pero 121
ocasionalmente (como las herramientas de escritura de software tales como 122
los depuradores) es útil para conocer el tipo exacto de un objeto-con su 123
información, puede realizar con frecuencia una operación en casos 124
especiales de forma más eficiente. Este capítulo explica para qué es RTTT y 125
como usarlo. 126
9. Herencia múltiple. Parece sencillo al principio: Una nueva clase hereda 127
de más de una clase existente. Sin embargo, puede terminar con copias 128
ambiguas y múltiples de objetos de la clase base. Ese problema está 129
resuelto con clases bases virtuales, pero la mayor cuestión continua: 130
¿Cuándo usarla? La herencia múltiple es sólo imprescindible cuando 131
5
necesite manipular un objeto por medio de más de un clase base común. 132
Este capítulo explica la sintaxis para la herencia múltiple y muestra enfoques 133
alternativos- en particular, como las plantillas solucionan un problema típico. 134
Usar herencia múltiple para reparar un interfaz de clase "dañada" demuestra 135
un uso valioso de esta cualidad. 136
10. Patrones de diseño. El más revolucionario avance en programación 137
desde los objetos es la introducción de los patrones de diseño. Un patrón de 138
diseño es una codificación independiente del lenguaje de una solución a un 139
problema de programación común, expresado de tal modo que puede 140
aplicarse a muchos contextos. Los patrones tales como Singleton, Factory 141
Method, y Visitor ahora tienen lugar en discusiones diarias alrededor del 142
teclado. Este capítulo muestra como implementar y usar algunos de los 143
patrones de diseño más usados en C++. 144
11. Programación concurrente. La gente ha llegado a esperar interfaces de 145
usuario sensibles que (parece que) procesan múltiples tareas 146
simultáneamente. Los sistemas operativos modernos permiten a los 147
procesos tener múltiples hilos que comparten el espacio de dirección del 148
proceso. La programación multihilo requiere una perspectiva diferente, sin 149
embargo, y viene con su propio conjunto de dificultades. Este capítulo utiliza 150
un biblioteca disponible gratuitamente (la biblioteca ZThread por Eric Crahen 151
de IBM) para mostrar como gestionar eficazmente aplicaciones multihilo en 152
C++. 153
1.3. Ejercicios 154
Hemos descubierto que los ejercicios sencillos son excepcionalmente 155
útiles durante un seminario para completar la comprensión de un estudiante. 156
Encontrará una colección al final de cada capítulo. 157
Estos son bastante sencillos, de modo que puedan ser acabados en una 158
suma de tiempo razonable en una situación de clase mientras el profesor 159
observa, asegurándose que todos los estudiantes están absorbiendo el 160
material. Algunos ejercicios son un poco más exigentes para mantener 161
entretenidos a los estudiantes avanzados. Están todos diseñados para ser 162
resueltos en un tiempo corto y sólo están allí para probar y refinar su 163
6
conocimiento más bien que presentar retos mayores (presumiblemente, 164
podrá encontrarlos o más probablemente ellos le encontrarán a usted). 165
1.3.1. Soluciones de los ejercicios 166
Las soluciones a los ejercicios pueden encontrarse en el documento 167
electrónico La Guía de Soluciones Comentada de C++, Volumen 2, 168
disponible por una cuota simbólica en http://www.MindView.net. 169
1.4. Código fuente 170
El código fuente para este libro está autorizado como software gratuito, 171
distribuido por medio del sitio web http://www.MindView.net. Los derechos de 172
autor le impiden volver a publicar el código impreso sin permiso. 173
En el directorio inicial donde desempaqueta el código encontrará el 174
siguiente aviso de derechos de autor: 175
Puede usar el código en sus proyectos y en clase siempre que el aviso de 176
los derechos de autor se conserve. 177
1.5. Compiladores 178
Su compilador podría no soportar todas las cualidades discutidas en este 179
libro, especialmente si no tiene la versión más nueva de su compilador. 180
Implementar un lenguaje como C++ es un tarea Hercúlea, y puede suponer 181
que las cualidades aparecen por partes en lugar de todas juntas. Pero si 182
intenta uno de los ejemplos del libro y obtiene muchos errores del 183
compilador, no es necesariamente un error en el código o en el compilador-184
puede que sencillamente no esté implementado todavía en su compilador 185
concreto. 186
Empleamos un número de compiladores para probar el código de este 187
libro, en un intento para asegurar que nuestro código cumple el Estándar 188
C++ y funcionará con todos los compiladores posibles. Desafortunadamente, 189
no todos los compiladores cumplen el Estándar C++, y de este modo 190
tenemos un modo de excluir ciertos ficheros de la construcción con esos 191
compiladores. Estas exclusiones se reflejadas automáticamente en los 192
7
makefiles creados para el paquete de código para este libro que puede 193
descargar desde www.MindView.net. Puede ver las etiquetas de exclusión 194
incrustadas en los comentarios al inicio de cada listado, de este modo sabrá 195
si exigir a un compilador concreto que funcione con ese código (en pocos 196
casos, el compilador realmente compilará el código pero el comportamiento 197
de ejecución es erróneo, y excluimos esos también). 198
Aquí están las etiquetas y los compiladores que se excluyen de la 199
construcción. 200
· {-dmc} El Compilador de Mars Digital de Walter Bright para Windows, 201
descargable gratuitamente en www.DigitalMars.com. Este compilador es muy 202
tolerante y así no verá casi ninguna de estas etiquetas en todo el libro. 203
· {-g++} La versión libre Gnu C++ 3.3.1, que viene preinstalada en la 204
mayoría de los paquetes Linux y Macintosh OSX. También es parte de 205
Cygwin para Windows (ver abajo). Está disponible para la mayoría de las 206
plataformas en gcc.gnu.org. 207
· {-msc} Microsoft Version 7 con Visual C++ .NET (viene sólo con Visual 208
Studio .NET; no descargable gratuitamente). 209
· {-bor} Borland C++ Version 6 (no la versión gratuita; éste está más 210
actualizado). 211
· {-edg} Edison Design Group (EDG) C++. Este es el compilador de 212
referencia para la conformidad con los estándares. Esta etiqueta existe a 213
causa de los temas de biblioteca, y porque estábamos usando un copia 214
gratis de la interfaz EDG con una implementación de la biblioteca gratuita de 215
Dinkumware, Ltd. No aparecieron errores de compilación a causa sólo del 216
compilador. 217
· {-mwcc} Metrowerks Code Warrior para Macintosh OS X. Fíjese que OS 218
X viene con Gnu C++ preinstalado, también. 219
Si descarga y desempaqueta el paquete de código de este libro de 220
www.MindView.net, encontrará los makefiles para construir el código para los 221
compiladores de más arriba. Usabamos GNU-make disponible gratuitamente, 222
que viene con Linux, Cygwin (una consola gratis de Unix que corre encima 223
de Windows; ver www.Cygwin.com), o puede instalar en su plataforma, ver 224
www.gnu.org/software/make. (Otros makes pueden o no funcionar con estos 225
8
ficheros, pero no están soportados.) Una vez que instale make, si teclea 226
make en la línea de comando obtendrá instrucciones de cómo construir el 227
código del libro para los compiladores de más arriba. 228
Fíjese que la colocación de estas etiquetas en los ficheros en este libro 229
indica el estado de la versión concreta del compilador en el momento que lo 230
probamos. Es posible y probable que el vendedor del compilador haya 231
mejorado el compilador desde la publicación de este libro. Es posible 232
también que mientras que realizamos el libro con tantos compiladores, 233
hayamos desconfigurado un compilador en concreto que en otro caso habría 234
compilado el código correctamente. Por consiguiente, debería probar el 235
código usted mismo con su compilador, y comprobar también el código 236
descargado de www.MindView.net para ver que es actual. 237
1.6. Estándares del lenguaje 238
A lo largo de este libro, cuando se hace referencia a la conformidad con el 239
ANSI/ISO C estándar, estaremos haciendo referencia al estándar de 1989, y 240
de forma general diremos solamente 'C.'. Sólo si es necesario distinguir entre 241
el Estándar de C de 1989 y anteriores, versiones pre-Estándares de C 242
haremos la distinción. No hacemos referencia a c99 en este libro. 243
El Comité de ANSI/ISO C++ hace mucho acabó el trabajo sobre el primer 244
Estándar C++, comunmente conocido como C++98. Usaremos el término 245
Standard C++ para referirnos a este lenguaje normalizado. Si nos referimos 246
sencillamente a C++, asuma que queremos decir "Standard C++". El Comité 247
de Estándares de C++ continua dirigiendo cuestiones importantes para la 248
comunidad de C++ que se convertirá en C++0x, un futuro Estándar de C++ 249
que no estará probablemente disponible durante muchos años. 250
1.7. Seminarios, CD-ROMs y consultoría 251
La compañía de Bruce Eckel, MindView, Inc., proporciona seminarios 252
públicos prácticos de formación basados en el material de este libro, y 253
también para temas avanzados. El material seleccionado de cada capítulo 254
representa una lección, que es sucedida por un periodo de ejercicios guiado 255
de tal modo que cada estudiante recibe atención personalizada. También 256
9
facilitamos formación en el lugar, consultoría, tutoría y comprobación de 257
diseño y código. La información y los formularios de inscripción para los 258
seminarios próximos y otra información de contacto se puede encontrar en 259
http://www.MindView.net. 260
1.8. Errores 261
No importa cuantos trucos usen los escritores para detectar errores, 262
algunos siempre pasan desapercibidos y éstos a menudo destacan para un 263
nuevo lector. Si descubre algo que cree ser un error, por favor use el sistema 264
de respuesta incorporado en la versión electrónica de este libro, que 265
encontrará en http://www.MindView.net. Su ayuda se valora. 266
Parte I. Construcción de Sistemas 267
estables 268
Los ingenieros de software gastan tanto tiempo en validar código como 269
el que tardan en crearlo. La calidad es, o debería ser, el objetivo de todo 270
programador, y se puede recorrer un largo camino hacia ese objetivo 271
eliminando problemas antes de que aparezcan. Además, los sistemas 272
software deberían ser lo suficientemente robustos como para comportarse 273
razonablemente en presencia de problemas imprevistos. 274
Las excepciones se introdujeron en C++ para facilitar una gestión de 275
errores sofisticada sin trocear el código con una innumerable cantidad de 276
lógica de error. El Capítulo 1 explica cómo el uso apropiado de las 277
excepciones puede hacer software FIXME:well-behaved, y también introduce 278
los principios de diseño que subyacen al código seguro. En el Capítulo 2 279
cubrimos las pruebas unitarias y las técnicas de depuración que prevén 280
maximizar la calidad del código antes de ser entregado. El uso de aserciones 281
para expresar y reforzar las invariantes de un programa es una señal 282
inequívoca de un ingeniero de software experimentado. También 283
introducimos un entorno simple para dar soporte a las pruebas unitarias. 284
Tabla de contenidos 285
2. Tratamiento de excepciones 286
10
2.1. Tratamiento tradicional de errores 287
2.2. Lanzar una excepción 288
2.3. Capturar una excepción 289
2.4. 290
2.5. Limpieza 291
2.6. Excepciones estándar 292
2.7. Especificaciones de excepciones 293
2.8. Seguridad de la excepción 294
2.9. Programar con excepciones 295
2.10. Sobrecarga 296
2.11. Resumen 297
2.12. Ejercicios 298
3. Programación defensiva 299
3.1. Aserciones 300
3.2. Un framework de pruebas unitarias sencillo 301
3.3. Técnicas de depuración 302
3.4. Resumen 303
3.5. Ejercicios 304
2: Tratamiento de excepciones 305
Tabla de contenidos 306
2.1. Tratamiento tradicional de errores 307
2.2. Lanzar una excepción 308
2.3. Capturar una excepción 309
2.4. 310
2.5. Limpieza 311
2.6. Excepciones estándar 312
2.7. Especificaciones de excepciones 313
2.8. Seguridad de la excepción 314
2.9. Programar con excepciones 315
2.10. Sobrecarga 316
2.11. Resumen 317
2.12. Ejercicios 318
Mejorar la recuperación de errores es una de las maneras más potentes de 319
incrementar la robustez de su código. 320
Una de las principales características de C++ es el tratamiento o manejo 321
de excepciones, el cual es una manera mejor de pensar acerca de los 322
errores y su tratamiento. Con el tratamiento de excepciones: 323
1. El código de manejo de errores no resulta tan tedioso de escribir y no se 324
entremezcla con su código «normal». Usted escribe el código que desea que 325
se ejecute, y más tarde, en una sección aparte, el código que se encarga de 326
los problemas. Si realiza varias llamadas a la misma función, el manejo de 327
errores de esa función se hará una sola vez, en un solo lugar. 328
11
2. Los errores no pueden ser ignorados. Si una función necesita enviar un 329
mensaje de error al invocador de esa función, ésta «lanza» un objeto que 330
representa a ese error fuera de la función. Si el invocador no «captura» el 331
error y lo trata, éste pasa al siguiente ámbito abarcador, y así hasta que el 332
error es capturado o el programa termina al no existir un manejador 333
adecuado para ese tipo de excepción. 334
2.1. Tratamiento tradicional de errores 335
En la mayoría de ejemplos de estos volúmenes, usamos la función assert() 336
para lo que fue concebida: para la depuración durante el desarrollo 337
insertando código que puede deshabilitarse con #define NDEBUG en un 338
producto comercial. Para la comprobación de errores en tiempo de ejecución 339
se utilizan las funciones de require.h (assure( ) y require( )) desarrolladas en 340
el capítulo 9 del Volumen 1 y repetidas aquí en el Apéndice B. Estas 341
funciones son un modo conveniente de decir, «Hay un problema aquí que 342
probablemente quiera manejar con un código algo más sofisticado, pero no 343
es necesario que se distraiga con eso en este ejemplo.» Las funciones de 344
require.h pueden parecer suficientes para programas pequeños, pero para 345
productos complicados deseará escribir un código de manejo de errores más 346
sofisticado. 347
El tratamiento de errores es bastante sencillo cuando uno sabe 348
exactamente qué hacer, puesto que se tiene toda la información necesaria 349
en ese contexto. Simplemente se trata el error en ese punto. 350
El problema ocurre cuando no se tiene suficiente información en ese 351
contexto, y se necesita pasar la información sobre el error a un contexto 352
diferente donde esa información sí que existe. En C, esta situación puede 353
tratarse usando tres enfoques: 354
2. Usar el poco conocido sistema de manejo de señales de la biblioteca 355
estándar de C, implementado en las funciones signal( ) (para determinar lo 356
que ocurre cuando se presenta un evento) y raise( ) (para generar un 357
evento). De nuevo, esta alternativa supone un alto acoplamiento debido a 358
que requiere que el usuario de cualquier biblioteca que genere señales 359
entienda e instale el mecanismo de manejo de señales adecuado. En 360
12
proyectos grandes los números de las señales de las diferentes bibliotecas 361
puede llegar a entrar en conflicto. 362
Cuando se consideran los esquemas de tratamiento de errores para C++, 363
hay un problema adicional que es crítico: Las técnicas de C de señales y 364
setjmp( )/longjmp( ) no llaman a los destructores, por lo que los objetos no se 365
limpian adecuadamente. (De hecho, si longjmp( ) salta más allá del final de 366
un ámbito donde los destructores deben ser llamados, el comportamiento del 367
programa es indefinido.) Esto hace casi imposible recuperarse efectivamente 368
de una condición excepcional, puesto que siempre se están dejando objetos 369
detrás sin limpiar y a los que ya no se tiene acceso. El siguiente ejemplo lo 370
demuestra con setjmp/longjmp: 371
//: C01:Nonlocal.cpp 372
// setjmp() & longjmp(). 373
#include <iostream> 374
#include <csetjmp> 375
using namespace std; 376
377
class Rainbow { 378
public: 379
Rainbow() { cout << "Rainbow()" << endl; } 380
~Rainbow() { cout << "~Rainbow()" << endl; } 381
}; 382
383
jmp_buf kansas; 384
385
void oz() { 386
Rainbow rb; 387
13
for(int i = 0; i < 3; i++) 388
cout << "there's no place like home" << endl; 389
longjmp(kansas, 47); 390
} 391
392
int main() { 393
if(setjmp(kansas) == 0) { 394
cout << "tornado, witch, munchkins..." << endl; 395
oz(); 396
} else { 397
cout << "Auntie Em! " 398
<< "I had the strangest dream..." 399
<< endl; 400
} 401
} ///:~ 402
Listado 2.1. C01/Nonlocal.cpp 403
404
El problema con C++ es que longjmp( ) no respeta los objetos; en 405
particular no llama a los destructores cuando salta fuera de un ámbito.[1] 406
Puesto que las llamadas a los destructores son esenciales, esta propuesta 407
no es válida para C++. De hecho, el estándar de C++ aclara que saltar a un 408
ámbito con goto (pasando por alto las llamadas a los constructores), o saltar 409
fuera de un ámbito con longjmp( ) donde un objeto en la pila posee un 410
destructor, constituye un comportamiento indefinido. 411
2.2. Lanzar una excepción 412
14
Si usted se encuentra en su código con una situación excepcional-es decir, 413
si no tiene suficiente información en el contexto actual para decidir lo que 414
hacer- puede enviar información acerca del error a un contexto mayor 415
creando un objeto que contenga esa información y «lanzándolo» fuera de su 416
contexto actual. Esto es lo que se llama lanzar una excepción. Este es el 417
aspecto que tiene: 418
//: C01:MyError.cpp {RunByHand} 419
420
class MyError { 421
const char* const data; 422
public: 423
MyError(const char* const msg = 0) : data(msg) {} 424
}; 425
426
void f() { 427
// Here we "throw" an exception object: 428
throw MyError("something bad happened"); 429
} 430
431
int main() { 432
// As you'll see shortly, we'll want a "try block" here: 433
f(); 434
} ///:~ 435
Listado 2.2. C01/MyError.cpp 436
437
15
MyError es una clase normal, que en este caso acepta un char* como 438
argumento del constructor. Usted puede usar cualquier tipo para lanzar 439
(incluyendo los tipos predefinidos), pero normalmente creará clases especial 440
para lanzar excepciones. 441
La palabra clave throw hace que suceda una serie de cosas relativamente 442
mágicas. En primer lugar se crea una copia del objeto que se está lanzando 443
y se «devuelve» desde la función que contiene la expresión throw, aun 444
cuando ese tipo de objeto no es lo que normalmente la función está diseñada 445
para devolver. Un modo simplificado de pensar acerca del tratamiento de 446
excepciones es como un mecanismo alternativo de retorno (aunque llegará a 447
tener problemas si lleva esta analogía demasiado lejos). También es posible 448
salir de ámbitos normales lanzando una excepción. En cualquier caso se 449
devuelve un valor y se sale de la función o ámbito. 450
Además es posible lanzar tantos tipos de objetos diferentes como se 451
quiera. Típicamente, para cada categoría de error se lanzará un tipo 452
diferente. La idea es almacenar la información en el objeto y en el nombre de 453
la clase con el fin de quien esté en el contexto invocador pueda averiguar lo 454
que hacer con esa excepción. 455
2.3. Capturar una excepción 456
2.3.1. El bloque try 457
try { 458
// Code that may generate exceptions 459
} 460
2.3.2. Manejadores de excepción 461
try { 462
// Code that may generate exceptions 463
} catch(type1 id1) { 464
16
// Handle exceptions of type1 465
} catch(type2 id2) { 466
// Handle exceptions of type2 467
} catch(type3 id3) 468
// Etc... 469
} catch(typeN idN) 470
// Handle exceptions of typeN 471
} 472
// Normal execution resumes here... 473
//: C01:Nonlocal2.cpp 474
// Illustrates exceptions. 475
#include <iostream> 476
using namespace std; 477
478
class Rainbow { 479
public: 480
Rainbow() { cout << "Rainbow()" << endl; } 481
~Rainbow() { cout << "~Rainbow()" << endl; } 482
}; 483
484
void oz() { 485
Rainbow rb; 486
for(int i = 0; i < 3; i++) 487
cout << "there's no place like home" << endl; 488
17
throw 47; 489
} 490
491
int main() { 492
try { 493
cout << "tornado, witch, munchkins..." << endl; 494
oz(); 495
} catch(int) { 496
cout << "Auntie Em! I had the strangest dream..." 497
<< endl; 498
} 499
} ///:~ 500
Listado 2.3. C01/Nonlocal2.cpp 501
502
2.3.3. 503
2.4. 504
//: C01:Autoexcp.cpp 505
// No matching conversions. 506
#include <iostream> 507
using namespace std; 508
509
class Except1 {}; 510
511
18
class Except2 { 512
public: 513
Except2(const Except1&) {} 514
}; 515
516
void f() { throw Except1(); } 517
518
int main() { 519
try { f(); 520
} catch(Except2&) { 521
cout << "inside catch(Except2)" << endl; 522
} catch(Except1&) { 523
cout << "inside catch(Except1)" << endl; 524
} 525
} ///:~ 526
Listado 2.4. C01/Autoexcp.cpp 527
528
//: C01:Basexcpt.cpp 529
// Exception hierarchies. 530
#include <iostream> 531
using namespace std; 532
533
class X { 534
public: 535
19
class Trouble {}; 536
class Small : public Trouble {}; 537
class Big : public Trouble {}; 538
void f() { throw Big(); } 539
}; 540
541
int main() { 542
X x; 543
try { 544
x.f(); 545
} catch(X::Trouble&) { 546
cout << "caught Trouble" << endl; 547
// Hidden by previous handler: 548
} catch(X::Small&) { 549
cout << "caught Small Trouble" << endl; 550
} catch(X::Big&) { 551
cout << "caught Big Trouble" << endl; 552
} 553
} ///:~ 554
Listado 2.5. C01/Basexcpt.cpp 555
556
2.4.1. Capturar cualquier excepción 557
catch(...) { 558
cout << "an exception was thrown" << endl; 559
20
} 560
2.4.2. Relanzar una excepción 561
catch(...) { 562
cout << "an exception was thrown" << endl; 563
// Deallocate your resource here, and then rethrow 564
throw; 565
} 566
2.4.3. Excepciones no capturadas 567
//: C01:Terminator.cpp 568
// Use of set_terminate(). Also shows uncaught exceptions. 569
#include <exception> 570
#include <iostream> 571
using namespace std; 572
573
void terminator() { 574
cout << "I'll be back!" << endl; 575
exit(0); 576
} 577
578
void (*old_terminate)() = set_terminate(terminator); 579
580
class Botch { 581
21
public: 582
class Fruit {}; 583
void f() { 584
cout << "Botch::f()" << endl; 585
throw Fruit(); 586
} 587
~Botch() { throw 'c'; } 588
}; 589
590
int main() { 591
try { 592
Botch b; 593
b.f(); 594
} catch(...) { 595
cout << "inside catch(...)" << endl; 596
} 597
} ///:~ 598
Listado 2.6. C01/Terminator.cpp 599
600
2.5. Limpieza 601
//: C01:Cleanup.cpp 602
// Exceptions clean up complete objects only. 603
#include <iostream> 604
22
using namespace std; 605
606
class Trace { 607
static int counter; 608
int objid; 609
public: 610
Trace() { 611
objid = counter++; 612
cout << "constructing Trace #" << objid << endl; 613
if(objid == 3) throw 3; 614
} 615
~Trace() { 616
cout << "destructing Trace #" << objid << endl; 617
} 618
}; 619
620
int Trace::counter = 0; 621
622
int main() { 623
try { 624
Trace n1; 625
// Throws exception: 626
Trace array[5]; 627
Trace n2; // Won't get here. 628
} catch(int i) { 629
23
cout << "caught " << i << endl; 630
} 631
} ///:~ 632
Listado 2.7. C01/Cleanup.cpp 633
634
constructing Trace #0 635 constructing Trace #1 636 constructing Trace #2 637 constructing Trace #3 638 destructing Trace #2 639 destructing Trace #1 640 destructing Trace #0 641
caught 3 642
2.5.1. Gestión de recursos 643
//: C01:Rawp.cpp 644
// Naked pointers. 645
#include <iostream> 646
#include <cstddef> 647
using namespace std; 648
649
class Cat { 650
public: 651
Cat() { cout << "Cat()" << endl; } 652
~Cat() { cout << "~Cat()" << endl; } 653
}; 654
655
class Dog { 656
public: 657
24
void* operator new(size_t sz) { 658
cout << "allocating a Dog" << endl; 659
throw 47; 660
} 661
void operator delete(void* p) { 662
cout << "deallocating a Dog" << endl; 663
::operator delete(p); 664
} 665
}; 666
667
class UseResources { 668
Cat* bp; 669
Dog* op; 670
public: 671
UseResources(int count = 1) { 672
cout << "UseResources()" << endl; 673
bp = new Cat[count]; 674
op = new Dog; 675
} 676
~UseResources() { 677
cout << "~UseResources()" << endl; 678
delete [] bp; // Array delete 679
delete op; 680
} 681
}; 682
25
683
int main() { 684
try { 685
UseResources ur(3); 686
} catch(int) { 687
cout << "inside handler" << endl; 688
} 689
} ///:~ 690
Listado 2.8. C01/Rawp.cpp 691
692
UseResources() 693 Cat() 694 Cat() 695 Cat() 696
allocating a Dog 697 inside handler 698
2.5.2. 699
//: C01:Wrapped.cpp 700
// Safe, atomic pointers. 701
#include <iostream> 702
#include <cstddef> 703
using namespace std; 704
705
// Simplified. Yours may have other arguments. 706
template<class T, int sz = 1> class PWrap { 707
T* ptr; 708
26
public: 709
class RangeError {}; // Exception class 710
PWrap() { 711
ptr = new T[sz]; 712
cout << "PWrap constructor" << endl; 713
} 714
~PWrap() { 715
delete[] ptr; 716
cout << "PWrap destructor" << endl; 717
} 718
T& operator[](int i) throw(RangeError) { 719
if(i >= 0 && i < sz) return ptr[i]; 720
throw RangeError(); 721
} 722
}; 723
724
class Cat { 725
public: 726
Cat() { cout << "Cat()" << endl; } 727
~Cat() { cout << "~Cat()" << endl; } 728
void g() {} 729
}; 730
731
class Dog { 732
public: 733
27
void* operator new[](size_t) { 734
cout << "Allocating a Dog" << endl; 735
throw 47; 736
} 737
void operator delete[](void* p) { 738
cout << "Deallocating a Dog" << endl; 739
::operator delete[](p); 740
} 741
}; 742
743
class UseResources { 744
PWrap<Cat, 3> cats; 745
PWrap<Dog> dog; 746
public: 747
UseResources() { cout << "UseResources()" << endl; } 748
~UseResources() { cout << "~UseResources()" << endl; } 749
void f() { cats[1].g(); } 750
}; 751
752
int main() { 753
try { 754
UseResources ur; 755
} catch(int) { 756
cout << "inside handler" << endl; 757
} catch(...) { 758
28
cout << "inside catch(...)" << endl; 759
} 760
} ///:~ 761
Listado 2.9. C01/Wrapped.cpp 762
763
Cat() 764 Cat() 765 Cat() 766
PWrap constructor 767 allocating a Dog 768
~Cat() 769 ~Cat() 770 ~Cat() 771
PWrap destructor 772 inside handler 773
2.5.3. auto_ptr 774
//: C01:Auto_ptr.cpp 775
// Illustrates the RAII nature of auto_ptr. 776
#include <memory> 777
#include <iostream> 778
#include <cstddef> 779
using namespace std; 780
781
class TraceHeap { 782
int i; 783
public: 784
static void* operator new(size_t siz) { 785
void* p = ::operator new(siz); 786
cout << "Allocating TraceHeap object on the heap " 787
29
<< "at address " << p << endl; 788
return p; 789
} 790
static void operator delete(void* p) { 791
cout << "Deleting TraceHeap object at address " 792
<< p << endl; 793
::operator delete(p); 794
} 795
TraceHeap(int i) : i(i) {} 796
int getVal() const { return i; } 797
}; 798
799
int main() { 800
auto_ptr<TraceHeap> pMyObject(new TraceHeap(5)); 801
cout << pMyObject->getVal() << endl; // Prints 5 802
} ///:~ 803
Listado 2.10. C01/Auto_ptr.cpp 804
805
2.5.4. Bloques try a nivel de función 806
//: C01:InitExcept.cpp {-bor} 807
// Handles exceptions from subobjects. 808
#include <iostream> 809
using namespace std; 810
30
811
class Base { 812
int i; 813
public: 814
class BaseExcept {}; 815
Base(int i) : i(i) { throw BaseExcept(); } 816
}; 817
818
class Derived : public Base { 819
public: 820
class DerivedExcept { 821
const char* msg; 822
public: 823
DerivedExcept(const char* msg) : msg(msg) {} 824
const char* what() const { return msg; } 825
}; 826
Derived(int j) try : Base(j) { 827
// Constructor body 828
cout << "This won't print" << endl; 829
} catch(BaseExcept&) { 830
throw DerivedExcept("Base subobject threw");; 831
} 832
}; 833
834
int main() { 835
31
try { 836
Derived d(3); 837
} catch(Derived::DerivedExcept& d) { 838
cout << d.what() << endl; // "Base subobject threw" 839
} 840
} ///:~ 841
Listado 2.11. C01/InitExcept.cpp 842
843
//: C01:FunctionTryBlock.cpp {-bor} 844
// Function-level try blocks. 845
// {RunByHand} (Don't run automatically by the makefile) 846
#include <iostream> 847
using namespace std; 848
849
int main() try { 850
throw "main"; 851
} catch(const char* msg) { 852
cout << msg << endl; 853
return 1; 854
} ///:~ 855
Listado 2.12. C01/FunctionTryBlock.cpp 856
857
2.6. Excepciones estándar 858
32
//: C01:StdExcept.cpp 859
// Derives an exception class from std::runtime_error. 860
#include <stdexcept> 861
#include <iostream> 862
using namespace std; 863
864
class MyError : public runtime_error { 865
public: 866
MyError(const string& msg = "") : runtime_error(msg) {} 867
}; 868
869
int main() { 870
try { 871
throw MyError("my message"); 872
} catch(MyError& x) { 873
cout << x.what() << endl; 874
} 875
} ///:~ 876
Listado 2.13. C01/StdExcept.cpp 877
878
2.7. Especificaciones de excepciones 879
void f() throw(toobig, toosmall, divzero); 880
void f(); 881
33
void f() throw(); 882
//: C01:Unexpected.cpp 883
// Exception specifications & unexpected(), 884
//{-msc} (Doesn't terminate properly) 885
#include <exception> 886
#include <iostream> 887
using namespace std; 888
889
class Up {}; 890
class Fit {}; 891
void g(); 892
893
void f(int i) throw(Up, Fit) { 894
switch(i) { 895
case 1: throw Up(); 896
case 2: throw Fit(); 897
} 898
g(); 899
} 900
901
// void g() {} // Version 1 902
void g() { throw 47; } // Version 2 903
904
void my_unexpected() { 905
34
cout << "unexpected exception thrown" << endl; 906
exit(0); 907
} 908
909
int main() { 910
set_unexpected(my_unexpected); // (Ignores return value) 911
for(int i = 1; i <=3; i++) 912
try { 913
f(i); 914
} catch(Up) { 915
cout << "Up caught" << endl; 916
} catch(Fit) { 917
cout << "Fit caught" << endl; 918
} 919
} ///:~ 920
Listado 2.14. C01/Unexpected.cpp 921
922
//: C01:BadException.cpp {-bor} 923
#include <exception> // For std::bad_exception 924
#include <iostream> 925
#include <cstdio> 926
using namespace std; 927
928
// Exception classes: 929
35
class A {}; 930
class B {}; 931
932
// terminate() handler 933
void my_thandler() { 934
cout << "terminate called" << endl; 935
exit(0); 936
} 937
938
// unexpected() handlers 939
void my_uhandler1() { throw A(); } 940
void my_uhandler2() { throw; } 941
942
// If we embed this throw statement in f or g, 943
// the compiler detects the violation and reports 944
// an error, so we put it in its own function. 945
void t() { throw B(); } 946
947
void f() throw(A) { t(); } 948
void g() throw(A, bad_exception) { t(); } 949
950
int main() { 951
set_terminate(my_thandler); 952
set_unexpected(my_uhandler1); 953
try { 954
36
f(); 955
} catch(A&) { 956
cout << "caught an A from f" << endl; 957
} 958
set_unexpected(my_uhandler2); 959
try { 960
g(); 961
} catch(bad_exception&) { 962
cout << "caught a bad_exception from g" << endl; 963
} 964
try { 965
f(); 966
} catch(...) { 967
cout << "This will never print" << endl; 968
} 969
} ///:~ 970
Listado 2.15. C01/BadException.cpp 971
972
2.7.1. ¿Mejores especificaciones de excepciones? 973
void f(); 974
void f() throw(...); // Not in C++ 975
2.7.2. Especificación de excepciones y herencia 976
37
//: C01:Covariance.cpp {-xo} 977
// Should cause compile error. {-mwcc}{-msc} 978
#include <iostream> 979
using namespace std; 980
981
class Base { 982
public: 983
class BaseException {}; 984
class DerivedException : public BaseException {}; 985
virtual void f() throw(DerivedException) { 986
throw DerivedException(); 987
} 988
virtual void g() throw(BaseException) { 989
throw BaseException(); 990
} 991
}; 992
993
class Derived : public Base { 994
public: 995
void f() throw(BaseException) { 996
throw BaseException(); 997
} 998
virtual void g() throw(DerivedException) { 999
throw DerivedException(); 1000
} 1001
38
}; ///:~ 1002
Listado 2.16. C01/Covariance.cpp 1003
1004
2.7.3. Cuándo no usar especificaciones de excepción 1005
T pop() throw(logic_error); 1006
2.8. Seguridad de la excepción 1007
void pop(); 1008
template<class T> T stack<T>::pop() { 1009
if(count == 0) 1010
throw logic_error("stack underflow"); 1011
else 1012
return data[--count]; 1013
} 1014
//: C01:SafeAssign.cpp 1015
// An Exception-safe operator=. 1016
#include <iostream> 1017
#include <new> // For std::bad_alloc 1018
#include <cstring> 1019
#include <cstddef> 1020
using namespace std; 1021
39
1022
// A class that has two pointer members using the heap 1023
class HasPointers { 1024
// A Handle class to hold the data 1025
struct MyData { 1026
const char* theString; 1027
const int* theInts; 1028
size_t numInts; 1029
MyData(const char* pString, const int* pInts, 1030
size_t nInts) 1031
: theString(pString), theInts(pInts), numInts(nInts) {} 1032
} *theData; // The handle 1033
// Clone and cleanup functions: 1034
static MyData* clone(const char* otherString, 1035
const int* otherInts, size_t nInts) { 1036
char* newChars = new char[strlen(otherString)+1]; 1037
int* newInts; 1038
try { 1039
newInts = new int[nInts]; 1040
} catch(bad_alloc&) { 1041
delete [] newChars; 1042
throw; 1043
} 1044
try { 1045
// This example uses built-in types, so it won't 1046
40
// throw, but for class types it could throw, so we 1047
// use a try block for illustration. (This is the 1048
// point of the example!) 1049
strcpy(newChars, otherString); 1050
for(size_t i = 0; i < nInts; ++i) 1051
newInts[i] = otherInts[i]; 1052
} catch(...) { 1053
delete [] newInts; 1054
delete [] newChars; 1055
throw; 1056
} 1057
return new MyData(newChars, newInts, nInts); 1058
} 1059
static MyData* clone(const MyData* otherData) { 1060
return clone(otherData->theString, otherData->theInts, 1061
otherData->numInts); 1062
} 1063
static void cleanup(const MyData* theData) { 1064
delete [] theData->theString; 1065
delete [] theData->theInts; 1066
delete theData; 1067
} 1068
public: 1069
HasPointers(const char* someString, const int* someInts, 1070
size_t numInts) { 1071
41
theData = clone(someString, someInts, numInts); 1072
} 1073
HasPointers(const HasPointers& source) { 1074
theData = clone(source.theData); 1075
} 1076
HasPointers& operator=(const HasPointers& rhs) { 1077
if(this != &rhs) { 1078
MyData* newData = clone(rhs.theData->theString, 1079
rhs.theData->theInts, rhs.theData->numInts); 1080
cleanup(theData); 1081
theData = newData; 1082
} 1083
return *this; 1084
} 1085
~HasPointers() { cleanup(theData); } 1086
friend ostream& 1087
operator<<(ostream& os, const HasPointers& obj) { 1088
os << obj.theData->theString << ": "; 1089
for(size_t i = 0; i < obj.theData->numInts; ++i) 1090
os << obj.theData->theInts[i] << ' '; 1091
return os; 1092
} 1093
}; 1094
1095
int main() { 1096
42
int someNums[] = { 1, 2, 3, 4 }; 1097
size_t someCount = sizeof someNums / sizeof someNums[0]; 1098
int someMoreNums[] = { 5, 6, 7 }; 1099
size_t someMoreCount = 1100
sizeof someMoreNums / sizeof someMoreNums[0]; 1101
HasPointers h1("Hello", someNums, someCount); 1102
HasPointers h2("Goodbye", someMoreNums, someMoreCount); 1103
cout << h1 << endl; // Hello: 1 2 3 4 1104
h1 = h2; 1105
cout << h1 << endl; // Goodbye: 5 6 7 1106
} ///:~ 1107
Listado 2.17. C01/SafeAssign.cpp 1108
1109
2.9. Programar con excepciones 1110
2.9.1. Cuándo evitar las excepciones 1111
2.9.2. Usos típicos de excepciones 1112
2.10. Sobrecarga 1113
//: C01:HasDestructor.cpp {O} 1114
class HasDestructor { 1115
public: 1116
~HasDestructor() {} 1117
}; 1118
43
1119
void g(); // For all we know, g may throw. 1120
1121
void f() { 1122
HasDestructor h; 1123
g(); 1124
} ///:~ 1125
Listado 2.18. C01/HasDestructor.cpp 1126
1127
2.11. Resumen 1128
2.12. Ejercicios 1129
3: Programación defensiva 1130
Tabla de contenidos 1131
3.1. Aserciones 1132
3.2. Un framework de pruebas unitarias sencillo 1133
3.3. Técnicas de depuración 1134
3.4. Resumen 1135
3.5. Ejercicios 1136
Escribir software puede ser un objetivo difícil para desarrolladores, pero 1137
unas pocas técnicas defensivas, aplicadas rutinariamente, pueden dirigir a un 1138
largo camino hacia la mejora de la calidad de su código. 1139
Aunque la complejidad de la producción típica de software garantiza que 1140
los probadores tendrán siempre trabajo, esperamos que anheles producir 1141
software sin defectos. Las técnicas de diseño orientada a objetos hacen 1142
mucho para limitar la dificultad de proyectos grandes, pero finalmente debe 1143
escribir bucles y funciones. Estos pequeños detalles de programación se 1144
convierten en los bloques de construcción de componentes mayores 1145
44
necesarios para sus diseños. Si sus blucles fallan por uno o sus funciones 1146
calculan los valores correctos sólo la mayoría de las veces, tiene problemas 1147
no importa como de elaborada sea su metodología general. En este capítulo, 1148
verá prácticas que ayudan a crear código robusto sin importar el tamaño de 1149
su proyecto. 1150
Su código es, entre otras cosas, una expresión de su intento de resolver 1151
un problema. Sería claro para el lector (incluyendo usted) exactamente lo 1152
que estaba pensando cuando diseño aquel bucle. En ciertos puntos de su 1153
programa, deberá crear atreverse con sentencias que considera alguna u 1154
otra condición. (Si no puede, no ha realmente solucionado todavía el 1155
problema.) Tales sentencias se llaman invariantes, puesto que deberían ser 1156
invariablemente verdad en el punto donde aparecen en el código; si no, o su 1157
diseño es defectuoso, o su código no refleja con precisión su diseño. 1158
Considere un programa que juega al juego de adivinanza mayor-menor. 1159
Una persona piensa un número entre el 1 y 100,y la otra persona adivina el 1160
número. (Permitirá al ordenador hacer la adivinanza.) La persona que piensa 1161
el número le dice al adivinador si su conjetura es mayor, menor o correcta. 1162
La mejor estrategia para el adivinador es la búsqueda binaria, que elige el 1163
punto medio del rango de los números donde el número buscado reside. La 1164
respuesta mayor-menor dice al adivinador que mitad de la lista ocupa el 1165
número, y el proceso se repite, reduciendo el tamaño del rango de búsqueda 1166
activo en cada iteración. ¿Entonces cómo escribe un bucle para realizar la 1167
repetición correctamente? No es suficiente simplemente decir 1168
bool adivinado = false; 1169
while(!adivinado) { ... } 1170
porque un usuario malintencionado podría responder engañosamente, y 1171
podría pasarse todo el día adivinando. ¿ Qué suposición, que sea sencilla, 1172
está haciendo cada vez que adivina? En otras palabras, ¿qué condición 1173
debería cumplir por diseño en cada iteración del bucle? 1174
La suposición sencilla es que el número secreto está dentro del actual 1175
rango activo de números sin adivinar: [1, 100]. Suponga que etiquetamos los 1176
puntos finales del rango con las variables bajo y alto. Cada vez que pasa por 1177
el bucle necesita asegurarse que si el número estaba en el rango [bajo, alto] 1178
45
al principio del bucle, calcule el nuevo rango de modo que todavía contenga 1179
el número al final de la iteración en curso. 1180
El objetivo es expresar el invariante del bucle en código de modo que una 1181
violación pueda ser detectada en tiempo de ejecución. Desafortunadamente, 1182
ya que el ordenador no conoce el número secreto, no puede expresar esta 1183
condición directamente en código, pero puede al menos hacer un comentario 1184
para este efecto: 1185
while(!adivinado) { // INVARIANTE: el número está en el rango [low, high] 1186
¿Qué ocurre cuando el usuario dice que una conjetura es demasiado alta o 1187
demasiado baja cuando no lo es? El engaño excluiría el número secreto del 1188
nuevo subrango. Porque una mentira siempre dirige a otra, finalmente su 1189
rango disminuirá a nada (puesto que se reduce a la mitad cada vez y el 1190
número secreto no está allí). Podemos expresar esta condición en el 1191
siguiente programa: 1192
//: C02:HiLo.cpp {RunByHand} 1193
// Plays the game of Hi-Lo to illustrate a loop invariant. 1194
#include <cstdlib> 1195
#include <iostream> 1196
#include <string> 1197
using namespace std; 1198
1199
int main() { 1200
cout << "Think of a number between 1 and 100" << endl 1201
<< "I will make a guess; " 1202
<< "tell me if I'm (H)igh or (L)ow" << endl; 1203
int low = 1, high = 100; 1204
bool guessed = false; 1205
while(!guessed) { 1206
46
// Invariant: the number is in the range [low, high] 1207
if(low > high) { // Invariant violation 1208
cout << "You cheated! I quit" << endl; 1209
return EXIT_FAILURE; 1210
} 1211
int guess = (low + high) / 2; 1212
cout << "My guess is " << guess << ". "; 1213
cout << "(H)igh, (L)ow, or (E)qual? "; 1214
string response; 1215
cin >> response; 1216
switch(toupper(response[0])) { 1217
case 'H': 1218
high = guess - 1; 1219
break; 1220
case 'L': 1221
low = guess + 1; 1222
break; 1223
case 'E': 1224
guessed = true; 1225
break; 1226
default: 1227
cout << "Invalid response" << endl; 1228
continue; 1229
} 1230
} 1231
47
cout << "I got it!" << endl; 1232
return EXIT_SUCCESS; 1233
} ///:~ 1234
Listado 3.1. C02/HiLo.cpp 1235
1236
La violación del invariante se detecta con la condición if(menor > mayor), 1237
porque si el usuario siempre dice la verdad, siempre encontraremos el 1238
número secreto antes que agotásemos los intentos. 1239
Usamos también una técnica del estándar C para informar sobre el estado 1240
de un programa al contexto llamante devolviendo diferentes valores desde 1241
main( ). Es portable para usar la sentencia return 0; para indicar éxito, pero 1242
no hay un valor portable para indicar fracaso. Por esta razón usamos la 1243
macro declarada para este propósito en <cstdlib>:EXIT_FAILURE. Por 1244
consistencia, cuando usamos EXIT_FAILURE también usamos 1245
EXIT_SUCCESS, a pesar de que éste es siempre definido como cero. 1246
3.1. Aserciones 1247
La condición en el programa mayor-menor depende de la entrada del 1248
usuario, por lo tanto no puede prevenir una violación del invariante. Sin 1249
embargo, los invariantes normalmente dependen solo del código que escribe, 1250
por eso comprobarán siempre si ha implementado su diseño correctamente. 1251
En este caso, es más claro hacer una aserción, que es un sentencia positiva 1252
que muestra sus decisiones de diseño. 1253
Suponga que está implementando un vector de enteros: un array 1254
expandible que crece a petición. La función que añade un elemento al vector 1255
debe primero verificar que hay un espacio vacío en el array subyacente que 1256
contiene los elementos; de lo contrario, necesita solicitar más espacio en la 1257
pila y copiar los elementos existentes al nuevo espacio antes de añadir el 1258
nuevo elemento (y borrar el viejo array). Tal función podría ser de la siguiente 1259
forma: 1260
48
void MyVector::push_back(int x) { 1261
if(nextSlot == capacity) 1262
grow(); 1263
assert(nextSlot < capacity); 1264
data[nextSlot++] = x; 1265
} 1266
En este ejemplo, la información es un array dinámico de ints con 1267
capacidad espacios y espacioSiguiente espacios en uso. El propósito de 1268
grow( ) es expandir el tamaño de la información para que el nuevo valor de 1269
capacidad sea estrictamente mayor que espacioSiguiente. El 1270
comportamiento correcto de MiVector depende de esta decisión de diseño, y 1271
nunca fallará si el resto del código secundario es correcto. Afirmamos la 1272
condición con la macro assert( ), que está definido en la cabecera <cassert>. 1273
La macro assert( ) de la biblioteca Estándar de C es breve, que resulta, 1274
portable. Si la condición en su parámetro no evalúa a cero, la ejecución 1275
continúa ininterrumpidamente; si no, un mensaje contiene el texto de la 1276
expresión culpable con su nombre de fichero fuente y el número de línea 1277
impreso en el canal de error estándar y el programa se suspende. ¿Es eso 1278
tan drástico? En la práctica, es mucho más drástico permitir que la ejecución 1279
continue cuando un supuesto de diseño básico ha fracasado. Su programa 1280
necesita ser arreglado. 1281
Si todo va bien, probará a conciencia su código con todas las aserciones 1282
intactas hasta el momento en que se haga uso del producto final. (Diremos 1283
más sobre pruebas más tarde.) Depende de la naturaleza de su aplicación, 1284
los ciclos de máquina necesarios para probar todas las aserciones en tiempo 1285
de ejecución podrían tener demasiado impacto en el rendimiento en 1286
producción. En ese caso, puede eliminar todas las aserciones del código 1287
automáticamente definiendo la macro NDEBUG y reconstruir la aplicación. 1288
Para ver como funciona esto, observe que una implementación típica de 1289
assert( ) se parece a esto: 1290
49
#ifdef NDEBUG 1291
#define assert(cond) ((void)0) 1292
#else 1293
void assertImpl(const char*, const char*, long); 1294
#define assert(cond) \ 1295
((cond) ? (void)0 : assertImpl(???)) 1296
#endif 1297
Cuando la macro NDEBUG está definida, el código se descompone a la 1298
expresión (void) 0, todo lo que queda en la cadena de compilación es una 1299
sentencia esencialmente vacía como un resultado de la semicolumna que 1300
añade a cada invocación de assert( ). Si NDEBUG no está definido, 1301
assert(cond) se expande a una sentencia condicional que, cuando cond es 1302
cero, llama a una función dependiente del compilador (que llamamos 1303
assertImpl( )) con argumento string representando el texto de cond, junto con 1304
el nombre de fichero y el número de línea donde aparece la aserción. 1305
(Usamos como un marcador de posición en el ejemplo, pero la cadena 1306
mencionada es de hecho computada allí, junto con el nombre del fichero y el 1307
número de línea donde la macro aparece en ese fichero. Como estos valores 1308
se obtienen es irrelevante para nuestra discusión.) Si quiere activar y 1309
desactivar aserciones en diferentes puntos de su programa, no debe solo 1310
#define o #undef NDEBUG, sino que debe también reincluir <cassert>. Las 1311
macros son evaluadas cuando el preprocesador los encuentra y así usa 1312
cualquier estado NDEBUG se aplica en el punto de inclusión. El camino más 1313
común define NDEBUG una vez para todo el programa es como una opción 1314
del compilador, o mediante la configuración del proyecto en su entorno visual 1315
o mediante la línea de comandos, como en: 1316
mycc NDEBUG myfile.cpp 1317
La mayoría de los compiladores usan la bandera para definir los nombres 1318
de las macros. (Substituya el nombre del ejecutable de su compiladores por 1319
mycc arriba.) La ventaja de este enfoque es que puede dejar sus aserciones 1320
50
en el código fuente como un inapreciable parte de documentación, y no hay 1321
aún castigo en tiempo de ejecución. Porque el código en una aserción 1322
desaparece cuando NDEBUG está definido, es importante que no haga 1323
trabajo en una aserción. Sólo las condiciones de prueba que no cambien el 1324
estado de su programa. 1325
Si usar NDEBUG para liberar código es una buena idea queda un tema de 1326
debate. Tony Hoare, una de los más influyentes expertos en informática de 1327
todos los tiempos,[15] ha sugerido que desactivando las comprobaciones en 1328
tiempo de ejecución como las aserciones es similar a un entusiasta de 1329
navegación que lleva un chaleco salvavidas mientras entrena en tierra y 1330
luego se deshace de él cuando va al mar.[16] Si una aserción falla en 1331
producción, tiene un problema mucho peor que la degradación en 1332
rendimiento, así que elija sabiamente. 1333
No todas las condiciones deberían ser cumplidas por aserciones. Los 1334
errores de usuario y los fallos de los recursos en tiempos de ejecución 1335
deberían ser señalados lanzando excepciones, como explicamos en detalle 1336
en el Capítulo 1. Es tentador usar aserciones para la mayoría de las 1337
condiciones de error mientras esbozamos código, con el propósito de 1338
remplazar muchos de ellos después con un manejador de excepciones 1339
robusto. Como cualquier otra tentación, úsese con moderación, pues podría 1340
olvidar hacer todos los cambios necesarios más tarde. Recuerde: las 1341
aserciones tienen la intención de verificar decisiones de diseño que fallarán 1342
sólo por lógica defectuosa del programador. Lo ideal es solucionar todas las 1343
violaciones de aserciones durante el desarrollo. No use aserciones para 1344
condiciones que no están totalmente en su control (por ejemplo, condiciones 1345
que dependen de la entrada del usuario). En particular, no querría usar 1346
aserciones para validar argumentos de función; lance un logic_error en su 1347
lugar. 1348
El uso de aserciones como una herramienta para asegurar la corrección de 1349
un programa fue formalizada por Bertran Meyer en su Diseño mediante 1350
metodología de contrato.[17] Cada función tiene un contrato implícito con los 1351
clientes que, dadas ciertas precondiciones, garantiza ciertas 1352
postcondiciones. En otras palabras, las precondiciones son los 1353
requerimientos para usar la función, como los argumentos que se facilitan 1354
51
dentro de ciertos rangos, y las postcondiciones son los resultados enviados 1355
por la función o por retorno por valor o por efecto colateral. 1356
Cuando los programas clientes fallan al darle un entrada válida, debe 1357
comentarles que han roto el contrato. Este no es el mejor momento para 1358
suspender el programa (aunque está justificado hacerlo desde que el 1359
contrato fue violado), pero una excepción es desde luego apropiada. Esto es 1360
porque la librería Estándar de C++ lanza excepciones derivadas de 1361
logic_error, como out_of_range.[18] Si hay funciones que sólo usted llama, 1362
no obstante, como funciones privadas en una clase de su propio diseño, la 1363
macro assert( ) es apropiada, puesto que tiene total control sobre la situación 1364
y desde luego quiere depurar su código antes de enviarlo. 1365
Una postcondición fallada indica un error de programa, y es apropiado usar 1366
aserciones para cualquier invariante en cualquier momento, incluyendo la 1367
postcondición de prueba al final de una función. Esto se aplica en particular a 1368
las funciones de una clase que mantienen el estado de un objeto. En el 1369
ejemplo MyVector previo, por ejemplo, un invariante razonable para todas las 1370
funciones sería: 1371
assert(0 <= siguienteEspacio && siguienteEspacio <= capacidad); 1372
o, si siguienteEspacio es un integer sin signo, sencillamente 1373
assert(siguienteEspacio <= capacidad); 1374
Tal tipo de invariante se llama invariante de clase y puede ser 1375
razonablemente forzada por una aserción. Las subclases juegan un papel de 1376
subcontratista para sus clases base porque deben mantener el contrato 1377
original entre la clase base y sus clientes. Por esta razón, las precondiciones 1378
en clases derivadas no deben imponer requerimientos adicionales más allá 1379
de aquellos del contrato base, y las postcondiciones deben cumplir al menos 1380
como mucho.[19] 1381
Validar resultados devueltos por el cliente, sin embargo, no es más o 1382
menos que probar, de manera que usar aserciones de postcondición en este 1383
caso sería duplicar trabajo. Sí, es buena documentación, pero más de un 1384
desarrollador has sido engañado usando incorrectamente las aserciones de 1385
post-condición como un substituto para pruebas de unidad. 1386
52
3.2. Un framework de pruebas unitarias sencillo 1387
Escribir software es todo sobre encontrar requerimientos.[20] Crear estos 1388
requerimientos es difícil, y pueden cambiar de un día a otro; podría descubrir 1389
en una reunión de proyecto semanal que lo que ha empleado la semana 1390
haciendo no es exactamente lo que los usuarios realmente quieren. 1391
Las personas no pueden articular requerimientos de software sin 1392
muestrear un sistema de trabajo en evolución. Es mucho mejor especificar 1393
un poco, diseñar un poco, codificar un poco y probar un poco. Entonces, 1394
después de evaluar el resultado, hacerlo todo de nuevo. La habilidad para 1395
desarrollar con una moda iterativa es uno de los mejores avances del 1396
enfoque orientado a objetos, pero requiere programadores ágiles que pueden 1397
hacer código fuerte. El cambio es duro. 1398
Otro ímpetu para el cambio viene de usted, el programador. El artífice que 1399
hay en usted quiere continuamente mejorar el diseño de su código. ¿Qué 1400
programador de mantenimiento no ha maldecido el envejecimiento, el 1401
producto de la compañía insignia como un mosaico de espaguetis 1402
inmodificable, enrevesado? La reluctancia de los supervisores en permitir 1403
que uno interfiera con un sistema que funciona le roba al código la flexibilidad 1404
que necesita para que perdure. Si no está roto, no arreglarlo finalmente le da 1405
el camino para, no podemos arreglarlo reescribámoslo. El cambio es 1406
necesario. 1407
Afortunadamente, nuestra industria está creciendo acostumbrada a la 1408
disciplina de refactoring, el arte de reestructura internamente código para 1409
mejorar su diseño, sin cambiar su comportamiento.[21] Tales mejoras 1410
incluyen extraer una nueva función de otra, o de forma inversa, combinar 1411
funciones, reemplazar una función con un objeto; parametrizar una función o 1412
clase; y reemplazar condicionales con polimorfismo. Refactorizar ayuda al 1413
código evolucionar. 1414
Si la fuerza para el cambio viene de los usuarios o programadores, los 1415
cambios hoy pueden destrozar lo trabajado ayer. Necesitamos un modo para 1416
construir código que resista el cambio y mejoras a lo largo del tiempo. 1417
La Programación Extrema (XP)[22] es sólo uno de las muchas prácticas 1418
que motivan la agilidad. En esta sección exploramos lo que pensamos es la 1419
53
clave para hacer un desarrollo flexible, incremental que tenga éxito: un 1420
framework de pruebas unitarias automatizada fácil de usar. (Note que los 1421
probadores, profesionales de software que prueban el código de otros para 1422
ganarse la vida, son todavía indispensables. Aquí, estamos simplemente 1423
describiendo un modo para ayudar a los desarrolladores a escribir mejor 1424
código.) 1425
Los desarrolladores escriben pruebas unitarias para conseguir confianza 1426
para decir las dos cosas más importantes que cualquier desarrollador puede 1427
decir: 1428
1. Entiendo los requerimientos. 1429
Mi código cumple esos requerimientos (hasta donde yo sé) 1430
No hay mejor modo para asegurar que sabe lo que el código que está por 1431
escribir debería hacer mejor que escribir primero pruebas unitarias. Este 1432
ejercicio sencillo ayuda a centrar la mente en las tareas siguientes y 1433
probablemente guiará a código que funcionalmente más rápido mejor que 1434
sólo saltar a codificar. O, expresarlo en términos XP: 1435
Probar + programar es más rápido que sólo programar. 1436
Escribir primero pruebas sólo le protegen contra condiciones límite que 1437
podrían destrozar su código, por lo tanto su código es más robusto. 1438
Cuando su código pasa todas sus pruebas, sabe que si el sistema no está 1439
funcionando, su código no es probablemente el problema. La frase todas mis 1440
pruebas funcionan es un fuerte razonamiento. 1441
3.2.1. Pruebas automatizadas 1442
Por lo tanto, ¿qué aspecto tiene una prueba unitaria? Demasiado a 1443
menudo los desarrolladores simplemente usan alguna entrada correcta para 1444
producir alguna salida esperada, que examinan visualmente. Existen dos 1445
peligros en este enfoque. Primero, los programas no siempre reciben sólo 1446
entradas correctas. Todos sabemos que deberíamos probar los límites de 1447
entrada de un programa, pero es duro pensar esto cuando está intentando 1448
simplemente hacer que las cosas funcionar. Si escribe primero la prueba 1449
para una función antes de comenzar a codificar, puede ponerse su traje de 1450
probador y preguntarse a si mismo, ¿qué haría posiblemente destrozar esto? 1451
54
Codificar una prueba que probará la función que escribirá no es erróneo, y 1452
luego ponerte el traje de desarrollador y hacerlo pasar. Escribirá mejor 1453
código que si no había escrito la prueba primero. 1454
El segundo peligro es que esperar una salida visualmente es tedioso y 1455
propenso a error. La mayoría de cualquier tipo de cosas que un humano 1456
puede hacer un ordenador puede hacerlas, pero sin el error humano. Es 1457
mejor formular pruebas como colecciones de expresiones boolean y tener un 1458
programa de prueba que informa de cualquier fallo. 1459
Por ejemplo, suponga que necesita construir una clase Fecha que tiene las 1460
siguientes propiedades: 1461
Una fecha puede estar inicializada con una cadena (AAAAMMDD), 3 1462
enteros (A, M, D), o nada (dando la fecha de hoy). 1463
Un objecto fecha puede producir su año, mes y día o una cadena de la 1464
forma AAAAMMDD. 1465
Todas las comparaciones relacionales están disponibles, además de 1466
calcular la duración entre dos fechas (en años, meses, y días). 1467
Las fechas para ser comparadas necesitan poder extenderse un número 1468
arbitrario de siglos(por ejemplo, 16002200). 1469
Su clase puede almacenar tres enteros que representan el año, mes y día. 1470
(Sólo asegúrese que el año es al menos de 16 bits de tamaño para satisfacer 1471
el último punto.) La interfaz de su clase Fecha se podría parecer a esto: 1472
//: C02:Date1.h 1473
// A first pass at Date.h. 1474
#ifndef DATE1_H 1475
#define DATE1_H 1476
#include <string> 1477
1478
class Date { 1479
public: 1480
55
// A struct to hold elapsed time: 1481
struct Duration { 1482
int years; 1483
int months; 1484
int days; 1485
Duration(int y, int m, int d) 1486
: years(y), months(m), days(d) {} 1487
}; 1488
Date(); 1489
Date(int year, int month, int day); 1490
Date(const std::string&); 1491
int getYear() const; 1492
int getMonth() const; 1493
int getDay() const; 1494
std::string toString() const; 1495
friend bool operator<(const Date&, const Date&); 1496
friend bool operator>(const Date&, const Date&); 1497
friend bool operator<=(const Date&, const Date&); 1498
friend bool operator>=(const Date&, const Date&); 1499
friend bool operator==(const Date&, const Date&); 1500
friend bool operator!=(const Date&, const Date&); 1501
friend Duration duration(const Date&, const Date&); 1502
}; 1503
#endif // DATE1_H ///:~ 1504
56
Listado 3.2. C02/Date1.h 1505
1506
Antes de que implemente esta clase, puede solidificar sus conocimientos 1507
de los requerimientos escribiendo el principio de un programa de prueba. 1508
Podría idear algo como lo siguiente: 1509
//: C02:SimpleDateTest.cpp 1510
//{L} Date 1511
#include <iostream> 1512
#include "Date.h" // From Appendix B 1513
using namespace std; 1514
1515
// Test machinery 1516
int nPass = 0, nFail = 0; 1517
void test(bool t) { if(t) nPass++; else nFail++; } 1518
1519
int main() { 1520
Date mybday(1951, 10, 1); 1521
test(mybday.getYear() == 1951); 1522
test(mybday.getMonth() == 10); 1523
test(mybday.getDay() == 1); 1524
cout << "Passed: " << nPass << ", Failed: " 1525
<< nFail << endl; 1526
} 1527
/* Expected output: 1528
Passed: 3, Failed: 0 1529
57
*/ ///:~ 1530
Listado 3.3. C02/SimpleDateTest.cpp 1531
1532
En este caso trivial, la función test( ) mantiene las variables globales 1533
nAprobar y nSuspender. La única revisión visual que hace es leer el 1534
resultado final. Si una prueba falla, un test( ) más sofisticado muestra un 1535
mensaje apropiado. El framework descrito más tarde en este capítulo tiene 1536
un función de prueba, entre otras cosas. 1537
Puede ahora implementar la clase Fecha para hacer pasar estas pruebas, 1538
y luego puede proceder iterativamente hasta que se satisfagan todos los 1539
requerimientos. Escribiendo primero pruebas, es más probable que piense 1540
en casos límite que podrían destrozar su próxima implementación, y es más 1541
probable que escriba el código correctamente la primera vez. Como ejercicio 1542
podría realizar la siguiente versión de una prueba para la clase Fecha: 1543
//: C02:SimpleDateTest2.cpp 1544
//{L} Date 1545
#include <iostream> 1546
#include "Date.h" 1547
using namespace std; 1548
1549
// Test machinery 1550
int nPass = 0, nFail = 0; 1551
void test(bool t) { if(t) ++nPass; else ++nFail; } 1552
1553
int main() { 1554
Date mybday(1951, 10, 1); 1555
Date today; 1556
58
Date myevebday("19510930"); 1557
1558
// Test the operators 1559
test(mybday < today); 1560
test(mybday <= today); 1561
test(mybday != today); 1562
test(mybday == mybday); 1563
test(mybday >= mybday); 1564
test(mybday <= mybday); 1565
test(myevebday < mybday); 1566
test(mybday > myevebday); 1567
test(mybday >= myevebday); 1568
test(mybday != myevebday); 1569
1570
// Test the functions 1571
test(mybday.getYear() == 1951); 1572
test(mybday.getMonth() == 10); 1573
test(mybday.getDay() == 1); 1574
test(myevebday.getYear() == 1951); 1575
test(myevebday.getMonth() == 9); 1576
test(myevebday.getDay() == 30); 1577
test(mybday.toString() == "19511001"); 1578
test(myevebday.toString() == "19510930"); 1579
1580
// Test duration 1581
59
Date d2(2003, 7, 4); 1582
Date::Duration dur = duration(mybday, d2); 1583
test(dur.years == 51); 1584
test(dur.months == 9); 1585
test(dur.days == 3); 1586
1587
// Report results: 1588
cout << "Passed: " << nPass << ", Failed: " 1589
<< nFail << endl; 1590
} ///:~ 1591
Listado 3.4. C02/SimpleDateTest2.cpp 1592
1593
Esta prueba puede ser desarrollada por completo. Por ejemplo, no hemos 1594
probado que duraciones grandes son manejadas correctamente. Pararemos 1595
aquí, pero coja la idea. La implementación entera para la case Fecha está 1596
disponible en los ficheros Date.h y Date.cpp en el apéndice.[23] 1597
3.2.2. El Framework TestSuite 1598
Algunas herramientas de pruebas unitarias automatizadas de C++ están 1599
disponibles en la World Wide Web para descargar, como CppUnit.[24] 1600
Nuestra intención aquí no es sólo presentar un mecanismo de prueba que 1601
sea fácil de usar, sino también fácil de entender internamente e incluso 1602
modificar si es necesario. Por lo tanto, en el espíritu de Hacer Lo Más Simple 1603
Que Podría Posiblemente Funcionar,[25] hemos desarrollado el Framework 1604
TestSuite, un espacio de nombres llamado TestSuite que contiene dos 1605
clases principales: Test y Suite. 1606
La clase Test es una clase base abstracta de la cual deriva un objeto test. 1607
Tiene constancia del número de éxitos y fracasos y muestra el texto de 1608
cualquier condición de prueba que falla. Simplemente para sobreescribir la 1609
60
función run( ), que debería llamar en turnos a la macro test_() para cada 1610
condición de prueba boolean que defina. 1611
Para definir una prueba para la clase Fecha usando el framework, puede 1612
heredar de Test como se muetra en el siguiente programa: 1613
//: C02:DateTest.h 1614
#ifndef DATETEST_H 1615
#define DATETEST_H 1616
#include "Date.h" 1617
#include "../TestSuite/Test.h" 1618
1619
class DateTest : public TestSuite::Test { 1620
Date mybday; 1621
Date today; 1622
Date myevebday; 1623
public: 1624
DateTest(): mybday(1951, 10, 1), myevebday("19510930") {} 1625
void run() { 1626
testOps(); 1627
testFunctions(); 1628
testDuration(); 1629
} 1630
void testOps() { 1631
test_(mybday < today); 1632
test_(mybday <= today); 1633
test_(mybday != today); 1634
61
test_(mybday == mybday); 1635
test_(mybday >= mybday); 1636
test_(mybday <= mybday); 1637
test_(myevebday < mybday); 1638
test_(mybday > myevebday); 1639
test_(mybday >= myevebday); 1640
test_(mybday != myevebday); 1641
} 1642
void testFunctions() { 1643
test_(mybday.getYear() == 1951); 1644
test_(mybday.getMonth() == 10); 1645
test_(mybday.getDay() == 1); 1646
test_(myevebday.getYear() == 1951); 1647
test_(myevebday.getMonth() == 9); 1648
test_(myevebday.getDay() == 30); 1649
test_(mybday.toString() == "19511001"); 1650
test_(myevebday.toString() == "19510930"); 1651
} 1652
void testDuration() { 1653
Date d2(2003, 7, 4); 1654
Date::Duration dur = duration(mybday, d2); 1655
test_(dur.years == 51); 1656
test_(dur.months == 9); 1657
test_(dur.days == 3); 1658
} 1659
62
}; 1660
#endif // DATETEST_H ///:~ 1661
Listado 3.5. C02/DateTest.h 1662
1663
Ejecutar la prueba es una sencilla cuestión de instaciación de un objeto 1664
DateTest y llamar a su función run( ): 1665
//: C02:DateTest.cpp 1666
// Automated testing (with a framework). 1667
//{L} Date ../TestSuite/Test 1668
#include <iostream> 1669
#include "DateTest.h" 1670
using namespace std; 1671
1672
int main() { 1673
DateTest test; 1674
test.run(); 1675
return test.report(); 1676
} 1677
/* Output: 1678
Test "DateTest": 1679
Passed: 21, Failed: 0 1680
*/ ///:~ 1681
Listado 3.6. C02/DateTest.cpp 1682
1683
63
La función Test::report( ) muestra la salida previa y devuelve el número de 1684
fallos, de este modo es conveniente usarlo como valor de retorno desde el 1685
main( ). 1686
La clase Test usa RTTI[26] para obtener el nombre de su clase(por 1687
ejemplo, DateTest) para el informe. Hay también una función setStream() si 1688
quiere enviar los resultados de la prueba a un fichero en lugar de la salida 1689
estándar (por defecto). Verá la implementación de la clase Test más tarde en 1690
este capítulo. 1691
La macro test_( ) puede extraer el texto de la condición booleana que falla, 1692
junto con el nombre del fichero y número de línea.[27] Para ver lo que ocurre 1693
cuando un fallo aparece, puede insertar un error intencionado en el código, 1694
por ejemplo invirtiendo la condición en la primera llamda a test_( ) en 1695
DateTest::testOps( ) en el código de ejemplo previo. La salida indica 1696
exactamente que la prueba tenía un error y dónde ocurrió: 1697
DateTest fallo: (mybday > hoy) , DateTest.h (línea 31) Test "DateTest": 1698
Passados: 20 Fallados: 1 1699
Además de test_( ), el framework incluye las funciones succed_( ) y fail_( ), 1700
para casos donde una prueba Boolean no funcionará. Estas funciones se 1701
aplican cuando la clase que está probando podría lanzar excepciones. 1702
Durante la prueba, crear un conjunto de entrada que causará que la 1703
excepción aparezca. Si no, es un error y puede llamar a fail_( ) 1704
explicitamente para mostrar un mensaje y actualizar el contador de fallos. Si 1705
lanza la excecpión como se esperaba, llame a succeed_( ) para actualizar el 1706
contador de éxitos. 1707
Para ilustrar, suponga que modificamos la especificación de los dos 1708
constructor no por defecto de Date para lanzar una excepción DateError (un 1709
tipo anidado dentro de Date y derivado de std::logic_error) si los parámetros 1710
de entrada no representa un fecha válida: Date(const string& s) 1711
throw(DateError); Date(int year, int month, int day) throw(DateError); 1712
La función DateTest::run( ) puede ahora llamar a la siguiente función para 1713
probar el manejo de excepciones: 1714
void testExceptions() { 1715
64
try { 1716
Date d(0,0,0); // Invalid 1717
fail_("Invalid date undetected in Date int ctor"); 1718
} catch(Date::DateError&) { 1719
succeed_(); 1720
} 1721
try { 1722
Date d(""); // Invalid 1723
fail_("Invalid date undetected in Date string ctor"); 1724
} catch(Date::DateError&) { 1725
succeed_(); 1726
} 1727
} 1728
En ambos casos, si una excepción no se lanza, es un error. Fíjese que 1729
debe pasar manualmente un mensaje a fail_( ), pues no se está evaluando 1730
una expresión booleana. 1731
3.2.3. Suites de test 1732
Los proyectos reales contienen normalmente muchas clases, por lo tanto 1733
necesita un modo para agrupar pruebas para que pueda simplemente pulsar 1734
un solo botón para probar el proyecto entero.[28] La clase Suite recoge 1735
pruebas en una unidad funcional. Añada objetos Test a Suite con la función 1736
addTest( ), o puede incluir una suite existente entera con addSuite( ). Para 1737
ilustrar, el siguiente ejemplo reúna los programas del Capítulo 3 que usa la 1738
clase Test en una sola suite. Fíjese que este fichero aparecerá en el 1739
subdirectorio del Capítulo 3: 1740
65
//: C03:StringSuite.cpp 1741
//{L} ../TestSuite/Test ../TestSuite/Suite 1742
//{L} TrimTest 1743
// Illustrates a test suite for code from Chapter 3 1744
#include <iostream> 1745
#include "../TestSuite/Suite.h" 1746
#include "StringStorage.h" 1747
#include "Sieve.h" 1748
#include "Find.h" 1749
#include "Rparse.h" 1750
#include "TrimTest.h" 1751
#include "CompStr.h" 1752
using namespace std; 1753
using namespace TestSuite; 1754
1755
int main() { 1756
Suite suite("String Tests"); 1757
suite.addTest(new StringStorageTest); 1758
suite.addTest(new SieveTest); 1759
suite.addTest(new FindTest); 1760
suite.addTest(new RparseTest); 1761
suite.addTest(new TrimTest); 1762
suite.addTest(new CompStrTest); 1763
suite.run(); 1764
long nFail = suite.report(); 1765
66
suite.free(); 1766
return nFail; 1767
} 1768
/* Output: 1769
s1 = 62345 1770
s2 = 12345 1771
Suite "String Tests" 1772
==================== 1773
Test "StringStorageTest": 1774
Passed: 2 Failed: 0 1775
Test "SieveTest": 1776
Passed: 50 Failed: 0 1777
Test "FindTest": 1778
Passed: 9 Failed: 0 1779
Test "RparseTest": 1780
Passed: 8 Failed: 0 1781
Test "TrimTest": 1782
Passed: 11 Failed: 0 1783
Test "CompStrTest": 1784
Passed: 8 Failed: 0 1785
*/ ///:~ 1786
Listado 3.7. C03/StringSuite.cpp 1787
1788
5 de los tests de más arriba están completamente contenidos en los 1789
ficheros de cabecera. TrimTest no lo está, porque contiene datos estáticos 1790
que deben estar definidos en un fichero de implementación. Las dos 1791
67
primeras líneas de salida son trazos de la prueba StringStorage. Debe dar a 1792
la suite un nombre como argumento del constructor. La función Suite::run( ) 1793
llama a Test::run( ) po cada una de las pruebas que tiene. Más de lo mismo 1794
pasa con Suite::report( ), excepto que puede enviar los informes de pruebas 1795
individuales a cadenas destinaciones diferentes mejor que el informe de la 1796
suite. Si la prueba pasa a addSuite( ) ya tiene un puntero de cadena 1797
asignado, que lo guarda. En otro caso, obtiene su cadena del objeto Suite. 1798
(Como con Test, hay un segundo argumento opcional para el constructor 1799
suite que no se presenta a std::cout.) El destructor para Suite no borra 1800
automáticamente los punteros contenidos en Test porque no necesitan 1801
residir en la pila; este es el trabajo de Suite::free( ). 1802
3.2.4. El código del framework de prueba 1803
El código del framework de pruebas es un subdirectorio llamado TestSuite 1804
en la distribución de código disponible en www.MindView.net. Para usarlo, 1805
incluya la ruta de búsqueda para el subdirectorio TestSuite en la ruta de 1806
búsqueda de la biblioteca. Aquí está la cabecera para Test.h: 1807
//: TestSuite:Test.h 1808
#ifndef TEST_H 1809
#define TEST_H 1810
#include <string> 1811
#include <iostream> 1812
#include <cassert> 1813
using std::string; 1814
using std::ostream; 1815
using std::cout; 1816
1817
// fail_() has an underscore to prevent collision with 1818
// ios::fail(). For consistency, test_() and succeed_() 1819
68
// also have underscores. 1820
1821
#define test_(cond) \ 1822
do_test(cond, #cond, __FILE__, __LINE__) 1823
#define fail_(str) \ 1824
do_fail(str, __FILE__, __LINE__) 1825
1826
namespace TestSuite { 1827
1828
class Test { 1829
ostream* osptr; 1830
long nPass; 1831
long nFail; 1832
// Disallowed: 1833
Test(const Test&); 1834
Test& operator=(const Test&); 1835
protected: 1836
void do_test(bool cond, const string& lbl, 1837
const char* fname, long lineno); 1838
void do_fail(const string& lbl, 1839
const char* fname, long lineno); 1840
public: 1841
Test(ostream* osptr = &cout) { 1842
this->osptr = osptr; 1843
nPass = nFail = 0; 1844
69
} 1845
virtual ~Test() {} 1846
virtual void run() = 0; 1847
long getNumPassed() const { return nPass; } 1848
long getNumFailed() const { return nFail; } 1849
const ostream* getStream() const { return osptr; } 1850
void setStream(ostream* osptr) { this->osptr = osptr; } 1851
void succeed_() { ++nPass; } 1852
long report() const; 1853
virtual void reset() { nPass = nFail = 0; } 1854
}; 1855
1856
} // namespace TestSuite 1857
#endif // TEST_H ///:~ 1858
Listado 3.8. 1859
1860
Hay tres funciones virtuales en la clase Test: 1861
Un destructor virtual 1862
La función reset( ) 1863
La función virtual pura run( ) 1864
Como se explicó en el Volumen 1, es un error eliminar un objeto derivado 1865
de la pila a través de un puntero base a menos que la clase base tenga un 1866
destructor virtual. Cualquier clase propuesta para ser una clase base 1867
(normalmente evidenciadas por la presencia de al menos una de las otras 1868
funciones virtuales) tendría un destructor virtual. La implementación por 1869
defecto de Test::reset( ) pone los contadores de éxitos y fallos a cero. Podría 1870
querer sobreescribir esta función para poner el estado de los datos en su 1871
70
objeto de test derivado; sólo asegúrese de llamar a Test::rest( ) 1872
explícitamente en su sobreescritura de modo que los contadores se 1873
reajusten. La función Test::run( ) es virtual pura ya que es necesario para 1874
sobreescribirla en su clase derivada. 1875
Las macros test_( ) y fail_( ) pueden incluir la información disponible del 1876
nombre del fichero y el número de línea del preprocesador. Originalmente 1877
omitimos el guión bajo en los nombres, pero la macro fail colisiona con 1878
ios::fail( ), provocando errores de compilación. 1879
Aquí está la implementación del resto de las funciones Test: 1880
//: TestSuite:Test.cpp {O} 1881
#include "Test.h" 1882
#include <iostream> 1883
#include <typeinfo> 1884
using namespace std; 1885
using namespace TestSuite; 1886
1887
void Test::do_test(bool cond, const std::string& lbl, 1888
const char* fname, long lineno) { 1889
if(!cond) 1890
do_fail(lbl, fname, lineno); 1891
else 1892
succeed_(); 1893
} 1894
1895
void Test::do_fail(const std::string& lbl, 1896
const char* fname, long lineno) { 1897
++nFail; 1898
71
if(osptr) { 1899
*osptr << typeid(*this).name() 1900
<< "failure: (" << lbl << ") , " << fname 1901
<< " (line " << lineno << ")" << endl; 1902
} 1903
} 1904
1905
long Test::report() const { 1906
if(osptr) { 1907
*osptr << "Test \"" << typeid(*this).name() 1908
<< "\":\n\tPassed: " << nPass 1909
<< "\tFailed: " << nFail 1910
<< endl; 1911
} 1912
return nFail; 1913
} ///:~ 1914
Listado 3.9. 1915
1916
La clase Test lleva la cuenta del número de éxitos y fracasos además de la 1917
cadena donde quiere que Test::report( ) muestre los resultados. Las macros 1918
test_( ) y fail_() extraen la información del nombre del fichero actual y el 1919
número de línea del preprocesador y pasa el nombre del fichero a do_test( ) 1920
y el número de línea a do_fail( ), que hacen el mismo trabajo de mostrar un 1921
mensaje y actualizar el contador apropiado. No podemos pensar una buena 1922
razón para permitir copiar y asignar objetos de prueba, por lo que hemos 1923
rechazado estas operaciones para hacer sus prototipos privados y omitir el 1924
cuerpo de sus respectivas funciones. 1925
72
Aquí está el fichero de cabecera para Suite: 1926
//: TestSuite:Suite.h 1927
#ifndef SUITE_H 1928
#define SUITE_H 1929
#include <vector> 1930
#include <stdexcept> 1931
#include "../TestSuite/Test.h" 1932
using std::vector; 1933
using std::logic_error; 1934
1935
namespace TestSuite { 1936
1937
class TestSuiteError : public logic_error { 1938
public: 1939
TestSuiteError(const string& s = "") 1940
: logic_error(s) {} 1941
}; 1942
1943
class Suite { 1944
string name; 1945
ostream* osptr; 1946
vector<Test*> tests; 1947
void reset(); 1948
// Disallowed ops: 1949
Suite(const Suite&); 1950
73
Suite& operator=(const Suite&); 1951
public: 1952
Suite(const string& name, ostream* osptr = &cout) 1953
: name(name) { this->osptr = osptr; } 1954
string getName() const { return name; } 1955
long getNumPassed() const; 1956
long getNumFailed() const; 1957
const ostream* getStream() const { return osptr; } 1958
void setStream(ostream* osptr) { this->osptr = osptr; } 1959
void addTest(Test* t) throw(TestSuiteError); 1960
void addSuite(const Suite&); 1961
void run(); // Calls Test::run() repeatedly 1962
long report() const; 1963
void free(); // Deletes tests 1964
}; 1965
1966
} // namespace TestSuite 1967
#endif // SUITE_H ///:~ 1968
Listado 3.10. 1969
1970
La clase Suite tiene punteros a sus objetos Test en un vector. Fíjese en la 1971
especificación de la excepción en la función addTest( ). Cuando añada una 1972
prueba a una suite, Suite::addTest( ) verifique que el puntero que pasa no 1973
sea null; si es null, se lanza una excepción TestSuiteError. Puesto que esto 1974
hace imposible añadir un puntero null a una suite, addSuite( ) afirma esta 1975
condición en cada prueba, como hacen las otras funciones que atraviesan el 1976
74
vector de pruebas (vea la siguiente implementación). Copiar y asignar están 1977
desestimados como están en la clase Test. 1978
//: TestSuite:Suite.cpp {O} 1979
#include "Suite.h" 1980
#include <iostream> 1981
#include <cassert> 1982
#include <cstddef> 1983
using namespace std; 1984
using namespace TestSuite; 1985
1986
void Suite::addTest(Test* t) throw(TestSuiteError) { 1987
// Verify test is valid and has a stream: 1988
if(t == 0) 1989
throw TestSuiteError("Null test in Suite::addTest"); 1990
else if(osptr && !t->getStream()) 1991
t->setStream(osptr); 1992
tests.push_back(t); 1993
t->reset(); 1994
} 1995
1996
void Suite::addSuite(const Suite& s) { 1997
for(size_t i = 0; i < s.tests.size(); ++i) { 1998
assert(tests[i]); 1999
addTest(s.tests[i]); 2000
} 2001
75
} 2002
2003
void Suite::free() { 2004
for(size_t i = 0; i < tests.size(); ++i) { 2005
delete tests[i]; 2006
tests[i] = 0; 2007
} 2008
} 2009
2010
void Suite::run() { 2011
reset(); 2012
for(size_t i = 0; i < tests.size(); ++i) { 2013
assert(tests[i]); 2014
tests[i]->run(); 2015
} 2016
} 2017
2018
long Suite::report() const { 2019
if(osptr) { 2020
long totFail = 0; 2021
*osptr << "Suite \"" << name 2022
<< "\"\n======="; 2023
size_t i; 2024
for(i = 0; i < name.size(); ++i) 2025
*osptr << '='; 2026
76
*osptr << "=" << endl; 2027
for(i = 0; i < tests.size(); ++i) { 2028
assert(tests[i]); 2029
totFail += tests[i]->report(); 2030
} 2031
*osptr << "======="; 2032
for(i = 0; i < name.size(); ++i) 2033
*osptr << '='; 2034
*osptr << "=" << endl; 2035
return totFail; 2036
} 2037
else 2038
return getNumFailed(); 2039
} 2040
2041
long Suite::getNumPassed() const { 2042
long totPass = 0; 2043
for(size_t i = 0; i < tests.size(); ++i) { 2044
assert(tests[i]); 2045
totPass += tests[i]->getNumPassed(); 2046
} 2047
return totPass; 2048
} 2049
2050
long Suite::getNumFailed() const { 2051
77
long totFail = 0; 2052
for(size_t i = 0; i < tests.size(); ++i) { 2053
assert(tests[i]); 2054
totFail += tests[i]->getNumFailed(); 2055
} 2056
return totFail; 2057
} 2058
2059
void Suite::reset() { 2060
for(size_t i = 0; i < tests.size(); ++i) { 2061
assert(tests[i]); 2062
tests[i]->reset(); 2063
} 2064
} ///:~ 2065
Listado 3.11. 2066
2067
Usaremos el framework TestSuite donde sea pertinente a lo largo del resto 2068
de este libro. 2069
3.3. Técnicas de depuración 2070
La mejor costumbre para eliminar fallos es usar aserciones como se 2071
explica al principio de este capítulo; haciendo esto le ayudará a encontrar 2072
errores lógicos antes de que causen problemas reales. Esta sección contiene 2073
otros consejos y técnicas que podrían ayudar durante la depuración. 2074
3.3.1. Macros de seguimiento 2075
78
Algunas veces es útil imprimir el código de cada sentencia cuando es 2076
ejecutada, o cout o trazar un fichero. Aquí esta una macro de preprocesaor 2077
para llevar a cabo esto: 2078
#define TRACE(ARG) cout << #ARG << endl; ARG 2079
Ahora puede ir a través y alrededor de las sentencias que traceé con esta 2080
macro. Sin embargo, esto puede introducir problemas. Por ejemplo, si coge 2081
la sentencia: 2082
for(int i = 0; i < 100; i++) cout << i << endl; 2083
y ponga ambas líneas dentro de la macro TRACE( ), obtiene esto: 2084
TRACE(for(int i = 0; i < 100; i++)) TRACE( cout << i << endl;) 2085
que se expande a esto: 2086
cout << "for(int i = 0; i < 100; i++)" << endl; for(int i = 0; i < 100; i++) cout << 2087
"cout << i << endl;" << endl; cout << i << endl; 2088
que no es exactamente lo que quiere. Por lo tanto, debe usar esta técnica 2089
cuidadosamente. 2090
Lo siguiente es una variación en la macro TRACE( ): 2091
#define D(a) cout << #a "=[" << a << "]" << endl; 2092
Si quiere mostrar una expresión, simplemente póngala dentro de una 2093
llamada a D( ). La expresión se muestra, seguida de su valor ( asumiendo 2094
que hay un operador sobrecargado << para el tipo de resultado). Por 2095
ejemplo, puede decir D(a + b). Puede usar esta macro en cualquier momento 2096
que quiera comprobar un valor intermedio. 2097
Estas dos macros representan las dos cosas fundamentales que hace con 2098
un depurador: trazar la ejecución de código y mostrar valores. Un buen 2099
depurador es una herramienta de productividad excelente, pero a veces los 2100
depuradores no están disponibles, o no es conveniente usarlos. Estas 2101
técnicas siempre funcionan, sin tener en cuenta la situación. 2102
3.3.2. Fichero de rastro 2103
ADVERTENCIA: Esta sección y la siguiente contienen código que está 2104
oficialmente sin aprobación por el Estándar C++. En particular, redefinimos 2105
79
cout y new mediante macros, que puede provocar resultados sorprendentes 2106
si no tiene cuidado. Nuestros ejemplos funcionan en todos los compiladores 2107
que usamos, comoquiera, y proporcionan información útil. Este es el único 2108
lugar en este libro donde nos desviaremos de la inviolabilidad de la práctica 2109
de codificar cumpliendo el estándar. ¡Úsalo bajo tu propio riesgo! Dese 2110
cuenta que para este trabajo, usar delcaraciones debe ser realizado, para 2111
que cout no esté prefijado por su nombre de espacio, p.e. std::cout no 2112
funcionará. 2113
El siguiente código crea fácilmente un fichero de seguimiento y envía todas 2114
las salidas que irían normalmente a cout a ese fichero. Todo lo que debe 2115
hacer es #define TRACEON e incluir el fichero de cabecera (por supuesto, es 2116
bastante fácil sólo escribir las dos líneas claves correctamente en su fichero): 2117
//: C03:Trace.h 2118
// Creating a trace file. 2119
#ifndef TRACE_H 2120
#define TRACE_H 2121
#include <fstream> 2122
2123
#ifdef TRACEON 2124
std::ofstream TRACEFILE__("TRACE.OUT"); 2125
#define cout TRACEFILE__ 2126
#endif 2127
2128
#endif // TRACE_H ///:~ 2129
Listado 3.12. C03/Trace.h 2130
2131
Aquí esta una prueba sencilla del fichero anterior: 2132
80
//: C03:Tracetst.cpp {-bor} 2133
#include <iostream> 2134
#include <fstream> 2135
#include "../require.h" 2136
using namespace std; 2137
2138
#define TRACEON 2139
#include "Trace.h" 2140
2141
int main() { 2142
ifstream f("Tracetst.cpp"); 2143
assure(f, "Tracetst.cpp"); 2144
cout << f.rdbuf(); // Dumps file contents to file 2145
} ///:~ 2146
Listado 3.13. C03/Tracetst.cpp 2147
2148
Porque cout ha sido textualmente convertido en algo más por Trace.h, 2149
todas las sentencias cout en su programa ahora envían información al fichero 2150
de seguimiento. Esto es una forma conveniente de capturar su salida en un 2151
fichero, en caso de que su sistema operativo no haga una fácil redirección de 2152
la salida. 2153
3.3.3. Encontrar agujeros en memoria 2154
Las siguientes técnicas sencillas de depuración están explicadas en el 2155
Volumen 1: 2156
1. Para comprobar los límites de un array, usa la plantilla Array en 2157
C16:Array3.cpp del Volumen 1 para todos los arrays. Puede desactivar la 2158
81
comprobación e incrementar la eficiencia cuando esté listo para enviar. 2159
(Aunque esto no trata con el caso de coger un puntero a un array.) 2160
2. Comprobar destructores no virtuales en clases base. Seguirle la pista a 2161
new/delete y malloc/free 2162
Los problemas comunes con la asignación de memoria incluyen llamadas 2163
por error a delete para memoria que no está libre, borrar el espacio libre más 2164
de una vez, y más a menudo, olvidando borrar un puntero. Esta sección 2165
discute un sistema que puede ayudarle a localizar estos tipos de problemas. 2166
Como cláusula adicional de exención de responsabilidad más allá de la 2167
sección precedente: por el modo que sobrecargamos new, la siguiente 2168
técnica puede no funcionar en todas las plataformas, y funcionará sólo para 2169
programas que no llaman explicitamente al operador de función new( ). 2170
Hemos sido bastante cuidadosos en este libro para presentar sólo código 2171
que se ajuste completamente al Estándar C++, pero en este ejemplo 2172
estamos haciendo una excepción por las siguientes razones: 2173
1. A pesar de que es técnicamente ilegal, funciona en muchos 2174
compiladores.[29] 2175
2. Ilustramos algunos pensamientos útiles en el trascurso del camino. 2176
Para usar el sistema de comprobación de memoria, simplemente incluya el 2177
fichero de cabecera MemCheck.h, conecte el fichero MemCheck.obj a su 2178
aplicación para interceptar todas las llamadas a new y delete, y llame a la 2179
macro MEM_ON( ) (se explica más tarde en esta sección) para iniciar el 2180
seguimiento de la memoria. Un seguimiento de todas las asignaciones y 2181
desasignaciones es impreso en la salida estándar (mediante stdout). Cuando 2182
use este sistema, todas las llamadas a new almacenan información sobre el 2183
fichero y la línea donde fueron llamados. Esto está dotado usando la sintaxis 2184
de colocación para el operador new.[30] Aunque normalmente use la sintaxis 2185
de colocación cuando necesite colocar objetos en un punto de memoria 2186
específico, puede también crear un operador new( ) con cualquier número de 2187
argumentos. Esto se usa en el siguiente ejemplo para almacenar los 2188
resultados de las macros __FILE__ y __LINE__ cuando se llama a new: 2189
//: C02:MemCheck.h 2190
82
#ifndef MEMCHECK_H 2191
#define MEMCHECK_H 2192
#include <cstddef> // For size_t 2193
2194
// Usurp the new operator (both scalar and array versions) 2195
void* operator new(std::size_t, const char*, long); 2196
void* operator new[](std::size_t, const char*, long); 2197
#define new new (__FILE__, __LINE__) 2198
2199
extern bool traceFlag; 2200
#define TRACE_ON() traceFlag = true 2201
#define TRACE_OFF() traceFlag = false 2202
2203
extern bool activeFlag; 2204
#define MEM_ON() activeFlag = true 2205
#define MEM_OFF() activeFlag = false 2206
2207
#endif // MEMCHECK_H ///:~ 2208
Listado 3.14. C02/MemCheck.h 2209
2210
Es importante incluir este fichero en cualquier fichero fuente en el que 2211
quiera seguir la actividad de la memoria libre, pero inclúyalo al final (después 2212
de sus otras directivas #include). La mayoría de las cabeceras en la 2213
biblioteca estándar son plantillas, y puesto que la mayoría de los 2214
compiladores usan el modelo de inclusión de compilación de plantilla 2215
(significa que todo el código fuente está en las cabeceras), la macro que 2216
reemplaza new en MemCheck.h usurpará todas las instancias del operador 2217
83
new en el código fuente de la biblioteca (y casi resultaría en errores de 2218
compilación). Además, está sólo interesado en seguir sus propios errores de 2219
memoria, no los de la biblioteca. 2220
En el siguiente fichero, que contiene la implementación del seguimiento de 2221
memoria, todo está hecho con C estándar I/O más que con iostreams C++. 2222
No debería influir, puesto que no estamos interfiriendo con el uso de iostream 2223
en la memoria libre, pero cuando lo intentamos, algunos compiladores se 2224
quejaron. Todos los compiladores estaban felices con la versión <cstdio>. 2225
//: C02:MemCheck.cpp {O} 2226
#include <cstdio> 2227
#include <cstdlib> 2228
#include <cassert> 2229
#include <cstddef> 2230
using namespace std; 2231
#undef new 2232
2233
// Global flags set by macros in MemCheck.h 2234
bool traceFlag = true; 2235
bool activeFlag = false; 2236
2237
namespace { 2238
2239
// Memory map entry type 2240
struct Info { 2241
void* ptr; 2242
const char* file; 2243
long line; 2244
84
}; 2245
2246
// Memory map data 2247
const size_t MAXPTRS = 10000u; 2248
Info memMap[MAXPTRS]; 2249
size_t nptrs = 0; 2250
2251
// Searches the map for an address 2252
int findPtr(void* p) { 2253
for(size_t i = 0; i < nptrs; ++i) 2254
if(memMap[i].ptr == p) 2255
return i; 2256
return -1; 2257
} 2258
2259
void delPtr(void* p) { 2260
int pos = findPtr(p); 2261
assert(pos >= 0); 2262
// Remove pointer from map 2263
for(size_t i = pos; i < nptrs-1; ++i) 2264
memMap[i] = memMap[i+1]; 2265
--nptrs; 2266
} 2267
2268
// Dummy type for static destructor 2269
85
struct Sentinel { 2270
~Sentinel() { 2271
if(nptrs > 0) { 2272
printf("Leaked memory at:\n"); 2273
for(size_t i = 0; i < nptrs; ++i) 2274
printf("\t%p (file: %s, line %ld)\n", 2275
memMap[i].ptr, memMap[i].file, memMap[i].line); 2276
} 2277
else 2278
printf("No user memory leaks!\n"); 2279
} 2280
}; 2281
2282
// Static dummy object 2283
Sentinel s; 2284
2285
} // End anonymous namespace 2286
2287
// Overload scalar new 2288
void* 2289
operator new(size_t siz, const char* file, long line) { 2290
void* p = malloc(siz); 2291
if(activeFlag) { 2292
if(nptrs == MAXPTRS) { 2293
printf("memory map too small (increase MAXPTRS)\n"); 2294
86
exit(1); 2295
} 2296
memMap[nptrs].ptr = p; 2297
memMap[nptrs].file = file; 2298
memMap[nptrs].line = line; 2299
++nptrs; 2300
} 2301
if(traceFlag) { 2302
printf("Allocated %u bytes at address %p ", siz, p); 2303
printf("(file: %s, line: %ld)\n", file, line); 2304
} 2305
return p; 2306
} 2307
2308
// Overload array new 2309
void* 2310
operator new[](size_t siz, const char* file, long line) { 2311
return operator new(siz, file, line); 2312
} 2313
2314
// Override scalar delete 2315
void operator delete(void* p) { 2316
if(findPtr(p) >= 0) { 2317
free(p); 2318
assert(nptrs > 0); 2319
87
delPtr(p); 2320
if(traceFlag) 2321
printf("Deleted memory at address %p\n", p); 2322
} 2323
else if(!p && activeFlag) 2324
printf("Attempt to delete unknown pointer: %p\n", p); 2325
} 2326
2327
// Override array delete 2328
void operator delete[](void* p) { 2329
operator delete(p); 2330
} ///:~ 2331
Listado 3.15. C02/MemCheck.cpp 2332
2333
Las banderas booleanas de traceFalg y activeFlag son globales, por lo que 2334
pueden ser modificados en su código por las macros TRACE_ON( ), 2335
TRACE_OFF( ), MEM_ON( ), y MEM_OFF( ). En general, encierre todo el 2336
código en su main( ) dentro una pareja MEM_ON( )-MEM_OFF( ) de modo 2337
que la memoria sea siempre trazada. Trazar, que repite la actividad de las 2338
funciones de sustitución por el operador new( ) y el operador delete( ), es por 2339
defecto, pero puede desactivarlo con TRACE_OFF( ). En cualquier caso, los 2340
resultados finales son siempre impresos (vea la prueba que se ejecuta más 2341
tarde en este capítulo). 2342
La facilidad MemCheck rastrea la memoria guardando todas las 2343
direcciones asignadas por el operador new( ) en un array de estructuras Info, 2344
que también tiene el nombre del fichero y el número de línea donde la 2345
llamada new se encuentra. Para prevenir la colisión con cualquier nombre 2346
que haya colocado en el espacio de nombres global, tanta información como 2347
sea posible se guarda dentro del espacio de nombre anónimo. La clase 2348
88
Sentinel existe únicamente para llamar a un destructor de objetos con 2349
estático cuando el programa termina. Este destructor inspecciona memMap 2350
para ver si algún puntero está esperando a ser borrado (indicando una 2351
perdida de memoria). 2352
Nuestro operador new( ) usa malloc( ) para conseguir memoria, y luego 2353
añade el puntero y su información de fichero asociado a memMap. La 2354
función de operador delete( ) deshace todo el trabajo llamando a free( ) y 2355
decrementando nptrs, pero primero se comprueba para ver si el puntero en 2356
cuestión está en el mapa en el primer lugar. Si no es así, o reintenta borrar 2357
una dirección que no está en el almacén libre, o re intenta borrar la que ya ha 2358
sido borrada y eliminada del mapa. La variable activeFlag es importante aquí 2359
porque no queremos procesar ninguna desasignación de alguna actividad del 2360
cierre del sistema. Llamando a MEM_OFF( ) al final de su código, activeFlag 2361
será puesta a falso, y posteriores llamadas para borrar serán ignoradas. 2362
(Está mal en un programa real, pero nuestra intención aquí es encontrar 2363
agujeros, no está depurando la biblioteca.) Por simplicidad, enviamos todo el 2364
trabajo por array new y delete a sus homólogos escalares. 2365
Lo siguiente es un test sencillo usando la facilidad MemCheck: 2366
//: C02:MemTest.cpp 2367
//{L} MemCheck 2368
// Test of MemCheck system. 2369
#include <iostream> 2370
#include <vector> 2371
#include <cstring> 2372
#include "MemCheck.h" // Must appear last! 2373
using namespace std; 2374
2375
class Foo { 2376
char* s; 2377
89
public: 2378
Foo(const char*s ) { 2379
this->s = new char[strlen(s) + 1]; 2380
strcpy(this->s, s); 2381
} 2382
~Foo() { delete [] s; } 2383
}; 2384
2385
int main() { 2386
MEM_ON(); 2387
cout << "hello" << endl; 2388
int* p = new int; 2389
delete p; 2390
int* q = new int[3]; 2391
delete [] q; 2392
int* r; 2393
delete r; 2394
vector<int> v; 2395
v.push_back(1); 2396
Foo s("goodbye"); 2397
MEM_OFF(); 2398
} ///:~ 2399
Listado 3.16. C02/MemTest.cpp 2400
2401
90
Este ejemplo verifica que puede usar MemCheck en presencia de streams, 2402
contenedores estándar, y clases que asignan memoria en constructores. Los 2403
punteros p y q son asignados y desasignados sin ningún problema, pero r no 2404
es un puntero de pila válido, así que la salida indica el error como un intento 2405
de borrar un puntero desconocido: 2406
hola Asignados 4 bytes en la dirección 0xa010778 (fichero: memtest.cpp, 2407
línea: 25) Deleted memory at address 0xa010778 Asignados 12 bytes en la 2408
dirección 0xa010778 (fichero: memtest.cpp, línea: 27) Memoria borrada en la 2409
dirección 0xa010778 Intento de borrar puntero desconocido: 0x1 Asignados 8 2410
bytes en la dirección 0xa0108c0 (fichero: memtest.cpp, línea: 14) Memoria 2411
borrada en la dirección 0xa0108c0 ¡No hay agujeros de memoria de usuario! 2412
A causa de la llamada a MEM_OFF( ), no se procesan posteriores 2413
llamadas al operador delete( ) por vector o ostream. Todavía podría 2414
conseguir algunas llamadas a delete realizadas dsede reasignaciones por los 2415
contenedores. 2416
Si llama a TRACE_OFF( ) al principio del programa, la salida es 2417
Hola Intento de borrar puntero desconocido: 0x1 ¡No hay agujeros de 2418
memoria de usuario! 2419
3.4. Resumen 2420
Muchos de los dolores de cabeza de la ingenería del software pueden ser 2421
evitados reflexionando sobre lo que está haciendo. Probablemente ha estado 2422
usando aserciones mentales cuando ha navegado por sus blucles y 2423
funciones, incluso si no ha usado rutinariamente la macro assert( ). Si usa 2424
assert( ), encontrará errores lógicos más pronto y acabará con código más 2425
legible también. Recuerde usar solo aserciones para invariantes, aunque, no 2426
para el manejo de error en tiempo de ejecución. 2427
Nada le dará más tranquilidad que código probado rigurosamente. Si ha 2428
sido un lío en el pasado, use un framework automatizado, como el que 2429
hemos presentado aquí, para integrar la rutina de pruebas en su trabajo 2430
diario. Usted (¡y sus usarios!) estarán contentos de que lo haga. 2431
91
3.5. Ejercicios 2432
Las soluciones para ejercicios seleccionados pueden encontrarse en el 2433
documento electrónico Pensar en C++ Volumen 2 Guía de Soluciones 2434
Comentadas disponible por una pequeña cuota en www.MindView.net. 2435
1. Escriba un programa de prueba usando el Framework TestSuite para la 2436
clase estándar vector que prueba rigurosamente prueba las siguientes 2437
funciones con un vector de enteros: push_back( ) (añade un elemento al final 2438
del vector) front( ) (devuelve el primer elemento en el vector), back( ) 2439
(devuelve el último elemento en el vector), pop_back( ) (elimina el último 2440
elemento sin devolverlo), at( ) (devuelve el elemento en una posición 2441
específica), y size( ) (devuelve el número de elementos). Asegúrese de 2442
verificar que vector::at( ) lanza una excepción std::out_of_range si el índice 2443
facilitado está fuera de rango. 2444
2. Supóngase que le piden desarrollar un clase llamada Rational que da 2445
soporte a números racionales (fracciones). La fracción en un objecto Rational 2446
debería siempre almacenarse en los términos más bajos, y un denominador 2447
de cero es un error. Aquí está una interfaz de ejemplo para esa clase 2448
Rational: 2449
//: C02:Rational.h {-xo} 2450
#ifndef RATIONAL_H 2451
#define RATIONAL_H 2452
#include <iosfwd> 2453
2454
class Rational { 2455
public: 2456
Rational(int numerator = 0, int denominator = 1); 2457
Rational operator-() const; 2458
friend Rational operator+(const Rational&, 2459
const Rational&); 2460
92
friend Rational operator-(const Rational&, 2461
const Rational&); 2462
friend Rational operator*(const Rational&, 2463
const Rational&); 2464
friend Rational operator/(const Rational&, 2465
const Rational&); 2466
friend std::ostream& 2467
operator<<(std::ostream&, const Rational&); 2468
friend std::istream& 2469
operator>>(std::istream&, Rational&); 2470
Rational& operator+=(const Rational&); 2471
Rational& operator-=(const Rational&); 2472
Rational& operator*=(const Rational&); 2473
Rational& operator/=(const Rational&); 2474
friend bool operator<(const Rational&, 2475
const Rational&); 2476
friend bool operator>(const Rational&, 2477
const Rational&); 2478
friend bool operator<=(const Rational&, 2479
const Rational&); 2480
friend bool operator>=(const Rational&, 2481
const Rational&); 2482
friend bool operator==(const Rational&, 2483
const Rational&); 2484
friend bool operator!=(const Rational&, 2485
93
const Rational&); 2486
}; 2487
#endif // RATIONAL_H ///:~ 2488
Listado 3.17. C02/Rational.h 2489
2490
Escriba una especificación completa para esta clase, incluyendo 2491
especificaciones de precondiciones, postcondiciones, y de excepción. 2492
3. Escriba un prueba usando el framework TestSuite que pruebe 2493
rigurosamente todas las especificaciones del ejercicio anterior, incluyendo 2494
probar las excepciones. 2495
4. Implemente la clase Rational de modo que pase todas las pruebas del 2496
ejercicio anterior. Use aserciones sólo para las invariantes. 2497
5. El fichero BuggedSearch.cpp de abajo contiene un función de búsqueda 2498
binaria que busca para el rango [pedir, final). Hay algunos errores en el 2499
algoritmo. Use las técnicas de seguimiento de este capítulo para depurar la 2500
función de búsqueda. 2501
Parte II. La librería Estándar de C++ 2502
El C++ Estándar no solo incorpora todas las librerías de Estándar C (con 2503
pequeños añadidos y cambios para permitir tipos seguros), también añade 2504
sus propias librerías. Estas librerías son mucho más potentes que las de 2505
C. La mejora al usarlas es análoga a la que se consigue al cambiar de C a 2506
C++. 2507
Esta sección del libro le da una introducción en profundidad a las partes 2508
clave de la librería Estándar de C++. 2509
La referencia más completa y también la más oscura para las librerías es 2510
el propio Estándar. The C++ Programming Language, Third Edition (Addison 2511
Wesley, 2000) de Bjarne Stroustrup sigue siendo una referencia fiable tanto 2512
para el lenguaje como para la librería. La referencia más aclamada en cuanto 2513
a la librería es The C++ Standard Library: A Tutorial and Reference, by 2514
94
Nicolai Josuttis (Addison Wesley, 1999). El objetivo de los capítulos de esta 2515
parte del libro es ofrecer un catálogo de descripciones y ejemplos para que 2516
disponga de un buen punto de partida para resolver cualquier problema que 2517
requiera el uso de las librerías Estándar. Sin embargo, algunas técnicas y 2518
temas se usan poco y no se tratan aquí. Si no puede encontrar algo en estos 2519
capítulos, mire en los dos libros que se citan anteriormente; esto libro no 2520
pretende reemplazarlos, más bien completarlos. En particular, esperamos 2521
que después de consular el material de los siguientes capítulos pueda 2522
comprender mejor esos libros. 2523
El lector notará que estos capítulos no contienen documentación 2524
exhaustiva describiendo cada función o clase del la Librería Estándar C++. 2525
Hemos dejado las descripciones completas a otros; en particular a 2526
Dinkumware C/C++ Library Reference de P.J. Plauger. Esta es una 2527
excelente documentación que puede puede ver con un navegador web cada 2528
vez que necesite buscar algo. Puede verla on-line o comprarla para verla en 2529
local. Contiene una referencia completa para las librerías de C y C++ (de 2530
modo que es buena para cualquier cuestión de programación en C/C++ 2531
Estándar). La documentación electrónica no sólo es efectiva porque pueda 2532
tenerla siempre a mano, sino porque también puede hacer búsquedas 2533
electrónicas. 2534
Cuando usted está programando activamente, estos recursos deberían 2535
satisfacer sus necesidades de referencias (y puede usarlas para buscar algo 2536
de este capítulo que no tenga claro). El Apéndice A incluye referencias 2537
adicionales. 2538
El primer capítulo de esta sección introduce la clase string del Estándar 2539
C++, que es una herramienta potente que simplifica la mayoría de las tareas 2540
de procesamiento de texto que podría tener que realizar. Casi cualquier 2541
cosas que tenga hecho para cadenas de caracteres en C puede hacerse con 2542
una llamada a un método de la clase string. 2543
El capítulo 4 cubre la librería iostreams, que contiene clases para procesar 2544
entrada y salida con ficheros, cadenas, y la consola del sistema. 2545
Aunque el Capítulo 5: «Las plantillas a fondo» no es explícitamente un 2546
capítulo de la librería, es una preparación necesaria para los dos siguientes 2547
capítulos. En el capítulo 6 examinaremos los algoritmos genéricos que ofrece 2548
95
la librería Estándar C++. Como están implementados con plantillas, esos 2549
algoritmos se pueden aplicar a cualquier secuencia de objetos. El Capítulo 7 2550
cubre los contenedores estándar y sus iteradores asociados Vemos los 2551
algoritmos primero porque se pueden utilizar usando únicamente arrays y el 2552
contenedor vector (que vimos en el Volumen 1). También es normal el uso 2553
de algoritmos estándar junto con contenedores, y es bueno que le resulten 2554
familiares antes de estudiar los contenedores. 2555
Tabla de contenidos 2556
4. Las cadenas a fondo 2557
4.1. ¿Qué es un string? 2558
4.2. Operaciones con cadenas 2559
4.3. Buscar en cadenas 2560
4.4. Una aplicación con cadenas 2561
4.5. Resumen 2562
4.6. Ejercicios 2563
5. Iostreams 2564
5.1. ¿Por que iostream? 2565
5.2. Iostreams al rescate 2566
5.3. Manejo errores de stream 2567
5.4. Iostreams de fichero 2568
5.5. Almacenamiento de iostream 2569
5.6. Buscar en iostreams 2570
5.7. Iostreams de string 2571
5.8. Formateo de stream de salida 2572
5.9. Manipuladores 2573
5.10. 2574
5.11. 2575
5.12. 2576
5.13. 2577
6. Las plantillas en profundidad 2578
6.1. 2579
6.2. 2580
6.3. 2581
6.4. 2582
6.5. 2583
6.6. 2584
6.7. 2585
6.8. 2586
7. Algoritmos genéricos 2587
7.1. Un primer vistazo 2588
7.2. Objetos-función 2589
7.3. Un catálogo de algoritmos STL 2590
7.4. Creando sus propios algoritmos tipo STL 2591
7.5. Resumen 2592
7.6. Ejercicios 2593
96
4: Las cadenas a fondo 2594
Tabla de contenidos 2595
4.1. ¿Qué es un string? 2596
4.2. Operaciones con cadenas 2597
4.3. Buscar en cadenas 2598
4.4. Una aplicación con cadenas 2599
4.5. Resumen 2600
4.6. Ejercicios 2601 El procesamiento de cadenas de caracteres en C es una de las mayores 2602
pérdidas de tiempo. Las cadenas de caracteres requieren que el 2603
programador tenga en cuenta las diferencias entre cadenas estáticas y las 2604
cadenas creadas en la pila y en el montón, además del hecho que a veces 2605
pasa como argumento un char* y a veces hay que copiar el arreglo entero. 2606
Precisamente porque la manipulación de cadenas es muy común, las 2607
cadenas de caracteres son una gran fuente de confusiones y errores. Es por 2608
ello que la creación de clases de cadenas sigue siendo desde hace años un 2609
ejercicio común para programadores novatos. La clase string de la 2610
biblioteca estándar de C++ resuelve el problema de la manipulación de 2611
caracteres de una vez por todas, gestionando la memoria incluso durante las 2612
asignaciones y las construcciones de copia. Simplemente no tiene que 2613
preocuparse por ello. 2614
Este capítulo[1] examina la clase string del Estándar C++; empieza con un 2615
vistazo a la composición de las string de C++ y como la versión de C++ 2616
difiere del tradicional arreglo de caracteres de C. Aprenderá sobre las 2617
operaciones y la manipulación usando objetos string, y verá como éstas se 2618
FIXME[acomodan a la variación] de conjuntos de caracteres y conversión de 2619
datos. 2620
Manipular texto es una de las aplicaciones más antiguas de la 2621
programación, por eso no resulta sorprendente que las string de C++ estén 2622
fuertemente inspiradas en las ideas y la terminología que ha usado 2623
continuamente en C y otros lenguajes. Conforme vaya aprendiendo sobre los 2624
string de C++, este hecho se debería ir viendo más claramente. Da igual el 2625
lenguaje de programación que escoja, hay tres cosas comunes que querrá 2626
hacer con las cadenas: 2627
97
Crear o modificar secuencias de caracteres almacenados en una 2628
cadena 2629
Detectar la presencia o ausencia de elementos dentro de la 2630
cadena 2631
Traducir entre diversos esquemas para representar cadenas de 2632
caracteres 2633
Verá como cada una de estas tareas se resuelve usando objetos string 2634
en C++. 2635
4.1. ¿Qué es un string? 2636
En C, una cadena es simplemente un arreglo de caracteres que siempre 2637
incluye un 0 binario (frecuentemente llamado terminador nulo) como 2638
elemento final del arreglo. Existen diferencias significativas entre los string 2639
de C++ y sus progenitoras en C. Primero, y más importante, los string de 2640
C++ esconden la implementación física de la secuencia de caracteres que 2641
contiene. No debe preocuparse de las dimensiones del arreglo o del 2642
terminador nulo. Un string también contiene cierta información para uso 2643
interno sobre el tamaño y la localización en memoria de los datos. 2644
Específicamente, un objeto string de C++ conoce su localización en 2645
memoria, su contenido, su longitud en caracteres, y la cantidad de caracteres 2646
que puede crecer antes de que el objeto string deba redimensionar su 2647
buffer interno de datos. Las string de C++, por tanto, reducen enormemente 2648
las probabilidades de cometer uno de los tres errores de programación en C 2649
más comunes y destructivos: sobrescribir los límites del arreglo, intentar 2650
acceder a un arreglo no inicializado o con valores de puntero incorrectos, y 2651
dejar punteros colgando después de que el arreglo deje de ocupar el espacio 2652
que estaba ocupando. 2653
La implementación exacta del esquema en memoria para una clase string 2654
no esta definida en el estándar C++. Esta arquitectura esta pensada para ser 2655
suficientemente flexible para permitir diferentes implementaciones de los 2656
fabricantes de compiladores, garantizando igualmente un comportamiento 2657
predecible por los usuarios. En particular, las condiciones exactas de cómo 2658
situar el almacenamiento para alojar los datos para un objeto string no 2659
están definidas. FIXME: Las reglas de alojamiento de un string fueron 2660
98
formuladas para permitir, pero no requerir, una implementación con 2661
referencias múltiples, pero dependiendo de la implementación usar 2662
referencias múltiples sin variar la semántica. Por decirlo de otra manera, en 2663
C, todos los arreglos de char ocupan una única región física de memoria. En 2664
C++, los objetos string individuales pueden o no ocupar regiones físicas 2665
únicas de memoria, pero si su conjunto de referencias evita almacenar 2666
copias duplicadas de datos, los objetos individuales deben parecer y actuar 2667
como si tuvieran sus propias regiones únicas de almacenamiento. 2668
//: C03:StringStorage.h 2669
#ifndef STRINGSTORAGE_H 2670
#define STRINGSTORAGE_H 2671
#include <iostream> 2672
#include <string> 2673
#include "../TestSuite/Test.h" 2674
using std::cout; 2675
using std::endl; 2676
using std::string; 2677
2678
class StringStorageTest : public TestSuite::Test { 2679
public: 2680
void run() { 2681
string s1("12345"); 2682
// This may copy the first to the second or 2683
// use reference counting to simulate a copy: 2684
string s2 = s1; 2685
test_(s1 == s2); 2686
// Either way, this statement must ONLY modify s1: 2687
99
s1[0] = '6'; 2688
cout << "s1 = " << s1 << endl; // 62345 2689
cout << "s2 = " << s2 << endl; // 12345 2690
test_(s1 != s2); 2691
} 2692
}; 2693
#endif // STRINGSTORAGE_H ///:~ 2694
Listado 4.1. C03/StringStorage.h 2695
2696
Decimos que cuando una implementación solo hace una sola copia al 2697
modificar el string usa una estrategia de copiar al escribir. Esta 2698
aproximación ahorra tiempo y espacio cuando usamos string como 2699
parámetros por valor o en otras situaciones de solo lectura. 2700
El uso de referencias múltiples en la implementación de una librería 2701
debería ser transparente al usuario de la clase string. Desgraciadamente, 2702
esto no es siempre el caso. En programas multihilo, es prácticamente 2703
imposible usar implementaciones con múltiples referencias de forma 2704
segura[32].[2] 2705
4.2. Operaciones con cadenas 2706
Si ha programado en C, estará acostumbrado a la familia de funciones que 2707
leen, escriben, modifican y copian cadenas. Existen dos aspectos poco 2708
afortunados en la funciones de la librería estándar de C para manipular 2709
cadenas. Primero, hay dos familias pobremente organizadas: el grupo plano, 2710
y aquellos que requieren que se les suministre el número de caracteres para 2711
ser consideradas en la operación a mano. La lista de funciones en la librería 2712
de cadenas de C sorprende al usuario desprevenido con una larga lista de 2713
nombres crípticos y mayoritariamente impronunciables. Aunque el tipo y 2714
número de argumentos es algo consistente, para usarlas adecuadamente 2715
100
debe estar atento a los detalles de nombres de la función y a los parámetros 2716
que le pasas. 2717
La segunda trampa inherente a las herramientas para cadenas del 2718
estándar de C es que todas ellas explícitamente confían en la asunción de 2719
que cada cadena incluye un terminador nulo. Si por confusión o error el 2720
terminador nulo es omitido o sobrescrito, poco se puede hacer para impedir 2721
que las funciones de cadena de C manipulen la memoria más allá de los 2722
límites del espacio de alojamiento, a veces con resultados desastrosos. 2723
C++ aporta una vasta mejora en cuanto a conveniencia y seguridad de los 2724
objetos string. Para los propósitos de las actuales operaciones de 2725
manipulación, existe el mismo número de funciones que la librería de C, pero 2726
gracias a la sobrecarga, la funcionalidad es mucho mayor. Además, con una 2727
nomenclatura más sensata y un acertado uso de los argumentos por defecto, 2728
estas características se combinan para hacer de la clase string mucho más 2729
fácil de usar que la biblioteca de funciones de cadena de C. 2730
4.2.1. Añadiendo, insertando y concatenando cadenas 2731
Uno de los aspectos más valiosos y convenientes de los string en C++ es 2732
que crecen cuando lo necesitan, sin intervención por parte del programador. 2733
No solo hace el código de manejo del string sea inherentemente mas 2734
confiable, además elimina por completo las tediosas funciones "caseras" 2735
para controlar los limites del almacenamiento en donde nuestra cadena 2736
reside. Por ejemplo, si crea un objeto string e inicializa este string con 50 2737
copias de "X", y después copia en el 50 copias de "Zowie", el objeto, por sí 2738
mismo, readecua suficiente almacenamiento para acomodar el crecimiento 2739
de los datos. Quizás en ningún otro lugar es más apreciada esta propiedad 2740
que cuando las cadenas manipuladas por su código cambian de tamaño y no 2741
sabe cuan grande puede ser este cambio. La función miembro append() e 2742
insert() de string reubican de manera transparente el almacenamiento 2743
cuando un string crece: 2744
//: C03:StrSize.cpp 2745
#include <string> 2746
101
#include <iostream> 2747
using namespace std; 2748
2749
int main() { 2750
string bigNews("I saw Elvis in a UFO. "); 2751
cout << bigNews << endl; 2752
// How much data have we actually got? 2753
cout << "Size = " << bigNews.size() << endl; 2754
// How much can we store without reallocating? 2755
cout << "Capacity = " << bigNews.capacity() << endl; 2756
// Insert this string in bigNews immediately 2757
// before bigNews[1]: 2758
bigNews.insert(1, " thought I"); 2759
cout << bigNews << endl; 2760
cout << "Size = " << bigNews.size() << endl; 2761
cout << "Capacity = " << bigNews.capacity() << endl; 2762
// Make sure that there will be this much space 2763
bigNews.reserve(500); 2764
// Add this to the end of the string: 2765
bigNews.append("I've been working too hard."); 2766
cout << bigNews << endl; 2767
cout << "Size = " << bigNews.size() << endl; 2768
cout << "Capacity = " << bigNews.capacity() << endl; 2769
} ///:~ 2770
102
Listado 4.2. C03/StrSize.cpp 2771
2772
Aquí la salida desde un compilador cualquiera: 2773
I saw Elvis in a UFO. 2774 Size = 22 2775
Capacity = 31 2776 I thought I saw Elvis in a UFO. 2777
Size = 32 2778 Capacity = 47 2779
I thought I saw Elvis in a UFO. I've been 2780 working too hard. 2781
Size = 59 2782 Capacity = 511 2783
Este ejemplo demuestra que aunque puede ignorar con seguridad muchas 2784
de las responsabilidades de reserva y gestión de la memoria que tus string 2785
ocupan, C++ provee a los string con varias herramientas para monitorizar y 2786
gestionar su tamaño. Nótese la facilidad con la que hemos cambiado el 2787
tamaño de la memoria reservada para los string. La función size() retorna 2788
el numero de caracteres actualmente almacenados en el string y es idéntico 2789
a la función miembro lenght(). La función capacity() retorna el tamaño de 2790
la memoria subyacente actual, es decir, el número de caracteres que el 2791
string puede almacenar sin tener que reservar más memoria. La función 2792
reserve() es una optimización del mecanismo que indica su intención de 2793
especificar cierta cantidad de memoria para un futuro uso; capacity() 2794
siempre retorna un valor al menos tan largo como la ultima llamada a 2795
reserve(). La función resize() añade espacio si el nuevo tamaño es mayor 2796
que el tamaño actual del string; sino trunca el string. (Una sobrecarga de 2797
resize() puede especificar una adición diferente de caracteres). 2798
La manera exacta en que las funciones miembro de string reservan 2799
espacio para sus datos depende de la implementación de la librería. Cuando 2800
testeamos una implementación con el ejemplo anterior, parece que se hacia 2801
una reserva de una palabra de memoria (esto es, un entero) dejando un byte 2802
en blanco entre cada una de ellas. Los arquitectos de la clase string se 2803
esforzaron para poder mezclar el uso de las cadenas de caracteres de C y 2804
los objetos string, por lo que es probable por lo que se puede observar en 2805
StrSize.cpp, en esta implementación en particular, el byte esté añadido para 2806
acomodar fácilmente la inserción de un terminador nulo. 2807
103
4.2.2. Reemplazar caracteres en cadenas 2808
La función insert() es particularmente útil por que te evita el tener que 2809
estar seguro de que la inserción de caracteres en un string no sobrepasa el 2810
espacio reservado o sobrescribe los caracteres que inmediatamente 2811
siguientes al punto de inserción. El espacio crece y los caracteres existentes 2812
se mueven graciosamente para acomodar a los nuevos elementos. A veces, 2813
puede que no sea esto exactamente lo que quiere. Si quiere que el tamaño 2814
del string permanezca sin cambios, use la función replace() para 2815
sobrescribir los caracteres. Existe un número de versiones sobrecargadas de 2816
replace(), pero la más simple toma tres argumentos: un entero indicando 2817
donde empezar en el string, un entero indicando cuantos caracteres para 2818
eliminar del string original, y el string con el que reemplazaremos (que 2819
puede ser diferente en numero de caracteres que la cantidad eliminada). 2820
Aquí un ejemplo simple: 2821
//: C03:StringReplace.cpp 2822
// Simple find-and-replace in strings. 2823
#include <cassert> 2824
#include <string> 2825
using namespace std; 2826
2827
int main() { 2828
string s("A piece of text"); 2829
string tag("$tag$"); 2830
s.insert(8, tag + ' '); 2831
assert(s == "A piece $tag$ of text"); 2832
int start = s.find(tag); 2833
assert(start == 8); 2834
assert(tag.size() == 5); 2835
104
s.replace(start, tag.size(), "hello there"); 2836
assert(s == "A piece hello there of text"); 2837
} ///:~ 2838
Listado 4.3. C03/StringReplace.cpp 2839
2840
Tag es insertada en s (notese que la inserción ocurre antes de que el valor 2841
indicando el punto de inserción y de que el espacio extra haya sido añadido 2842
despues de Tag), y entonces es encontrada y reemplazada. 2843
Debería cerciorarse de que ha encontrado algo antes de realizar el 2844
replace(). En los ejemplos anteriores se reemplaza con un char*, pero 2845
existe una versión sobrecargada que reemplaza con un string. Aqui hay un 2846
ejempl más completo de demostración de replace(): 2847
//: C03:Replace.cpp 2848
#include <cassert> 2849
#include <cstddef> // For size_t 2850
#include <string> 2851
using namespace std; 2852
2853
void replaceChars(string& modifyMe, 2854
const string& findMe, const string& newChars) { 2855
// Look in modifyMe for the "find string" 2856
// starting at position 0: 2857
size_t i = modifyMe.find(findMe, 0); 2858
// Did we find the string to replace? 2859
if(i != string::npos) 2860
// Replace the find string with newChars: 2861
105
modifyMe.replace(i, findMe.size(), newChars); 2862
} 2863
2864
int main() { 2865
string bigNews = "I thought I saw Elvis in a UFO. " 2866
"I have been working too hard."; 2867
string replacement("wig"); 2868
string findMe("UFO"); 2869
// Find "UFO" in bigNews and overwrite it: 2870
replaceChars(bigNews, findMe, replacement); 2871
assert(bigNews == "I thought I saw Elvis in a " 2872
"wig. I have been working too hard."); 2873
} ///:~ 2874
Listado 4.4. C03/Replace.cpp 2875
2876
Si replace() no encuentra la cadena buscada, retorna un string::npos. El 2877
dato miembro npos es una constante estatica de la clase string que 2878
representa una posición de carácter que no existe[33]. [3] 2879
A diferencia de insert(), replace() no aumentará el espacio de 2880
alamcenamiento de string si copia nuevos caracteres en el medio de una 2881
serie de elementos de array existentes. Sin embargo, sí que cerecerá su 2882
espacio si es necesario, por ejemplo, cuando hace un "reemplazamiento" 2883
que pueda expandir el string más allá del final de la memoria reservada 2884
actual. Aquí un ejemplo: 2885
//: C03:ReplaceAndGrow.cpp 2886
#include <cassert> 2887
106
#include <string> 2888
using namespace std; 2889
2890
int main() { 2891
string bigNews("I have been working the grave."); 2892
string replacement("yard shift."); 2893
// The first argument says "replace chars 2894
// beyond the end of the existing string": 2895
bigNews.replace(bigNews.size() - 1, 2896
replacement.size(), replacement); 2897
assert(bigNews == "I have been working the " 2898
"graveyard shift."); 2899
} ///:~ 2900
Listado 4.5. C03/ReplaceAndGrow.cpp 2901
2902
La llamada a replace() empieza "reemplazando" más allá del final del 2903
array existente, que es equivalente a la operación append(). Nótese que en 2904
este ejemplo replace() expande el array coherentemente. 2905
Puede que haya estado buscando a través del capítulo; intentando hacer 2906
algo relativamente fácil como reemplazar todas las ocurrencias de un 2907
carácter con diferentes caracteres. Al buscar el material previo sobre 2908
reemplazar, puede que haya encontrado la respuesta, pero entonces ha 2909
empezaro viendo grupos de caracteres y contadores y otras cosas que 2910
parecen un poco demasiado complejas. ¿No tiene string una manera para 2911
reemplazar un carácter con otro simplemente? 2912
Puede escribir fácilmente cada funcin usando las funciones miembro 2913
find() y replace() como se muestra acontinuacion. 2914
107
//: C03:ReplaceAll.h 2915
#ifndef REPLACEALL_H 2916
#define REPLACEALL_H 2917
#include <string> 2918
2919
std::string& replaceAll(std::string& context, 2920
const std::string& from, const std::string& to); 2921
#endif // REPLACEALL_H ///:~ 2922
Listado 4.6. C03/ReplaceAll.h 2923
2924
//: C03:ReplaceAll.cpp {O} 2925
#include <cstddef> 2926
#include "ReplaceAll.h" 2927
using namespace std; 2928
2929
string& replaceAll(string& context, const string& from, 2930
const string& to) { 2931
size_t lookHere = 0; 2932
size_t foundHere; 2933
while((foundHere = context.find(from, lookHere)) 2934
!= string::npos) { 2935
context.replace(foundHere, from.size(), to); 2936
lookHere = foundHere + to.size(); 2937
} 2938
108
return context; 2939
} ///:~ 2940
Listado 4.7. C03/ReplaceAll.cpp 2941
2942
La versión de find() usada aquí toma como segundo argumento la 2943
posición donde empezar a buscar y retorna string::npos si no lo encuentra. 2944
Es importante avanzar en la posición contenida por la variable lookHere 2945
pasada como subcadena, en caso de que from es una subcadena de to. El 2946
siguiente programa comprueba la funcion replaceAll(): 2947
//: C03:ReplaceAllTest.cpp 2948
//{L} ReplaceAll 2949
#include <cassert> 2950
#include <iostream> 2951
#include <string> 2952
#include "ReplaceAll.h" 2953
using namespace std; 2954
2955
int main() { 2956
string text = "a man, a plan, a canal, Panama"; 2957
replaceAll(text, "an", "XXX"); 2958
assert(text == "a mXXX, a plXXX, a cXXXal, PXXXama"); 2959
} ///:~ 2960
Listado 4.8. C03/ReplaceAllTest.cpp 2961
2962
109
Como puede comprobar, la clase string por ella sola no resuelve todos los 2963
posibles problemas. Muchas soluciones se han dejado en los algoritmos de 2964
la librería estándar[4] por que la clase string puede parece justamente como 2965
una secuencia STL(gracias a los iteradores descritos antes). Todos los 2966
algoritmos genéricos funcionan en un "rango" de elementos dentro de un 2967
contenedor. Generalmente este rango es justamente desde el principio del 2968
contenedor hasta el final. Un objeto string se parece a un contenedor de 2969
caracteres: para obtener el principio de este rango use string::begin(), y 2970
para obtener el final del rango use string::end(). El siguiente 2971
ejemplomuestra el uso del algoritmo replace() para reemplazar todas las 2972
instancias de un determinado carácter "X" con "Y" 2973
//: C03:StringCharReplace.cpp 2974
#include <algorithm> 2975
#include <cassert> 2976
#include <string> 2977
using namespace std; 2978
2979
int main() { 2980
string s("aaaXaaaXXaaXXXaXXXXaaa"); 2981
replace(s.begin(), s.end(), 'X', 'Y'); 2982
assert(s == "aaaYaaaYYaaYYYaYYYYaaa"); 2983
} ///:~ 2984
Listado 4.9. C03/StringCharReplace.cpp 2985
2986
Nótese que esta función replace() no es llamada como función miembro 2987
de string. Además, a diferencia de la función string::replace(), que solo 2988
realiza un reemplazo, el algoritmo replace() reemplaza todas las instancias 2989
de un carácter con otro. 2990
110
El algoritmo replace() solo funciona con objetos individuales (en este 2991
caso, objetos char) y no reemplazará arreglos constantes o objetos string. 2992
Desde que un string se copmporta como una secuencia STL, un conjunto 2993
de algoritmos pueden serle aplicados, que resolverán otros problemas que 2994
las funciones miembro de string no resuelven. 2995
4.2.3. Concatenación usando operadores no-miembro 2996
sobrecargados 2997
Uno de los descubrimientos más deliciosos que esperan al programador 2998
de C que está aprendiendo sobre el manejo de cadenas en C++, es lo simple 2999
que es combinar y añadir string usando los operadores operator+ y 3000
operator+=. Estos operadores hacen combinaciones de cadenas 3001
sintacticamente parecidas a la suma de datos numéricos: 3002
//: C03:AddStrings.cpp 3003
#include <string> 3004
#include <cassert> 3005
using namespace std; 3006
3007
int main() { 3008
string s1("This "); 3009
string s2("That "); 3010
string s3("The other "); 3011
// operator+ concatenates strings 3012
s1 = s1 + s2; 3013
assert(s1 == "This That "); 3014
// Another way to concatenates strings 3015
s1 += s3; 3016
assert(s1 == "This That The other "); 3017
111
// You can index the string on the right 3018
s1 += s3 + s3[4] + "ooh lala"; 3019
assert(s1 == "This That The other The other oooh lala"); 3020
} ///:~ 3021
Listado 4.10. C03/AddStrings.cpp 3022
3023
Usar los operadores operator+ y operator+= es una manera flexible y 3024
conveniente de combinar los datos de las cadenas. En la parte derecha de la 3025
sentencia, puede usar casi cualquier tipo que evalúe a un grupo de uno o 3026
más caracteres. 3027
4.3. Buscar en cadenas 3028
La familia de funciones miembro de string find localiza un carácter o 3029
grupo de caracteres en una cadena dada. Aquí los miembros de la familia 3030
find() y su uso general: 3031
Función miembro de búsqueda en un string 3032
¿Qué/Cómo lo encuentra? 3033
find() 3034
Busca en un string un carácter determinado o un grupo de caracteres y 3035
retorna la posición de inicio de la primera ocurrencia o npos si ha sido 3036
encontrado. 3037
find_first_of() 3038
Busca en un string y retorna la posición de la primera ocurrencia de 3039
cualquier carácter en un grupo especifico. Si no encuentra ocurrencias, 3040
retorna npos. 3041
find_last_of() 3042
Busca en un string y retorna la posición de la última ocurrencia de 3043
cualquier carácter en un grupo específico. Si no encuentra ocurrencias, 3044
retorna npos. 3045
112
find_first_not_of( ) 3046
Busca en un string y retorna la posición de la primera ocurrencia que no 3047
pertenece a un grupo específico. Si no encontramos ningún elemento, 3048
retorna un npos 3049
find_last_not_of( ) 3050
Busca en un string y retorna la posición del elemento con el indice mayor 3051
que no pertenece a un grupo específico. Si no encontramos ningún 3052
elemento, retorna un npos 3053
rfind() 3054
Busca en un string, desde el final hasta el origen, un carácter o grupo de 3055
caracteres y retorna la posición inicial de la ocurrencia si se ha encontrado 3056
alguna. Si no encuentra ocurrencias, retorna npos. 3057
El uso más simple de find(), busca uno o más caracteres en un string. 3058
La versión sobrecargada de find() toma un parámetro que especifica el/los 3059
carácter(es) que buscar y opcionalmente un parámetro que dice donde 3060
empezar a buscar en el string la primera ocurrencia. (Por defecto la posición 3061
de incio es 0). Insertando la llamada a la función find() dentro de un bucle 3062
puede buscar fácilmente todas las ocurrencias de un carácter dado o un 3063
grupo de caracteres dentro de un string. 3064
El siguiente programa usa el método del Tamiz de Eratostenes para hallar 3065
los números primos menores de 50. Este método empieza con el número 2, 3066
marca todos los subsecuentes múltiplos de 2 ya que no son primos, y repite 3067
el proceso para el siguiente candidato a primo. El constructor de sieveTest 3068
inicializa sieveChars poniendo el tamaño inicial del arreglo de carácter y 3069
escribiendo el valor 'P' para cada miembro. 3070
//: C03:Sieve.h 3071
#ifndef SIEVE_H 3072
#define SIEVE_H 3073
#include <cmath> 3074
#include <cstddef> 3075
113
#include <string> 3076
#include "../TestSuite/Test.h" 3077
using std::size_t; 3078
using std::sqrt; 3079
using std::string; 3080
3081
class SieveTest : public TestSuite::Test { 3082
string sieveChars; 3083
public: 3084
// Create a 50 char string and set each 3085
// element to 'P' for Prime: 3086
SieveTest() : sieveChars(50, 'P') {} 3087
void run() { 3088
findPrimes(); 3089
testPrimes(); 3090
} 3091
bool isPrime(int p) { 3092
if(p == 0 || p == 1) return false; 3093
int root = int(sqrt(double(p))); 3094
for(int i = 2; i <= root; ++i) 3095
if(p % i == 0) return false; 3096
return true; 3097
} 3098
void findPrimes() { 3099
// By definition neither 0 nor 1 is prime. 3100
114
// Change these elements to "N" for Not Prime: 3101
sieveChars.replace(0, 2, "NN"); 3102
// Walk through the array: 3103
size_t sieveSize = sieveChars.size(); 3104
int root = int(sqrt(double(sieveSize))); 3105
for(int i = 2; i <= root; ++i) 3106
// Find all the multiples: 3107
for(size_t factor = 2; factor * i < sieveSize; 3108
++factor) 3109
sieveChars[factor * i] = 'N'; 3110
} 3111
void testPrimes() { 3112
size_t i = sieveChars.find('P'); 3113
while(i != string::npos) { 3114
test_(isPrime(i++)); 3115
i = sieveChars.find('P', i); 3116
} 3117
i = sieveChars.find_first_not_of('P'); 3118
while(i != string::npos) { 3119
test_(!isPrime(i++)); 3120
i = sieveChars.find_first_not_of('P', i); 3121
} 3122
} 3123
}; 3124
#endif // SIEVE_H ///:~ 3125
115
Listado 4.11. C03/Sieve.h 3126
3127
//: C03:Sieve.cpp 3128
//{L} ../TestSuite/Test 3129
#include "Sieve.h" 3130
3131
int main() { 3132
SieveTest t; 3133
t.run(); 3134
return t.report(); 3135
} ///:~ 3136
Listado 4.12. C03/Sieve.cpp 3137
3138
La función find() puede recorrer el string, detectando múltiples 3139
ocurrencias de un carácter o un grupo de caracteres, y find_first_not_of() 3140
encuentra otros caracteres o subcadenas. 3141
No existen funciones en la clase string para cambiar entre 3142
mayúsculas/minúsculas en una cadena, pero puede crear esa función 3143
fácilmente usando la función de la libreria estándar de C toupper() y 3144
tolower(), que cambian los caracteres entre mayúsculas/minúsculas de uno 3145
en uno. El ejemplo siguiente ilustra una búsqueda sensible a 3146
mayúsculas/minúsculas. 3147
//: C03:Find.h 3148
#ifndef FIND_H 3149
#define FIND_H 3150
#include <cctype> 3151
116
#include <cstddef> 3152
#include <string> 3153
#include "../TestSuite/Test.h" 3154
using std::size_t; 3155
using std::string; 3156
using std::tolower; 3157
using std::toupper; 3158
3159
// Make an uppercase copy of s 3160
inline string upperCase(const string& s) { 3161
string upper(s); 3162
for(size_t i = 0; i < s.length(); ++i) 3163
upper[i] = toupper(upper[i]); 3164
return upper; 3165
} 3166
3167
// Make a lowercase copy of s 3168
inline string lowerCase(const string& s) { 3169
string lower(s); 3170
for(size_t i = 0; i < s.length(); ++i) 3171
lower[i] = tolower(lower[i]); 3172
return lower; 3173
} 3174
3175
class FindTest : public TestSuite::Test { 3176
117
string chooseOne; 3177
public: 3178
FindTest() : chooseOne("Eenie, Meenie, Miney, Mo") {} 3179
void testUpper() { 3180
string upper = upperCase(chooseOne); 3181
const string LOWER = "abcdefghijklmnopqrstuvwxyz"; 3182
test_(upper.find_first_of(LOWER) == string::npos); 3183
} 3184
void testLower() { 3185
string lower = lowerCase(chooseOne); 3186
const string UPPER = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; 3187
test_(lower.find_first_of(UPPER) == string::npos); 3188
} 3189
void testSearch() { 3190
// Case sensitive search 3191
size_t i = chooseOne.find("een"); 3192
test_(i == 8); 3193
// Search lowercase: 3194
string test = lowerCase(chooseOne); 3195
i = test.find("een"); 3196
test_(i == 0); 3197
i = test.find("een", ++i); 3198
test_(i == 8); 3199
i = test.find("een", ++i); 3200
test_(i == string::npos); 3201
118
// Search uppercase: 3202
test = upperCase(chooseOne); 3203
i = test.find("EEN"); 3204
test_(i == 0); 3205
i = test.find("EEN", ++i); 3206
test_(i == 8); 3207
i = test.find("EEN", ++i); 3208
test_(i == string::npos); 3209
} 3210
void run() { 3211
testUpper(); 3212
testLower(); 3213
testSearch(); 3214
} 3215
}; 3216
#endif // FIND_H ///:~ 3217
Listado 4.13. C03/Find.h 3218
3219
//: C03:Find.cpp 3220
//{L} ../TestSuite/Test 3221
#include "Find.h" 3222
#include "../TestSuite/Test.h" 3223
3224
int main() { 3225
119
FindTest t; 3226
t.run(); 3227
return t.report(); 3228
} ///:~ 3229
Listado 4.14. C03/Find.cpp 3230
3231
Tanto las funciones upperCase() como lowerCase() siguen la misma 3232
forma: hacen una copia de la cadena argumento y cambian entre 3233
mayúsculas/minúsculas. El programa Find.cpp no es la mejor solución para 3234
el problema para las mayúsculas/minúsculas, por lo que lo revisitaremos 3235
cuando examinemos la comparación entre cadenas. 3236
4.3.1. Busqueda inversa 3237
Si necesita buscar en una cadena desde el final hasta el principio (para 3238
encontrar datos en orden "último entra / primero sale"), puede usar la 3239
función miembro de string rfind(). 3240
//: C03:Rparse.h 3241
#ifndef RPARSE_H 3242
#define RPARSE_H 3243
#include <cstddef> 3244
#include <string> 3245
#include <vector> 3246
#include "../TestSuite/Test.h" 3247
using std::size_t; 3248
using std::string; 3249
using std::vector; 3250
3251
120
class RparseTest : public TestSuite::Test { 3252
// To store the words: 3253
vector<string> strings; 3254
public: 3255
void parseForData() { 3256
// The ';' characters will be delimiters 3257
string s("now.;sense;make;to;going;is;This"); 3258
// The last element of the string: 3259
int last = s.size(); 3260
// The beginning of the current word: 3261
size_t current = s.rfind(';'); 3262
// Walk backward through the string: 3263
while(current != string::npos) { 3264
// Push each word into the vector. 3265
// Current is incremented before copying 3266
// to avoid copying the delimiter: 3267
++current; 3268
strings.push_back(s.substr(current, last - current)); 3269
// Back over the delimiter we just found, 3270
// and set last to the end of the next word: 3271
current -= 2; 3272
last = current + 1; 3273
// Find the next delimiter: 3274
current = s.rfind(';', current); 3275
} 3276
121
// Pick up the first word -- it's not 3277
// preceded by a delimiter: 3278
strings.push_back(s.substr(0, last)); 3279
} 3280
void testData() { 3281
// Test them in the new order: 3282
test_(strings[0] == "This"); 3283
test_(strings[1] == "is"); 3284
test_(strings[2] == "going"); 3285
test_(strings[3] == "to"); 3286
test_(strings[4] == "make"); 3287
test_(strings[5] == "sense"); 3288
test_(strings[6] == "now."); 3289
string sentence; 3290
for(size_t i = 0; i < strings.size() - 1; i++) 3291
sentence += strings[i] += " "; 3292
// Manually put last word in to avoid an extra space: 3293
sentence += strings[strings.size() - 1]; 3294
test_(sentence == "This is going to make sense now."); 3295
} 3296
void run() { 3297
parseForData(); 3298
testData(); 3299
} 3300
}; 3301
122
#endif // RPARSE_H ///:~ 3302
Listado 4.15. C03/Rparse.h 3303
3304
//: C03:Rparse.cpp 3305
//{L} ../TestSuite/Test 3306
#include "Rparse.h" 3307
3308
int main() { 3309
RparseTest t; 3310
t.run(); 3311
return t.report(); 3312
} ///:~ 3313
Listado 4.16. C03/Rparse.cpp 3314
3315
La función miembro de string rfind() vuelve por la cadena buscando 3316
elementos y reporta el indice del arreglo de las coincidencias de caracteres o 3317
string::npos si no tiene éxito. 3318
4.3.2. Encontrar el primero/último de un conjunto de caracteres 3319
La función miembro find_first_of( ) y find_last_of( ) pueden ser 3320
convenientemente usadas para crear una pequeña utilidad the ayude a 3321
deshechar los espacion en blanco del final e inicio de la cadena. Nótese que 3322
no se toca el string originar sino que se devuelve una nuevo string: 3323
//: C03:Trim.h 3324
// General tool to strip spaces from both ends. 3325
123
#ifndef TRIM_H 3326
#define TRIM_H 3327
#include <string> 3328
#include <cstddef> 3329
3330
inline std::string trim(const std::string& s) { 3331
if(s.length() == 0) 3332
return s; 3333
std::size_t beg = s.find_first_not_of(" \a\b\f\n\r\t\v"); 3334
std::size_t end = s.find_last_not_of(" \a\b\f\n\r\t\v"); 3335
if(beg == std::string::npos) // No non-spaces 3336
return ""; 3337
return std::string(s, beg, end - beg + 1); 3338
} 3339
#endif // TRIM_H ///:~ 3340
Listado 4.17. C03/Trim.h 3341
3342
La primera prueba checkea si el string esta vacío; en ese caso, ya no se 3343
realizan más test, y se retorna una copia. Nótese que una vez los puntos del 3344
final son encontrados, el constructor de string construye un nuevo string 3345
desde el viejo, dándole el contador incial y la longitud. 3346
Las pruebas de una herramienta tan general den ser cuidadosas 3347
//: C03:TrimTest.h 3348
#ifndef TRIMTEST_H 3349
#define TRIMTEST_H 3350
124
#include "Trim.h" 3351
#include "../TestSuite/Test.h" 3352
3353
class TrimTest : public TestSuite::Test { 3354
enum {NTESTS = 11}; 3355
static std::string s[NTESTS]; 3356
public: 3357
void testTrim() { 3358
test_(trim(s[0]) == "abcdefghijklmnop"); 3359
test_(trim(s[1]) == "abcdefghijklmnop"); 3360
test_(trim(s[2]) == "abcdefghijklmnop"); 3361
test_(trim(s[3]) == "a"); 3362
test_(trim(s[4]) == "ab"); 3363
test_(trim(s[5]) == "abc"); 3364
test_(trim(s[6]) == "a b c"); 3365
test_(trim(s[7]) == "a b c"); 3366
test_(trim(s[8]) == "a \t b \t c"); 3367
test_(trim(s[9]) == ""); 3368
test_(trim(s[10]) == ""); 3369
} 3370
void run() { 3371
testTrim(); 3372
} 3373
}; 3374
#endif // TRIMTEST_H ///:~ 3375
125
Listado 4.18. C03/TrimTest.h 3376
3377
//: C03:TrimTest.cpp {O} 3378
#include "TrimTest.h" 3379
3380
// Initialize static data 3381
std::string TrimTest::s[TrimTest::NTESTS] = { 3382
" \t abcdefghijklmnop \t ", 3383
"abcdefghijklmnop \t ", 3384
" \t abcdefghijklmnop", 3385
"a", "ab", "abc", "a b c", 3386
" \t a b c \t ", " \t a \t b \t c \t ", 3387
"\t \n \r \v \f", 3388
"" // Must also test the empty string 3389
}; ///:~ 3390
Listado 4.19. C03/TrimTest.cpp 3391
3392
//: C03:TrimTestMain.cpp 3393
//{L} ../TestSuite/Test TrimTest 3394
#include "TrimTest.h" 3395
3396
int main() { 3397
TrimTest t; 3398
t.run(); 3399
126
return t.report(); 3400
} ///:~ 3401
Listado 4.20. C03/TrimTestMain.cpp 3402
3403
En el arrglo de string, puede ver que los arreglos de carácter son 3404
automáticamente convertidos a objetos string. Este arreglo provee casos 3405
para checkear el borrado de espacios en blanco y tabuladores en los 3406
extremos, además de asegurar que los espacios y tabuladores no son 3407
borrados de la mitad de un string. 3408
4.3.3. Borrar caracteres de cadenas 3409
Borrar caracteres es fácil y eficiente con la función miembro erase(), que 3410
toma dos argumentos: donde empezar a borrar caracteres (que por defecto 3411
es 0), y cuantos caracteres borrar (que por defecto es string::npos). Si 3412
especifica más caracteres que los que quedan en el string, los caracteres 3413
restantes se borran igualmente (llamando erase() sin argumentos borra 3414
todos los caracteres del string). A veces es útil abrir un fichero HTML y 3415
borrar sus etiquetas y caracteres especiales de manera que tengamos algo 3416
aproximadamente igual al texto que obtendríamos en el navegador Web, sólo 3417
como un fichero de texto plano. El siguiente ejemplo usa erase() para hacer 3418
el trabajo: 3419
//: C03:HTMLStripper.cpp {RunByHand} 3420
//{L} ReplaceAll 3421
// Filter to remove html tags and markers. 3422
#include <cassert> 3423
#include <cmath> 3424
#include <cstddef> 3425
#include <fstream> 3426
127
#include <iostream> 3427
#include <string> 3428
#include "ReplaceAll.h" 3429
#include "../require.h" 3430
using namespace std; 3431
3432
string& stripHTMLTags(string& s) { 3433
static bool inTag = false; 3434
bool done = false; 3435
while(!done) { 3436
if(inTag) { 3437
// The previous line started an HTML tag 3438
// but didn't finish. Must search for '>'. 3439
size_t rightPos = s.find('>'); 3440
if(rightPos != string::npos) { 3441
inTag = false; 3442
s.erase(0, rightPos + 1); 3443
} 3444
else { 3445
done = true; 3446
s.erase(); 3447
} 3448
} 3449
else { 3450
// Look for start of tag: 3451
128
size_t leftPos = s.find('<'); 3452
if(leftPos != string::npos) { 3453
// See if tag close is in this line: 3454
size_t rightPos = s.find('>'); 3455
if(rightPos == string::npos) { 3456
inTag = done = true; 3457
s.erase(leftPos); 3458
} 3459
else 3460
s.erase(leftPos, rightPos - leftPos + 1); 3461
} 3462
else 3463
done = true; 3464
} 3465
} 3466
// Remove all special HTML characters 3467
replaceAll(s, "<", "<"); 3468
replaceAll(s, ">", ">"); 3469
replaceAll(s, "&", "&"); 3470
replaceAll(s, " ", " "); 3471
// Etc... 3472
return s; 3473
} 3474
3475
int main(int argc, char* argv[]) { 3476
129
requireArgs(argc, 1, 3477
"usage: HTMLStripper InputFile"); 3478
ifstream in(argv[1]); 3479
assure(in, argv[1]); 3480
string s; 3481
while(getline(in, s)) 3482
if(!stripHTMLTags(s).empty()) 3483
cout << s << endl; 3484
} ///:~ 3485
Listado 4.21. C03/HTMLStripper.cpp 3486
3487
Este ejemplo borrará incluso las etiquetas HTML que se extienden a lo 3488
largo de varias líneas.[5] Esto se cumple gracias a la bandera estática inTag, 3489
que evalúa a cierto si el principio de una etiqueta es encontrada, pero la 3490
etiqueta de finalización correspondiente no es encontrada en la misma línea. 3491
Todas la formas de erase() aparecen en la función stripHTMLFlags().[6] La 3492
versión de getline() que usamos aquí es una función (global) declarada en 3493
la cabecera de string y es útil porque guarda una línea arbitrariamente larga 3494
en su argumento string. No necesita preocuparse de las dimensiones de un 3495
arreglo cuando trabaja con istream::getline(). Nótese que este programa 3496
usa la función replaceAll() vista antes en este capítulo. En el póximo 3497
capitulo, usaremos los flujos de cadena para crear una solución más 3498
elegante. 3499
4.3.4. Comparar cadenas 3500
Comparar cadenas es inherentemente diferente a comparar enteros. Los 3501
nombres tienen un significado universal y constante. Para evaluar la relación 3502
entre las magnitudes de dos cadenas, se necesita hacer una comparación 3503
léxica. Una comparación léxica significa que cuando se comprueba un 3504
130
carácter para saber si es "mayor que" o "menor que" otro carácter, está en 3505
realidad comparando la representación numérica de aquellos caracteres tal 3506
como están especificados en el orden del conjunto de caracteres que está 3507
siendo usado. La ordenación más habitual suele ser la secuencia ASCII, que 3508
asigna a los caracteres imprimibles para el lenguaje inglés números en un 3509
rango del 32 al 127 decimal. En la codificación ASCII, el primer "carácter" 3510
en la lista es el espacio, seguido de diversas marcas de puntuación común, y 3511
después las letras mayúsculas y minúsculas. Respecto al alfabeto, esto 3512
significa que las letras cercanas al principio tienen un valor ASCII menor a 3513
aquellos más cercanos al final. Con estos detalles en mente, se vuelve más 3514
fácil recordar que cuando una comparació léxica reporta que s1 es "mayor 3515
que" s2, simplemente significa que cuando fueron comparados, el primer 3516
carácter diferente en s1 estaba atrás en el alfabeto que el carácter en la 3517
misma posición en s2. 3518
C++ provee varias maneras de comparar cadenas, y cada una tiene 3519
ventajas. La más simple de usar son las funciones no-miembro 3520
sobrecargadas de operador: operator==, operator!= operator>, operator<, 3521
operator>= y operator<=. 3522
//: C03:CompStr.h 3523
#ifndef COMPSTR_H 3524
#define COMPSTR_H 3525
#include <string> 3526
#include "../TestSuite/Test.h" 3527
using std::string; 3528
3529
class CompStrTest : public TestSuite::Test { 3530
public: 3531
void run() { 3532
// Strings to compare 3533
131
string s1("This"); 3534
string s2("That"); 3535
test_(s1 == s1); 3536
test_(s1 != s2); 3537
test_(s1 > s2); 3538
test_(s1 >= s2); 3539
test_(s1 >= s1); 3540
test_(s2 < s1); 3541
test_(s2 <= s1); 3542
test_(s1 <= s1); 3543
} 3544
}; 3545
#endif // COMPSTR_H ///:~ 3546
Listado 4.22. C03/CompStr.h 3547
3548
//: C03:CompStr.cpp 3549
//{L} ../TestSuite/Test 3550
#include "CompStr.h" 3551
3552
int main() { 3553
CompStrTest t; 3554
t.run(); 3555
return t.report(); 3556
132
} ///:~ 3557
Listado 4.23. C03/CompStr.cpp 3558
3559
Los operadores de comaración sobrecargados son útiles para comparar 3560
dos cadenas completas y elementos individuales de una cadena de 3561
caracteres. 3562
Nótese en el siguiente ejemplo la flexibilidad de los tipos de argumento 3563
ambos lados de los operadores de comparación. Por eficiencia, la clase 3564
string provee operadores sobrecargados para la comparación directa de 3565
objetos string, literales de cadena, y punteros a cadenas estilo C sin tener 3566
que crear objetos string temporales. 3567
//: C03:Equivalence.cpp 3568
#include <iostream> 3569
#include <string> 3570
using namespace std; 3571
3572
int main() { 3573
string s2("That"), s1("This"); 3574
// The lvalue is a quoted literal 3575
// and the rvalue is a string: 3576
if("That" == s2) 3577
cout << "A match" << endl; 3578
// The left operand is a string and the right is 3579
// a pointer to a C-style null terminated string: 3580
if(s1 != s2.c_str()) 3581
cout << "No match" << endl; 3582
133
} ///:~ 3583
Listado 4.24. C03/Equivalence.cpp 3584
3585
La función c_str() retorna un const char* que apunta a una cadena estilo 3586
C terminada en nulo, equivalente en contenidos al objeto string. Esto se 3587
vuelve muy útil cuando se quiere pasar un strin a una función C, como atoi() 3588
o cualquiera de las funciones definidas en la cabecera cstring. Es un error 3589
usar el valor retornado por c_str() como un argumento constante en 3590
cualquier función. 3591
No encontrará el operador not (!) o los operadores de comparación 3592
lógicos (&& y ||) entre los operadore para string. (No encontrará ninguna 3593
versión sobrecargada de los operadores de bits de C: &, |, ^, o ~.) Los 3594
operadores de conversión no miembros sobrecargados para la clases string 3595
están limitados a un subconjunto que tiene una aplicación clara y no ambigua 3596
para caracteres individuales o grupos de caracteres. 3597
La función miembro compare() le ofrece un gran modo de comparación 3598
más sofisticado y preciso que el conjunto de operadores nomiembro. Provee 3599
versiones sobrecargadas para comparar: 3600
Dos string completos 3601
Parte de un string con un string completo 3602
Partes de dos string 3603
//: C03:Compare.cpp 3604
// Demonstrates compare() and swap(). 3605
#include <cassert> 3606
#include <string> 3607
using namespace std; 3608
3609
int main() { 3610
134
string first("This"); 3611
string second("That"); 3612
assert(first.compare(first) == 0); 3613
assert(second.compare(second) == 0); 3614
// Which is lexically greater? 3615
assert(first.compare(second) > 0); 3616
assert(second.compare(first) < 0); 3617
first.swap(second); 3618
assert(first.compare(second) < 0); 3619
assert(second.compare(first) > 0); 3620
} ///:~ 3621
Listado 4.25. C03/Compare.cpp 3622
3623
La función swap() en este ejemplo hace lo que su nombre implica: cambia 3624
el contenido del objeto por el del parámetro. Para comparar un subconjunto 3625
de caracteres en un o ambos string, añada argumentos que definen donde 3626
empezar y cuantos caracteres considerar. Por ejemplo, puede usar las 3627
siguientes versiones sobrecargadas de compare(): 3628
s1.compare(s1StartPos, s1NumberChars, s2, s2StartPos, 3629
s2NumberChars); 3630
Aqui un ejemplo: 3631
//: C03:Compare2.cpp 3632
// Illustrate overloaded compare(). 3633
#include <cassert> 3634
#include <string> 3635
using namespace std; 3636
135
3637
int main() { 3638
string first("This is a day that will live in infamy"); 3639
string second("I don't believe that this is what " 3640
"I signed up for"); 3641
// Compare "his is" in both strings: 3642
assert(first.compare(1, 7, second, 22, 7) == 0); 3643
// Compare "his is a" to "his is w": 3644
assert(first.compare(1, 9, second, 22, 9) < 0); 3645
} ///:~ 3646
Listado 4.26. C03/Compare2.cpp 3647
3648
Hasta ahora, en los ejemplos, hemos usado la sintaxis de indexación de 3649
arrays estilo C para referirnos a un carácter individual en un string. C++ 3650
provee de una alternativa a la notación s[n]: el miembro at(). Estos dos 3651
mecanismos de indexación producen los mismos resultados si todo va bien: 3652
//: C03:StringIndexing.cpp 3653
#include <cassert> 3654
#include <string> 3655
using namespace std; 3656
3657
int main() { 3658
string s("1234"); 3659
assert(s[1] == '2'); 3660
assert(s.at(1) == '2'); 3661
136
} ///:~ 3662
Listado 4.27. C03/StringIndexing.cpp 3663
3664
Sin embargo, existe una importante diferencia entre [ ] y at() . Cuando 3665
usted intenta referenciar el elemento de un arreglo que esta fuera de sus 3666
límites, at() tiene la delicadeza de lanzar una excepción, mientras que 3667
ordinariamente [ ] le dejará a su suerte. 3668
//: C03:BadStringIndexing.cpp 3669
#include <exception> 3670
#include <iostream> 3671
#include <string> 3672
using namespace std; 3673
3674
int main() { 3675
string s("1234"); 3676
// at() saves you by throwing an exception: 3677
try { 3678
s.at(5); 3679
} catch(exception& e) { 3680
cerr << e.what() << endl; 3681
} 3682
} ///:~ 3683
Listado 4.28. C03/BadStringIndexing.cpp 3684
3685
137
Los programadores responsables no usarán índices erráticos, pero puede 3686
que quiera beneficiarse de la comprobación automática de indices, 3687
usandoat() en el lugar de [ ] le da la oportunidad de recuperar 3688
diligentemente de las referencias a elementos de un arreglo que no existen. 3689
La ejecución de sobre uno de nuestros compiladores le da la siguiente salida: 3690
"invalid string position" 3691
La función miembro at() lanza un objeto de clase out_of_class, que 3692
deriva finalmente de std::exception. Capturando este objeto en un 3693
manejador de excepciones, puede tomar las medidas adecuadas como 3694
recalcular el índice incorrecto o hacer crecer el arreglo. Usar 3695
string::operator[]( ) no proporciona ningún tipo de protección y es tan 3696
peligroso como el procesado de arreglos de caracteres en C.[37] [7] 3697
4.3.5. Cadenas y rasgos de caracteres 3698
El programa Find.cpp anterior en este capítulo nos lleva a hacernos la 3699
pregunta obvia: ¿por que la comparación sensible a mayúsculas/minúsculas 3700
no es parte de la clase estándar string? La respuesta nos brinda un 3701
interesante transfondo sobre la verdadera naturaleza de los objetos string 3702
en C++. 3703
Considere qué significa para un carácter tener "mayúscula/minúscula". El 3704
Hebreo escrito, el Farsi, y el Kanji no usan el concepto de 3705
"mayúscula/minúscula", con lo que para esas lenguas esta idea carece de 3706
significado. Esto daria a entender que si existiera una manera de designar 3707
algunos lenguages como "todo mayúsculas" o "todo minúsculas", podriamos 3708
diseñar una solución generalizada. Sin embargo, algunos leguajes que 3709
emplean el concepto de "mayúscula/minúscula", tambien cambian el 3710
significado de caracteres particulares con acentos diacríticos, por ejemplo la 3711
cedilla del Español, el circumflexo en Francés y la diéresis en Alemán. Por 3712
esta razón, cualquier codificación sensible a mayúsculas que intenta ser 3713
comprensiva acaba siendo una pesadilla en su uso. 3714
Aunque tratamos habitualmente el string de C++ como una clase, esto no 3715
es del todo cierto. El tipo string es una especialización de algo más general, 3716
la plantilla basic_string< >. Observe como está declarada string en el 3717
fichero de cabecera de C++ estándar. 3718
138
typedef basic_string<char> string; 3719
Para comprender la naturaleza de la clase string, mire la plantilla 3720
basic_string< > 3721
template<class charT, class traits = char_traits<charT>, class 3722
allocator = allocator<charT> > class basic_string; 3723
En el Capítulo 5, examinamos las plantillas con gran detalle (mucho más 3724
que en el Capítulo 16 del volúmen 1). Por ahora nótese que el tipo string es 3725
creada cuando instanciamos la plantilla basic_string con char. Dentro de la 3726
declaración plantilla basic_string< > la línea: 3727
class traits = char_traits<charT<, 3728
nos dice que el comportamiento de la clase hecha a partir de 3729
basic_string< > es defineida por una clase basada en la plantilla 3730
char_traits< >. Así, la plantilla basic_string< > produce clases orientadas 3731
a string que manipulan otros tipos que char (caracteres anchos, por 3732
ejemplo). Para hacer esto, la plantilla char_traits< > controla el contenido y 3733
el comportamiento de la ordenación de una variedad de conjuntos de 3734
caracteres usando las funciones de comparación eq() (equal), ne() (not 3735
equal), y lt() (less than). Las funciones de comparación de basic_string< > 3736
confian en esto. 3737
Es por esto por lo que la clase string no incluye funciones miembro 3738
sensibles a mayúsculas/minúsculas: eso no esta en la descripción de su 3739
trabajo. Para cambiar la forma en que la clase string trata la comparación de 3740
caracteres, tiene que suministrar una plantilla char_traits< > diferente ya 3741
que define el comportamiento individual de las funciones miembro de 3742
comparación carácteres. 3743
Puede usar esta información para hacer un nuevo tipo de string que 3744
ignora las mayúsculas/minúsculas. Primero, definiremos una nueva plantilla 3745
no sensible a mayúsculas/minúsculas de char_traits< > que hereda de una 3746
plantilla existente. Luego, sobrescribiremos sólo los miembros que 3747
necesitamos cambiar para hacer la comparación carácter por carácter. 3748
(Además de los tres miembros de comparación léxica mencionados antes, 3749
daremos una nueva implementación para laspara las funciones de 3750
char_traits find() y compare()). Finalmente, haremos un typedef de una 3751
nueva clase basada en basic_string, pero usando nuestra plantilla 3752
139
insensible a mayúsculas/minúsculas, ichar_traits, como segundo 3753
argumento: 3754
//: C03:ichar_traits.h 3755
// Creating your own character traits. 3756
#ifndef ICHAR_TRAITS_H 3757
#define ICHAR_TRAITS_H 3758
#include <cassert> 3759
#include <cctype> 3760
#include <cmath> 3761
#include <cstddef> 3762
#include <ostream> 3763
#include <string> 3764
using std::allocator; 3765
using std::basic_string; 3766
using std::char_traits; 3767
using std::ostream; 3768
using std::size_t; 3769
using std::string; 3770
using std::toupper; 3771
using std::tolower; 3772
3773
struct ichar_traits : char_traits<char> { 3774
// We'll only change character-by- 3775
// character comparison functions 3776
static bool eq(char c1st, char c2nd) { 3777
140
return toupper(c1st) == toupper(c2nd); 3778
} 3779
static bool ne(char c1st, char c2nd) { 3780
return !eq(c1st, c2nd); 3781
} 3782
static bool lt(char c1st, char c2nd) { 3783
return toupper(c1st) < toupper(c2nd); 3784
} 3785
static int 3786
compare(const char* str1, const char* str2, size_t n) { 3787
for(size_t i = 0; i < n; ++i) { 3788
if(str1 == 0) 3789
return -1; 3790
else if(str2 == 0) 3791
return 1; 3792
else if(tolower(*str1) < tolower(*str2)) 3793
return -1; 3794
else if(tolower(*str1) > tolower(*str2)) 3795
return 1; 3796
assert(tolower(*str1) == tolower(*str2)); 3797
++str1; ++str2; // Compare the other chars 3798
} 3799
return 0; 3800
} 3801
static const char* 3802
141
find(const char* s1, size_t n, char c) { 3803
while(n-- > 0) 3804
if(toupper(*s1) == toupper(c)) 3805
return s1; 3806
else 3807
++s1; 3808
return 0; 3809
} 3810
}; 3811
3812
typedef basic_string<char, ichar_traits> istring; 3813
3814
inline ostream& operator<<(ostream& os, const istring& s) { 3815
return os << string(s.c_str(), s.length()); 3816
} 3817
#endif // ICHAR_TRAITS_H ///:~ 3818
Listado 4.29. C03/ichar_traits.h 3819
3820
Proporcionamos un typedef llamado istring ya que nuestra clase actuará 3821
como un string ordinario en todas sus formas, excepto que realizará todas 3822
las comparaciones sin respetar las mayúsculas/minúsculas. Por 3823
conveniencia, damos un operador sobrecargado operator<<() para que 3824
pueda imprimir los istring. Aque hay un ejemplo: 3825
//: C03:ICompare.cpp 3826
#include <cassert> 3827
142
#include <iostream> 3828
#include "ichar_traits.h" 3829
using namespace std; 3830
3831
int main() { 3832
// The same letters except for case: 3833
istring first = "tHis"; 3834
istring second = "ThIS"; 3835
cout << first << endl; 3836
cout << second << endl; 3837
assert(first.compare(second) == 0); 3838
assert(first.find('h') == 1); 3839
assert(first.find('I') == 2); 3840
assert(first.find('x') == string::npos); 3841
} ///:~ 3842
Listado 4.30. C03/ICompare.cpp 3843
3844
Este es solo un ejemplo de prueba. Para hacer istring completamente 3845
equivalente a un string, deberiamos haber creado las otras funciones 3846
necesarias para soportar el nuevo tipo istring. 3847
La cabecera <string> provee de un string ancho [8] gracias al siguiente 3848
typedef: 3849
typedef basic_string<wchar_t> wstring; 3850
El soporte para string ancho se revela tambien en los streams anchos 3851
(wostream en lugar de ostream, tambien definido en <iostream>) y en la 3852
especialización de wchar_t de los char_traits en la libreria estándar le da la 3853
posibilidad de hacer una version de carácter ancho de ichar_traits 3854
143
//: C03:iwchar_traits.h {-g++} 3855
// Creating your own wide-character traits. 3856
#ifndef IWCHAR_TRAITS_H 3857
#define IWCHAR_TRAITS_H 3858
#include <cassert> 3859
#include <cmath> 3860
#include <cstddef> 3861
#include <cwctype> 3862
#include <ostream> 3863
#include <string> 3864
3865
using std::allocator; 3866
using std::basic_string; 3867
using std::char_traits; 3868
using std::size_t; 3869
using std::towlower; 3870
using std::towupper; 3871
using std::wostream; 3872
using std::wstring; 3873
3874
struct iwchar_traits : char_traits<wchar_t> { 3875
// We'll only change character-by- 3876
// character comparison functions 3877
static bool eq(wchar_t c1st, wchar_t c2nd) { 3878
return towupper(c1st) == towupper(c2nd); 3879
144
} 3880
static bool ne(wchar_t c1st, wchar_t c2nd) { 3881
return towupper(c1st) != towupper(c2nd); 3882
} 3883
static bool lt(wchar_t c1st, wchar_t c2nd) { 3884
return towupper(c1st) < towupper(c2nd); 3885
} 3886
static int compare( 3887
const wchar_t* str1, const wchar_t* str2, size_t n) { 3888
for(size_t i = 0; i < n; i++) { 3889
if(str1 == 0) 3890
return -1; 3891
else if(str2 == 0) 3892
return 1; 3893
else if(towlower(*str1) < towlower(*str2)) 3894
return -1; 3895
else if(towlower(*str1) > towlower(*str2)) 3896
return 1; 3897
assert(towlower(*str1) == towlower(*str2)); 3898
++str1; ++str2; // Compare the other wchar_ts 3899
} 3900
return 0; 3901
} 3902
static const wchar_t* 3903
find(const wchar_t* s1, size_t n, wchar_t c) { 3904
145
while(n-- > 0) 3905
if(towupper(*s1) == towupper(c)) 3906
return s1; 3907
else 3908
++s1; 3909
return 0; 3910
} 3911
}; 3912
3913
typedef basic_string<wchar_t, iwchar_traits> iwstring; 3914
3915
inline wostream& operator<<(wostream& os, 3916
const iwstring& s) { 3917
return os << wstring(s.c_str(), s.length()); 3918
} 3919
#endif // IWCHAR_TRAITS_H ///:~ 3920
Listado 4.31. C03/iwchar_traits.h 3921
3922
Como puede ver, esto es principalmente un ejercicio de poner 'w' en el 3923
lugar adecuado del código fuente. El programa de prueba podria ser asi: 3924
//: C03:IWCompare.cpp {-g++} 3925
#include <cassert> 3926
#include <iostream> 3927
#include "iwchar_traits.h" 3928
146
using namespace std; 3929
3930
int main() { 3931
// The same letters except for case: 3932
iwstring wfirst = L"tHis"; 3933
iwstring wsecond = L"ThIS"; 3934
wcout << wfirst << endl; 3935
wcout << wsecond << endl; 3936
assert(wfirst.compare(wsecond) == 0); 3937
assert(wfirst.find('h') == 1); 3938
assert(wfirst.find('I') == 2); 3939
assert(wfirst.find('x') == wstring::npos); 3940
} ///:~ 3941
Listado 4.32. C03/IWCompare.cpp 3942
3943
Desgraciadamente, todavia algunos compiladores siguen sin ofrecer un 3944
soporte robusto para caracteres anchos. 3945
4.4. Una aplicación con cadenas 3946
Si ha observado atentamente los códigos de ejemplo de este libro, habrá 3947
observado que ciertos elementos en los comentarios envuelven el código. 3948
Son usados por un programa en Python que escribió Bruce para extraer el 3949
código en ficheros y configurar makefiles para construir el código. Por 3950
ejemplo, una doble barra segida de dos puntos en el comienzo de una línea 3951
denota la primera línea de un fichero de código . El resto de la línea contiene 3952
información describiendo el nombre del fichero y su locaización y cuando 3953
deberia ser solo compilado en vez constituir un fichero ejecutable. Por 3954
ejemplo, la primera línea del programa anterior contiene la cadena 3955
147
C03:IWCompare.cpp, indicando que el fichero IWCompare.cpp deberia ser 3956
extraido en el directorio C03. 3957
La última línea del fichero fuente contiene una triple barra seguida de dos 3958
puntos y un signo "~". Es la primera línea tiene una exclamación 3959
inmediatamente después de los dos puntos, la primera y la última línea del 3960
código fuente no son para ser extraídas en un fichero (solo es para ficheros 3961
solo de datos). (Si se está preguntando por que evitamos mostrar estos 3962
elementos, es por que no queremos romper el extractor de código cuando lo 3963
aplicamos al texto del libro!). 3964
El programa en Python de Bruce hace muchas más cosas que 3965
simplemente extraer el código. Si el elemento "{O}" sigue al nombre del 3966
fichero, su entrada en el makefile solo será configurada para compilar y no 3967
para enlazarla en un ejecutable. (El Test Framework en el Capítulo 2 está 3968
contruida de esta manera). Para enlazar un fichero con otro fuente de 3969
ejemplo, el fichero fuente del ejecutable objetivo contendrá una directiva 3970
"{L}", como aquí: 3971
//{L} ../TestSuite/Test 3972
Esta sección le presentará un programa para extraer todo el código para 3973
que pueda compilarlo e inspeccionarlo manualmente. Puede usar este 3974
programa para extraer todo el codigo de este libro salvando el fichero como 3975
un fichero de texto[9] (llamémosle TICV2.txt)y ejecutando algo como la 3976
siguiente línea de comandos: C:> extractCode TICV2.txt /TheCode 3977
Este comando lee el fichero de texto TICV2.txt y escribe todos los archivos 3978
de código fuente en subdirectorios bajo el definido /TheCode. El arbol de 3979
directorios se mostrará como sigue: 3980
TheCode/ C0B/ C01/ C02/ C03/ C04/ C05/ C06/ C07/ C08/ C09/ C10/ 3981
C11/ TestSuite/ 3982
Los ficheros de código fuente que contienen los ejemplos de cada capítulo 3983
estarán en el correspondiente directorio. 3984
Aquí está el programa: 3985
//: C03:ExtractCode.cpp {-edg} {RunByHand} 3986
// Extracts code from text. 3987
148
#include <cassert> 3988
#include <cstddef> 3989
#include <cstdio> 3990
#include <cstdlib> 3991
#include <fstream> 3992
#include <iostream> 3993
#include <string> 3994
using namespace std; 3995
3996
// Legacy non-standard C header for mkdir() 3997
#if defined(__GNUC__) || defined(__MWERKS__) 3998
#include <sys/stat.h> 3999
#elif defined(__BORLANDC__) || defined(_MSC_VER) \ 4000
|| defined(__DMC__) 4001
#include <direct.h> 4002
#else 4003
#error Compiler not supported 4004
#endif 4005
4006
// Check to see if directory exists 4007
// by attempting to open a new file 4008
// for output within it. 4009
bool exists(string fname) { 4010
size_t len = fname.length(); 4011
if(fname[len-1] != '/' && fname[len-1] != '\\') 4012
149
fname.append("/"); 4013
fname.append("000.tmp"); 4014
ofstream outf(fname.c_str()); 4015
bool existFlag = outf; 4016
if(outf) { 4017
outf.close(); 4018
remove(fname.c_str()); 4019
} 4020
return existFlag; 4021
} 4022
4023
int main(int argc, char* argv[]) { 4024
// See if input file name provided 4025
if(argc == 1) { 4026
cerr << "usage: extractCode file [dir]" << endl; 4027
exit(EXIT_FAILURE); 4028
} 4029
// See if input file exists 4030
ifstream inf(argv[1]); 4031
if(!inf) { 4032
cerr << "error opening file: " << argv[1] << endl; 4033
exit(EXIT_FAILURE); 4034
} 4035
// Check for optional output directory 4036
string root("./"); // current is default 4037
150
if(argc == 3) { 4038
// See if output directory exists 4039
root = argv[2]; 4040
if(!exists(root)) { 4041
cerr << "no such directory: " << root << endl; 4042
exit(EXIT_FAILURE); 4043
} 4044
size_t rootLen = root.length(); 4045
if(root[rootLen-1] != '/' && root[rootLen-1] != '\\') 4046
root.append("/"); 4047
} 4048
// Read input file line by line 4049
// checking for code delimiters 4050
string line; 4051
bool inCode = false; 4052
bool printDelims = true; 4053
ofstream outf; 4054
while(getline(inf, line)) { 4055
size_t findDelim = line.find("//" "/:~"); 4056
if(findDelim != string::npos) { 4057
// Output last line and close file 4058
if(!inCode) { 4059
cerr << "Lines out of order" << endl; 4060
exit(EXIT_FAILURE); 4061
} 4062
151
assert(outf); 4063
if(printDelims) 4064
outf << line << endl; 4065
outf.close(); 4066
inCode = false; 4067
printDelims = true; 4068
} else { 4069
findDelim = line.find("//" ":"); 4070
if(findDelim == 0) { 4071
// Check for '!' directive 4072
if(line[3] == '!') { 4073
printDelims = false; 4074
++findDelim; // To skip '!' for next search 4075
} 4076
// Extract subdirectory name, if any 4077
size_t startOfSubdir = 4078
line.find_first_not_of(" \t", findDelim+3); 4079
findDelim = line.find(':', startOfSubdir); 4080
if(findDelim == string::npos) { 4081
cerr << "missing filename information\n" << endl; 4082
exit(EXIT_FAILURE); 4083
} 4084
string subdir; 4085
if(findDelim > startOfSubdir) 4086
subdir = line.substr(startOfSubdir, 4087
152
findDelim - startOfSubdir); 4088
// Extract file name (better be one!) 4089
size_t startOfFile = findDelim + 1; 4090
size_t endOfFile = 4091
line.find_first_of(" \t", startOfFile); 4092
if(endOfFile == startOfFile) { 4093
cerr << "missing filename" << endl; 4094
exit(EXIT_FAILURE); 4095
} 4096
// We have all the pieces; build fullPath name 4097
string fullPath(root); 4098
if(subdir.length() > 0) 4099
fullPath.append(subdir).append("/"); 4100
assert(fullPath[fullPath.length()-1] == '/'); 4101
if(!exists(fullPath)) 4102
#if defined(__GNUC__) || defined(__MWERKS__) 4103
mkdir(fullPath.c_str(), 0); // Create subdir 4104
#else 4105
mkdir(fullPath.c_str()); // Create subdir 4106
#endif 4107
fullPath.append(line.substr(startOfFile, 4108
endOfFile - startOfFile)); 4109
outf.open(fullPath.c_str()); 4110
if(!outf) { 4111
cerr << "error opening " << fullPath 4112
153
<< " for output" << endl; 4113
exit(EXIT_FAILURE); 4114
} 4115
inCode = true; 4116
cout << "Processing " << fullPath << endl; 4117
if(printDelims) 4118
outf << line << endl; 4119
} 4120
else if(inCode) { 4121
assert(outf); 4122
outf << line << endl; // Output middle code line 4123
} 4124
} 4125
} 4126
exit(EXIT_SUCCESS); 4127
} ///:~ 4128
Listado 4.33. C03/ExtractCode.cpp 4129
4130
Primero observará algunas directivas de compilación condicionales. La 4131
función mkdir(), que crea un directorio en el sistema de ficheros, se define 4132
por el estándar POSIX[10] en la cabecera (<direct.h>). La respectiva signatura 4133
de mkdir() también difiere: POSIX especifica dos argumentos, las viejas 4134
versiones sólo uno. Por esta razón, existe más de una directiva de 4135
compilación condicional después en el programa para elegir la llamada 4136
correcta a mkdir(). Normalmente no usamos compilaciones condicionales en 4137
los ejemplos de este libro, pero en este programa en particular es demasiado 4138
154
útil para no poner un poco de trabajo extra dentro, ya que puede usarse para 4139
extraer todo el código con él. 4140
La función exists() en ExtractCode.cpp prueba que un directorio existe 4141
abriendo un fiechero temporal en él. Si la obertura falla, el directorio no 4142
existe. Borre el fichero enviando su nombre como unchar* a std::remove(). 4143
El programa principal valida los argumentos de la línea de comandos y 4144
después lee el fichero de entrada línea por línea, mirando por los 4145
delimitadores especiales de código fuente. La bandera booleana inCode 4146
indica que el programa esta en el medio de un fichero fuente, así que las 4147
lineas deben ser extraídas. La bandera printDelims será verdadero si el 4148
elemento de obertura no está seguido de un signo de exclamanción; si no la 4149
primera y la última línea no son escritas. Es importante comprobar el último 4150
delimitador primero, por que el elemnto inicial es un subconjuntom y 4151
buscando por el elemento inicial debería retornar cierto en ambos casos. Si 4152
encontramos el elemento final, verificamos que estamos en el medio del 4153
procesamiento de un fichero fuente; sino, algo va mal con la manera en que 4154
los delimitadores han sido colocados en el fichero de texto. Si inCode es 4155
verdadero, todo está bien, y escribiremos (opcionalmente) la última linea y 4156
cerraremos el fichero. Cuando el elemento de obertura se encuentra, 4157
procesamos el directorio y el nombre del fichero y abrimos el fichero. Las 4158
siguientes funciones relacionadas con string fueron usadas en este ejemplo: 4159
length( ), append( ), getline( ), find( ) (dos versiones), 4160
find_first_not_of( ), substr( ),find_first_of( ), c_str( ), y, por 4161
supuesto, operator<<( ) 4162
4.5. Resumen 4163
Los objetos string proporcionan a los desarrolladores un gran número de 4164
ventajas sobre sus contrapartidas en C. La mayoria de veces, la clase string 4165
hacen a las cadenas con punteros a caracteres innecesarios. Esto elimina 4166
por completo una clase de defectos de software que radican en el uso de 4167
punteros no inicializados o con valores incorrectos. 4168
FIXME: Los string de C++, de manera transparente y dinámica, hacen 4169
crecer el espacio de alamcenamiento para acomodar los cambios de tamaño 4170
155
de los datos de la cadena. Cuando los datos en n string crece por encima 4171
de los límites de la memoria asignada inicialmente para ello, el objeto string 4172
hará las llamadas para la gestión de la memoria para obtener el espacio y 4173
retornar el espacio al montón. La gestión consistente de la memoria 4174
previente lagunas de memoria y tiene el potencial de ser mucho más 4175
eficiente que un "hágalo usted mismo". 4176
Las funciones de la clase string proporcionan un sencillo y comprensivo 4177
conjunto de herramientas para crear, modificar y buscar en cadenas. Las 4178
comparaciones entre string siempre son sensibles a 4179
mayúsculas/minúsculas, pero usted puede solucionar el problema copiando 4180
los datos a una cadena estilo C acabada en nulo y usando funciones no 4181
sensibles a mayúsculas/minúsculas, convirtiendo temporalmente los datos 4182
contenidos a mayúsculas o minúsculas, o creando una clase string sensible 4183
que sobreescribe los rasgos de carácter usados para crear un objeto 4184
basic_string 4185
4.6. Ejercicios 4186
Las soluciones a los ejercicios se pueden encontrar en el documento 4187
electrónico titulado «The Thinking in C++ Annotated Solution Guide», 4188
disponible por poco dinero en http://www.bruceeckel.com/. 4189
1. Escriba y pruebe una función que invierta el orden de los 4190
caracteres en una cadena. 4191
2. 2. Un palindromo es una palabra o grupo de palabras que tanto 4192
hacia delante hacia atrás se leen igual. Por ejemplo "madam" o 4193
"wow". Escriba un programa que tome un string como argumento 4194
desde la línea de comandos y, usando la función del ejercicio anterior, 4195
escriba si el string es un palíndromo o no. 4196
3. 3. Haga que el programa del Ejercicio 2 retorne verdadero 4197
incluso si las letras simetricas difieren en mayúsculas/minúsculas. Por 4198
ejemplo, "Civic" debería retornar verdadero aunque la primera letra 4199
sea mayúscula. 4200
156
4. 4. Cambie el programa del Ejercicio 3 para ignorar la puntuación 4201
y los espacios también. Por ejemplo "Able was I, ere I saw Elba." 4202
debería retornar verdadero. 4203
5. 5. Usando las siguientes declaraciones de string y solo char (no 4204
literales de cadena o números mágicos): 4205
string one("I walked down the canyon with the moving 4206
mountain bikers."); string two("The bikers passed by me too 4207
close for comfort."); string three("I went hiking instead."); 4208
produzca la siguiente frase: 4209
I moved down the canyon with the mountain bikers. The mountain 4210
bikers passed by me too close for comfort. So I went hiking instead. 4211
6. 6. Escriba un programa llamado "reemplazo" que tome tres 4212
argumentos de la línea de comandos representando un fichero de 4213
texto de entrada, una frase para reemplazar (llámela from), y una 4214
cadena de reemplazo (llámela to). El programa debería escribir un 4215
nuevo fichero en la salida estandar con todas las ocurrencias de from 4216
reemplazadas por to. 4217
7. 7. Repetir el ejercicio anterior pero reemplazando todas las 4218
instancias pero ignorando las mayúsculas/minúsculas. 4219
8. 8. Haga su programa a partir del Ejercicio 3 tomando un nombre 4220
de fichero de la linea de comandos, y despues mostrando todas las 4221
palabras que son palíndromos (ignorando las mayúsculas/minúsculas) 4222
en el fichero. No intente buscar palabras para palíndromos que son 4223
mas largos que una palabra (a diferencia del ejercicio 4). 4224
9. 9. Modifique HTMLStripper.cpp para que cuando encuentre una 4225
etiqueta, muestre el nombre de la etiqueta, entonces muestre el 4226
contenido del fichero entre la etiqueta y la etiqueta de finalización de 4227
fichero. Asuma que no existen etiquetas anidadas, y que todas las 4228
etiquetas tienen etiquetas de finalizacion (denotadas con 4229
</TAGNAME>). 4230
10. 10. Escriba un programa que tome tres argumentos de la línea 4231
de comandos (un nombre de fichero y dos cadenas) y muestre en la 4232
consola todas la líneas en el fichero que tengan las dos cadenas en la 4233
157
línea, alguna cadena, solo una cadena o ninguna de ellas, basándose 4234
en la entreada de un usuario al principio del programa (el usuario 4235
elegirá que modo de búsqueda usar). Para todo excepto para la 4236
opción "ninguna cadena", destaque la cadena(s) de entrada colocando 4237
un asterisco (*) al principio y al final de cada cadena que coincida 4238
cuando sea mostrada. 4239
11. 11. Escriba un programa que tome dos argumentos de la linea 4240
de comandos (un nombre de fichero y una cadena) y cuente el 4241
numero de veces que la cadena esta en el fichero, incluso si es una 4242
subcadena (pero ignorando los solapamientos). Por ejemplo, una 4243
cadena de entrada de "ba" debería coincidir dos veces en la palabra 4244
"basquetball", pero la cadena de entrada "ana" solo deberia coincidir 4245
una vez en "banana". Muestre por la consola el número de veces que 4246
la cadena coincide en el fichero, igual que la longitud media de las 4247
palabras donde la cadena coincide. (Si la cadena coincide más de una 4248
vez en una palabra, cuente solamente la palabra una vez en el cálculo 4249
de la media). 4250
12. 12. Escriba un programa que tome un nombre de fichero de la 4251
línea de comandos y perfile el uso del carácter, incluyendo la 4252
puntuación y los espacios (todos los valores de caracteres desde el 4253
0x21 [33] hasta el 0x7E [126], además del carácter de espacio). Esto 4254
es, cuente el numero de ocurrencias para cada carácter en el fichero, 4255
después muestre los resultados ordenados secuencialmente (espacio, 4256
despues !, ", #, etc.) o por frecuencia descendente o ascendente 4257
basado en una entrada de usuario al principio del programa. Para el 4258
espacio, muestre la palabra "espacio" en vez del carácter ' '. Una 4259
ejecución de ejemplo debe mostrarse como esto: 4260
Formato secuencial, ascendente o descendente (S/A/D): D t: 526 r: 4261
490 etc. 4262
13. 13. Usando find() y rfind(), escriba un programa que tome dos 4263
argumentos de lánea de comandos (un nombre de fichero y una 4264
cadena) y muestre la primera y la última palapra (y sus indices) que no 4265
coinciden con la cadena, asi como los indice de la primera y la última 4266
158
instancia de la cadena. Muestre "No Encontrado" si alguna de las 4267
busquedas fallan. 4268
14. 14. Usando la familia de fuciones find_first_of (pero no 4269
exclusivamente), escriba un programa que borrará todos los 4270
caracteres no alfanuméricos excepto los espacios y los puntos de un 4271
fichero. Despues convierta a mayúsculas la primera letra que siga a 4272
un punto. 4273
15. 15. Otra vez, usando la familia de funciones find_first_of, 4274
escriba un programa que acepte un nombre de fichero como 4275
argumentod elinea de comandos y después formatee todos los 4276
números en un fichero de moneda. Ignore los puntos decimales 4277
después del primero despues de un carácter no mumérico, e 4278
redondee al 4279
16. 16. Escriba un programa que acepte dos argumentos por línea 4280
de comandos (un nombre de fichero y un numero) y mezcle cada 4281
paralabra en el fichero cambiando aleatoriamente dos de sus letras el 4282
número de veces especificado en el segundo parametro. (Esto es, si 4283
le pasamos 0 a su programa desde la línea de comandos, las palabras 4284
no serán mezcladas; si le pasamos un 1, un par de letras 4285
aleatoriamente elegidas deben ser cambiadas, para una entrada de 2, 4286
dos parejas aleatorias deben ser intercambiadas, etc.). 4287
17. 17. Escriba un programa que acepte un nombre de fichero desde 4288
la línea de comandos y muestre el numero de frases (definido como el 4289
numero de puntos en el fichero), el número medio de caracteres por 4290
frase, y el número total de caracteres en el fichero. 4291
4292
4293
[1] Algunos materiales de este capítulo fueron creados originalmente por 4294
Nancy Nicolaisen 4295
[2] Es dificil hacer implementaciones con multiples referencias para trabajar de 4296
manera segura en multihilo. (Ver [More Exceptional C++, pp.104-14]). Ver 4297
Capitulo 10 para más información sobre multiples hilos 4298
[3] Es una abrviación de "no position", y su valor más alto puede ser 4299
representado por el ubicador de string size_type (std::size_t por defecto). 4300
159
[4] Descrito en profundidad en el Capítulo 6. 4301
[5] Para mantener la exposición simple, esta version no maneja etiquetas 4302
anidadas, como los comentarios. 4303
[6] Es tentador usar aquí las matemáticas para evitar algunas llamadas a 4304
erase(), pero como en algunos casos uno de los operandos es string::npos 4305
(el entero sin signo más grande posible), ocurre un desbordamiento del entero 4306
y se cuelga el algoritmo. 4307
[7] Por las razones de seguridad mencionadas, el C++ Standards Committee 4308
está considerando una propuesta de redefinición del string::operator[] para 4309
comportarse de manera idéntica al string::at() para C++0x. 4310
[8] (N.del T.) Se refiere a string amplio puesto que esta formado por 4311
caracteres anchos wchar_t que deben soportar la codificación mas grande que 4312
soporte el compilador. Casi siempre esta codificación es Unicode, por lo que 4313
casi siempre el ancho de wchar_t es 2 bytes 4314
[9] Esté alerta porque algunas versiones de Microsoft Word que substituyen 4315
erroneamente los caracteres con comilla simple con un carácter ASCII cuando 4316
salva el documento como texto, causan un error de compilación. No tenemos 4317
idea de porqué pasa esto. Simplemente reemplace el carácter manualmente 4318
con un apóstrofe. 4319
[10] POSIX, un estándar IEEE, es un "Portable Operating System Interface" 4320
(Interficie de Sistema Operativo Portable) y es una generalización de muchas 4321
de las llamadas a sistema de bajo nivel encontradas en los sistemas UNIX. 4322
5: Iostreams 4323
Tabla de contenidos 4324
5.1. ¿Por que iostream? 4325
5.2. Iostreams al rescate 4326
5.3. Manejo errores de stream 4327
5.4. Iostreams de fichero 4328
5.5. Almacenamiento de iostream 4329
5.6. Buscar en iostreams 4330
5.7. Iostreams de string 4331
5.8. Formateo de stream de salida 4332
5.9. Manipuladores 4333
5.10. 4334
5.11. 4335
5.12. 4336
5.13. 4337 Puedes hacer mucho más con el problema general de E/S que 4338
simplemente coger el E/S estándar y convertirlo en una clase. 4339
¿No seria genial si pudiera hacer que todos los 'receptáculos' -E/S 4340
estándar, ficheros, e incluso boques de memoria- parecieran iguales de 4341
160
manera que solo tuviera que recordar una interficie? Esta es la idea que hay 4342
detrás de los iostreams. Son mucho más sencillos, seguros, y a veces 4343
incluso más eficientes que el conjunto de funciones de la libreria estándar de 4344
C stdio. 4345
Las clases de iostream son generalmente la primera parte de la libreria de 4346
C++ que los nuevos programadores de C++ parender a usar. En este 4347
capítulo se discute sobre las mejoras que representan los iostream sobre las 4348
funciones de stdio de C y explora el comprotamiento de los ficheros y 4349
streams de strings además de los streams de consola. 4350
5.1. ¿Por que iostream? 4351
Se debe estar preguntando que hay de malo en la buena y vieja librería de 4352
C. ¿Por que no 'incrustar' la libreria de C en una clase y ya está? A veces 4353
esta solución es totalmente válida. Por ejemplo, suponga que quiere estar 4354
seguro que un fichero representado por un puntero de stdio FILE siempre es 4355
abierto de forma segura y cerrado correctamente sin tener que confiar en que 4356
el usuario se acuerde de llamar a la función close(). El siguiente programa 4357
es este intento: 4358
//: C04:FileClass.h 4359
// stdio files wrapped. 4360
#ifndef FILECLASS_H 4361
#define FILECLASS_H 4362
#include <cstdio> 4363
#include <stdexcept> 4364
4365
class FileClass { 4366
std::FILE* f; 4367
public: 4368
struct FileClassError : std::runtime_error { 4369
161
FileClassError(const char* msg) 4370
: std::runtime_error(msg) {} 4371
}; 4372
FileClass(const char* fname, const char* mode = "r"); 4373
~FileClass(); 4374
std::FILE* fp(); 4375
}; 4376
#endif // FILECLASS_H ///:~ 4377
Listado 5.1. C04/FileClass.h 4378
4379
Cuando trabaja con ficheros E/S en C, usted trabaja con punteros 4380
desnudos a una struct de FILE, pero esta clase envuelve los punteros y 4381
garantiza que es correctamente inicializada y destruida usando el constructor 4382
y el destructor. El segundo parámetro del constructor es el modo del fichero, 4383
que por defecto es 'r' para 'leer' 4384
Para pedir el valor del puntero para usarlo en las funciones de fichero de 4385
E/S, use la función de acceso fp(). Aquí están las definiciones de las 4386
funciones miembro: 4387
//: C04:FileClass.cpp {O} 4388
// FileClass Implementation. 4389
#include "FileClass.h" 4390
#include <cstdlib> 4391
#include <cstdio> 4392
using namespace std; 4393
4394
FileClass::FileClass(const char* fname, const char* mode) { 4395
162
if((f = fopen(fname, mode)) == 0) 4396
throw FileClassError("Error opening file"); 4397
} 4398
4399
FileClass::~FileClass() { fclose(f); } 4400
4401
FILE* FileClass::fp() { return f; } ///:~ 4402
Listado 5.2. C04/FileClass.cpp 4403
4404
El constructor llama a fopen(), tal como se haría normalmente, pero 4405
además se asegura que el resultado no es cero, que indica un error al abrir el 4406
fichero. Si el fichero no se abre correctamente, se lanza una excepción. 4407
El destructor cierra el fichero, y la función de acceso fp() retorna f. Este 4408
es un ejemplo de uso de FileClass: 4409
//: C04:FileClassTest.cpp 4410
//{L} FileClass 4411
#include <cstdlib> 4412
#include <iostream> 4413
#include "FileClass.h" 4414
using namespace std; 4415
4416
int main() { 4417
try { 4418
FileClass f("FileClassTest.cpp"); 4419
const int BSIZE = 100; 4420
163
char buf[BSIZE]; 4421
while(fgets(buf, BSIZE, f.fp())) 4422
fputs(buf, stdout); 4423
} catch(FileClass::FileClassError& e) { 4424
cout << e.what() << endl; 4425
return EXIT_FAILURE; 4426
} 4427
return EXIT_SUCCESS; 4428
} // File automatically closed by destructor 4429
///:~ 4430
Listado 5.3. C04/FileClassTest.cpp 4431
4432
Se crea el objeto FileClass y se usa en llamadas a funciones E/S de 4433
fichero normal de C, llamando a fp(). Cuando haya acabado con ella, 4434
simplemente olvídese; el fichero será cerrado por el destructor al final del 4435
ámbito de la variable. 4436
Incluso teniendo en cuenta que FILE es un puntero privado, no es 4437
particularmente seguro porque fp() lo recupera. Ya que el único efecto que 4438
parece estar garantizado es la inicialización y la liberación, ¿por que no 4439
hacerlo público o usar una struct en su lugar? Nótese que mientras se 4440
puede obtener una copia de f usando fp(), no se puede asignar a f -que 4441
está completamente bajo el control de la clase. Después de capturar el 4442
puntero retornado por fp(), el programador cliente todavía puede asignar a 4443
la estructura elementos o incluso cerrarlo, con lo que la seguridad esta en la 4444
garantía de un puntero a FILE válido mas que en el correcto contenido de la 4445
estructura. 4446
Si quiere completa seguridad, tiene que evitar que el usuario acceda 4447
directamente al puntero FILE. Cada una de las versiones de las funciones 4448
normales de E/S a ficheros deben ser mostradas como miembros de clase 4449
164
para que todo lo que se pueda hacer desde el acercamiento de C esté 4450
disponible en la clase de C++. 4451
//: C04:Fullwrap.h 4452
// Completely hidden file IO. 4453
#ifndef FULLWRAP_H 4454
#define FULLWRAP_H 4455
#include <cstddef> 4456
#include <cstdio> 4457
#undef getc 4458
#undef putc 4459
#undef ungetc 4460
using std::size_t; 4461
using std::fpos_t; 4462
4463
class File { 4464
std::FILE* f; 4465
std::FILE* F(); // Produces checked pointer to f 4466
public: 4467
File(); // Create object but don't open file 4468
File(const char* path, const char* mode = "r"); 4469
~File(); 4470
int open(const char* path, const char* mode = "r"); 4471
int reopen(const char* path, const char* mode); 4472
int getc(); 4473
int ungetc(int c); 4474
165
int putc(int c); 4475
int puts(const char* s); 4476
char* gets(char* s, int n); 4477
int printf(const char* format, ...); 4478
size_t read(void* ptr, size_t size, size_t n); 4479
size_t write(const void* ptr, size_t size, size_t n); 4480
int eof(); 4481
int close(); 4482
int flush(); 4483
int seek(long offset, int whence); 4484
int getpos(fpos_t* pos); 4485
int setpos(const fpos_t* pos); 4486
long tell(); 4487
void rewind(); 4488
void setbuf(char* buf); 4489
int setvbuf(char* buf, int type, size_t sz); 4490
int error(); 4491
void clearErr(); 4492
}; 4493
#endif // FULLWRAP_H ///:~ 4494
Listado 5.4. C04/Fullwrap.h 4495
4496
Esta clase contiene casi todas las funciones de E/S de fichero de 4497
<cstdio>. (vfprintf() no esta; se implementa en la función miembro 4498
printf() ) 4499
166
El fichero tiene el mismo constructor que en el ejemplo anterior, y también 4500
tiene un constructor por defecto. El constructor por defecto es importante si 4501
se crea un array de objetos File o se usa un objeto File como miembro de 4502
otra clase donde la inicialización no se realiza en el contructor, sino cierto 4503
tiempo después de que el objeto envolvente se cree. 4504
El constructor por defecto pone a cero el puntero a FILE privado f. Pero 4505
ahora , antes de cualquier referencia a f, el valor debe ser comprobado para 4506
asegurarse que no es cero. Esto se consigue con F(), que es privado porque 4507
está pensado para ser usado solamente por otras funciones miembro. (No 4508
queremos dar acceso directo a usuarios a la estructura de FILE subyacente 4509
en esta clase). 4510
Este acercamiento no es terrible en ningún sentido. Es bastante funcional, 4511
y se puede imaginar haciendo clases similares para la E/S estándar 4512
(consola) y para los formateos en el core (leer/escribir un trozo de la memoria 4513
en vez de un fichero o la consola). 4514
Este bloque de código es el interprete en tiempo de ejecución usado para 4515
las listas variables de argumentos. Este es el código que analiza el formato 4516
de su cadena en tiempo de ejecución y recoge e interpreta argumentos 4517
desde una lista variable de argumentos. Es un problema por cuatro razones: 4518
1. Incluso si solo se usa una fracción de la funcionalidad del 4519
interprete, se carga todo en el ejecutable. Luego si quiere usar un 4520
printf("%c", 'x'); , usted tendrá todo el paquete, incluido las partes 4521
que imprimen números en coma flotante y cadenas. No hay una 4522
opción estándar para reducir el la cantidad de espacio usado por el 4523
programa. 4524
2. Como la interpretación pasa en tiempo de ejecución, no se 4525
puede evitar un empeoramiento del rendimiento. Esto es frustrante por 4526
que toda la información está allí, en el formato de la cadena, en 4527
tiempo de compilación, pero no se evalua hasta la ejecución. Por otro 4528
lado, si se pudieran analizar los argumentos en el formateo de la 4529
cadena durante la compilación, se podrían hacer llamadas directas a 4530
funciones que tuvieran el potencial de ser mucho más rápidas que un 4531
interprete en tiempo de ejecución (aunque la familia de funciones de 4532
printf() acostumbran a estar bastante bien optimizadas). 4533
167
3. Como el formateo de la cadena no se evalua hasta la ejecución, 4534
no se hace una comprobación de errores al compilar. Probalblemente 4535
está familiarizado con este problema si ha intentado buscar errores 4536
que provienen del uso de un número o tipo de argumentos incorrecto 4537
en una sentencia printf(). C++ ayuda mucho a encontrar 4538
rápidamente errores durante la compilación y hacerle la vida más fácil. 4539
Parece una tonteria desechar la seguridad en los tipos de datos para 4540
la libreria de E/S, especialmente cuando usamos intensivamente las 4541
E/S. 4542
4. Para C++, el más crucial de los problemas es que la familia de 4543
funciones de printf() no es particularmente extensible. Esta realmente 4544
diseñada para manejar solo los tipos básicos de datos en C (char, int, 4545
float, double, wchar_t, char*, wchar_t*, y void*) y sus variaciones. 4546
Debe estar pensando que cada vez que añade una nueva clase, 4547
puede añadir funciones sobrecargadas printf() y scanf() (y sus 4548
variaciones para ficheros y strings), pero recuerde: las funciones 4549
sobrecargadas deben tener diferentes tipos de listas de argumentos, y 4550
la familia de funciones de printf() esconde esa información en la 4551
cadena formateada y su lista variable de argumentos. Para un 4552
lenguage como C++, cuya virtud es que se pueden añadir fácilmente 4553
nuevos tipos de datos, esta es una restricción inaceptable. 4554
5.2. Iostreams al rescate 4555
Estos problemas dejan claro que la E/S es una de las principales 4556
prioridades para la librería de clases estándar de C++. Como 'hello, worlod' 4557
es el primer programa que cualquiera escribe en un nuevo lenguaje, y porque 4558
la E/S es parte de virtualmente cualquier programa, la librería de E/S en C++ 4559
debe ser particularmente fácil de usar. Tembién tiene el reto mucho mayor de 4560
acomodar cualquier nueva clase. Por tanto, estas restricciones requieren que 4561
esta librería de clases fundamentales tengan un diseño realmente inspirado. 4562
Además de ganar en abstracción y claridad en su trabajo con las E/S y el 4563
formateo, en este capítulo verá lo potente que puede llegar a ser esta librería 4564
de C++. 4565
168
5.2.1. Insertadores y extractores 4566
Un stream es un objeto que transporta y formatea carácteres de un ancho 4567
fijo. Puede tener un stream de entrada (por medio de los descendientes de la 4568
clase istream), o un stream de salida (con objetos derivados de ostream), o 4569
un stream que hace las dos cosas simultáneamente (con objetos derivados 4570
de iostream). La librería iostream provee tipos diferentes de estas clases: 4571
ifstream, ofstream y fstream para ficheros, y istringstream, 4572
ostringstream, y stringstream para comunicarese con la clase string del 4573
estándar C++. Todas estas clases stream tiene prácticamente la misma 4574
interfaz, por lo que usted puede usar streams de manera uniforme, aunque 4575
esté trabajando con un fichero, la E/S estándar, una región de la memoria, o 4576
un objeto string. La única interfaz que aprenderá también funciona para 4577
extensiones añadidas para soportar nuevas clases. Algunas funciones 4578
implementan sus comandos de formateo, y algunas funciones leen y 4579
escriben caracteres sin formatear. 4580
Las clases stream mencionadas antes son actualmente especializaciones 4581
de plantillas, muchas como la clase estándar string son especializaciones 4582
de la plantilla basic_string. Las clases básicas en la jerarquia de herencias 4583
son mostradas en la siguiente figura: [11] 4584
La clase ios_base declara todo aquello que es común a todos los stream, 4585
independientemente del tipo de carácteres que maneja el stream. Estas 4586
declaraciones son principalmente constantes y funciones para manejarlas, 4587
algunas de ella las verá a durante este capítulo. El resto de clases son 4588
plantillas que tienen un tipo de caracter subyacente como parámetro. La 4589
clase istream, por ejemplo, está definida a continuación: 4590
typedef basic_istream<char< istream; 4591
Todas las clases mencionadas antes estan definidas de manera similar. 4592
También hay definiciones de tipo para todas las clases de stream usando 4593
wchar_t (la anchura de este tipo de carácteres se discute en el Capítulo 3) en 4594
lugar de char. Miraremos esto al final de este capítulo. La plantilla basic_ios 4595
define funciones comunes para la entrada y la salida, pero depende del tipo 4596
169
de carácter subyacente (no vamos a usarlo mucho). La plantilla 4597
basic_istream define funciones genéricas para la entrada y basic_ostream 4598
hace lo mismo para la salida. Las clases para ficheros y streams de strings 4599
introducidas después añaden funcionalidad para sus tipos especificos de 4600
stream. 4601
En la librería de iostream, se han sobrecargado dos operadores para 4602
simplificar el uso de iostreams. El operador << se denomina frecuentemente 4603
instertador para iostreams, y el operador >> se denomina frecuentemente 4604
extractor. 4605
Los extractores analizan la información esperada por su objeto destino de 4606
acuerdo con su tipo. Para ver un ejemplo de esto, puede usar el objeto cin, 4607
que es el equivalente de iostream de stdin en C, esto es, entrada estándar 4608
redireccionable. Este objeto viene predefinido cuando usted incluye la 4609
cabecera <iostream>. 4610
int i; 4611
cin >> i; 4612
4613
float f; 4614
cin >> f; 4615
4616
char c; 4617
cin >> c; 4618
4619
char buf[100]; 4620
cin >> buf; 4621
Existe un operador sobrecargado >> para cada tipo fundamental de dato. 4622
Usted también puede sobrecargar los suyos, como verá más adelante. 4623
170
Para recuperar el contenido de las variables, puede usar el objeto cout 4624
(correspondiente con la salida estándar; también existe un objeto cerr 4625
correspondiente con la salida de error estándar) con el insertador <<: 4626
cout << "i = "; 4627
cout << i; 4628
cout << "\n"; 4629
cout << "f = "; 4630
cout << f; 4631
cout << "\n"; 4632
cout << "c = "; 4633
cout << c; 4634
cout << "\n"; 4635
cout << "buf = "; 4636
cout << buf; 4637
cout << "\n"; 4638
Esto es tedioso y no parece ser un gran avance sobre printf(), aparte de 4639
la mejora en la comprobación de tipos. Afortunadamente, los insertadores y 4640
extractores sobrecargados están diseñados para ser encadenados dentro de 4641
expresiones más complejas que son mucho más fáciles de escribir (y leer): 4642
cout << "i = " << i << endl; 4643
cout << "f = " << f << endl; 4644
cout << "c = " << c << endl; 4645
cout << "buf = " << buf << endl; 4646
171
Definir insertadores y extractores para sus propias clases es simplemente 4647
una cuestion de sobrecargar los operadores asociados para hacer el trabajo 4648
correcto, de la siguente manera: 4649
Hacer del primer parámetro una referencia no constante al stream (istream 4650
para la entrada, ostream para la salida). 4651
Realizar la operación de insertar/extraer datos hacia/desde el stream 4652
(procesando los componentes del objeto). 4653
Retornar una referencia al stream 4654
El stream no debe ser constante porque el procesado de los datos del 4655
stream cambian el estado del stream. Retornando el stream, usted permite el 4656
encadenado de operaciones en una sentencia individual, como se mostró 4657
antes. 4658
Como ejemplo, considere como representar la salida de un objeto Date en 4659
formato MM-DD-AAAA . El siguiente insertador hace este trabajo: 4660
ostream& operator<<(ostream& os, const Date& d) { 4661
char fillc = os.fill('0'); 4662
os << setw(2) << d.getMonth() << '-' 4663
<< setw(2) << d.getDay() << '-' 4664
<< setw(4) << setfill(fillc) << d.getYear(); 4665
return os; 4666
} 4667
Esta función no puede ser miembro de la clase Date por que el operando 4668
de la izquierda << debe ser el stream de salida. La función miembro fill() 4669
de ostream cambia el carácter de relleno usado cuando la anchura del campo 4670
de salida, determinada por el manipulador setw(), es mayor que el 4671
necesitado por los datos. Usamos un caracter '0' ya que los meses anteriores 4672
a Octubre mostrarán un cero en primer lugar, como '09' para Septiembre. La 4673
funcion fill() también retorna el caracter de relleno anterior (que por 4674
172
defecto es un espacio en blanco) para que podamos recuperarlo después 4675
con el manipulador setfill(). Discutiremos los manipuladores en 4676
profundidad más adelante en este capítulo. 4677
Los extractores requieren algo más cuidado porque las cosas pueden ir 4678
mal con los datos de entrada. La manera de avisar sobre errores en el 4679
stream es activar el bit de error del stream, como se muestra a continuación: 4680
istream& operator>>(istream& is, Date& d) { 4681
is >> d.month; 4682
char dash; 4683
is >> dash; 4684
if(dash != '-') 4685
is.setstate(ios::failbit); 4686
is >> d.day; 4687
is >> dash; 4688
if(dash != '-') 4689
is.setstate(ios::failbit); 4690
is >> d.year; 4691
return is; 4692
} 4693
Cuando se activa el bit de error en un stream, todas las operaciones 4694
posteriores serán ignoradas hasta que el stream sea devuelto a un estado 4695
correcto (explicado brevemente). Esto es porque el código de arriba continua 4696
extrayendo incluso is ios::failbit está activado. Esta implementación es 4697
poco estricta ya que permite espacios en blanco entre los numeros y guiones 4698
en la cadena de la fecha (por que el operador >> ignora los espacios en 4699
blanco por defecto cuado lee tipos fundamentales). La cadena de fecha a 4700
continuación es válida para este extractor: 4701
173
"08-10-2003" 4702
"8-10-2003" 4703
"08 - 10 - 2003" 4704
Pero estas no: 4705
"A-10-2003" // No alpha characters allowed 4706
"08%10/2003" // Only dashes allowed as a delimiter 4707
Discutiremos los estados de los stream en mayor profundidad en la 4708
sección 'Manejar errores de stream' después en este capítulo. 4709
5.2.2. Uso común 4710
Como se ilustraba en el extractor de Date, debe estar alerta por las 4711
entradas erróneas. Si la entrada produce un valor inesperado, el proceso se 4712
tuerce y es difícil de recuperar. Además, por defecto, la entrada formateada 4713
está delimitada por espacios en blanco. Considere que ocurre cuando 4714
recogemos los fragmentos de código anteriores en un solo programa: 4715
//: V2C04:Iosexamp.cpp {RunByHand} 4716
y le proporcionamos la siguiente entrada: 4717
12 1.4 c this is a test 4718
esperamos la misma salida que si le hubieramos proporcionado esto: 4719
12 4720
1.4 4721
174
c 4722
this is a test 4723
pero la salida es algo inesperado 4724
i = 12 4725
f = 1.4 4726
c = c 4727
buf = this 0xc 4728
Nótese que buf solo tiene la primera palabra porque la rutina de entrada 4729
busca un espacio que delimite la entrada, que es el que se encuentra 4730
después de 'tihs.' Además, si la entrada continua de datos es mayor que el 4731
espacio reservado por buf, sobrepasamos los limites del buffer. 4732
En la práctica, usualmente deseará obtener la entrada desde programas 4733
interactivos, una linea cada vez como secuencia de carácteres, leerla, y 4734
después hacer las conversiones necesarias hasta que estén seguras en un 4735
buffer. De esta manera no deberá preocuparse por la rutina de entrada 4736
fallando por datos inesperados. 4737
Otra consideración es todo el concepto de interfaz de línea de comandos. 4738
Esto tenia sentido en el pasado cuando la consola era la única interfaz con la 4739
máquina, pero el mundo está cambiando rápidamente hacia otro donde la 4740
interfaz gráfica de usuario (GUI) domina. ¿Cual es el sentido de la E/S por 4741
consola en este mundo? Esto le da mucho más sentido a ignorar cin en 4742
general, salvo para ejemplos simples y tests, y hacer los siguientes 4743
acercamientos: 4744
1. Si su programa requiere entrada, ¿leer esta entrada desde un 4745
fichero? Pronto verá que es remarcablemente fácil usar ficheros con 4746
iostream. Iostream para ficheros todavia funciona perfectamente con 4747
una GUI. 4748
175
2. Leer la entrada sin intentar convertirla, como hemos sugerido. 4749
Cuando la entrada es algun sitio donde no podemos arriesgarnos 4750
durante la conversión, podemos escanearla de manera segura. 4751
3. La salida es diferente. Si está usando una interfaz gráfica, cout 4752
no necesariamente funciona, y usted debe mandarlo a un fichero (que 4753
es indéntico a mandarlo a un cout) o usar los componentes del GUI 4754
para mostrar los datos. En cualquier otra situacion, a menudo tiene 4755
sentido mandarlo a cout. En ambos casos, la funciones de formateo 4756
de la salida de iostream son muy útiles. 4757
Otra práctica común ahorra tiempo en compilaciones largas. Consideres, 4758
por ejemplo, cómo quiere declarar los operadores del stream Date 4759
introducidos antes en el capítulo en un fichero de cabecera. Usted solo 4760
necesita incluir los prototipos para las funciones, luego no es necesario 4761
incluir la cabecera entera de <iostream> en Date.h. La práctica estándar es 4762
declarar solo las clases, algo como esto: 4763
class ostream; 4764
Esta es una vieja tecnica para separar la interfaz de la implementación y a 4765
menudo la llaman declaración avanzada( y ostream en este punto debe ser 4766
considerada un tipo incompleto, ya que la definición de la clase no ha sido 4767
vista todavia por el compilador). 4768
Esto con funcionará asi, igualmente, por dos razones: 4769
Las clases stream estan definidas en el espacio de nombres std. 4770
Son plantillas. 4771
La declaración correcta debería ser: 4772
namespace std { 4773
template<class charT, class traits = char_traits<charT> > 4774
class basic_ostream; 4775
typedef basic_ostream<char> ostream; 4776
176
} 4777
(Como puede ver, como las clase string, las clases stream usan las clases 4778
de rasgos de caracter mencionadas en el Capítulo 3). Como puede ser 4779
terriblemente tedioso darle un tipo a todas las clases stream a las que quiere 4780
referenciar, el estándar provee una cabecera que lo hace por usted: 4781
// Date.h 4782
#include <iosfwd> 4783
4784
class Date { 4785
friend std::ostream& operator<<(std::ostream&, 4786
const Date&); 4787
friend std::istream& operator>>(std::istream&, Date&); 4788
// Etc. 4789
5.2.3. Entrada orientada a líneas 4790
Para recoger la entrada de línea en línea, tiene tres opciones: 4791
La función miembre get() 4792
La función miembro getline() 4793
La función global getline() definida en la cabecera <string> 4794
Las primeras dos funciones toman tres parámentros: 4795
Un puntero a un buffer de carácters donde se guarda el resultado. 4796
El tamaño de este buffer (para no sobrepasarlo). 4797
El carácter de finalización, para conocer cuando parar de leer la entrada. 4798
177
El carácter de finalización tiene un valor por defecto de '\n', que es el que 4799
usted usará usualmente. Ambas funciones almacenan un cero en el buffer 4800
resultante cuando encuentran el caracter de terminación en la entrada. 4801
Entonces, ¿cual es la diferencia? Sutil pero importante: get() se detiene 4802
cuando vee el delimitador en el stream de entrada, pero no lo extrae de 4803
stream de entrada. Entonces, si usted hace otro get() usando el mismo 4804
delimitador, retornará inmediatamente sin ninguna entrada contenida. 4805
(Presumiblemente, en su lugar usará un delimitador diferente en la siguiente 4806
sentencia get() o una función de entrada diferente.) La función getline(), 4807
por el contrario, extrae el delimitador del stream de entrada, pero tampoco lo 4808
almacena en el buffer resultante. 4809
La función getline() definida en <string> es conveniente. No es una 4810
función miembro, sino una función aislada declarada en el espacio de 4811
nombres std. Sólo toma dos parámetros que no son por defecto, el stream 4812
de entrada y el objeto string para rellenar. Como su propio nombre dice, lee 4813
carácteres hasta que encuentra la primera aparición del delimitador ('\n' por 4814
defecto) y consume y descarta el delimitador. La ventaja de esta función es 4815
que lo lee dentro del objeto string, así que no se tiene que preocuparse del 4816
tamaño del buffer. 4817
Generalmente, cuando esta procesando un fichero de texto en el que 4818
usted quiere leer de línea en línea, usted querra usar una de las funciones 4819
getline(). Versiones sobrecargadas de get() 4820
Versiones sobrecargadas de get() 4821
La función get() también viene en tres versiones sobrecargadas: una sin 4822
argumentos que retorna el siguiente carácter usando un valor de retorno int; 4823
una que recoge un carácter dentro de su argumento char usando una 4824
referencia; y una que almacena directamente dentro del buffer subyacente de 4825
otro objeto iostream. Este último se explora después en el capítulo. 4826
Leyendo bytes sin formato 4827
Si usted sabe exactamente con que esta tratando y quiere mover los bytes 4828
directamente dentro de una variable, un array, o una estructura de memoria, 4829
puede usar la función de E/S sin formatear read(). El primer argumento para 4830
178
esta función es un puntero a la destinación en memoria, y el segundo es el 4831
número de bytes para leer. Es especialmente útil su usted ha almacenado 4832
previamente la información a un fichero, por ejemplo, en formato binario 4833
usando la función miembro complementaria write() para el stream de salida 4834
(usando el mismo compilador, por supuesto). Verá ejemplos de todas estas 4835
funciones más adelante. 4836
5.3. Manejo errores de stream 4837
El extractor de Date mostrado antes activa el bit de error de un stream bajo 4838
ciertas condiciones. ¿Como sabe un usuario que este error ha ocurrido? 4839
Puede detectar errores del stream llamando a ciertas funciones miembro del 4840
stream para ver si tenemos un estado de error, o si a usted no le preocupa 4841
qué tipo de error ha pasado, puede evaluar el stream en un contexto 4842
Booleano. Ambas técnicas derivan del estado del bit de error de un stream. 4843
5.3.1. Estados del stream 4844
La clase ios_base, desde la que ios deriva,[12]define cuatro banderas que 4845
puede usar para comprobar el estado de un stream: 4846
Bandera 4847
Significado 4848
badbit 4849
Algún error fatal (quizás físico) ha ocurrido. El stream debe considerarse 4850
no usable. 4851
eofbit 4852
Ha ocurrido un final de entrada (ya sea por haber encontrado un final físico 4853
de un stream de fichero o por que el usuario ha terminado el stream de 4854
consola, (usando un Ctrl-Z o Ctrl-D). 4855
failbit 4856
Una operación de E/S ha fallado, casi seguro que por datos inválidos (p.e. 4857
encontrar letras cuando se intentaba leer un número). El stream todavía se 4858
puede usar. El failbit también se activa cuando ocurre un final de entrada. 4859
goodbit 4860
179
Todo va bien; no hay errores. La final de la entrada todavía no ha ocurrido. 4861
Puede comprobar si alguna de estas condiciones ha ocurrido llamando a la 4862
función miembro correspondiente que retorna un valor Booleano indicando 4863
cual de estas ha sido activada. La función miembro de stream good() retorna 4864
cierto si ninguno de los otros tres bits se han activado. La función eof() 4865
retorna cierto si eofbit está activado, que ocurre con un intento de leer de un 4866
stream que ya no tiene datos (generalmente un fichero). Como el final de una 4867
entrada ocurre en C++ cuando tratamos de leer pasado el final del medio 4868
físico, failbit también se activa para indicar que los datos esperados no 4869
han sido correctamente leídos. La función fail() retorna cierto si failbit o 4870
badbit están activados, y bad() retorna cierto solo si badbit está activado. 4871
Una vez alguno de los bit de error de un stream se activa, permanece 4872
activo, cosa que no siempre es lo que se quiere. Cuando leemos un fichero, 4873
usted puede querer colocarse en una posición anterior en el fichero antes de 4874
su final. Simplemenete moviendo el puntero del fichero no se desactiva el 4875
eofbit o el failbit; debe hacerlo usted mismo con la función clear(), 4876
haciendo algo así: 4877
myStream.clear(); // Clears all error bits 4878
Después de llamar a clear(), good() retornará cierto si es llamada 4879
inmediatamente. Como vió en el extractor de Date antes, la función 4880
setstate() activa los bits que usted le pasa.¿Eso significa que setstate no 4881
afecta a los otros bits? Si ya esta activo, permanece activo. Si usted quiere 4882
activar ciertos bits pero en el mismo momento, desactivar el resto, usted 4883
puede llamar una versión sobrecargada de clear(), pasandole una 4884
expresion binaria representando los bits que quiere que se activen, así: 4885
myStream.clear(ios::failbit | ios::eofbit); 4886
La mayoría del tiempo usted no estará interesado en comprobar los bits de 4887
estado del stream individualmente. Generalmente usted simplemente quiere 4888
conocer si todo va bien. Ese es el caso cuando quiere leer un fichero del 4889
180
principio al final; usted quiere saber simplemente cuando la entrada de datos 4890
se ha agotado. Puede usar una conversion de la función definida para void* 4891
que es automáticamente llamada cuando un stream esta en una expresión 4892
booleana. Leer un stream hasta el final de la entrada usando este idioma se 4893
parece a lo siguiente: 4894
int i; 4895
while(myStream >> i) 4896
cout << i << endl; 4897
Recuerde que operator>>() retorna su argumento stream, así que la 4898
sentencia while anterior comprueba el stream como una expresión booleana. 4899
Este ejemplo particular asume que el stream de entrada myStream contiene 4900
enteros separados por un espacio en blanco. La función ios_base::operator 4901
void*() simplemente llama a good() en su stream y retorna el resultado.[13] 4902
Como la mayoría de operaciones de stream retornan su stream, usar ese 4903
idioma es conveniente. 4904
5.3.2. Streams y excepciones 4905
Los iostream han existido como parte de C++ mucho antes que hubieran 4906
excepciones, luego comprobar el estado de un stream manualmente era la 4907
manera en que se hacia. Para mantener la compatibilidad, este es todavía el 4908
status quo, pero los modernos iostream pueden lanzar excepciones en su 4909
lugar. La función miembro de stream exceptions() toma un parámetro 4910
representando los bits de estado para los que usted quiere lanzar la 4911
excepcion. Siempre que el stream encuentra este estado,este lanza una 4912
excepcion de tipo std::ios_base::failure, que hereda de std::exception. 4913
Aunque usted puede disparar una excepción para alguno de los cuatro 4914
estados de un stream, no es necesariamente una buena idea activar las 4915
excepciones para cada uno de ellos. Tal como explica el Capítulo uno, se 4916
usan las excepciones para condiciones verdaderamente excepcionales, 4917
¡pero el final de un fichero no solo no es excepcional! ¡Es lo que se espera! 4918
181
Por esta razón, solo debe querer activar las excepciones para errores 4919
representados por badbit, que deberia ser como esto: 4920
myStream.exceptions(ios::badbit); 4921
Usted activa las excepciones stream por stream, ya que exceptions() es 4922
una función miembro para los streams. La función exceptions() retorna una 4923
máscara de bits [14] (de tipo iostate, que es un tipo dependiente del 4924
compilador convertible a int) indicando que estados de stream causarán 4925
excepciones. Si estos estados ya han sido activados, la excepción será 4926
lanzada inmediatamente. Por supuesto, si usa excepciones en conexiones a 4927
streams, debería estar preparado paracapturarlas, lo que quiere decir que 4928
necesita envolver todos los stream bon bloques try que tengan un manejador 4929
ios::failure. Muchos programadores encuentran tedioso y simplemente 4930
comprueban manualmente donde esperan encontrar errores (ya que, por 4931
ejemplo, no esperan encontrar bad() al retornar true la mayoria de veces). 4932
Esto es otra razón que tienen los streams para que el lanzamiento de 4933
excepciones sea opcional y no por defecto. en cualquier caso, usted peude 4934
elegir como quiere manejar los errores de stream. Por las mismas razones 4935
que recomendamos el uso de excepciones para el manejo de rrores en otros 4936
contextos, lo hacemos aqui. 4937
5.4. Iostreams de fichero 4938
Manipular ficheros con iostream es mucho más fácil y seguro que usar 4939
stdio en C. Todo lo que tiene que hacer es crear un objeto - el constructor 4940
hace el trabajo. No necesita cerrar el fichero explícitamente (aunque puede, 4941
usando la función miembro close()) porque el destructor lo cerrará cuando el 4942
objeto salga del ámbito. Para crear un fichero que por defecto sea de 4943
entrada, cree un objeto ifstream . Para crear un fichero que por defecto es 4944
de salida, cree un objeto ofstream. Un fstream puede hacer ambas cosas. 4945
Las clases de stream de fichero encajan dentro de las clases iostream 4946
como se muestra en la siguiente figura: 4947
182
Como antes, las clases que usted usa en realidad son especializaciones 4948
de plantillas definidas por definiciones de tipo. Por ejemplo, ifstream, que 4949
procesa ficheros de char, es definida como: 4950
typedef basic_ifstream<char> ifstream; 4951
5.4.1. Un ejemplo de procesado de fichero. 4952
Aqui tiene un ejemplo que muestra algunas de las características 4953
discutidas antes. Nótese que la inclusión de <fstream> para delarar las 4954
clases de fichero de E/S. Aunque en muchas plataformas esto también 4955
incluye <iostream> automáticamente, los compiladores no están obligados a 4956
hacer esto. Si usted quiere compatibilidad, incluya siempre ambas 4957
cabeceras. 4958
//: C04:Strfile.cpp 4959
// Stream I/O with files; 4960
// The difference between get() & getline(). 4961
#include <fstream> 4962
#include <iostream> 4963
#include "../require.h" 4964
using namespace std; 4965
4966
int main() { 4967
const int SZ = 100; // Buffer size; 4968
char buf[SZ]; 4969
{ 4970
ifstream in("Strfile.cpp"); // Read 4971
assure(in, "Strfile.cpp"); // Verify open 4972
183
ofstream out("Strfile.out"); // Write 4973
assure(out, "Strfile.out"); 4974
int i = 1; // Line counter 4975
4976
// A less-convenient approach for line input: 4977
while(in.get(buf, SZ)) { // Leaves \n in input 4978
in.get(); // Throw away next character (\n) 4979
cout << buf << endl; // Must add \n 4980
// File output just like standard I/O: 4981
out << i++ << ": " << buf << endl; 4982
} 4983
} // Destructors close in & out 4984
4985
ifstream in("Strfile.out"); 4986
assure(in, "Strfile.out"); 4987
// More convenient line input: 4988
while(in.getline(buf, SZ)) { // Removes \n 4989
char* cp = buf; 4990
while(*cp != ':') 4991
++cp; 4992
cp += 2; // Past ": " 4993
cout << cp << endl; // Must still add \n 4994
} 4995
} ///:~ 4996
184
Listado 5.5. C04/Strfile.cpp 4997
4998
La creación tanto del ifstream como del ofstream están seguidas de un 4999
assure() para garantizar que el fichero ha sido abierto exitosamente. El 5000
objeto resultante, usado en una situación donde el compilador espera un 5001
resultado booleano, produce un valor que indica éxito o fracaso. 5002
El primer while demuestra el uso de dos formas de la función get(). La 5003
primera toma los carácteres dentro de un buffer y pone un delimitador cero 5004
en el buffer cuando bien SZ-1 carácteres han sido leidos o bien el tercer 5005
argumento (que por defecto es '\n') es encontrado. La función get() deja el 5006
carácter delimitador en el stream de entrada, así que este delimitador debe 5007
ser eliminado via in.get() usando la forma de get() sin argumentos. Puede 5008
usar tambien la función miembro ignore(), que tiene dos parámetros por 5009
defecto. El primer argumento es el número de carácteres para descartar y 5010
por defecto es uno. El segundo argumento es el carácter en el que ignore() 5011
se detiene (después de extraerlo) y por defecto es EOF. 5012
A continuación, se muestran dos sentencias de salida similares: una hacia 5013
cout y la otra al fichero de salida. Nótese la conveniencia aquí - no necesita 5014
preocuparse del tipo de objeto porque las sentencias de formateo trabajan 5015
igual con todos los objetos ostream. El primero hace eco de la linea en la 5016
salida estándar, y el segundo escribe la línea hacia el fichero de salida e 5017
incluye el número de línea. 5018
Para demostrar getline(), abra el fichero recién creado y quite los 5019
números de linea. Para asegurarse que el fichero se cierra correctamente 5020
antes de abrirlo para la lectura, usted tiene dos opciones. Puede envolver la 5021
primera parte del programa con llaves para forzar que el objeto out salga del 5022
ámbito, llamando así al destructor y cerrando el fichero, que es lo que se 5023
hace aquí. Tambien puede l lamar a close() para ambos ficheros; si hace 5024
esto, puede despues rehusar el objeto de entrada llamando a la función 5025
miembro open(). 5026
El segundo while muestra como getline() borra el caracter terminador 5027
(su tercer argumento, que por defecto es '\n') del stream de entrada cuando 5028
este es encontrado. Aunque getline(), como get(), pone un cero en el 5029
buffer, este todavía no inserta el carácter de terminación. 5030
185
Este ejemplo, así como la mayoría de ejemplos en este capítulo, asume 5031
que cada llamada a alguna sobrecarga de getline() encontrará un carácter 5032
de nueva línea. Si este no es el caso, la estado eofbit del stream será 5033
activado y la llamada a getline() retornará falso, causando que el programa 5034
pierda la última línea de la entrada. 5035
5.4.2. Modos de apertura 5036
Puede controlar la manera en que un fichero es abierto sobreescribiendo 5037
los argumentos por defecto del constructor. La siguiente tabla muestra las 5038
banderas que controlan el modo de un fichero: 5039
Bandera 5040
Función 5041
ios::in 5042
Abre el fichero de entrada. Use esto como un modo de apertura para un 5043
ofstream para prevenir que un fichero existente sea truncado. 5044
ios::out 5045
Abre un fichero de salida. Cuando es usado por un ofstream sin ios::app, 5046
ios::ate o ios::in, ios::trunc es implicado. 5047
ios::app 5048
Abre un fichero de salida para solo añadir . 5049
ios::ate 5050
Abre un fichero existente (ya sea de entrada o salida) y busca el final. 5051
ios::trunc 5052
Trunca el fichero antiguo si este ya existe. 5053
ios::binary 5054
Abre un fichero en modo binario. Por defecto es en modo texto. 5055
Puede combinar estas banderas usando la operación or para bits 5056
El flag binario, aun siendo portable, solo tiene efecto en algunos sistemas 5057
no UNIX, como sistemas operativos derivados de MS-DOS, que tiene 5058
convenciones especiales para el almacenamiento de delimitadores de final 5059
de línea. Por ejemplo, en sistemas MS-DOS en modo texto (el cual es por 5060
186
defecto), cada vez que usted inserta un nuevo carácter de nueva línea ('\n'), 5061
el sistema de ficheros en realidad inserta dos carácteres, un par retorno de 5062
carro/fin de línea (CRLF), que es el par de carácteres ASCII 0x0D y 0x0A. En 5063
sentido opuesto, cuando usted lee este fichero de vuelta a memoria en modo 5064
texto, cada ocurrencia de este par de bytes causa que un '\n' sea enviado al 5065
programa en su lugar. Si quiere sobrepasar este procesado especial, puede 5066
abrir el fichero en modo binario. El modo binario no tiene nada que ver ya 5067
que usted puede escribir bytes sin formato en un fichero - siempre puede 5068
(llamando a write()). Usted debería, por tanto, abrir un fichero en modo 5069
binario cuando vaya a usar read() o write(), porque estas funciones toman 5070
un contador de bytes como parámetro. Tener carácteres extra '\r' estropeará 5071
su contador de bytes en estas instancias. Usted también puede abrir un 5072
fichero en formato binario si va a usar comandos de posicionamiento en el 5073
stream que se discuten más adelante. 5074
Usted puede abrir un fichero tanto para entrada como salida declarando un 5075
objeto fstream. cuando declara un objeto fstream, debe usar suficientes 5076
banderas de modos de apertura mencionados antes para dejar que el 5077
sistema de ficheros sepa si quiere leer, escribir, o ambos. Para cambiar de 5078
salida a entrada, necesita o bien limpiar el stream o bien cambiar la posición 5079
en el fichero. Para cambiar de entrada a salida, cambie la posicion en el 5080
fichero. Para crear un fichero usando un objeto fstream, use la bandera de 5081
modo de apertura ios::trunc en la llamada al constructor para usar entrada 5082
y salida. 5083
5.5. Almacenamiento de iostream 5084
Las buenas prácticas de diseño dictan que, cuando cree una nueva clase, 5085
debe esforzarse en ocultar los detalles de la implementación subyacente 5086
tanto como sea posible al usuario de la clase. Usted le muestra solo aquello 5087
que necesita conocer y el resto se hace privado para evitar confusiones. 5088
Cuando usamos insertadores y extractores, normalmente usted no conoce o 5089
tiene cuidado con los bytes que se consumen o se producen, ya que usted 5090
está tratando con E/S estándar, ficheros, memoria, o alguna nueva clase o 5091
dispositivo creado. 5092
187
Llega un momento, no obstante, en el que es importante comunicar con la 5093
parte del iostream que produce o consume bytes. Para proveer esta parte 5094
con una interfaz común y esconder todavía su implementación subyacente, 5095
la librería estándar la abstrae dentro de su clase, llamada streambuf. Cada 5096
objeto iostream contiene un puntero a alguna clase de streambuf. (El tipo 5097
depende de que se esté tratando con E/S estándar, ficheros, memoria, etc.). 5098
Puede acceder al streambuf directamente; por ejemplo, puede mover bytes 5099
sin formatear dentro y fuera del streambuf sin formatearlos a través de la 5100
encapsulación del iostream. Esto es posible llamando a las funciones 5101
miembro del objeto streambuf. 5102
Actualmente, la cosa más importante que debe conocer es que cada 5103
objeto iostream contiene un puntero a un objeto streambuf, y el objeto 5104
streambuf tiene algunas funciones miembro que puede llamar si es 5105
necesario. Para ficheros y streams de string, hay tipos especializados de 5106
buffers de stream, como ilustra la figura siguiente: 5107
Para permitirle el acceso al streambuf, cada objeto iostream tiene una 5108
función miembro llamada rdbuf() que retorna el puntero a un objeto 5109
streambuf. De esta manera usted puede llamar cualquier función miembro 5110
del streambuf subyacente. No obstante, una de las cosas más interesantes 5111
que usted puede hacer con el puntero al streambuf es conectarlo con otro 5112
objeto iostream usando el operador <<. Esto inserta todos los carácteres del 5113
objeto dentro del que está al lado izquierdo del <<. Si quiere mover todos los 5114
carácteres de un iostream a otro, no necesita ponerse con el tedioso (y 5115
potencialmente inclinado a errores de código) proceso de leer de carácter por 5116
carácter o línea por línea. Este es un acercamiento mucho más elegante. 5117
Aqui está un programa muy simple que abre un fichero y manda el 5118
contenido a la salida estándar (similar al ejemplo previo): 5119
//: C04:Stype.cpp 5120
// Type a file to standard output. 5121
#include <fstream> 5122
#include <iostream> 5123
188
#include "../require.h" 5124
using namespace std; 5125
5126
int main() { 5127
ifstream in("Stype.cpp"); 5128
assure(in, "Stype.cpp"); 5129
cout << in.rdbuf(); // Outputs entire file 5130
} ///:~ 5131
Listado 5.6. C04/Stype.cpp 5132
5133
Un ifstream se crea usando el fichero de código fuente para este programa 5134
como argumento. La función assure() reporta un fallo si el fichero no puede 5135
ser abierto. Todo el trabajo pasa realmente en la sentencia 5136
cout << in.rdbuf(); 5137
que manda todo el contenido del fichero a cout. No solo es un código más 5138
sucinto, a menudo es más eficiente que mover los byte de uno en uno. 5139
Una forma de get() escribe directamente dentro del streambuf de otro 5140
objeto. El primer argumento es una referencia al streambuf de destino, y el 5141
segundo es el carácter de terminación ('\n' por defecto), que detiene la 5142
función get(). Así que existe todavía otra manera de imprimir el resultado de 5143
un fichero en la salida estándar: 5144
//: C04:Sbufget.cpp 5145
// Copies a file to standard output. 5146
#include <fstream> 5147
#include <iostream> 5148
189
#include "../require.h" 5149
using namespace std; 5150
5151
int main() { 5152
ifstream in("Sbufget.cpp"); 5153
assure(in); 5154
streambuf& sb = *cout.rdbuf(); 5155
while(!in.get(sb).eof()) { 5156
if(in.fail()) // Found blank line 5157
in.clear(); 5158
cout << char(in.get()); // Process '\n' 5159
} 5160
} ///:~ 5161
Listado 5.7. C04/Sbufget.cpp 5162
5163
La función rdbuf() retorna un puntero, que tiene que ser desreferenciado 5164
para satisfacer las necesidades de la función para ver el objeto. Los buffers 5165
de stream no estan pensados para ser copiados (no tienen contructor de 5166
copia), por lo que definimos sb como una referencia al buffer de stream de 5167
cout. Necesitamos las llamadas a fail() y clear() en caso de que el fichero 5168
de entrada tenga una línea en blanco (este la tiene). Cuando esta particular 5169
versión sobrecargada de get() vee dos carácteres de nueva línea en una fila 5170
(una evidencia de una línea en blanco), activa el bit de error del stream de 5171
entrada, asi que se debe llamar a clear() para resetearlo y que así el stream 5172
pueda continuar siendo leído. La segunda llamada a get() extrae y hace eco 5173
de cualquier delimitador de nueva línea. (Recuerde, la función get() no 5174
extrae este delimitador como sí lo hace getline()). 5175
190
Probablemente no necesitará usar una técnica como esta a menudo, pero 5176
es bueno saber que existe.[15] 5177
5.6. Buscar en iostreams 5178
Cada tipo de iostream tiene el concepto de donde está el 'siguiente' 5179
carácter que proviene de (si es un istream) o que va hacia (si es un 5180
ostream). En algunas situaciones, puede querer mover la posición en este 5181
stream. Puede hacer esto usando dos modelos: uno usa una localización 5182
absoluta en el stream llamada streampos; el segundo trabaja como las 5183
funciones fseek() de la librería estándar de C para un fichero y se mueve un 5184
número dado de bytes desde el principio, final o la posición actual en el 5185
fichero. 5186
El acercamiento de streampos requiere que primero llame una función 5187
'tell':( tellp() para un ostream o tellg() para un istream. (La 'p' se refiere a 5188
'put pointer' y la 'g' se refiere a 'get pointer'). Esta función retorna un 5189
streampos que puede usar después en llamadas a seekp() para un ostream o 5190
seekg() para un ostream cuando usted quiere retornar a la posición en el 5191
stream. 5192
La segunda aproximación es una búsqueda relativa y usa versiones 5193
sobrecargadas de seekp() y seekg(). El primer argumento es el número de 5194
carácteres a mover: puede ser positivo o negativo. El segundo argumento es 5195
la dirección desde donde buscar: 5196
ios::beg 5197
Desde el principio del stream 5198
ios::cur 5199
Posición actual del stream 5200
ios::end 5201
Desde el principio del stream 5202
Aquí un ejemplo que muestra el movimiento por un fichero, pero recuerde, 5203
no esta limitado a buscar en ficheros como lo está con stdio de C. Con C++, 5204
puede buscar en cualquier tipo de iostream (aunque los objetos stream 5205
estándar, como cin y cout, lo impiden explícitamente): 5206
191
//: C04:Seeking.cpp 5207
// Seeking in iostreams. 5208
#include <cassert> 5209
#include <cstddef> 5210
#include <cstring> 5211
#include <fstream> 5212
#include "../require.h" 5213
using namespace std; 5214
5215
int main() { 5216
const int STR_NUM = 5, STR_LEN = 30; 5217
char origData[STR_NUM][STR_LEN] = { 5218
"Hickory dickory dus. . .", 5219
"Are you tired of C++?", 5220
"Well, if you have,", 5221
"That's just too bad,", 5222
"There's plenty more for us!" 5223
}; 5224
char readData[STR_NUM][STR_LEN] = {{ 0 }}; 5225
ofstream out("Poem.bin", ios::out | ios::binary); 5226
assure(out, "Poem.bin"); 5227
for(int i = 0; i < STR_NUM; i++) 5228
out.write(origData[i], STR_LEN); 5229
out.close(); 5230
ifstream in("Poem.bin", ios::in | ios::binary); 5231
192
assure(in, "Poem.bin"); 5232
in.read(readData[0], STR_LEN); 5233
assert(strcmp(readData[0], "Hickory dickory dus. . .") 5234
== 0); 5235
// Seek -STR_LEN bytes from the end of file 5236
in.seekg(-STR_LEN, ios::end); 5237
in.read(readData[1], STR_LEN); 5238
assert(strcmp(readData[1], "There's plenty more for us!") 5239
== 0); 5240
// Absolute seek (like using operator[] with a file) 5241
in.seekg(3 * STR_LEN); 5242
in.read(readData[2], STR_LEN); 5243
assert(strcmp(readData[2], "That's just too bad,") == 0); 5244
// Seek backwards from current position 5245
in.seekg(-STR_LEN * 2, ios::cur); 5246
in.read(readData[3], STR_LEN); 5247
assert(strcmp(readData[3], "Well, if you have,") == 0); 5248
// Seek from the begining of the file 5249
in.seekg(1 * STR_LEN, ios::beg); 5250
in.read(readData[4], STR_LEN); 5251
assert(strcmp(readData[4], "Are you tired of C++?") 5252
== 0); 5253
} ///:~ 5254
Listado 5.8. C04/Seeking.cpp 5255
193
5256
Este programa escribe un poema a un fichero usando un stream de salida 5257
binaria. Como reabrimos como un ifstream, usamos seekg() para posicionar 5258
el 'get pointer'. Como puede ver, puede buscar desde el principio o el final del 5259
archivo o desde la posición actual del archivo. Obviamente, debe proveer un 5260
número positivo para mover desde el principio del archivo y un número 5261
negativo para mover hacia atrás. 5262
Ahora que ya conoce el streambuf y como buscar, ya puede entender un 5263
método alternativo (aparte de usar un objeto fstream) para crear un objeto 5264
stream que podrá leer y escribir en un archivo. El siguiente código crea un 5265
ifstream con banderas que dicen que es un fichero de entrada y de salida. 5266
Usted no puede escribir en un ifstream, así que necesita crear un ostream 5267
con el buffer subyacente del stream: 5268
ifstream in("filename", ios::in | ios::out); 5269
ostream out(in.rdbuf()); 5270
Debe estar preguntándose que ocurre cuando usted lee en uno de estos 5271
objetos. Aqui tiene un ejemplo: 5272
//: C04:Iofile.cpp 5273
// Reading & writing one file. 5274
#include <fstream> 5275
#include <iostream> 5276
#include "../require.h" 5277
using namespace std; 5278
5279
int main() { 5280
ifstream in("Iofile.cpp"); 5281
assure(in, "Iofile.cpp"); 5282
194
ofstream out("Iofile.out"); 5283
assure(out, "Iofile.out"); 5284
out << in.rdbuf(); // Copy file 5285
in.close(); 5286
out.close(); 5287
// Open for reading and writing: 5288
ifstream in2("Iofile.out", ios::in | ios::out); 5289
assure(in2, "Iofile.out"); 5290
ostream out2(in2.rdbuf()); 5291
cout << in2.rdbuf(); // Print whole file 5292
out2 << "Where does this end up?"; 5293
out2.seekp(0, ios::beg); 5294
out2 << "And what about this?"; 5295
in2.seekg(0, ios::beg); 5296
cout << in2.rdbuf(); 5297
} ///:~ 5298
Listado 5.9. C04/Iofile.cpp 5299
5300
Las primeras cinco líneas copian el código fuente de este programa en un 5301
fichero llamado iofile.out y después cierra los ficheros. Esto le da un texto 5302
seguro con el que practicar. Entonces, la técnica antes mencionada se usa 5303
para crear dos objetos que leen y escriben en el mismo fichero. En cout << 5304
in2.rebuf(), puede ver como puntero 'get' es inicializado al principio del 5305
fichero. El puntero 'put', en cambio, se coloca en el final del fichero para que 5306
'Where does this end up' aparezca añadido al fichero. No obstante, si el 5307
puntero 'put' es movido al principio con un seekp(), todo el texto insertado 5308
sobreescribe el existente. Ambas escrituras pueden verse cuando el puntero 5309
195
'get' se mueve otra vez al principio con seekg(), y el fichero se muestra. El 5310
fichero es automáticamente guardado cuando out2 sale del ámbito y su 5311
destructor es invocado. 5312
5.7. Iostreams de string 5313
Un stream de cadena funciona directamente en memoria en vez de con 5314
ficheros o la salida estándar. Usa las mismas funciones de lectura y formateo 5315
que usó con cin y cout para manipular bits en memoria. En ordenadores 5316
antiguos, la memoria se refería al núcleo, con lo que este tipo de 5317
funcionalidad se llama a menudo formateo en el núcleo. 5318
Los nombres de clases para streams de cadena son una copia de los 5319
streams de ficheros. Si usted quiere crear un stream de cadena para extraer 5320
carácteres de él, puede crear un istringstream. Si quiere poner carácteres 5321
en un stream de cadena, puede crear un ostringstream. Todas las 5322
declaraciones para streams de cadena están en la cabecera estándar 5323
<sstream>. Como es habitual, hay plantillas de clases dentro de la jerarquia 5324
de los iostreams, como se muestra en la siguiente figura: 5325
5.7.1. Streams de cadena de entrada 5326
Para leer de un string usando operaciones de stream, cree un objeto 5327
istringstream inicializado con el string. El siguiente programa muestra 5328
como usar un objeto istringstream: 5329
//: C04:Istring.cpp 5330
// Input string streams. 5331
#include <cassert> 5332
#include <cmath> // For fabs() 5333
#include <iostream> 5334
#include <limits> // For epsilon() 5335
#include <sstream> 5336
196
#include <string> 5337
using namespace std; 5338
5339
int main() { 5340
istringstream s("47 1.414 This is a test"); 5341
int i; 5342
double f; 5343
s >> i >> f; // Whitespace-delimited input 5344
assert(i == 47); 5345
double relerr = (fabs(f) - 1.414) / 1.414; 5346
assert(relerr <= numeric_limits<double>::epsilon()); 5347
string buf2; 5348
s >> buf2; 5349
assert(buf2 == "This"); 5350
cout << s.rdbuf(); // " is a test" 5351
} ///:~ 5352
Listado 5.10. C04/Istring.cpp 5353
5354
Puede ver que es un acercamiento más flexible y general para transformar 5355
cadenas de carácteres para valores con tipo que la librería de funciones del 5356
estándar de C, como atof() o atoi(), aunque esta última puede ser más 5357
eficaz para las conversiones individuales. 5358
En la expresión s >> i >> f, el primer número se extrae en i, y en el 5359
segundo en f. Este no es 'el primer conjunto de carácteres delimitado por 5360
espacios en blanco' por que depende del tipo de datos que está siendo 5361
extraído. Por ejemplo, si la cadena fuera '1.414 47 This is a test', entonces i 5362
tomaría el valor 1 porque la rutina de entrada se pararía en el punto decimal. 5363
197
Entonces f tomaría 0.414. Esto puede ser muy útil i si quiere partir un 5364
número de coma flotante entre la parte entera y la decimal. De otra manera 5365
parecería un error. El segundo assert() calcula el error relativo entre lo que 5366
leemos y lo que esperamos; siempre es mejor hacer esto que comparar la 5367
igualdad de números de coma flotante. La constante devuelta por epsilon(), 5368
definida en <limits>, representa la epsilon de la máquina para números de 5369
doble precisión, el cual es la mejor tolerancia que se puede esperar para 5370
satisfacer las comparaciones de double.[16]. 5371
Como debe haber supuesto, buf2 no toma el resto del string, 5372
simplemente la siguiente palabra delimitada por espacios en blanco. En 5373
general, el mejor usar el extractor en iostreams cuando usted conoce 5374
exactamente la secuencia de datos en el stream de entrada y los convierte a 5375
algún otro tipo que un string de carácteres. No obstante, si quiere extraer el 5376
resto del string de una sola vez y enviarlo a otro iostream, puede usar 5377
rdbuf() como se muestra. 5378
Para probar el extractor de Date al principio de este capítulo, hemos usado 5379
un stream de cadena de entrada con el siguiente programa de prueba: 5380
//: C04:DateIOTest.cpp 5381
//{L} ../C02/Date 5382
#include <iostream> 5383
#include <sstream> 5384
#include "../C02/Date.h" 5385
using namespace std; 5386
5387
void testDate(const string& s) { 5388
istringstream os(s); 5389
Date d; 5390
os >> d; 5391
if(os) 5392
198
cout << d << endl; 5393
else 5394
cout << "input error with \"" << s << "\"" << endl; 5395
} 5396
5397
int main() { 5398
testDate("08-10-2003"); 5399
testDate("8-10-2003"); 5400
testDate("08 - 10 - 2003"); 5401
testDate("A-10-2003"); 5402
testDate("08%10/2003"); 5403
} ///:~ 5404
Listado 5.11. C04/DateIOTest.cpp 5405
5406
Cada literal de cadena en main() se pasa por referencia a testDate(), que 5407
a su vez lo envuelve en un istringstream con lo que podemos probar el 5408
extractor de stream que escribimos para los objetos Date. La función 5409
testDate() también empieza por probar el insertador, operator<<(). 5410
5.7.2. Streams de cadena de salida 5411
Para crear un stream de cadena de salida, simplemente cree un objeto 5412
ostringstream, que maneja un buffer de carácteres dinamicamente 5413
dimensionado para guardar cualquier cosas que usted inserte. Para tomar el 5414
resultado formateado como un objeto de string, llame a la función miembro 5415
setr(). Aqui tiene un ejemplo: 5416
//: C04:Ostring.cpp {RunByHand} 5417
199
// Illustrates ostringstream. 5418
#include <iostream> 5419
#include <sstream> 5420
#include <string> 5421
using namespace std; 5422
5423
int main() { 5424
cout << "type an int, a float and a string: "; 5425
int i; 5426
float f; 5427
cin >> i >> f; 5428
cin >> ws; // Throw away white space 5429
string stuff; 5430
getline(cin, stuff); // Get rest of the line 5431
ostringstream os; 5432
os << "integer = " << i << endl; 5433
os << "float = " << f << endl; 5434
os << "string = " << stuff << endl; 5435
string result = os.str(); 5436
cout << result << endl; 5437
} ///:~ 5438
Listado 5.12. C04/Ostring.cpp 5439
5440
Esto es similar al ejemplo Istring.cpp anterior que pedía un int y un float. 5441
A continueación una simple ejecución (la entrada por teclado está escrita en 5442
negrita). 5443
200
type an int, a float and a string: FIXME:10 20.5 the end 5444 integer = 10 5445 float = 20.5 5446
string = the end 5447
Puede ver que, como otros stream de salida, puede usar las herramientas 5448
ordinarias de formateo, como el operador << y endl, para enviar bytes hacia 5449
el ostringstream. La función str() devuelve un nuevo objeto string cada 5450
vez que usted la llama con lo que el stringbuf contenido permanece 5451
inalterado. 5452
En el capítulo previo, presentamos un programa, HTMLStripper.cpp, que 5453
borraba todas las etiqietas HTML y los códigos especiales de un fichero de 5454
texto. Como prometíamos, aqui está una versión más elegante usando 5455
streams de cadena. 5456
//: C04:HTMLStripper2.cpp {RunByHand} 5457
//{L} ../C03/ReplaceAll 5458
// Filter to remove html tags and markers. 5459
#include <cstddef> 5460
#include <cstdlib> 5461
#include <fstream> 5462
#include <iostream> 5463
#include <sstream> 5464
#include <stdexcept> 5465
#include <string> 5466
#include "../C03/ReplaceAll.h" 5467
#include "../require.h" 5468
using namespace std; 5469
5470
string& stripHTMLTags(string& s) throw(runtime_error) { 5471
201
size_t leftPos; 5472
while((leftPos = s.find('<')) != string::npos) { 5473
size_t rightPos = s.find('>', leftPos+1); 5474
if(rightPos == string::npos) { 5475
ostringstream msg; 5476
msg << "Incomplete HTML tag starting in position " 5477
<< leftPos; 5478
throw runtime_error(msg.str()); 5479
} 5480
s.erase(leftPos, rightPos - leftPos + 1); 5481
} 5482
// Remove all special HTML characters 5483
replaceAll(s, "<", "<"); 5484
replaceAll(s, ">", ">"); 5485
replaceAll(s, "&", "&"); 5486
replaceAll(s, " ", " "); 5487
// Etc... 5488
return s; 5489
} 5490
5491
int main(int argc, char* argv[]) { 5492
requireArgs(argc, 1, 5493
"usage: HTMLStripper2 InputFile"); 5494
ifstream in(argv[1]); 5495
assure(in, argv[1]); 5496
202
// Read entire file into string; then strip 5497
ostringstream ss; 5498
ss << in.rdbuf(); 5499
try { 5500
string s = ss.str(); 5501
cout << stripHTMLTags(s) << endl; 5502
return EXIT_SUCCESS; 5503
} catch(runtime_error& x) { 5504
cout << x.what() << endl; 5505
return EXIT_FAILURE; 5506
} 5507
} ///:~ 5508
Listado 5.13. C04/HTMLStripper2.cpp 5509
5510
En este programa leemos el fichero entero dentro de un string insertando 5511
una llamada rdbuf() del stream de fichero al ostringstream. Ahora es fácil 5512
buscar parejas de delimitadores HTML y borrarlas sin tener que 5513
preocuparnos de límites de líneas como teniamos con la versión previa en el 5514
Capítulo 3. 5515
El siguiente ejemplo muestra como usar un stream de cadena bidireccional 5516
(esto es, lectura/escritura): 5517
//: C04:StringSeeking.cpp {-bor}{-dmc} 5518
// Reads and writes a string stream. 5519
#include <cassert> 5520
#include <sstream> 5521
#include <string> 5522
203
using namespace std; 5523
5524
int main() { 5525
string text = "We will hook no fish"; 5526
stringstream ss(text); 5527
ss.seekp(0, ios::end); 5528
ss << " before its time."; 5529
assert(ss.str() == 5530
"We will hook no fish before its time."); 5531
// Change "hook" to "ship" 5532
ss.seekg(8, ios::beg); 5533
string word; 5534
ss >> word; 5535
assert(word == "hook"); 5536
ss.seekp(8, ios::beg); 5537
ss << "ship"; 5538
// Change "fish" to "code" 5539
ss.seekg(16, ios::beg); 5540
ss >> word; 5541
assert(word == "fish"); 5542
ss.seekp(16, ios::beg); 5543
ss << "code"; 5544
assert(ss.str() == 5545
"We will ship no code before its time."); 5546
ss.str("A horse of a different color."); 5547
204
assert(ss.str() == "A horse of a different color."); 5548
} ///:~ 5549
Listado 5.14. C04/StringSeeking.cpp 5550
5551
Como siempre para mover el puntero de inserción, usted llama a seekp(), 5552
y para reposicionar el fichero de lectura, usted llama a seekg(). Incluso 5553
aunque no lo hemos mostrado con este ejemplo, los stream de cadeana son 5554
un poco más permisivos que los stream de fichero ya que podemos cambiar 5555
de lectura a escritura y viceversa en cualquier momento. No necesita 5556
reposicionar el puntero de lectura o de escritura o vaciar el stream. Este 5557
progrma también ilustra la sobrecarga de str() que reemplaza el stringbuf 5558
contenido en el stream con una nueva cadena. 5559
5.8. Formateo de stream de salida 5560
El objetivo del diseño de los iostream es permitir que usted pueda mover 5561
y/o formatear carácteres fácilmente. Ciertamente no podria ser de mucha 5562
utilidad si no se pudiera hacer la mayoria de los formateos provistos por la 5563
familia de funciones de printf() en C. Es esta sección, usted aprenderá 5564
todo sobre las funciones de formateo de salida que estan disponibles para 5565
iostream, con lo que puede formatear los bytes de la manera que usted 5566
quiera. 5567
Las funciones de formateo en iostream pueden ser algo cunfusas al 5568
principio porque a menudo existe más de una manera de controlar el 5569
formateo: a través de funciones miembro y manipuladores. Para confundir 5570
más las cosas, una función miembro genérica pone banderas de estado para 5571
controlar el formateo, como la justificación a la derecha o izquierda, el uso de 5572
letras mayúsculas para la notación hexadecimal, para siempre usar un punto 5573
decimal para valores de coma flotante, y cosas así. En el otro lado, funciones 5574
miembro separadas activan y leen valores para el caracter de relleno, la 5575
anchura del campo, y la precisión. 5576
205
En un intento de clarificar todo esto, primero examinaremos el formateo 5577
interno de los datos de un iostream, y las funciones miembro que pueden 5578
modificar estos datos. (Todo puede ser controlado por funciones miembro si 5579
se desea). Cubriremos los manipuladores aparte. 5580
5.8.1. Banderas de formateo 5581
La clase ios contiene los miembros de datos para guardar toda la 5582
información de formateo perteneciente a un stream. Algunos de estos datos 5583
tiene un rango de valores de datos y son guardados en variables: la precisión 5584
de la coma flotante, la anchura del campo de salida, y el carácter usado para 5585
rellenar la salida (normalmente un espacio). El resto del formateo es 5586
determinado por banderas, que generalmente están combinadas para 5587
ahorrar espacio y se llaman colectivamente banderas de formateo. Puede 5588
recuperar los valores de las banderas de formateo con la función miembro 5589
ios::flag(), que no toma argumentos y retorna un objeto de tipo fmtflags 5590
(usualmente un sinónimo de long) que contiene las banderas de formateo 5591
actuales. El resto de funciones hacen cambios en las banderas de formateo y 5592
retornan el valor previo de las banderas de formateo. 5593
fmtflags ios::flags(fmtflags newflags); 5594
fmtflags ios::setf(fmtflags ored_flag); 5595
fmtflags ios::unsetf(fmtflags clear_flag); 5596
fmtflags ios::setf(fmtflags bits, fmtflags field); 5597
La primera función fuerza que todas las banderas cambien, que a veces es 5598
lo que usted quiere. Más a menudo, usted cambia una bandera cada vez 5599
usando las otras tres funciones. 5600
El uso de setf() puede parecer algo confusa. Para conocer qué versión 5601
sobrecargada usar, debe conocer el tipo de la bandera que está cambiando. 5602
Existen dos tipos de banderas: las que simplemente estan activadas o no, y 5603
aquellas que trabajan en grupo con otras banderas. La banderas que estan 5604
encendidas/apagadas son las más simples de entender por que usted las 5605
206
enciende con setf(fmtflags) y las apaga con unsetf(fmtflags). Estas 5606
banderas se muestran en la siguiente tabla: 5607
bandera activa/inactiva 5608
Efecto 5609
ios::skipws 5610
Se salta los espacios en blanco. ( Para la entrada esto es por defecto). 5611
ios::showbase 5612
Indica la base numérica (que puede ser, por ejemplo, decimal, octal o 5613
hexadecimal) cuando imprimimos el valor entero. Los stream de entrada 5614
tambié reconocen el prefijo de base cuando showbase está activo. 5615
ios::showpoint 5616
Muestra el punto decimal insertando ceros para valores de coma flotante. 5617
ios::uppercase 5618
Muestra A-F mayúsculas para valores hexadecimales y E para científicos. 5619
ios::showpos 5620
Muestra el signo de sumar (+) para los valores positivos 5621
ios::unitbuf 5622
'Unit buffering.' El stream es borrado después de cada inseción. 5623
Por ejemplo, para mostrar el signo de sumar para cout, puede usar 5624
cout.setf(ios::showpos). Para dejar de mostrar el signo de sumar, escriba 5625
cout.unsetf(ios::showpos). 5626
La bandera de unitbuf controla el almacenamiento unitario, que significa 5627
que cada inserción es lanzada a su stream de salida inmediatamente. Esto 5628
es útil para hacer recuento de errores, ya que en caso de fallo del programa, 5629
sus datos son todavía escritos al fichero de log. El siguiente programa ilustra 5630
el almacenamiento unitario. 5631
//: C04:Unitbuf.cpp {RunByHand} 5632
#include <cstdlib> // For abort() 5633
#include <fstream> 5634
207
using namespace std; 5635
5636
int main() { 5637
ofstream out("log.txt"); 5638
out.setf(ios::unitbuf); 5639
out << "one" << endl; 5640
out << "two" << endl; 5641
abort(); 5642
} ///:~ 5643
Listado 5.15. C04/Unitbuf.cpp 5644
5645
Es necesario activar el almacenamiento unitario antes de que cualquier 5646
inserción sea hecha en el stream. Cuando hemos descomentado la llamada 5647
a setf(), un compilador en particular ha escrito solo la letra 'o' en el fichero 5648
log.txt. Con el almacenamiento unitario, ningun dato se perdió. 5649
El stream de salida estándar cerr tiene el almacenamiento unitario 5650
activado por defecto. Hay un coste para el almacenamiento unitario, asi que 5651
si un stream de salida se usa intensivamente, no active el almacenamiento 5652
unitario a menos que la eficiencia no sea una consideración. 5653
5.8.2. Campos de formateo 5654
El segundo tipo de banderas de formateo trabajan en grupo. Solo una de 5655
estas banderas pueden ser activadas cada vez, como los botones de una 5656
vieja radio de coche - usted apretaba una y el resto saltaban. 5657
Desafortunadamente esto no pasa automáticamente, y usted tiene que poner 5658
atención a que bandera está activando para no llamar accidentalmente a la 5659
función setf() incorrecta. Por ejemplo, hay una bandera para cada una de 5660
las bases numéricas: hexadecimal, decimal y octal. A estas banderas se 5661
refiere en conjunto ios::basefield. Si la bandera ios::dec está activa y 5662
208
usted llama setf(ios::hex), usted activará la bandera de ios::hex, pero no 5663
desactivará la bandera de ios::dec, resultando en un comportamiento 5664
indeterminado. En vez de esto,llame a la segunda forma de la función setf() 5665
como esta: setf(ios::hex, ios::basefield) . Esta función primero limpia 5666
todos los bits de ios::basefield y luego activa ios::hex. Así, esta forma de 5667
setf() asegura que las otras banderas en el grupo 'saltan' cuando usted 5668
activa una. El manipulador ios::hex lo hace todo por usted, 5669
automáticamente, asi que no tiene que preocuparse con los detalles de la 5670
implementación interna de esta clase o tener cuidado de que esto es una 5671
seria de banderas binarias. Más adelante verá que hay manipuladores para 5672
proveer de la funcionalidad equivalente en todas las parts donde usted fuera 5673
a usar setf(). 5674
Aquí están los grupos de banderas y sus efectos: 5675
ios::basefield 5676
Efecto 5677
ios::dec 5678
Formatea valores enteros en base 10 (decimal)(Formateo por defecto - 5679
ningún prefijo es visible). 5680
ios::hex 5681
Formatea valores enteros en base 16 (hexadecimal). 5682
ios::oct 5683
Formatea valores enteros en base 8 (octal). 5684
ios::floatfield 5685
Efecto 5686
ios::scientific 5687
Muestra números en coma flotante en formato científico. El campo 5688
precisión indica el numero de dígitos después del punto decimal. 5689
ios::fixed 5690
Muestra números en coma flotante en formato fijado. El campo precisión 5691
indica en número de dígitos después del punto decimal. 5692
'automatic' (Ninguno de los bits está activado). 5693
209
El campo precisión indica el número total de dígitos significativos. 5694
ios::adjustfield 5695
Efecto 5696
ios::left 5697
Valores con alineación izquierda; se llena hasta la derecha con el carácter 5698
de relleno. 5699
ios::right 5700
Valores con alineación derecha; se llena hasta la izquierda con el carácter 5701
de relleno. Esta es la alineación por defecto. 5702
ios::internal 5703
Añade carácteres de relleno despues de algún signo inicial o indicador de 5704
base, pero antes del valor. (En otras palabras, el signo, si está presente, se 5705
justifica a la izquierda mientras el número se justifica a la derecha). 5706
5.8.3. Anchura, relleno y precisión 5707
La variables internas que controlan la anchura del campo de salida, el 5708
carácter usado para rellenar el campo de salida, y la precisión para escribir 5709
números de coma flotante son escritos y leídos por funciones miembro del 5710
mismo nombre. 5711
Función 5712
Efecto 5713
int ios::width( ) 5714
Retorna la anchura actual. Por defecto es 0. Se usa para la inserción y la 5715
extracción. 5716
int ios::width(int n) 5717
Pone la anchura, retorna la anchura previa. 5718
int ios::fill( ) 5719
Retorna el carácter de relleno actual. Por defecto es el espacio. 5720
int ios::fill(int n) 5721
Poner el carácter de relleno, retorna el carácter de relleno anterior. 5722
int ios::precision( ) 5723
210
Retorna la precisión actual de la coma flotante. Por defecto es 6. 5724
int ios::precision(int n) 5725
Pone la precisión de la coma flotante, retorna la precisión anteriot. Vea la 5726
tabla ios::floatfield para el significado de 'precisión'. 5727
El relleno y la precisión son bastante claras, pero la anchura requiera 5728
alguna explicación. Cuando la anchura es 0, insertar un valor produce el 5729
mínimo número de carácteres necesario para representar este valor. Una 5730
anchura positiva significa que insertar un valor producirá al menos tantos 5731
carácteres como la anchura; si el valor tiene menos carácteres que la 5732
anchura, el carácter de relleno llena el campo. No obstante, el valor nunca 5733
será truncado, con lo que si usted intenta escribir 123 con una anchura de 5734
dos, seguirá obteniendo 123. El campo anchura especifica un minimo 5735
número de carácteres; no hay forma de especificar un número mínimo. 5736
La anchura también es diferente por que vuelve a ser cero por cada 5737
insertador o extractor que puede ser influenciado por este valor. Realmente 5738
no es una variable de estado, sino más bien un argumento implícito para los 5739
extractores y insertadores. Si quiere una anchura constante, llame a width() 5740
despues de cada inserción o extracción. 5741
5.8.4. Un ejemplo exhaustivo 5742
Para estar seguros de que usted conoce como llamar a todas las funciones 5743
discutidas previamente, aquí tiene un ejemplo que las llama a todas: 5744
//: C04:Format.cpp 5745
// Formatting Functions. 5746
#include <fstream> 5747
#include <iostream> 5748
#include "../require.h" 5749
using namespace std; 5750
#define D(A) T << #A << endl; A 5751
211
5752
int main() { 5753
ofstream T("format.out"); 5754
assure(T); 5755
D(int i = 47;) 5756
D(float f = 2300114.414159;) 5757
const char* s = "Is there any more?"; 5758
5759
D(T.setf(ios::unitbuf);) 5760
D(T.setf(ios::showbase);) 5761
D(T.setf(ios::uppercase | ios::showpos);) 5762
D(T << i << endl;) // Default is dec 5763
D(T.setf(ios::hex, ios::basefield);) 5764
D(T << i << endl;) 5765
D(T.setf(ios::oct, ios::basefield);) 5766
D(T << i << endl;) 5767
D(T.unsetf(ios::showbase);) 5768
D(T.setf(ios::dec, ios::basefield);) 5769
D(T.setf(ios::left, ios::adjustfield);) 5770
D(T.fill('0');) 5771
D(T << "fill char: " << T.fill() << endl;) 5772
D(T.width(10);) 5773
T << i << endl; 5774
D(T.setf(ios::right, ios::adjustfield);) 5775
D(T.width(10);) 5776
212
T << i << endl; 5777
D(T.setf(ios::internal, ios::adjustfield);) 5778
D(T.width(10);) 5779
T << i << endl; 5780
D(T << i << endl;) // Without width(10) 5781
5782
D(T.unsetf(ios::showpos);) 5783
D(T.setf(ios::showpoint);) 5784
D(T << "prec = " << T.precision() << endl;) 5785
D(T.setf(ios::scientific, ios::floatfield);) 5786
D(T << endl << f << endl;) 5787
D(T.unsetf(ios::uppercase);) 5788
D(T << endl << f << endl;) 5789
D(T.setf(ios::fixed, ios::floatfield);) 5790
D(T << f << endl;) 5791
D(T.precision(20);) 5792
D(T << "prec = " << T.precision() << endl;) 5793
D(T << endl << f << endl;) 5794
D(T.setf(ios::scientific, ios::floatfield);) 5795
D(T << endl << f << endl;) 5796
D(T.setf(ios::fixed, ios::floatfield);) 5797
D(T << f << endl;) 5798
5799
D(T.width(10);) 5800
T << s << endl; 5801
213
D(T.width(40);) 5802
T << s << endl; 5803
D(T.setf(ios::left, ios::adjustfield);) 5804
D(T.width(40);) 5805
T << s << endl; 5806
} ///:~ 5807
Listado 5.16. C04/Format.cpp 5808
5809
Este ejemplo usa un truco para crear un fichero de traza para que pueda 5810
monitorizar lo que está pasando. La macro D(a) usa el preprocesador 5811
'convirtiendo a string' para convertir a en una cadena para mostrar. Entonces 5812
se reitera a con lo que la sentencia se ejecuta. La macro envia toda la 5813
información a un fichero llamado T, que es el fichero de traza. La salida es: 5814
int i = 47; 5815 float f = 2300114.414159; 5816
T.setf(ios::unitbuf); 5817 T.setf(ios::showbase); 5818
T.setf(ios::uppercase | ios::showpos); 5819 T << i << endl; 5820
+47 5821 T.setf(ios::hex, ios::basefield); 5822
T << i << endl; 5823 0X2F 5824
T.setf(ios::oct, ios::basefield); 5825 T << i << endl; 5826
057 5827 T.unsetf(ios::showbase); 5828
T.setf(ios::dec, ios::basefield); 5829 T.setf(ios::left, ios::adjustfield); 5830
T.fill('0'); 5831 T << "fill char: " << T.fill() << endl; 5832
fill char: 0 5833 T.width(10); 5834 +470000000 5835
T.setf(ios::right, ios::adjustfield); 5836 T.width(10); 5837 0000000+47 5838
T.setf(ios::internal, ios::adjustfield); 5839 T.width(10); 5840 +000000047 5841
T << i << endl; 5842 +47 5843
T.unsetf(ios::showpos); 5844
214
T.setf(ios::showpoint); 5845 T << "prec = " << T.precision() << endl; 5846
prec = 6 5847 T.setf(ios::scientific, ios::floatfield); 5848
T << endl << f << endl; 5849 5850
2.300114E+06 5851 T.unsetf(ios::uppercase); 5852 T << endl << f << endl; 5853
5854 2.300114e+06 5855
T.setf(ios::fixed, ios::floatfield); 5856 T << f << endl; 5857 2300114.500000 5858
T.precision(20); 5859 T << "prec = " << T.precision() << endl; 5860
prec = 20 5861 T << endl << f << endl; 5862
5863 2300114.50000000000000000000 5864
T.setf(ios::scientific, ios::floatfield); 5865 T << endl << f << endl; 5866
5867 2.30011450000000000000e+06 5868
T.setf(ios::fixed, ios::floatfield); 5869 T << f << endl; 5870
2300114.50000000000000000000 5871 T.width(10); 5872
Is there any more? 5873 T.width(40); 5874
0000000000000000000000Is there any more? 5875 T.setf(ios::left, ios::adjustfield); 5876
T.width(40); 5877 Is there any more?0000000000000000000000 5878
Estudiar esta salida debería clarificar su entendimiento del formateo de las 5879
funciones miembro de iostream . 5880
5.9. Manipuladores 5881
Como puede ver en el programa previo, llamar a funciones miembro para 5882
operaciones de formateo de stream puede ser un poco tedioso. Para hacer 5883
las cosas mas fáciles de leer y escribir, existe un conjunto de manipuladores 5884
para duplicar las acciones previstas por las funciones miembro. Las 5885
manipuladores son convenientes por que usted puede insertarlos para que 5886
actuen dentro de una expresion contenedora; no necesita crear una 5887
sentencia de llamada a función separada. 5888
Los manipuladores cambian el estado de un stream en vez de (o además 5889
de) procesar los datos. Cuando insertamos un endl en una expresión de 5890
215
salida, por ejemplo, no solo inserta un carácter de nueva linea, sino que 5891
ademas termina el stream (esto es, saca todos los carácteres pendientes que 5892
han sido almacenadas en el buffer interno del stream pero todavia no en la 5893
salida). Puede terminar el stream simplemente asi: 5894
cout << flush; 5895
Lo que causa una llamada a la función miembro flush(), como esta: 5896
cout.flush(); 5897
como efecto lateral (nada es insertado dentro de stream). Adicionalmente 5898
los manipuladores básicos cambirán la base del número a oct (octal), dec 5899
(decimal) o hex (hexadecimal). 5900
cout << hex << "0x" << i << endl; 5901
En este caso, la salida numérica continuará en modo hexadecimal hasta 5902
que usted lo cambie insertando o dec o oct en el stream de salida. 5903
También existe un manipulador para la extracción que se 'come' los 5904
espacios en blanco: 5905
cin >> ws; 5906
Los manipuladores sin argumentos son provistos en <iostream>. Esto 5907
incluye dec, oct,y hex, que hacen las mismas acciones que, respectivamente, 5908
setf(ios::dec, ios::basefield), setf(ios::oct, ios::basefield), y 5909
setf(ios::hex, ios::basefield), aunque más sucintamente. La cabecera 5910
<iostream> también incluye ws, endl, y flush y el conjunto adicional mostrado 5911
aquí: 5912
Manipulador 5913
216
Efecto 5914
showbase noshowbase 5915
Indica la base numérica (dec, oct, o hex) cuando imprimimos un entero. 5916
showpos noshowpos 5917
Muestra el signo más (+) para valores positivos. 5918
uppercase nouppercase 5919
Muestra mayúsculas A-F para valores hexadecimales, y muestra E para 5920
valores científicos. 5921
showpoint noshowpoint 5922
Muestra punto decimal y ceros arrastrados para valores de coma flotante. 5923
skipws noskipws 5924
Escapa los espacios en blanco en la entrada. 5925
left right internal 5926
Alineación izquierda, relleno a la derecha. Alineación derecha, relleno a la 5927
izquierda. Rellenar entre el signo o el indicador de base y el valor. 5928
scientific fixed 5929
Indica la preferencia al mostrar la salida para coma flotante (notación 5930
científica versus coma flotante decimal). 5931
5.9.1. Manipuladores con argumentos 5932
Existen seis manipuladores estándar, como setw(), que toman 5933
argumentos. Están definidos en el fichero de cabecera <iomanip>, y están 5934
enumerados en la siguiente tabla: 5935
Manipulador 5936
Efecto 5937
setiosflags(fmtflags n) 5938
Equivalente a una llamada a setf(n). La activación continua hasta el 5939
siguiente cambio, como ios::setf(). 5940
resetiosflags(fmtflags n) 5941
217
Limpia solo las banderas de formato especificadas por n. La activación 5942
permanece hasta el siguiente cambio, como ios::unsetf(). 5943
setbase(base n) 5944
Cambia la base a n, donde n es 10, 8 o 16. (Caulquier otra opción resulta 5945
en 0). Si n es cero, la salida es base 10, pero la entrada usa convenciones 5946
de C: 10 es 10, 010 es 8, y 0xf es 15. Puede usar también dec, oct y hex 5947
para la salida. 5948
setfill(char n) 5949
Cambia el carácter de relleno a n, como ios::fill(). 5950
setprecision(int n) 5951
Cambia la precision a n, como ios::precision(). 5952
setw(int n) 5953
Cambia la anchura del campo a n, como en ios::width() 5954
Si está usando mucho el formateo, usted puede ver como usar los 5955
manipuladores en vez de llamar a funciones miembro de stream puede 5956
limpiar su código. Como ejemplo, aquí tiene un programa de la sección 5957
previa reescrito para usar los manipuladores. (La macro D() ha sido borrada 5958
para hacerlo más fácil de leer). 5959
//: C04:Manips.cpp 5960
// Format.cpp using manipulators. 5961
#include <fstream> 5962
#include <iomanip> 5963
#include <iostream> 5964
using namespace std; 5965
5966
int main() { 5967
ofstream trc("trace.out"); 5968
int i = 47; 5969
218
float f = 2300114.414159; 5970
char* s = "Is there any more?"; 5971
5972
trc << setiosflags(ios::unitbuf 5973
| ios::showbase | ios::uppercase 5974
| ios::showpos); 5975
trc << i << endl; 5976
trc << hex << i << endl 5977
<< oct << i << endl; 5978
trc.setf(ios::left, ios::adjustfield); 5979
trc << resetiosflags(ios::showbase) 5980
<< dec << setfill('0'); 5981
trc << "fill char: " << trc.fill() << endl; 5982
trc << setw(10) << i << endl; 5983
trc.setf(ios::right, ios::adjustfield); 5984
trc << setw(10) << i << endl; 5985
trc.setf(ios::internal, ios::adjustfield); 5986
trc << setw(10) << i << endl; 5987
trc << i << endl; // Without setw(10) 5988
5989
trc << resetiosflags(ios::showpos) 5990
<< setiosflags(ios::showpoint) 5991
<< "prec = " << trc.precision() << endl; 5992
trc.setf(ios::scientific, ios::floatfield); 5993
trc << f << resetiosflags(ios::uppercase) << endl; 5994
219
trc.setf(ios::fixed, ios::floatfield); 5995
trc << f << endl; 5996
trc << f << endl; 5997
trc << setprecision(20); 5998
trc << "prec = " << trc.precision() << endl; 5999
trc << f << endl; 6000
trc.setf(ios::scientific, ios::floatfield); 6001
trc << f << endl; 6002
trc.setf(ios::fixed, ios::floatfield); 6003
trc << f << endl; 6004
trc << f << endl; 6005
6006
trc << setw(10) << s << endl; 6007
trc << setw(40) << s << endl; 6008
trc.setf(ios::left, ios::adjustfield); 6009
trc << setw(40) << s << endl; 6010
} ///:~ 6011
Listado 5.17. C04/Manips.cpp 6012
6013
Puede ver que un montón de sentencias múltiples han sido condensadas 6014
dentro de una sola inserción encadenada. Nótese que la llamada a 6015
setiosflags() en que se pasa el OR binario de las banderas. Esto se podría 6016
haber hecho también con setf() y unsetf() como en el ejemplo previo. 6017
//: C04:InputWidth.cpp 6018
// Shows limitations of setw with input. 6019
220
#include <cassert> 6020
#include <cmath> 6021
#include <iomanip> 6022
#include <limits> 6023
#include <sstream> 6024
#include <string> 6025
using namespace std; 6026
6027
int main() { 6028
istringstream is("one 2.34 five"); 6029
string temp; 6030
is >> setw(2) >> temp; 6031
assert(temp == "on"); 6032
is >> setw(2) >> temp; 6033
assert(temp == "e"); 6034
double x; 6035
is >> setw(2) >> x; 6036
double relerr = fabs(x - 2.34) / x; 6037
assert(relerr <= numeric_limits<double>::epsilon()); 6038
} ///:~ 6039
Listado 5.18. C04/InputWidth.cpp 6040
6041
5.9.2. 6042
221
ostream& endl(ostream&); 6043
cout << "howdy" << endl; 6044
ostream& ostream::operator<<(ostream& (*pf)(ostream&)) { 6045
return pf(*this); 6046
} 6047
//: C04:nl.cpp 6048
// Creating a manipulator. 6049
#include <iostream> 6050
using namespace std; 6051
6052
ostream& nl(ostream& os) { 6053
return os << '\n'; 6054
} 6055
6056
int main() { 6057
cout << "newlines" << nl << "between" << nl 6058
<< "each" << nl << "word" << nl; 6059
} ///:~ 6060
Listado 5.19. C04/nl.cpp 6061
6062
cout.operator<<(nl) è nl(cout) 6063
os << '\n'; 6064
222
5.9.3. 6065
//: C04:Effector.cpp 6066
// Jerry Schwarz's "effectors." 6067
#include <cassert> 6068
#include <limits> // For max() 6069
#include <sstream> 6070
#include <string> 6071
using namespace std; 6072
6073
// Put out a prefix of a string: 6074
class Fixw { 6075
string str; 6076
public: 6077
Fixw(const string& s, int width) : str(s, 0, width) {} 6078
friend ostream& operator<<(ostream& os, const Fixw& fw) { 6079
return os << fw.str; 6080
} 6081
}; 6082
6083
// Print a number in binary: 6084
typedef unsigned long ulong; 6085
6086
class Bin { 6087
ulong n; 6088
public: 6089
223
Bin(ulong nn) { n = nn; } 6090
friend ostream& operator<<(ostream& os, const Bin& b) { 6091
const ulong ULMAX = numeric_limits<ulong>::max(); 6092
ulong bit = ~(ULMAX >> 1); // Top bit set 6093
while(bit) { 6094
os << (b.n & bit ? '1' : '0'); 6095
bit >>= 1; 6096
} 6097
return os; 6098
} 6099
}; 6100
6101
int main() { 6102
string words = "Things that make us happy, make us wise"; 6103
for(int i = words.size(); --i >= 0;) { 6104
ostringstream s; 6105
s << Fixw(words, i); 6106
assert(s.str() == words.substr(0, i)); 6107
} 6108
ostringstream xs, ys; 6109
xs << Bin(0xCAFEBABEUL); 6110
assert(xs.str() == 6111
"1100""1010""1111""1110""1011""1010""1011""1110"); 6112
ys << Bin(0x76543210UL); 6113
assert(ys.str() == 6114
224
"0111""0110""0101""0100""0011""0010""0001""0000"); 6115
} ///:~ 6116
Listado 5.20. C04/Effector.cpp 6117
6118
5.10. 6119
5.10.1. 6120
//: C04:Cppcheck.cpp 6121
// Configures .h & .cpp files to conform to style 6122
// standard. Tests existing files for conformance. 6123
#include <fstream> 6124
#include <sstream> 6125
#include <string> 6126
#include <cstddef> 6127
#include "../require.h" 6128
using namespace std; 6129
6130
bool startsWith(const string& base, const string& key) { 6131
return base.compare(0, key.size(), key) == 0; 6132
} 6133
6134
void cppCheck(string fileName) { 6135
enum bufs { BASE, HEADER, IMPLEMENT, HLINE1, GUARD1, 6136
GUARD2, GUARD3, CPPLINE1, INCLUDE, BUFNUM }; 6137
225
string part[BUFNUM]; 6138
part[BASE] = fileName; 6139
// Find any '.' in the string: 6140
size_t loc = part[BASE].find('.'); 6141
if(loc != string::npos) 6142
part[BASE].erase(loc); // Strip extension 6143
// Force to upper case: 6144
for(size_t i = 0; i < part[BASE].size(); i++) 6145
part[BASE][i] = toupper(part[BASE][i]); 6146
// Create file names and internal lines: 6147
part[HEADER] = part[BASE] + ".h"; 6148
part[IMPLEMENT] = part[BASE] + ".cpp"; 6149
part[HLINE1] = "//" ": " + part[HEADER]; 6150
part[GUARD1] = "#ifndef " + part[BASE] + "_H"; 6151
part[GUARD2] = "#define " + part[BASE] + "_H"; 6152
part[GUARD3] = "#endif // " + part[BASE] +"_H"; 6153
part[CPPLINE1] = string("//") + ": " + part[IMPLEMENT]; 6154
part[INCLUDE] = "#include \"" + part[HEADER] + "\""; 6155
// First, try to open existing files: 6156
ifstream existh(part[HEADER].c_str()), 6157
existcpp(part[IMPLEMENT].c_str()); 6158
if(!existh) { // Doesn't exist; create it 6159
ofstream newheader(part[HEADER].c_str()); 6160
assure(newheader, part[HEADER].c_str()); 6161
newheader << part[HLINE1] << endl 6162
226
<< part[GUARD1] << endl 6163
<< part[GUARD2] << endl << endl 6164
<< part[GUARD3] << endl; 6165
} else { // Already exists; verify it 6166
stringstream hfile; // Write & read 6167
ostringstream newheader; // Write 6168
hfile << existh.rdbuf(); 6169
// Check that first three lines conform: 6170
bool changed = false; 6171
string s; 6172
hfile.seekg(0); 6173
getline(hfile, s); 6174
bool lineUsed = false; 6175
// The call to good() is for Microsoft (later too): 6176
for(int line = HLINE1; hfile.good() && line <= GUARD2; 6177
++line) { 6178
if(startsWith(s, part[line])) { 6179
newheader << s << endl; 6180
lineUsed = true; 6181
if(getline(hfile, s)) 6182
lineUsed = false; 6183
} else { 6184
newheader << part[line] << endl; 6185
changed = true; 6186
lineUsed = false; 6187
227
} 6188
} 6189
// Copy rest of file 6190
if(!lineUsed) 6191
newheader << s << endl; 6192
newheader << hfile.rdbuf(); 6193
// Check for GUARD3 6194
string head = hfile.str(); 6195
if(head.find(part[GUARD3]) == string::npos) { 6196
newheader << part[GUARD3] << endl; 6197
changed = true; 6198
} 6199
// If there were changes, overwrite file: 6200
if(changed) { 6201
existh.close(); 6202
ofstream newH(part[HEADER].c_str()); 6203
assure(newH, part[HEADER].c_str()); 6204
newH << "//@//\n" // Change marker 6205
<< newheader.str(); 6206
} 6207
} 6208
if(!existcpp) { // Create cpp file 6209
ofstream newcpp(part[IMPLEMENT].c_str()); 6210
assure(newcpp, part[IMPLEMENT].c_str()); 6211
newcpp << part[CPPLINE1] << endl 6212
228
<< part[INCLUDE] << endl; 6213
} else { // Already exists; verify it 6214
stringstream cppfile; 6215
ostringstream newcpp; 6216
cppfile << existcpp.rdbuf(); 6217
// Check that first two lines conform: 6218
bool changed = false; 6219
string s; 6220
cppfile.seekg(0); 6221
getline(cppfile, s); 6222
bool lineUsed = false; 6223
for(int line = CPPLINE1; 6224
cppfile.good() && line <= INCLUDE; ++line) { 6225
if(startsWith(s, part[line])) { 6226
newcpp << s << endl; 6227
lineUsed = true; 6228
if(getline(cppfile, s)) 6229
lineUsed = false; 6230
} else { 6231
newcpp << part[line] << endl; 6232
changed = true; 6233
lineUsed = false; 6234
} 6235
} 6236
// Copy rest of file 6237
229
if(!lineUsed) 6238
newcpp << s << endl; 6239
newcpp << cppfile.rdbuf(); 6240
// If there were changes, overwrite file: 6241
if(changed) { 6242
existcpp.close(); 6243
ofstream newCPP(part[IMPLEMENT].c_str()); 6244
assure(newCPP, part[IMPLEMENT].c_str()); 6245
newCPP << "//@//\n" // Change marker 6246
<< newcpp.str(); 6247
} 6248
} 6249
} 6250
6251
int main(int argc, char* argv[]) { 6252
if(argc > 1) 6253
cppCheck(argv[1]); 6254
else 6255
cppCheck("cppCheckTest.h"); 6256
} ///:~ 6257
Listado 5.21. C04/Cppcheck.cpp 6258
6259
// CPPCHECKTEST.h 6260
#ifndef CPPCHECKTEST_H 6261
230
#define CPPCHECKTEST_H 6262
#endif // CPPCHECKTEST_H 6263
// PPCHECKTEST.cpp 6264
#include "CPPCHECKTEST.h" 6265
5.10.2. 6266
//: C04:Showerr.cpp {RunByHand} 6267
// Un-comment error generators. 6268
#include <cstddef> 6269
#include <cstdlib> 6270
#include <cstdio> 6271
#include <fstream> 6272
#include <iostream> 6273
#include <sstream> 6274
#include <string> 6275
#include "../require.h" 6276
using namespace std; 6277
6278
const string USAGE = 6279
"usage: showerr filename chapnum\n" 6280
"where filename is a C++ source file\n" 6281
"and chapnum is the chapter name it's in.\n" 6282
"Finds lines commented with //! and removes\n" 6283
"the comment, appending //(#) where # is unique\n" 6284
231
"across all files, so you can determine\n" 6285
"if your compiler finds the error.\n" 6286
"showerr /r\n" 6287
"resets the unique counter."; 6288
6289
class Showerr { 6290
const int CHAP; 6291
const string MARKER, FNAME; 6292
// File containing error number counter: 6293
const string ERRNUM; 6294
// File containing error lines: 6295
const string ERRFILE; 6296
stringstream edited; // Edited file 6297
int counter; 6298
public: 6299
Showerr(const string& f, const string& en, 6300
const string& ef, int c) 6301
: CHAP(c), MARKER("//!"), FNAME(f), ERRNUM(en), 6302
ERRFILE(ef), counter(0) {} 6303
void replaceErrors() { 6304
ifstream infile(FNAME.c_str()); 6305
assure(infile, FNAME.c_str()); 6306
ifstream count(ERRNUM.c_str()); 6307
if(count) count >> counter; 6308
int linecount = 1; 6309
232
string buf; 6310
ofstream errlines(ERRFILE.c_str(), ios::app); 6311
assure(errlines, ERRFILE.c_str()); 6312
while(getline(infile, buf)) { 6313
// Find marker at start of line: 6314
size_t pos = buf.find(MARKER); 6315
if(pos != string::npos) { 6316
// Erase marker: 6317
buf.erase(pos, MARKER.size() + 1); 6318
// Append counter & error info: 6319
ostringstream out; 6320
out << buf << " // (" << ++counter << ") " 6321
<< "Chapter " << CHAP 6322
<< " File: " << FNAME 6323
<< " Line " << linecount << endl; 6324
edited << out.str(); 6325
errlines << out.str(); // Append error file 6326
} 6327
else 6328
edited << buf << "\n"; // Just copy 6329
++linecount; 6330
} 6331
} 6332
void saveFiles() { 6333
ofstream outfile(FNAME.c_str()); // Overwrites 6334
233
assure(outfile, FNAME.c_str()); 6335
outfile << edited.rdbuf(); 6336
ofstream count(ERRNUM.c_str()); // Overwrites 6337
assure(count, ERRNUM.c_str()); 6338
count << counter; // Save new counter 6339
} 6340
}; 6341
6342
int main(int argc, char* argv[]) { 6343
const string ERRCOUNT("../errnum.txt"), 6344
ERRFILE("../errlines.txt"); 6345
requireMinArgs(argc, 1, USAGE.c_str()); 6346
if(argv[1][0] == '/' || argv[1][0] == '-') { 6347
// Allow for other switches: 6348
switch(argv[1][1]) { 6349
case 'r': case 'R': 6350
cout << "reset counter" << endl; 6351
remove(ERRCOUNT.c_str()); // Delete files 6352
remove(ERRFILE.c_str()); 6353
return EXIT_SUCCESS; 6354
default: 6355
cerr << USAGE << endl; 6356
return EXIT_FAILURE; 6357
} 6358
} 6359
234
if(argc == 3) { 6360
Showerr s(argv[1], ERRCOUNT, ERRFILE, atoi(argv[2])); 6361
s.replaceErrors(); 6362
s.saveFiles(); 6363
} 6364
} ///:~ 6365
Listado 5.22. C04/Showerr.cpp 6366
6367
5.10.3. 6368
//: C04:DataLogger.h 6369
// Datalogger record layout. 6370
#ifndef DATALOG_H 6371
#define DATALOG_H 6372
#include <ctime> 6373
#include <iosfwd> 6374
#include <string> 6375
using std::ostream; 6376
6377
struct Coord { 6378
int deg, min, sec; 6379
Coord(int d = 0, int m = 0, int s = 0) 6380
: deg(d), min(m), sec(s) {} 6381
std::string toString() const; 6382
235
}; 6383
6384
ostream& operator<<(ostream&, const Coord&); 6385
6386
class DataPoint { 6387
std::time_t timestamp; // Time & day 6388
Coord latitude, longitude; 6389
double depth, temperature; 6390
public: 6391
DataPoint(std::time_t ts, const Coord& lat, 6392
const Coord& lon, double dep, double temp) 6393
: timestamp(ts), latitude(lat), longitude(lon), 6394
depth(dep), temperature(temp) {} 6395
DataPoint() : timestamp(0), depth(0), temperature(0) {} 6396
friend ostream& operator<<(ostream&, const DataPoint&); 6397
}; 6398
#endif // DATALOG_H ///:~ 6399
Listado 5.23. C04/DataLogger.h 6400
6401
//: C04:DataLogger.cpp {O} 6402
// Datapoint implementations. 6403
#include "DataLogger.h" 6404
#include <iomanip> 6405
#include <iostream> 6406
236
#include <sstream> 6407
#include <string> 6408
using namespace std; 6409
6410
ostream& operator<<(ostream& os, const Coord& c) { 6411
return os << c.deg << '*' << c.min << '\'' 6412
<< c.sec << '"'; 6413
} 6414
6415
string Coord::toString() const { 6416
ostringstream os; 6417
os << *this; 6418
return os.str(); 6419
} 6420
6421
ostream& operator<<(ostream& os, const DataPoint& d) { 6422
os.setf(ios::fixed, ios::floatfield); 6423
char fillc = os.fill('0'); // Pad on left with '0' 6424
tm* tdata = localtime(&d.timestamp); 6425
os << setw(2) << tdata->tm_mon + 1 << '\\' 6426
<< setw(2) << tdata->tm_mday << '\\' 6427
<< setw(2) << tdata->tm_year+1900 << ' ' 6428
<< setw(2) << tdata->tm_hour << ':' 6429
<< setw(2) << tdata->tm_min << ':' 6430
<< setw(2) << tdata->tm_sec; 6431
237
os.fill(' '); // Pad on left with ' ' 6432
streamsize prec = os.precision(4); 6433
os << " Lat:" << setw(9) << d.latitude.toString() 6434
<< ", Long:" << setw(9) << d.longitude.toString() 6435
<< ", depth:" << setw(9) << d.depth 6436
<< ", temp:" << setw(9) << d.temperature; 6437
os.fill(fillc); 6438
os.precision(prec); 6439
return os; 6440
} ///:~ 6441
Listado 5.24. C04/DataLogger.cpp 6442
6443
//: C04:Datagen.cpp 6444
// Test data generator. 6445
//{L} DataLogger 6446
#include <cstdlib> 6447
#include <ctime> 6448
#include <cstring> 6449
#include <fstream> 6450
#include "DataLogger.h" 6451
#include "../require.h" 6452
using namespace std; 6453
6454
int main() { 6455
238
time_t timer; 6456
srand(time(&timer)); // Seed the random number generator 6457
ofstream data("data.txt"); 6458
assure(data, "data.txt"); 6459
ofstream bindata("data.bin", ios::binary); 6460
assure(bindata, "data.bin"); 6461
for(int i = 0; i < 100; i++, timer += 55) { 6462
// Zero to 199 meters: 6463
double newdepth = rand() % 200; 6464
double fraction = rand() % 100 + 1; 6465
newdepth += 1.0 / fraction; 6466
double newtemp = 150 + rand() % 200; // Kelvin 6467
fraction = rand() % 100 + 1; 6468
newtemp += 1.0 / fraction; 6469
const DataPoint d(timer, Coord(45,20,31), 6470
Coord(22,34,18), newdepth, 6471
newtemp); 6472
data << d << endl; 6473
bindata.write(reinterpret_cast<const char*>(&d), 6474
sizeof(d)); 6475
} 6476
} ///:~ 6477
Listado 5.25. C04/Datagen.cpp 6478
6479
239
//: C04:Datascan.cpp 6480
//{L} DataLogger 6481
#include <fstream> 6482
#include <iostream> 6483
#include "DataLogger.h" 6484
#include "../require.h" 6485
using namespace std; 6486
6487
int main() { 6488
ifstream bindata("data.bin", ios::binary); 6489
assure(bindata, "data.bin"); 6490
DataPoint d; 6491
while(bindata.read(reinterpret_cast<char*>(&d), 6492
sizeof d)) 6493
cout << d << endl; 6494
} ///:~ 6495
Listado 5.26. C04/Datascan.cpp 6496
6497
5.11. 6498
5.11.1. 6499
5.11.2. 6500
//: C04:Locale.cpp {-g++}{-bor}{-edg} {RunByHand} 6501
// Illustrates effects of locales. 6502
240
#include <iostream> 6503
#include <locale> 6504
using namespace std; 6505
6506
int main() { 6507
locale def; 6508
cout << def.name() << endl; 6509
locale current = cout.getloc(); 6510
cout << current.name() << endl; 6511
float val = 1234.56; 6512
cout << val << endl; 6513
// Change to French/France 6514
cout.imbue(locale("french")); 6515
current = cout.getloc(); 6516
cout << current.name() << endl; 6517
cout << val << endl; 6518
6519
cout << "Enter the literal 7890,12: "; 6520
cin.imbue(cout.getloc()); 6521
cin >> val; 6522
cout << val << endl; 6523
cout.imbue(def); 6524
cout << val << endl; 6525
} ///:~ 6526
241
Listado 5.27. C04/Locale.cpp 6527
6528
//: C04:Facets.cpp {-bor}{-g++}{-mwcc}{-edg} 6529
#include <iostream> 6530
#include <locale> 6531
#include <string> 6532
using namespace std; 6533
6534
int main() { 6535
// Change to French/France 6536
locale loc("french"); 6537
cout.imbue(loc); 6538
string currency = 6539
use_facet<moneypunct<char> >(loc).curr_symbol(); 6540
char point = 6541
use_facet<moneypunct<char> >(loc).decimal_point(); 6542
cout << "I made " << currency << 12.34 << " today!" 6543
<< endl; 6544
} ///:~ 6545
Listado 5.28. C04/Facets.cpp 6546
6547
5.12. 6548
5.13. 6549
242
//: C04:Exercise14.cpp 6550
#include <fstream> 6551
#include <iostream> 6552
#include <sstream> 6553
#include "../require.h" 6554
using namespace std; 6555
6556
#define d(a) cout << #a " ==\t" << a << endl; 6557
6558
void tellPointers(fstream& s) { 6559
d(s.tellp()); 6560
d(s.tellg()); 6561
cout << endl; 6562
} 6563
void tellPointers(stringstream& s) { 6564
d(s.tellp()); 6565
d(s.tellg()); 6566
cout << endl; 6567
} 6568
int main() { 6569
fstream in("Exercise14.cpp"); 6570
assure(in, "Exercise14.cpp"); 6571
in.seekg(10); 6572
tellPointers(in); 6573
in.seekp(20); 6574
243
tellPointers(in); 6575
stringstream memStream("Here is a sentence."); 6576
memStream.seekg(10); 6577
tellPointers(memStream); 6578
memStream.seekp(5); 6579
tellPointers(memStream); 6580
} ///:~ 6581
Listado 5.29. C04/Exercise14.cpp 6582
6583
//: C04:Exercise15.txt 6584
Australia 6585
5E56,7667230284,Langler,Tyson,31.2147,0.00042117361 6586
2B97,7586701,Oneill,Zeke,553.429,0.0074673053156065 6587
4D75,7907252710,Nickerson,Kelly,761.612,0.010276276 6588
9F2,6882945012,Hartenbach,Neil,47.9637,0.0006471644 6589
Austria 6590
480F,7187262472,Oneill,Dee,264.012,0.00356226040013 6591
1B65,4754732628,Haney,Kim,7.33843,0.000099015948475 6592
DA1,1954960784,Pascente,Lester,56.5452,0.0007629529 6593
3F18,1839715659,Elsea,Chelsy,801.901,0.010819887645 6594
Belgium 6595
BDF,5993489554,Oneill,Meredith,283.404,0.0038239127 6596
5AC6,6612945602,Parisienne,Biff,557.74,0.0075254727 6597
6AD,6477082,Pennington,Lizanne,31.0807,0.0004193544 6598
244
4D0E,7861652688,Sisca,Francis,704.751,0.00950906238 6599
Bahamas 6600
37D8,6837424208,Parisienne,Samson,396.104,0.0053445 6601
5E98,6384069,Willis,Pam,90.4257,0.00122009564059246 6602
1462,1288616408,Stover,Hazal,583.939,0.007878970561 6603
5FF3,8028775718,Stromstedt,Bunk,39.8712,0.000537974 6604
1095,3737212,Stover,Denny,3.05387,0.000041205248883 6605
7428,2019381883,Parisienne,Shane,363.272,0.00490155 6606
///:~ 6607
Listado 5.30. C04/Exercise15.txt 6608
6609
6610
6611
[11] Explicadas en profundidad en el capítulo 5. 6612
[12] Por esa razón usted puede escribir ios::failbit en lugar de 6613
ios_base::failbit para ahorrar pulsaciones. 6614
[13] Es común el uso de operator void*() en vez de operator bool() porque 6615
las conversiones implícitas de booleano a entero pueden causar sorpresas; 6616
pueden emplazarle incorrectamente un stream en un contexto donde una 6617
conversion a integer puede ser aplicada. La función operator void*() solo 6618
será llamada implícitamente en el cuerpo de una expresión booleana. 6619
[14] un tipo integral usado para alojar bits aislados. 6620
[15] Un tratado mucho más en profundidad de buffers de stream y streams en 6621
general puede ser encontrado en[ Langer & Kreft's, Standar C++ iostreams and 6622
Locales, Addison-Wesley, 1999.] 6623
[16] Para más información sobre la epsilon de la máquina y el cómputo de 6624
punto flotante en general, vea el artículo de Chuck, "The Standard C Library, 6625
Part 3", C/C++ Users Journal, Marzo 1995, disponible en 6626
www.freshsources.com/1995006a.htm 6627
245
6: Las plantillas en profundidad 6628
Tabla de contenidos 6629
6.1. 6630
6.2. 6631
6.3. 6632
6.4. 6633
6.5. 6634
6.6. 6635
6.7. 6636
6.8. 6637
6.1. 6638
template<class T> class Stack { 6639
T* data; 6640
size_t count; 6641
public: 6642
void push(const T& t); 6643
// Etc. 6644
}; 6645
Stack<int> myStack; // A Stack of ints 6646
6.1.1. 6647
template<class T, size_t N> class Stack { 6648
T data[N]; // Fixed capacity is N 6649
size_t count; 6650
public: 6651
void push(const T& t); 6652
// Etc. 6653
246
}; 6654
Stack<int, 100> myFixedStack; 6655
//: C05:Urand.h {-bor} 6656
// Unique randomizer. 6657
#ifndef URAND_H 6658
#define URAND_H 6659
#include <bitset> 6660
#include <cstddef> 6661
#include <cstdlib> 6662
#include <ctime> 6663
using std::size_t; 6664
using std::bitset; 6665
6666
template<size_t UpperBound> class Urand { 6667
bitset<UpperBound> used; 6668
public: 6669
Urand() { srand(time(0)); } // Randomize 6670
size_t operator()(); // The "generator" function 6671
}; 6672
6673
template<size_t UpperBound> 6674
inline size_t Urand<UpperBound>::operator()() { 6675
if(used.count() == UpperBound) 6676
used.reset(); // Start over (clear bitset) 6677
247
size_t newval; 6678
while(used[newval = rand() % UpperBound]) 6679
; // Until unique value is found 6680
used[newval] = true; 6681
return newval; 6682
} 6683
#endif // URAND_H ///:~ 6684
Listado 6.1. C05/Urand.h 6685
6686
//: C05:UrandTest.cpp {-bor} 6687
#include <iostream> 6688
#include "Urand.h" 6689
using namespace std; 6690
6691
int main() { 6692
Urand<10> u; 6693
for(int i = 0; i < 20; ++i) 6694
cout << u() << ' '; 6695
} ///:~ 6696
Listado 6.2. C05/UrandTest.cpp 6697
6698
6.1.2. 6699
248
template<class T, size_t N = 100> class Stack { 6700
T data[N]; // Fixed capacity is N 6701
size_t count; 6702
public: 6703
void push(const T& t); 6704
// Etc. 6705
}; 6706
template<class T = int, size_t N = 100> // Both defaulted 6707
class Stack { 6708
T data[N]; // Fixed capacity is N 6709
size_t count; 6710
public: 6711
void push(const T& t); 6712
// Etc. 6713
}; 6714
6715
Stack<> myStack; // Same as Stack<int, 100> 6716
template<class T, class Allocator = allocator<T> > 6717
class vector; 6718
//: C05:FuncDef.cpp 6719
#include <iostream> 6720
using namespace std; 6721
6722
template<class T> T sum(T* b, T* e, T init = T()) { 6723
249
while(b != e) 6724
init += *b++; 6725
return init; 6726
} 6727
6728
int main() { 6729
int a[] = { 1, 2, 3 }; 6730
cout << sum(a, a + sizeof a / sizeof a[0]) << endl; // 6 6731
} ///:~ 6732
Listado 6.3. C05/FuncDef.cpp 6733
6734
6.1.3. 6735
//: C05:TempTemp.cpp 6736
// Illustrates a template template parameter. 6737
#include <cstddef> 6738
#include <iostream> 6739
using namespace std; 6740
6741
template<class T> 6742
class Array { // A simple, expandable sequence 6743
enum { INIT = 10 }; 6744
T* data; 6745
size_t capacity; 6746
250
size_t count; 6747
public: 6748
Array() { 6749
count = 0; 6750
data = new T[capacity = INIT]; 6751
} 6752
~Array() { delete [] data; } 6753
void push_back(const T& t) { 6754
if(count == capacity) { 6755
// Grow underlying array 6756
size_t newCap = 2 * capacity; 6757
T* newData = new T[newCap]; 6758
for(size_t i = 0; i < count; ++i) 6759
newData[i] = data[i]; 6760
delete [] data; 6761
data = newData; 6762
capacity = newCap; 6763
} 6764
data[count++] = t; 6765
} 6766
void pop_back() { 6767
if(count > 0) 6768
--count; 6769
} 6770
T* begin() { return data; } 6771
251
T* end() { return data + count; } 6772
}; 6773
6774
template<class T, template<class> class Seq> 6775
class Container { 6776
Seq<T> seq; 6777
public: 6778
void append(const T& t) { seq.push_back(t); } 6779
T* begin() { return seq.begin(); } 6780
T* end() { return seq.end(); } 6781
}; 6782
6783
int main() { 6784
Container<int, Array> container; 6785
container.append(1); 6786
container.append(2); 6787
int* p = container.begin(); 6788
while(p != container.end()) 6789
cout << *p++ << endl; 6790
} ///:~ 6791
Listado 6.4. C05/TempTemp.cpp 6792
6793
Seq<T> seq; 6794
template<class T, template<class> class Seq> 6795
252
template<class T, template<class U> class Seq> 6796
T operator++(int); 6797
//: C05:TempTemp2.cpp 6798
// A multi-variate template template parameter. 6799
#include <cstddef> 6800
#include <iostream> 6801
using namespace std; 6802
6803
template<class T, size_t N> class Array { 6804
T data[N]; 6805
size_t count; 6806
public: 6807
Array() { count = 0; } 6808
void push_back(const T& t) { 6809
if(count < N) 6810
data[count++] = t; 6811
} 6812
void pop_back() { 6813
if(count > 0) 6814
--count; 6815
} 6816
T* begin() { return data; } 6817
T* end() { return data + count; } 6818
}; 6819
253
6820
template<class T,size_t N,template<class,size_t> class Seq> 6821
class Container { 6822
Seq<T,N> seq; 6823
public: 6824
void append(const T& t) { seq.push_back(t); } 6825
T* begin() { return seq.begin(); } 6826
T* end() { return seq.end(); } 6827
}; 6828
6829
int main() { 6830
const size_t N = 10; 6831
Container<int, N, Array> container; 6832
container.append(1); 6833
container.append(2); 6834
int* p = container.begin(); 6835
while(p != container.end()) 6836
cout << *p++ << endl; 6837
} ///:~ 6838
Listado 6.5. C05/TempTemp2.cpp 6839
6840
//: C05:TempTemp3.cpp {-bor}{-msc} 6841
// Template template parameters and default arguments. 6842
#include <cstddef> 6843
254
#include <iostream> 6844
using namespace std; 6845
6846
template<class T, size_t N = 10> // A default argument 6847
class Array { 6848
T data[N]; 6849
size_t count; 6850
public: 6851
Array() { count = 0; } 6852
void push_back(const T& t) { 6853
if(count < N) 6854
data[count++] = t; 6855
} 6856
void pop_back() { 6857
if(count > 0) 6858
--count; 6859
} 6860
T* begin() { return data; } 6861
T* end() { return data + count; } 6862
}; 6863
6864
template<class T, template<class, size_t = 10> class Seq> 6865
class Container { 6866
Seq<T> seq; // Default used 6867
public: 6868
255
void append(const T& t) { seq.push_back(t); } 6869
T* begin() { return seq.begin(); } 6870
T* end() { return seq.end(); } 6871
}; 6872
6873
int main() { 6874
Container<int, Array> container; 6875
container.append(1); 6876
container.append(2); 6877
int* p = container.begin(); 6878
while(p != container.end()) 6879
cout << *p++ << endl; 6880
} ///:~ 6881
Listado 6.6. C05/TempTemp3.cpp 6882
6883
template<class T, template<class, size_t = 10> class Seq> 6884
//: C05:TempTemp4.cpp {-bor}{-msc} 6885
// Passes standard sequences as template arguments. 6886
#include <iostream> 6887
#include <list> 6888
#include <memory> // Declares allocator<T> 6889
#include <vector> 6890
using namespace std; 6891
256
6892
template<class T, template<class U, class = allocator<U> > 6893
class Seq> 6894
class Container { 6895
Seq<T> seq; // Default of allocator<T> applied implicitly 6896
public: 6897
void push_back(const T& t) { seq.push_back(t); } 6898
typename Seq<T>::iterator begin() { return seq.begin(); } 6899
typename Seq<T>::iterator end() { return seq.end(); } 6900
}; 6901
6902
int main() { 6903
// Use a vector 6904
Container<int, vector> vContainer; 6905
vContainer.push_back(1); 6906
vContainer.push_back(2); 6907
for(vector<int>::iterator p = vContainer.begin(); 6908
p != vContainer.end(); ++p) { 6909
cout << *p << endl; 6910
} 6911
// Use a list 6912
Container<int, list> lContainer; 6913
lContainer.push_back(3); 6914
lContainer.push_back(4); 6915
for(list<int>::iterator p2 = lContainer.begin(); 6916
257
p2 != lContainer.end(); ++p2) { 6917
cout << *p2 << endl; 6918
} 6919
} ///:~ 6920
Listado 6.7. C05/TempTemp4.cpp 6921
6922
6.1.4. 6923
//: C05:TypenamedID.cpp {-bor} 6924
// Uses 'typename' as a prefix for nested types. 6925
6926
template<class T> class X { 6927
// Without typename, you should get an error: 6928
typename T::id i; 6929
public: 6930
void f() { i.g(); } 6931
}; 6932
6933
class Y { 6934
public: 6935
class id { 6936
public: 6937
void g() {} 6938
}; 6939
258
}; 6940
6941
int main() { 6942
X<Y> xy; 6943
xy.f(); 6944
} ///:~ 6945
Listado 6.8. C05/TypenamedID.cpp 6946
6947
//: C05:PrintSeq.cpp {-msc}{-mwcc} 6948
// A print function for Standard C++ sequences. 6949
#include <iostream> 6950
#include <list> 6951
#include <memory> 6952
#include <vector> 6953
using namespace std; 6954
6955
template<class T, template<class U, class = allocator<U> > 6956
class Seq> 6957
void printSeq(Seq<T>& seq) { 6958
for(typename Seq<T>::iterator b = seq.begin(); 6959
b != seq.end();) 6960
cout << *b++ << endl; 6961
} 6962
6963
259
int main() { 6964
// Process a vector 6965
vector<int> v; 6966
v.push_back(1); 6967
v.push_back(2); 6968
printSeq(v); 6969
// Process a list 6970
list<int> lst; 6971
lst.push_back(3); 6972
lst.push_back(4); 6973
printSeq(lst); 6974
} ///:~ 6975
Listado 6.9. C05/PrintSeq.cpp 6976
6977
typename Seq<T>::iterator It; 6978
typedef typename Seq<It>::iterator It; 6979
//: C05:UsingTypename.cpp 6980
// Using 'typename' in the template argument list. 6981
6982
template<typename T> class X {}; 6983
6984
int main() { 6985
260
X<int> x; 6986
} ///:~ 6987
Listado 6.10. C05/UsingTypename.cpp 6988
6989
6.1.5. 6990
//: C05:DotTemplate.cpp 6991
// Illustrate the .template construct. 6992
#include <bitset> 6993
#include <cstddef> 6994
#include <iostream> 6995
#include <string> 6996
using namespace std; 6997
6998
template<class charT, size_t N> 6999
basic_string<charT> bitsetToString(const bitset<N>& bs) { 7000
return bs. template to_string<charT, char_traits<charT>, 7001
allocator<charT> >(); 7002
} 7003
7004
int main() { 7005
bitset<10> bs; 7006
bs.set(1); 7007
bs.set(5); 7008
261
cout << bs << endl; // 0000100010 7009
string s = bitsetToString<char>(bs); 7010
cout << s << endl; // 0000100010 7011
} ///:~ 7012
Listado 6.11. C05/DotTemplate.cpp 7013
7014
template<class charT, class traits, class Allocator> 7015
basic_string<charT, traits, Allocator> to_string() const; 7016
wstring s = bitsetToString<wchar_t>(bs); 7017
6.1.6. 7018
template<typename T> class complex { 7019
public: 7020
template<class X> complex(const complex<X>&); 7021
complex<float> z(1, 2); 7022
complex<double> w(z); 7023
template<typename T> 7024
template<typename X> 7025
complex<T>::complex(const complex<X>& c) {/* Body here' */} 7026
int data[5] = { 1, 2, 3, 4, 5 }; 7027
vector<int> v1(data, data+5); 7028
vector<double> v2(v1.begin(), v1.end()); 7029
template<class InputIterator> 7030
262
vector(InputIterator first, InputIterator last, 7031
const Allocator& = Allocator()); 7032
//: C05:MemberClass.cpp 7033
// A member class template. 7034
#include <iostream> 7035
#include <typeinfo> 7036
using namespace std; 7037
7038
template<class T> class Outer { 7039
public: 7040
template<class R> class Inner { 7041
public: 7042
void f(); 7043
}; 7044
}; 7045
7046
template<class T> template<class R> 7047
void Outer<T>::Inner<R>::f() { 7048
cout << "Outer == " << typeid(T).name() << endl; 7049
cout << "Inner == " << typeid(R).name() << endl; 7050
cout << "Full Inner == " << typeid(*this).name() << endl; 7051
} 7052
7053
int main() { 7054
263
Outer<int>::Inner<bool> inner; 7055
inner.f(); 7056
} ///:~ 7057
Listado 6.12. C05/MemberClass.cpp 7058
7059
Outer == int 7060 Inner == bool 7061
Full Inner == Outer<int>::Inner<bool> 7062
6.2. 7063
template<typename T> const T& min(const T& a, const T& b) { 7064
return (a < b) ? a : b; 7065
} 7066
int z = min<int>(i, j); 7067
6.2.1. 7068
int z = min(i, j); 7069
int z = min(x, j); // x is a double 7070
int z = min<double>(x, j); 7071
template<typename T, typename U> 7072
const T& min(const T& a, const U& b) { 7073
return (a < b) ? a : b; 7074
} 7075
//: C05:StringConv.h 7076
264
// Function templates to convert to and from strings. 7077
#ifndef STRINGCONV_H 7078
#define STRINGCONV_H 7079
#include <string> 7080
#include <sstream> 7081
7082
template<typename T> T fromString(const std::string& s) { 7083
std::istringstream is(s); 7084
T t; 7085
is >> t; 7086
return t; 7087
} 7088
7089
template<typename T> std::string toString(const T& t) { 7090
std::ostringstream s; 7091
s << t; 7092
return s.str(); 7093
} 7094
#endif // STRINGCONV_H ///:~ 7095
Listado 6.13. C05/StringConv.h 7096
7097
//: C05:StringConvTest.cpp 7098
#include <complex> 7099
#include <iostream> 7100
265
#include "StringConv.h" 7101
using namespace std; 7102
7103
int main() { 7104
int i = 1234; 7105
cout << "i == \"" << toString(i) << "\"" << endl; 7106
float x = 567.89; 7107
cout << "x == \"" << toString(x) << "\"" << endl; 7108
complex<float> c(1.0, 2.0); 7109
cout << "c == \"" << toString(c) << "\"" << endl; 7110
cout << endl; 7111
7112
i = fromString<int>(string("1234")); 7113
cout << "i == " << i << endl; 7114
x = fromString<float>(string("567.89")); 7115
cout << "x == " << x << endl; 7116
c = fromString<complex<float> >(string("(1.0,2.0)")); 7117
cout << "c == " << c << endl; 7118
} ///:~ 7119
Listado 6.14. C05/StringConvTest.cpp 7120
7121
i == "1234" 7122 x == "567.89" 7123 c == "(1,2)" 7124
7125 i == 1234 7126
x == 567.89 7127 c == (1,2) 7128
266
//: C05:ImplicitCast.cpp 7129
7130
template<typename R, typename P> 7131
R implicit_cast(const P& p) { 7132
return p; 7133
} 7134
7135
int main() { 7136
int i = 1; 7137
float x = implicit_cast<float>(i); 7138
int j = implicit_cast<int>(x); 7139
//! char* p = implicit_cast<char*>(i); 7140
} ///:~ 7141
Listado 6.15. C05/ImplicitCast.cpp 7142
7143
//: C05:ArraySize.cpp 7144
#include <cstddef> 7145
using std::size_t; 7146
7147
template<size_t R, size_t C, typename T> 7148
void init1(T a[R][C]) { 7149
for(size_t i = 0; i < R; ++i) 7150
for(size_t j = 0; j < C; ++j) 7151
a[i][j] = T(); 7152
267
} 7153
7154
template<size_t R, size_t C, class T> 7155
void init2(T (&a)[R][C]) { // Reference parameter 7156
for(size_t i = 0; i < R; ++i) 7157
for(size_t j = 0; j < C; ++j) 7158
a[i][j] = T(); 7159
} 7160
7161
int main() { 7162
int a[10][20]; 7163
init1<10,20>(a); // Must specify 7164
init2(a); // Sizes deduced 7165
} ///:~ 7166
Listado 6.16. C05/ArraySize.cpp 7167
7168
6.2.2. 7169
//: C05:MinTest.cpp 7170
#include <cstring> 7171
#include <iostream> 7172
using std::strcmp; 7173
using std::cout; 7174
using std::endl; 7175
268
7176
template<typename T> const T& min(const T& a, const T& b) { 7177
return (a < b) ? a : b; 7178
} 7179
7180
const char* min(const char* a, const char* b) { 7181
return (strcmp(a, b) < 0) ? a : b; 7182
} 7183
7184
double min(double x, double y) { 7185
return (x < y) ? x : y; 7186
} 7187
7188
int main() { 7189
const char *s2 = "say \"Ni-!\"", *s1 = "knights who"; 7190
cout << min(1, 2) << endl; // 1: 1 (template) 7191
cout << min(1.0, 2.0) << endl; // 2: 1 (double) 7192
cout << min(1, 2.0) << endl; // 3: 1 (double) 7193
cout << min(s1, s2) << endl; // 4: knights who (const 7194
// char*) 7195
cout << min<>(s1, s2) << endl; // 5: say "Ni-!" 7196
// (template) 7197
} ///:~ 7198
Listado 6.17. C05/MinTest.cpp 7199
269
7200
template<typename T> 7201
const T& min(const T& a, const T& b, const T& c); 7202
6.2.3. 7203
//: C05:TemplateFunctionAddress.cpp {-mwcc} 7204
// Taking the address of a function generated 7205
// from a template. 7206
7207
template<typename T> void f(T*) {} 7208
7209
void h(void (*pf)(int*)) {} 7210
7211
template<typename T> void g(void (*pf)(T*)) {} 7212
7213
int main() { 7214
h(&f<int>); // Full type specification 7215
h(&f); // Type deduction 7216
g<int>(&f<int>); // Full type specification 7217
g(&f<int>); // Type deduction 7218
g<int>(&f); // Partial (but sufficient) specification 7219
} ///:~ 7220
Listado 6.18. C05/TemplateFunctionAddress.cpp 7221
7222
270
// The variable s is a std::string 7223
transform(s.begin(), s.end(), s.begin(), tolower); 7224
//: C05:FailedTransform.cpp {-xo} 7225
#include <algorithm> 7226
#include <cctype> 7227
#include <iostream> 7228
#include <string> 7229
using namespace std; 7230
7231
int main() { 7232
string s("LOWER"); 7233
transform(s.begin(), s.end(), s.begin(), tolower); 7234
cout << s << endl; 7235
} ///:~ 7236
Listado 6.19. C05/FailedTransform.cpp 7237
7238
template<class charT> charT toupper(charT c, 7239
const locale& loc); 7240
template<class charT> charT tolower(charT c, 7241
const locale& loc); 7242
transform(s.begin(),s.end(),s.begin() 7243
static_cast<int(*)(int)>(tolower)); 7244
271
//: C05:StrTolower.cpp {O} {-mwcc} 7245
#include <algorithm> 7246
#include <cctype> 7247
#include <string> 7248
using namespace std; 7249
7250
string strTolower(string s) { 7251
transform(s.begin(), s.end(), s.begin(), tolower); 7252
return s; 7253
} ///:~ 7254
Listado 6.20. C05/StrTolower.cpp 7255
7256
//: C05:Tolower.cpp {-mwcc} 7257
//{L} StrTolower 7258
#include <algorithm> 7259
#include <cctype> 7260
#include <iostream> 7261
#include <string> 7262
using namespace std; 7263
string strTolower(string); 7264
7265
int main() { 7266
string s("LOWER"); 7267
cout << strTolower(s) << endl; 7268
272
} ///:~ 7269
Listado 6.21. C05/Tolower.cpp 7270
7271
//: C05:ToLower2.cpp {-mwcc} 7272
#include <algorithm> 7273
#include <cctype> 7274
#include <iostream> 7275
#include <string> 7276
using namespace std; 7277
7278
template<class charT> charT strTolower(charT c) { 7279
return tolower(c); // One-arg version called 7280
} 7281
7282
int main() { 7283
string s("LOWER"); 7284
transform(s.begin(),s.end(),s.begin(),&strTolower<char>); 7285
cout << s << endl; 7286
} ///:~ 7287
Listado 6.22. C05/ToLower2.cpp 7288
7289
6.2.4. 7290
273
//: C05:ApplySequence.h 7291
// Apply a function to an STL sequence container. 7292
7293
// const, 0 arguments, any type of return value: 7294
template<class Seq, class T, class R> 7295
void apply(Seq& sq, R (T::*f)() const) { 7296
typename Seq::iterator it = sq.begin(); 7297
while(it != sq.end()) 7298
((*it++)->*f)(); 7299
} 7300
7301
// const, 1 argument, any type of return value: 7302
template<class Seq, class T, class R, class A> 7303
void apply(Seq& sq, R(T::*f)(A) const, A a) { 7304
typename Seq::iterator it = sq.begin(); 7305
while(it != sq.end()) 7306
((*it++)->*f)(a); 7307
} 7308
7309
// const, 2 arguments, any type of return value: 7310
template<class Seq, class T, class R, 7311
class A1, class A2> 7312
void apply(Seq& sq, R(T::*f)(A1, A2) const, 7313
A1 a1, A2 a2) { 7314
typename Seq::iterator it = sq.begin(); 7315
274
while(it != sq.end()) 7316
((*it++)->*f)(a1, a2); 7317
} 7318
7319
// Non-const, 0 arguments, any type of return value: 7320
template<class Seq, class T, class R> 7321
void apply(Seq& sq, R (T::*f)()) { 7322
typename Seq::iterator it = sq.begin(); 7323
while(it != sq.end()) 7324
((*it++)->*f)(); 7325
} 7326
7327
// Non-const, 1 argument, any type of return value: 7328
template<class Seq, class T, class R, class A> 7329
void apply(Seq& sq, R(T::*f)(A), A a) { 7330
typename Seq::iterator it = sq.begin(); 7331
while(it != sq.end()) 7332
((*it++)->*f)(a); 7333
} 7334
7335
// Non-const, 2 arguments, any type of return value: 7336
template<class Seq, class T, class R, 7337
class A1, class A2> 7338
void apply(Seq& sq, R(T::*f)(A1, A2), 7339
A1 a1, A2 a2) { 7340
275
typename Seq::iterator it = sq.begin(); 7341
while(it != sq.end()) 7342
((*it++)->*f)(a1, a2); 7343
} 7344
// Etc., to handle maximum likely arguments ///:~ 7345
Listado 6.23. C05/ApplySequence.h 7346
7347
//: C05:Gromit.h 7348
// The techno-dog. Has member functions 7349
// with various numbers of arguments. 7350
#include <iostream> 7351
7352
class Gromit { 7353
int arf; 7354
int totalBarks; 7355
public: 7356
Gromit(int arf = 1) : arf(arf + 1), totalBarks(0) {} 7357
void speak(int) { 7358
for(int i = 0; i < arf; i++) { 7359
std::cout << "arf! "; 7360
++totalBarks; 7361
} 7362
std::cout << std::endl; 7363
} 7364
276
char eat(float) const { 7365
std::cout << "chomp!" << std::endl; 7366
return 'z'; 7367
} 7368
int sleep(char, double) const { 7369
std::cout << "zzz..." << std::endl; 7370
return 0; 7371
} 7372
void sit() const { 7373
std::cout << "Sitting..." << std::endl; 7374
} 7375
}; ///:~ 7376
Listado 6.24. C05/Gromit.h 7377
7378
//: C05:ApplyGromit.cpp 7379
// Test ApplySequence.h. 7380
#include <cstddef> 7381
#include <iostream> 7382
#include <vector> 7383
#include "ApplySequence.h" 7384
#include "Gromit.h" 7385
#include "../purge.h" 7386
using namespace std; 7387
7388
277
int main() { 7389
vector<Gromit*> dogs; 7390
for(size_t i = 0; i < 5; i++) 7391
dogs.push_back(new Gromit(i)); 7392
apply(dogs, &Gromit::speak, 1); 7393
apply(dogs, &Gromit::eat, 2.0f); 7394
apply(dogs, &Gromit::sleep, 'z', 3.0); 7395
apply(dogs, &Gromit::sit); 7396
purge(dogs); 7397
} ///:~ 7398
Listado 6.25. C05/ApplyGromit.cpp 7399
7400
6.2.5. 7401
template<class T> void f(T); 7402
template<class T> void f(T*); 7403
template<class T> void f(const T*); 7404
//: C05:PartialOrder.cpp 7405
// Reveals ordering of function templates. 7406
#include <iostream> 7407
using namespace std; 7408
7409
template<class T> void f(T) { 7410
278
cout << "T" << endl; 7411
} 7412
7413
template<class T> void f(T*) { 7414
cout << "T*" << endl; 7415
} 7416
7417
template<class T> void f(const T*) { 7418
cout << "const T*" << endl; 7419
} 7420
7421
int main() { 7422
f(0); // T 7423
int i = 0; 7424
f(&i); // T* 7425
const int j = 0; 7426
f(&j); // const T* 7427
} ///:~ 7428
Listado 6.26. C05/PartialOrder.cpp 7429
7430
6.3. 7431
6.3.1. 7432
const char* min(const char* a, const char* b) { 7433
279
return (strcmp(a, b) < 0) ? a : b; 7434
} 7435
//: C05:MinTest2.cpp 7436
#include <cstring> 7437
#include <iostream> 7438
using std::strcmp; 7439
using std::cout; 7440
using std::endl; 7441
7442
template<class T> const T& min(const T& a, const T& b) { 7443
return (a < b) ? a : b; 7444
} 7445
7446
// An explicit specialization of the min template 7447
template<> 7448
const char* const& min<const char*>(const char* const& a, 7449
const char* const& b) { 7450
return (strcmp(a, b) < 0) ? a : b; 7451
} 7452
7453
int main() { 7454
const char *s2 = "say \"Ni-!\"", *s1 = "knights who"; 7455
cout << min(s1, s2) << endl; 7456
cout << min<>(s1, s2) << endl; 7457
280
} ///:~ 7458
Listado 6.27. C05/MinTest2.cpp 7459
7460
template<class T, class Allocator = allocator<T> > 7461
class vector {...}; 7462
template<> class vector<bool, allocator<bool> > {...}; 7463
6.3.2. 7464
template<class Allocator> class vector<bool, Allocator>; 7465
//: C05:PartialOrder2.cpp 7466
// Reveals partial ordering of class templates. 7467
#include <iostream> 7468
using namespace std; 7469
7470
template<class T, class U> class C { 7471
public: 7472
void f() { cout << "Primary Template\n"; } 7473
}; 7474
7475
template<class U> class C<int, U> { 7476
public: 7477
void f() { cout << "T == int\n"; } 7478
281
}; 7479
7480
template<class T> class C<T, double> { 7481
public: 7482
void f() { cout << "U == double\n"; } 7483
}; 7484
7485
template<class T, class U> class C<T*, U> { 7486
public: 7487
void f() { cout << "T* used\n"; } 7488
}; 7489
7490
template<class T, class U> class C<T, U*> { 7491
public: 7492
void f() { cout << "U* used\n"; } 7493
}; 7494
7495
template<class T, class U> class C<T*, U*> { 7496
public: 7497
void f() { cout << "T* and U* used\n"; } 7498
}; 7499
7500
template<class T> class C<T, T> { 7501
public: 7502
void f() { cout << "T == U\n"; } 7503
282
}; 7504
7505
int main() { 7506
C<float, int>().f(); // 1: Primary template 7507
C<int, float>().f(); // 2: T == int 7508
C<float, double>().f(); // 3: U == double 7509
C<float, float>().f(); // 4: T == U 7510
C<float*, float>().f(); // 5: T* used [T is float] 7511
C<float, float*>().f(); // 6: U* used [U is float] 7512
C<float*, int*>().f(); // 7: T* and U* used [float,int] 7513
// The following are ambiguous: 7514
// 8: C<int, int>().f(); 7515
// 9: C<double, double>().f(); 7516
// 10: C<float*, float*>().f(); 7517
// 11: C<int, int*>().f(); 7518
// 12: C<int*, int*>().f(); 7519
} ///:~ 7520
Listado 6.28. C05/PartialOrder2.cpp 7521
7522
6.3.3. 7523
//: C05:Sortable.h 7524
// Template specialization. 7525
#ifndef SORTABLE_H 7526
283
#define SORTABLE_H 7527
#include <cstring> 7528
#include <cstddef> 7529
#include <string> 7530
#include <vector> 7531
using std::size_t; 7532
7533
template<class T> 7534
class Sortable : public std::vector<T> { 7535
public: 7536
void sort(); 7537
}; 7538
7539
template<class T> 7540
void Sortable<T>::sort() { // A simple sort 7541
for(size_t i = this->size(); i > 0; --i) 7542
for(size_t j = 1; j < i; ++j) 7543
if(this->at(j-1) > this->at(j)) { 7544
T t = this->at(j-1); 7545
this->at(j-1) = this->at(j); 7546
this->at(j) = t; 7547
} 7548
} 7549
7550
// Partial specialization for pointers: 7551
284
template<class T> 7552
class Sortable<T*> : public std::vector<T*> { 7553
public: 7554
void sort(); 7555
}; 7556
7557
template<class T> 7558
void Sortable<T*>::sort() { 7559
for(size_t i = this->size(); i > 0; --i) 7560
for(size_t j = 1; j < i; ++j) 7561
if(*this->at(j-1) > *this->at(j)) { 7562
T* t = this->at(j-1); 7563
this->at(j-1) = this->at(j); 7564
this->at(j) = t; 7565
} 7566
} 7567
7568
// Full specialization for char* 7569
// (Made inline here for convenience -- normally you would 7570
// place the function body in a separate file and only 7571
// leave the declaration here). 7572
template<> inline void Sortable<char*>::sort() { 7573
for(size_t i = this->size(); i > 0; --i) 7574
for(size_t j = 1; j < i; ++j) 7575
if(std::strcmp(this->at(j-1), this->at(j)) > 0) { 7576
285
char* t = this->at(j-1); 7577
this->at(j-1) = this->at(j); 7578
this->at(j) = t; 7579
} 7580
} 7581
#endif // SORTABLE_H ///:~ 7582
Listado 6.29. C05/Sortable.h 7583
7584
//: C05:Sortable.cpp 7585
//{-bor} (Because of bitset in Urand.h) 7586
// Testing template specialization. 7587
#include <cstddef> 7588
#include <iostream> 7589
#include "Sortable.h" 7590
#include "Urand.h" 7591
using namespace std; 7592
7593
#define asz(a) (sizeof a / sizeof a[0]) 7594
7595
char* words[] = { "is", "running", "big", "dog", "a", }; 7596
char* words2[] = { "this", "that", "theother", }; 7597
7598
int main() { 7599
Sortable<int> is; 7600
286
Urand<47> rnd; 7601
for(size_t i = 0; i < 15; ++i) 7602
is.push_back(rnd()); 7603
for(size_t i = 0; i < is.size(); ++i) 7604
cout << is[i] << ' '; 7605
cout << endl; 7606
is.sort(); 7607
for(size_t i = 0; i < is.size(); ++i) 7608
cout << is[i] << ' '; 7609
cout << endl; 7610
7611
// Uses the template partial specialization: 7612
Sortable<string*> ss; 7613
for(size_t i = 0; i < asz(words); ++i) 7614
ss.push_back(new string(words[i])); 7615
for(size_t i = 0; i < ss.size(); ++i) 7616
cout << *ss[i] << ' '; 7617
cout << endl; 7618
ss.sort(); 7619
for(size_t i = 0; i < ss.size(); ++i) { 7620
cout << *ss[i] << ' '; 7621
delete ss[i]; 7622
} 7623
cout << endl; 7624
7625
287
// Uses the full char* specialization: 7626
Sortable<char*> scp; 7627
for(size_t i = 0; i < asz(words2); ++i) 7628
scp.push_back(words2[i]); 7629
for(size_t i = 0; i < scp.size(); ++i) 7630
cout << scp[i] << ' '; 7631
cout << endl; 7632
scp.sort(); 7633
for(size_t i = 0; i < scp.size(); ++i) 7634
cout << scp[i] << ' '; 7635
cout << endl; 7636
} ///:~ 7637
Listado 6.30. C05/Sortable.cpp 7638
7639
6.3.4. 7640
//: C05:DelayedInstantiation.cpp 7641
// Member functions of class templates are not 7642
// instantiated until they're needed. 7643
7644
class X { 7645
public: 7646
void f() {} 7647
}; 7648
288
7649
class Y { 7650
public: 7651
void g() {} 7652
}; 7653
7654
template<typename T> class Z { 7655
T t; 7656
public: 7657
void a() { t.f(); } 7658
void b() { t.g(); } 7659
}; 7660
7661
int main() { 7662
Z<X> zx; 7663
zx.a(); // Doesn't create Z<X>::b() 7664
Z<Y> zy; 7665
zy.b(); // Doesn't create Z<Y>::a() 7666
} ///:~ 7667
Listado 6.31. C05/DelayedInstantiation.cpp 7668
7669
//: C05:Nobloat.h 7670
// Shares code for storing pointers in a Stack. 7671
#ifndef NOBLOAT_H 7672
289
#define NOBLOAT_H 7673
#include <cassert> 7674
#include <cstddef> 7675
#include <cstring> 7676
7677
// The primary template 7678
template<class T> class Stack { 7679
T* data; 7680
std::size_t count; 7681
std::size_t capacity; 7682
enum { INIT = 5 }; 7683
public: 7684
Stack() { 7685
count = 0; 7686
capacity = INIT; 7687
data = new T[INIT]; 7688
} 7689
void push(const T& t) { 7690
if(count == capacity) { 7691
// Grow array store 7692
std::size_t newCapacity = 2 * capacity; 7693
T* newData = new T[newCapacity]; 7694
for(size_t i = 0; i < count; ++i) 7695
newData[i] = data[i]; 7696
delete [] data; 7697
290
data = newData; 7698
capacity = newCapacity; 7699
} 7700
assert(count < capacity); 7701
data[count++] = t; 7702
} 7703
void pop() { 7704
assert(count > 0); 7705
--count; 7706
} 7707
T top() const { 7708
assert(count > 0); 7709
return data[count-1]; 7710
} 7711
std::size_t size() const { return count; } 7712
}; 7713
7714
// Full specialization for void* 7715
template<> class Stack<void *> { 7716
void** data; 7717
std::size_t count; 7718
std::size_t capacity; 7719
enum { INIT = 5 }; 7720
public: 7721
Stack() { 7722
291
count = 0; 7723
capacity = INIT; 7724
data = new void*[INIT]; 7725
} 7726
void push(void* const & t) { 7727
if(count == capacity) { 7728
std::size_t newCapacity = 2*capacity; 7729
void** newData = new void*[newCapacity]; 7730
std::memcpy(newData, data, count*sizeof(void*)); 7731
delete [] data; 7732
data = newData; 7733
capacity = newCapacity; 7734
} 7735
assert(count < capacity); 7736
data[count++] = t; 7737
} 7738
void pop() { 7739
assert(count > 0); 7740
--count; 7741
} 7742
void* top() const { 7743
assert(count > 0); 7744
return data[count-1]; 7745
} 7746
std::size_t size() const { return count; } 7747
292
}; 7748
7749
// Partial specialization for other pointer types 7750
template<class T> class Stack<T*> : private Stack<void *> { 7751
typedef Stack<void *> Base; 7752
public: 7753
void push(T* const & t) { Base::push(t); } 7754
void pop() {Base::pop();} 7755
T* top() const { return static_cast<T*>(Base::top()); } 7756
std::size_t size() { return Base::size(); } 7757
}; 7758
#endif // NOBLOAT_H ///:~ 7759
Listado 6.32. C05/Nobloat.h 7760
7761
//: C05:NobloatTest.cpp 7762
#include <iostream> 7763
#include <string> 7764
#include "Nobloat.h" 7765
using namespace std; 7766
7767
template<class StackType> 7768
void emptyTheStack(StackType& stk) { 7769
while(stk.size() > 0) { 7770
cout << stk.top() << endl; 7771
293
stk.pop(); 7772
} 7773
} 7774
7775
// An overload for emptyTheStack (not a specialization!) 7776
template<class T> 7777
void emptyTheStack(Stack<T*>& stk) { 7778
while(stk.size() > 0) { 7779
cout << *stk.top() << endl; 7780
stk.pop(); 7781
} 7782
} 7783
7784
int main() { 7785
Stack<int> s1; 7786
s1.push(1); 7787
s1.push(2); 7788
emptyTheStack(s1); 7789
Stack<int *> s2; 7790
int i = 3; 7791
int j = 4; 7792
s2.push(&i); 7793
s2.push(&j); 7794
emptyTheStack(s2); 7795
} ///:~ 7796
294
Listado 6.33. C05/NobloatTest.cpp 7797
7798
6.4. 7799
6.4.1. 7800
MyClass::f(); 7801
x.f(); 7802
p->f(); 7803
#include <iostream> 7804
#include <string> 7805
// ... 7806
std::string s("hello"); 7807
std::cout << s << std::endl; 7808
std::operator<<(std::operator<<(std::cout,s),std::endl); 7809
operator<<(std::cout, s); 7810
(f)(x, y); // ADL suppressed 7811
//: C05:Lookup.cpp 7812
// Only produces correct behavior with EDG, 7813
// and Metrowerks using a special option. 7814
#include <iostream> 7815
using std::cout; 7816
using std::endl; 7817
7818
void f(double) { cout << "f(double)" << endl; } 7819
295
7820
template<class T> class X { 7821
public: 7822
void g() { f(1); } 7823
}; 7824
7825
void f(int) { cout << "f(int)" << endl; } 7826
7827
int main() { 7828
X<int>().g(); 7829
} ///:~ 7830
Listado 6.34. C05/Lookup.cpp 7831
7832
f(double) 7833
//: C05:Lookup2.cpp {-bor}{-g++}{-dmc} 7834
// Microsoft: use option -Za (ANSI mode) 7835
#include <algorithm> 7836
#include <iostream> 7837
#include <typeinfo> 7838
using std::cout; 7839
using std::endl; 7840
7841
void g() { cout << "global g()" << endl; } 7842
296
7843
template<class T> class Y { 7844
public: 7845
void g() { 7846
cout << "Y<" << typeid(T).name() << ">::g()" << endl; 7847
} 7848
void h() { 7849
cout << "Y<" << typeid(T).name() << ">::h()" << endl; 7850
} 7851
typedef int E; 7852
}; 7853
7854
typedef double E; 7855
7856
template<class T> void swap(T& t1, T& t2) { 7857
cout << "global swap" << endl; 7858
T temp = t1; 7859
t1 = t2; 7860
t2 = temp; 7861
} 7862
7863
template<class T> class X : public Y<T> { 7864
public: 7865
E f() { 7866
g(); 7867
297
this->h(); 7868
T t1 = T(), t2 = T(1); 7869
cout << t1 << endl; 7870
swap(t1, t2); 7871
std::swap(t1, t2); 7872
cout << typeid(E).name() << endl; 7873
return E(t2); 7874
} 7875
}; 7876
7877
int main() { 7878
X<int> x; 7879
cout << x.f() << endl; 7880
} ///:~ 7881
Listado 6.35. C05/Lookup2.cpp 7882
7883
global g() 7884 Y<int>::h() 7885
0 7886 global swap 7887
double 7888 1 7889
6.4.2. 7890
//: C05:FriendScope.cpp 7891
#include <iostream> 7892
using namespace std; 7893
298
7894
class Friendly { 7895
int i; 7896
public: 7897
Friendly(int theInt) { i = theInt; } 7898
friend void f(const Friendly&); // Needs global def. 7899
void g() { f(*this); } 7900
}; 7901
7902
void h() { 7903
f(Friendly(1)); // Uses ADL 7904
} 7905
7906
void f(const Friendly& fo) { // Definition of friend 7907
cout << fo.i << endl; 7908
} 7909
7910
int main() { 7911
h(); // Prints 1 7912
Friendly(2).g(); // Prints 2 7913
} ///:~ 7914
Listado 6.36. C05/FriendScope.cpp 7915
7916
//: C05:FriendScope2.cpp 7917
299
#include <iostream> 7918
using namespace std; 7919
7920
// Necessary forward declarations: 7921
template<class T> class Friendly; 7922
template<class T> void f(const Friendly<T>&); 7923
7924
template<class T> class Friendly { 7925
T t; 7926
public: 7927
Friendly(const T& theT) : t(theT) {} 7928
friend void f<>(const Friendly<T>&); 7929
void g() { f(*this); } 7930
}; 7931
7932
void h() { 7933
f(Friendly<int>(1)); 7934
} 7935
7936
template<class T> void f(const Friendly<T>& fo) { 7937
cout << fo.t << endl; 7938
} 7939
7940
int main() { 7941
h(); 7942
300
Friendly<int>(2).g(); 7943
} ///:~ 7944
Listado 6.37. C05/FriendScope2.cpp 7945
7946
//: C05:FriendScope3.cpp {-bor} 7947
// Microsoft: use the -Za (ANSI-compliant) option 7948
#include <iostream> 7949
using namespace std; 7950
7951
template<class T> class Friendly { 7952
T t; 7953
public: 7954
Friendly(const T& theT) : t(theT) {} 7955
friend void f(const Friendly<T>& fo) { 7956
cout << fo.t << endl; 7957
} 7958
void g() { f(*this); } 7959
}; 7960
7961
void h() { 7962
f(Friendly<int>(1)); 7963
} 7964
7965
int main() { 7966
301
h(); 7967
Friendly<int>(2).g(); 7968
} ///:~ 7969
Listado 6.38. C05/FriendScope3.cpp 7970
7971
template<class T> class Box { 7972
T t; 7973
public: 7974
Box(const T& theT) : t(theT) {} 7975
}; 7976
//: C05:Box1.cpp 7977
// Defines template operators. 7978
#include <iostream> 7979
using namespace std; 7980
7981
// Forward declarations 7982
template<class T> class Box; 7983
7984
template<class T> 7985
Box<T> operator+(const Box<T>&, const Box<T>&); 7986
7987
template<class T> 7988
ostream& operator<<(ostream&, const Box<T>&); 7989
302
7990
template<class T> class Box { 7991
T t; 7992
public: 7993
Box(const T& theT) : t(theT) {} 7994
friend Box operator+<>(const Box<T>&, const Box<T>&); 7995
friend ostream& operator<< <>(ostream&, const Box<T>&); 7996
}; 7997
7998
template<class T> 7999
Box<T> operator+(const Box<T>& b1, const Box<T>& b2) { 8000
return Box<T>(b1.t + b2.t); 8001
} 8002
8003
template<class T> 8004
ostream& operator<<(ostream& os, const Box<T>& b) { 8005
return os << '[' << b.t << ']'; 8006
} 8007
8008
int main() { 8009
Box<int> b1(1), b2(2); 8010
cout << b1 + b2 << endl; // [3] 8011
// cout << b1 + 2 << endl; // No implicit conversions! 8012
} ///:~ 8013
303
Listado 6.39. C05/Box1.cpp 8014
8015
//: C05:Box2.cpp 8016
// Defines non-template operators. 8017
#include <iostream> 8018
using namespace std; 8019
8020
template<class T> class Box { 8021
T t; 8022
public: 8023
Box(const T& theT) : t(theT) {} 8024
friend Box<T> operator+(const Box<T>& b1, 8025
const Box<T>& b2) { 8026
return Box<T>(b1.t + b2.t); 8027
} 8028
friend ostream& 8029
operator<<(ostream& os, const Box<T>& b) { 8030
return os << '[' << b.t << ']'; 8031
} 8032
}; 8033
8034
int main() { 8035
Box<int> b1(1), b2(2); 8036
cout << b1 + b2 << endl; // [3] 8037
cout << b1 + 2 << endl; // [3] 8038
304
} ///:~ 8039
Listado 6.40. C05/Box2.cpp 8040
8041
// Inside Friendly: 8042
friend void f<>(const Friendly<double>&); 8043
// Inside Friendly: 8044
friend void g(int); // g(int) befriends all Friendlys 8045
template<class T> class Friendly { 8046
template<class U> friend void f<>(const Friendly<U>&); 8047
6.5. 8048
6.5.1. 8049
template<class T> class numeric_limits { 8050
public: 8051
static const bool is_specialized = false; 8052
static T min() throw(); 8053
static T max() throw(); 8054
static const int digits = 0; 8055
static const int digits10 = 0; 8056
static const bool is_signed = false; 8057
static const bool is_integer = false; 8058
static const bool is_exact = false; 8059
static const int radix = 0; 8060
305
static T epsilon() throw(); 8061
static T round_error() throw(); 8062
static const int min_exponent = 0; 8063
static const int min_exponent10 = 0; 8064
static const int max_exponent = 0; 8065
static const int max_exponent10 = 0; 8066
static const bool has_infinity = false; 8067
static const bool has_quiet_NaN = false; 8068
static const bool has_signaling_NaN = false; 8069
static const float_denorm_style has_denorm = 8070
denorm_absent; 8071
static const bool has_denorm_loss = false; 8072
static T infinity() throw(); 8073
static T quiet_NaN() throw(); 8074
static T signaling_NaN() throw(); 8075
static T denorm_min() throw(); 8076
static const bool is_iec559 = false; 8077
static const bool is_bounded = false; 8078
static const bool is_modulo = false; 8079
static const bool traps = false; 8080
static const bool tinyness_before = false; 8081
static const float_round_style round_style = 8082
round_toward_zero; 8083
}; 8084
template<class charT, 8085
306
class traits = char_traits<charT>, 8086
class allocator = allocator<charT> > 8087
class basic_string; 8088
template<> struct char_traits<char> { 8089
typedef char char_type; 8090
typedef int int_type; 8091
typedef streamoff off_type; 8092
typedef streampos pos_type; 8093
typedef mbstate_t state_type; 8094
static void assign(char_type& c1, const char_type& c2); 8095
static bool eq(const char_type& c1, const char_type& c2); 8096
static bool lt(const char_type& c1, const char_type& c2); 8097
static int compare(const char_type* s1, 8098
const char_type* s2, size_t n); 8099
static size_t length(const char_type* s); 8100
static const char_type* find(const char_type* s, 8101
size_t n, 8102
const char_type& a); 8103
static char_type* move(char_type* s1, 8104
const char_type* s2, size_t n); 8105
static char_type* copy(char_type* s1, 8106
const char_type* s2, size_t n); 8107
static char_type* assign(char_type* s, size_t n, 8108
char_type a); 8109
static int_type not_eof(const int_type& c); 8110
307
static char_type to_char_type(const int_type& c); 8111
static int_type to_int_type(const char_type& c); 8112
static bool eq_int_type(const int_type& c1, 8113
const int_type& c2); 8114
static int_type eof(); 8115
}; 8116
std::string s; 8117
std::basic_string<char, std::char_traits<char>, 8118
std::allocator<char> > s; 8119
//: C05:BearCorner.h 8120
#ifndef BEARCORNER_H 8121
#define BEARCORNER_H 8122
#include <iostream> 8123
using std::ostream; 8124
8125
// Item classes (traits of guests): 8126
class Milk { 8127
public: 8128
friend ostream& operator<<(ostream& os, const Milk&) { 8129
return os << "Milk"; 8130
} 8131
}; 8132
8133
class CondensedMilk { 8134
308
public: 8135
friend ostream& 8136
operator<<(ostream& os, const CondensedMilk &) { 8137
return os << "Condensed Milk"; 8138
} 8139
}; 8140
8141
class Honey { 8142
public: 8143
friend ostream& operator<<(ostream& os, const Honey&) { 8144
return os << "Honey"; 8145
} 8146
}; 8147
8148
class Cookies { 8149
public: 8150
friend ostream& operator<<(ostream& os, const Cookies&) { 8151
return os << "Cookies"; 8152
} 8153
}; 8154
8155
// Guest classes: 8156
class Bear { 8157
public: 8158
friend ostream& operator<<(ostream& os, const Bear&) { 8159
309
return os << "Theodore"; 8160
} 8161
}; 8162
8163
class Boy { 8164
public: 8165
friend ostream& operator<<(ostream& os, const Boy&) { 8166
return os << "Patrick"; 8167
} 8168
}; 8169
8170
// Primary traits template (empty-could hold common types) 8171
template<class Guest> class GuestTraits; 8172
8173
// Traits specializations for Guest types 8174
template<> class GuestTraits<Bear> { 8175
public: 8176
typedef CondensedMilk beverage_type; 8177
typedef Honey snack_type; 8178
}; 8179
8180
template<> class GuestTraits<Boy> { 8181
public: 8182
typedef Milk beverage_type; 8183
typedef Cookies snack_type; 8184
310
}; 8185
#endif // BEARCORNER_H ///:~ 8186
Listado 6.41. C05/BearCorner.h 8187
8188
6.5.2. 8189
template<> struct char_traits<wchar_t> { 8190
typedef wchar_t char_type; 8191
typedef wint_t int_type; 8192
typedef streamoff off_type; 8193
typedef wstreampos pos_type; 8194
typedef mbstate_t state_type; 8195
static void assign(char_type& c1, const char_type& c2); 8196
static bool eq(const char_type& c1, const char_type& c2); 8197
static bool lt(const char_type& c1, const char_type& c2); 8198
static int compare(const char_type* s1, 8199
const char_type* s2, size_t n); 8200
static size_t length(const char_type* s); 8201
static const char_type* find(const char_type* s, 8202
size_t n, 8203
const char_type& a); 8204
static char_type* move(char_type* s1, 8205
const char_type* s2, size_t n); 8206
static char_type* copy(char_type* s1, 8207
311
const char_type* s2, size_t n); 8208
static char_type* assign(char_type* s, size_t n, 8209
char_type a); 8210
static int_type not_eof(const int_type& c); 8211
static char_type to_char_type(const int_type& c); 8212
static int_type to_int_type(const char_type& c); 8213
static bool eq_int_type(const int_type& c1, 8214
const int_type& c2); 8215
static int_type eof(); 8216
}; 8217
//: C05:BearCorner2.cpp 8218
// Illustrates policy classes. 8219
#include <iostream> 8220
#include "BearCorner.h" 8221
using namespace std; 8222
8223
// Policy classes (require a static doAction() function): 8224
class Feed { 8225
public: 8226
static const char* doAction() { return "Feeding"; } 8227
}; 8228
8229
class Stuff { 8230
public: 8231
312
static const char* doAction() { return "Stuffing"; } 8232
}; 8233
8234
// The Guest template (uses a policy and a traits class) 8235
template<class Guest, class Action, 8236
class traits = GuestTraits<Guest> > 8237
class BearCorner { 8238
Guest theGuest; 8239
typedef typename traits::beverage_type beverage_type; 8240
typedef typename traits::snack_type snack_type; 8241
beverage_type bev; 8242
snack_type snack; 8243
public: 8244
BearCorner(const Guest& g) : theGuest(g) {} 8245
void entertain() { 8246
cout << Action::doAction() << " " << theGuest 8247
<< " with " << bev 8248
<< " and " << snack << endl; 8249
} 8250
}; 8251
8252
int main() { 8253
Boy cr; 8254
BearCorner<Boy, Feed> pc1(cr); 8255
pc1.entertain(); 8256
313
Bear pb; 8257
BearCorner<Bear, Stuff> pc2(pb); 8258
pc2.entertain(); 8259
} ///:~ 8260
Listado 6.42. C05/BearCorner2.cpp 8261
8262
6.5.3. 8263
//: C05:CountedClass.cpp 8264
// Object counting via static members. 8265
#include <iostream> 8266
using namespace std; 8267
8268
class CountedClass { 8269
static int count; 8270
public: 8271
CountedClass() { ++count; } 8272
CountedClass(const CountedClass&) { ++count; } 8273
~CountedClass() { --count; } 8274
static int getCount() { return count; } 8275
}; 8276
8277
int CountedClass::count = 0; 8278
8279
314
int main() { 8280
CountedClass a; 8281
cout << CountedClass::getCount() << endl; // 1 8282
CountedClass b; 8283
cout << CountedClass::getCount() << endl; // 2 8284
{ // An arbitrary scope: 8285
CountedClass c(b); 8286
cout << CountedClass::getCount() << endl; // 3 8287
a = c; 8288
cout << CountedClass::getCount() << endl; // 3 8289
} 8290
cout << CountedClass::getCount() << endl; // 2 8291
} ///:~ 8292
Listado 6.43. C05/CountedClass.cpp 8293
8294
//: C05:CountedClass2.cpp 8295
// Erroneous attempt to count objects. 8296
#include <iostream> 8297
using namespace std; 8298
8299
class Counted { 8300
static int count; 8301
public: 8302
Counted() { ++count; } 8303
315
Counted(const Counted&) { ++count; } 8304
~Counted() { --count; } 8305
static int getCount() { return count; } 8306
}; 8307
8308
int Counted::count = 0; 8309
8310
class CountedClass : public Counted {}; 8311
class CountedClass2 : public Counted {}; 8312
8313
int main() { 8314
CountedClass a; 8315
cout << CountedClass::getCount() << endl; // 1 8316
CountedClass b; 8317
cout << CountedClass::getCount() << endl; // 2 8318
CountedClass2 c; 8319
cout << CountedClass2::getCount() << endl; // 3 (Error) 8320
} ///:~ 8321
Listado 6.44. C05/CountedClass2.cpp 8322
8323
//: C05:CountedClass3.cpp 8324
#include <iostream> 8325
using namespace std; 8326
8327
316
template<class T> class Counted { 8328
static int count; 8329
public: 8330
Counted() { ++count; } 8331
Counted(const Counted<T>&) { ++count; } 8332
~Counted() { --count; } 8333
static int getCount() { return count; } 8334
}; 8335
8336
template<class T> int Counted<T>::count = 0; 8337
8338
// Curious class definitions 8339
class CountedClass : public Counted<CountedClass> {}; 8340
class CountedClass2 : public Counted<CountedClass2> {}; 8341
8342
int main() { 8343
CountedClass a; 8344
cout << CountedClass::getCount() << endl; // 1 8345
CountedClass b; 8346
cout << CountedClass::getCount() << endl; // 2 8347
CountedClass2 c; 8348
cout << CountedClass2::getCount() << endl; // 1 (!) 8349
} ///:~ 8350
Listado 6.45. C05/CountedClass3.cpp 8351
317
8352
6.6. 8353
//: C05:Factorial.cpp 8354
// Compile-time computation using templates. 8355
#include <iostream> 8356
using namespace std; 8357
8358
template<int n> struct Factorial { 8359
enum { val = Factorial<n-1>::val * n }; 8360
}; 8361
8362
template<> struct Factorial<0> { 8363
enum { val = 1 }; 8364
}; 8365
8366
int main() { 8367
cout << Factorial<12>::val << endl; // 479001600 8368
} ///:~ 8369
Listado 6.46. C05/Factorial.cpp 8370
8371
double nums[Factorial<5>::val]; 8372
assert(sizeof nums == sizeof(double)*120); 8373
318
6.6.1. 8374
//: C05:Fibonacci.cpp 8375
#include <iostream> 8376
using namespace std; 8377
8378
template<int n> struct Fib { 8379
enum { val = Fib<n-1>::val + Fib<n-2>::val }; 8380
}; 8381
8382
template<> struct Fib<1> { enum { val = 1 }; }; 8383
8384
template<> struct Fib<0> { enum { val = 0 }; }; 8385
8386
int main() { 8387
cout << Fib<5>::val << endl; // 6 8388
cout << Fib<20>::val << endl; // 6765 8389
} ///:~ 8390
Listado 6.47. C05/Fibonacci.cpp 8391
8392
int val = 1; 8393
while(p--) 8394
val *= n; --> 8395
int power(int n, int p) { 8396
return (p == 0) ? 1 : n*power(n, p - 1); 8397
319
} 8398
//: C05:Power.cpp 8399
#include <iostream> 8400
using namespace std; 8401
8402
template<int N, int P> struct Power { 8403
enum { val = N * Power<N, P-1>::val }; 8404
}; 8405
8406
template<int N> struct Power<N, 0> { 8407
enum { val = 1 }; 8408
}; 8409
8410
int main() { 8411
cout << Power<2, 5>::val << endl; // 32 8412
} ///:~ 8413
Listado 6.48. C05/Power.cpp 8414
8415
//: C05:Accumulate.cpp 8416
// Passes a "function" as a parameter at compile time. 8417
#include <iostream> 8418
using namespace std; 8419
8420
320
// Accumulates the results of F(0)..F(n) 8421
template<int n, template<int> class F> struct Accumulate { 8422
enum { val = Accumulate<n-1, F>::val + F<n>::val }; 8423
}; 8424
8425
// The stopping criterion (returns the value F(0)) 8426
template<template<int> class F> struct Accumulate<0, F> { 8427
enum { val = F<0>::val }; 8428
}; 8429
8430
// Various "functions": 8431
template<int n> struct Identity { 8432
enum { val = n }; 8433
}; 8434
8435
template<int n> struct Square { 8436
enum { val = n*n }; 8437
}; 8438
8439
template<int n> struct Cube { 8440
enum { val = n*n*n }; 8441
}; 8442
8443
int main() { 8444
cout << Accumulate<4, Identity>::val << endl; // 10 8445
321
cout << Accumulate<4, Square>::val << endl; // 30 8446
cout << Accumulate<4, Cube>::val << endl; // 100 8447
} ///:~ 8448
Listado 6.49. C05/Accumulate.cpp 8449
8450
void mult(int a[ROWS][COLS], int x[COLS], int y[COLS]) { 8451
for(int i = 0; i < ROWS; ++i) { 8452
y[i] = 0; 8453
for(int j = 0; j < COLS; ++j) 8454
y[i] += a[i][j]*x[j]; 8455
} 8456
} 8457
void mult(int a[ROWS][COLS], int x[COLS], int y[COLS]) { 8458
for(int i = 0; i < ROWS; ++i) { 8459
y[i] = 0; 8460
for(int j = 0; j < COLS; j += 2) 8461
y[i] += a[i][j]*x[j] + a[i][j+1]*x[j+1]; 8462
} 8463
} 8464
//: C05:Unroll.cpp 8465
// Unrolls an implicit loop via inlining. 8466
#include <iostream> 8467
using namespace std; 8468
322
8469
template<int n> inline int power(int m) { 8470
return power<n-1>(m) * m; 8471
} 8472
8473
template<> inline int power<1>(int m) { 8474
return m; 8475
} 8476
8477
template<> inline int power<0>(int m) { 8478
return 1; 8479
} 8480
8481
int main() { 8482
int m = 4; 8483
cout << power<3>(m) << endl; 8484
} ///:~ 8485
Listado 6.50. C05/Unroll.cpp 8486
8487
//: C05:Max.cpp 8488
#include <iostream> 8489
using namespace std; 8490
8491
template<int n1, int n2> struct Max { 8492
323
enum { val = n1 > n2 ? n1 : n2 }; 8493
}; 8494
8495
int main() { 8496
cout << Max<10, 20>::val << endl; // 20 8497
} ///:~ 8498
Listado 6.51. C05/Max.cpp 8499
8500
//: C05:Conditionals.cpp 8501
// Uses compile-time conditions to choose code. 8502
#include <iostream> 8503
using namespace std; 8504
8505
template<bool cond> struct Select {}; 8506
8507
template<> class Select<true> { 8508
static void statement1() { 8509
cout << "This is statement1 executing\n"; 8510
} 8511
public: 8512
static void f() { statement1(); } 8513
}; 8514
8515
template<> class Select<false> { 8516
324
static void statement2() { 8517
cout << "This is statement2 executing\n"; 8518
} 8519
public: 8520
static void f() { statement2(); } 8521
}; 8522
8523
template<bool cond> void execute() { 8524
Select<cond>::f(); 8525
} 8526
8527
int main() { 8528
execute<sizeof(int) == 4>(); 8529
} ///:~ 8530
Listado 6.52. C05/Conditionals.cpp 8531
8532
if(cond) 8533
statement1(); 8534
else 8535
statement2(); 8536
//: C05:StaticAssert1.cpp {-xo} 8537
// A simple, compile-time assertion facility 8538
325
8539
#define STATIC_ASSERT(x) \ 8540
do { typedef int a[(x) ? 1 : -1]; } while(0) 8541
8542
int main() { 8543
STATIC_ASSERT(sizeof(int) <= sizeof(long)); // Passes 8544
STATIC_ASSERT(sizeof(double) <= sizeof(int)); // Fails 8545
} ///:~ 8546
Listado 6.53. C05/StaticAssert1.cpp 8547
8548
//: C05:StaticAssert2.cpp {-g++} 8549
#include <iostream> 8550
using namespace std; 8551
8552
// A template and a specialization 8553
template<bool> struct StaticCheck { 8554
StaticCheck(...); 8555
}; 8556
8557
template<> struct StaticCheck<false> {}; 8558
8559
// The macro (generates a local class) 8560
#define STATIC_CHECK(expr, msg) { \ 8561
class Error_##msg {}; \ 8562
326
sizeof((StaticCheck<expr>(Error_##msg()))); \ 8563
} 8564
8565
// Detects narrowing conversions 8566
template<class To, class From> To safe_cast(From from) { 8567
STATIC_CHECK(sizeof(From) <= sizeof(To), 8568
NarrowingConversion); 8569
return reinterpret_cast<To>(from); 8570
} 8571
8572
int main() { 8573
void* p = 0; 8574
int i = safe_cast<int>(p); 8575
cout << "int cast okay" << endl; 8576
//! char c = safe_cast<char>(p); 8577
} ///:~ 8578
Listado 6.54. C05/StaticAssert2.cpp 8579
8580
int i = safe_cast<int>(p); 8581
{ \ 8582
class Error_NarrowingConversion {}; \ 8583
sizeof(StaticCheck<sizeof(void*) <= sizeof(int)> \ 8584
(Error_NarrowingConversion())); \ 8585
} 8586
327
char c = safe_cast<char>(p); 8587
{ \ 8588
class Error_NarrowingConversion {}; \ 8589
sizeof(StaticCheck<sizeof(void*) <= sizeof(char)> \ 8590
(Error_NarrowingConversion())); \ 8591
} 8592
sizeof(StaticCheck<false>(Error_NarrowingConversion())); 8593
Cannot cast from 'Error_NarrowingConversion' to 'StaticCheck<0>' in 8594 function 8595
char safe_cast<char,void *>(void *) 8596
6.6.2. 8597
D = A + B + C; 8598
//: C05:MyVector.cpp 8599
// Optimizes away temporaries via templates. 8600
#include <cstddef> 8601
#include <cstdlib> 8602
#include <ctime> 8603
#include <iostream> 8604
using namespace std; 8605
8606
// A proxy class for sums of vectors 8607
template<class, size_t> class MyVectorSum; 8608
8609
328
template<class T, size_t N> class MyVector { 8610
T data[N]; 8611
public: 8612
MyVector<T,N>& operator=(const MyVector<T,N>& right) { 8613
for(size_t i = 0; i < N; ++i) 8614
data[i] = right.data[i]; 8615
return *this; 8616
} 8617
MyVector<T,N>& operator=(const MyVectorSum<T,N>& right); 8618
const T& operator[](size_t i) const { return data[i]; } 8619
T& operator[](size_t i) { return data[i]; } 8620
}; 8621
8622
// Proxy class hold references; uses lazy addition 8623
template<class T, size_t N> class MyVectorSum { 8624
const MyVector<T,N>& left; 8625
const MyVector<T,N>& right; 8626
public: 8627
MyVectorSum(const MyVector<T,N>& lhs, 8628
const MyVector<T,N>& rhs) 8629
: left(lhs), right(rhs) {} 8630
T operator[](size_t i) const { 8631
return left[i] + right[i]; 8632
} 8633
}; 8634
329
8635
// Operator to support v3 = v1 + v2 8636
template<class T, size_t N> MyVector<T,N>& 8637
MyVector<T,N>::operator=(const MyVectorSum<T,N>& right) { 8638
for(size_t i = 0; i < N; ++i) 8639
data[i] = right[i]; 8640
return *this; 8641
} 8642
8643
// operator+ just stores references 8644
template<class T, size_t N> inline MyVectorSum<T,N> 8645
operator+(const MyVector<T,N>& left, 8646
const MyVector<T,N>& right) { 8647
return MyVectorSum<T,N>(left, right); 8648
} 8649
8650
// Convenience functions for the test program below 8651
template<class T, size_t N> void init(MyVector<T,N>& v) { 8652
for(size_t i = 0; i < N; ++i) 8653
v[i] = rand() % 100; 8654
} 8655
8656
template<class T, size_t N> void print(MyVector<T,N>& v) { 8657
for(size_t i = 0; i < N; ++i) 8658
cout << v[i] << ' '; 8659
330
cout << endl; 8660
} 8661
8662
int main() { 8663
srand(time(0)); 8664
MyVector<int, 5> v1; 8665
init(v1); 8666
print(v1); 8667
MyVector<int, 5> v2; 8668
init(v2); 8669
print(v2); 8670
MyVector<int, 5> v3; 8671
v3 = v1 + v2; 8672
print(v3); 8673
MyVector<int, 5> v4; 8674
// Not yet supported: 8675
//! v4 = v1 + v2 + v3; 8676
} ///:~ 8677
Listado 6.55. C05/MyVector.cpp 8678
8679
v1 = v2 + v3; // Add two vectors 8680
v3.operator=<int,5>(MyVectorSum<int,5>(v2, v3)); 8681
v4 = v1 + v2 + v3; 8682
331
(v1 + v2) + v3; 8683
//: C05:MyVector2.cpp 8684
// Handles sums of any length with expression templates. 8685
#include <cstddef> 8686
#include <cstdlib> 8687
#include <ctime> 8688
#include <iostream> 8689
using namespace std; 8690
8691
// A proxy class for sums of vectors 8692
template<class, size_t, class, class> class MyVectorSum; 8693
8694
template<class T, size_t N> class MyVector { 8695
T data[N]; 8696
public: 8697
MyVector<T,N>& operator=(const MyVector<T,N>& right) { 8698
for(size_t i = 0; i < N; ++i) 8699
data[i] = right.data[i]; 8700
return *this; 8701
} 8702
template<class Left, class Right> MyVector<T,N>& 8703
operator=(const MyVectorSum<T,N,Left,Right>& right); 8704
const T& operator[](size_t i) const { 8705
return data[i]; 8706
332
} 8707
T& operator[](size_t i) { 8708
return data[i]; 8709
} 8710
}; 8711
8712
// Allows mixing MyVector and MyVectorSum 8713
template<class T, size_t N, class Left, class Right> 8714
class MyVectorSum { 8715
const Left& left; 8716
const Right& right; 8717
public: 8718
MyVectorSum(const Left& lhs, const Right& rhs) 8719
: left(lhs), right(rhs) {} 8720
T operator[](size_t i) const { 8721
return left[i] + right[i]; 8722
} 8723
}; 8724
8725
template<class T, size_t N> 8726
template<class Left, class Right> 8727
MyVector<T,N>& 8728
MyVector<T,N>:: 8729
operator=(const MyVectorSum<T,N,Left,Right>& right) { 8730
for(size_t i = 0; i < N; ++i) 8731
333
data[i] = right[i]; 8732
return *this; 8733
} 8734
// operator+ just stores references 8735
template<class T, size_t N> 8736
inline MyVectorSum<T,N,MyVector<T,N>,MyVector<T,N> > 8737
operator+(const MyVector<T,N>& left, 8738
const MyVector<T,N>& right) { 8739
return MyVectorSum<T,N,MyVector<T,N>,MyVector<T,N> > 8740
(left,right); 8741
} 8742
8743
template<class T, size_t N, class Left, class Right> 8744
inline MyVectorSum<T, N, MyVectorSum<T,N,Left,Right>, 8745
MyVector<T,N> > 8746
operator+(const MyVectorSum<T,N,Left,Right>& left, 8747
const MyVector<T,N>& right) { 8748
return MyVectorSum<T,N,MyVectorSum<T,N,Left,Right>, 8749
MyVector<T,N> > 8750
(left, right); 8751
} 8752
// Convenience functions for the test program below 8753
template<class T, size_t N> void init(MyVector<T,N>& v) { 8754
for(size_t i = 0; i < N; ++i) 8755
v[i] = rand() % 100; 8756
334
} 8757
8758
template<class T, size_t N> void print(MyVector<T,N>& v) { 8759
for(size_t i = 0; i < N; ++i) 8760
cout << v[i] << ' '; 8761
cout << endl; 8762
} 8763
8764
int main() { 8765
srand(time(0)); 8766
MyVector<int, 5> v1; 8767
init(v1); 8768
print(v1); 8769
MyVector<int, 5> v2; 8770
init(v2); 8771
print(v2); 8772
MyVector<int, 5> v3; 8773
v3 = v1 + v2; 8774
print(v3); 8775
// Now supported: 8776
MyVector<int, 5> v4; 8777
v4 = v1 + v2 + v3; 8778
print(v4); 8779
MyVector<int, 5> v5; 8780
v5 = v1 + v2 + v3 + v4; 8781
335
print(v5); 8782
} ///:~ 8783
Listado 6.56. C05/MyVector2.cpp 8784
8785
v4 = v1 + v2 + v3; 8786
v4.operator+(MVS(MVS(v1, v2), v3)); 8787
6.7. 8788
6.7.1. 8789
6.7.2. 8790
//: C05:OurMin.h 8791
#ifndef OURMIN_H 8792
#define OURMIN_H 8793
// The declaration of min() 8794
template<typename T> const T& min(const T&, const T&); 8795
#endif // OURMIN_H ///:~ 8796
Listado 6.57. C05/OurMin.h 8797
8798
//: C05:MinInstances.cpp {O} 8799
#include "OurMin.cpp" 8800
8801
336
// Explicit Instantiations for int and double 8802
template const int& min<int>(const int&, const int&); 8803
template const double& min<double>(const double&, 8804
const double&); 8805
///:~ 8806
Listado 6.58. C05/MinInstances.cpp 8807
8808
//: C05:OurMin.cpp {O} 8809
#ifndef OURMIN_CPP 8810
#define OURMIN_CPP 8811
#include "OurMin.h" 8812
8813
template<typename T> const T& min(const T& a, const T& b) { 8814
return (a < b) ? a : b; 8815
} 8816
#endif // OURMIN_CPP ///:~ 8817
Listado 6.59. C05/OurMin.cpp 8818
8819
1 8820 3.1 8821
6.7.3. 8822
//: C05:OurMin2.h 8823
// Declares min as an exported template 8824
337
// (Only works with EDG-based compilers) 8825
#ifndef OURMIN2_H 8826
#define OURMIN2_H 8827
export template<typename T> const T& min(const T&, 8828
const T&); 8829
#endif // OURMIN2_H ///:~ 8830
Listado 6.60. C05/OurMin2.h 8831
8832
// C05:OurMin2.cpp 8833
// The definition of the exported min template 8834
// (Only works with EDG-based compilers) 8835
#include "OurMin2.h" 8836
export 8837
template<typename T> const T& min(const T& a, const T& b) { 8838
return (a < b) ? a : b; 8839
} ///:~ 8840
Listado 6.61. C05/OurMin2.cpp 8841
8842
6.7.4. 8843
6.8. 8844
//: C05:Exercise4.cpp {-xo} 8845
338
class Noncomparable {}; 8846
8847
struct HardLogic { 8848
Noncomparable nc1, nc2; 8849
void compare() { 8850
return nc1 == nc2; // Compiler error 8851
} 8852
}; 8853
8854
template<class T> struct SoftLogic { 8855
Noncomparable nc1, nc2; 8856
void noOp() {} 8857
void compare() { 8858
nc1 == nc2; 8859
} 8860
}; 8861
8862
int main() { 8863
SoftLogic<Noncomparable> l; 8864
l.noOp(); 8865
} ///:~ 8866
Listado 6.62. C05/Exercise4.cpp 8867
8868
//: C05:Exercise7.cpp {-xo} 8869
339
class Buddy {}; 8870
8871
template<class T> class My { 8872
int i; 8873
public: 8874
void play(My<Buddy>& s) { 8875
s.i = 3; 8876
} 8877
}; 8878
8879
int main() { 8880
My<int> h; 8881
My<Buddy> me, bud; 8882
h.play(bud); 8883
me.play(bud); 8884
} ///:~ 8885
Listado 6.63. C05/Exercise7.cpp 8886
8887
//: C05:Exercise8.cpp {-xo} 8888
template<class T> double pythag(T a, T b, T c) { 8889
return (-b + sqrt(double(b*b - 4*a*c))) / 2*a; 8890
} 8891
8892
int main() { 8893
340
pythag(1, 2, 3); 8894
pythag(1.0, 2.0, 3.0); 8895
pythag(1, 2.0, 3.0); 8896
pythag<double>(1, 2.0, 3.0); 8897
} ///:~ 8898
Listado 6.64. C05/Exercise8.cpp 8899
8900
7: Algoritmos genéricos 8901
Tabla de contenidos 8902
7.1. Un primer vistazo 8903
7.2. Objetos-función 8904
7.3. Un catálogo de algoritmos STL 8905
7.4. Creando sus propios algoritmos tipo STL 8906
7.5. Resumen 8907
7.6. Ejercicios 8908
Los algoritmos son la base de la computación. Ser capaz de escribir un 8909
algoritmo que funcione con cualquier tipo de se secuencia hace que sus 8910
programas sean simples y seguros. La habilidad para adaptar algoritmos en 8911
tiempo de ejecución a revolucionado el desarrollo de software. 8912
El subconjunto de la Librería Estándar de C++ conocido como Standard 8913
Template Library (STL)[17] fue diseñado entorno a algoritmos genéricos —8914
código que procesa secuencias de cualquier tipo de valores de un modo 8915
seguro. El objetivo era usar algoritmos predefinidos para casi cualquier tarea, 8916
en lugar de codificar a mano cada vez que se necesitara procesar una 8917
colección de datos. Sin embargo, ese potencial requiere cierto aprendizaje. 8918
Para cuando llegue al final de este capítulo, debería ser capaz de decidir por 8919
sí mismo si los algoritmos le resultan útiles o demasiado confusos de 8920
recordar. Si es como la mayoría de la gente, se resistirá al principio pero 8921
entonces tenderá a usarlos más y más con el tiempo. 8922
7.1. Un primer vistazo 8923
341
Entre otras cosas, los algoritmos genéricos de la librería estándar 8924
proporcionan un vocabulario con el que desribir soluciones. Una vez que los 8925
algoritmos le sean familiares, tendrá un nuevo conjunto de palabras con el 8926
que discutir que está haciendo, y esas palabras son de un nivel mayor que 8927
las que tenía antes. No necesitará decir «Este bucle recorre y asigna de aquí 8928
a ahí... oh, ya veo, ¡está copiando!» En su lugar dirá simplemente copy(). 8929
Esto es lo que hemos estado haciendo desde el principio de la programación 8930
de computadores —creando abstracciones de alto nivel para expresar lo que 8931
está haciendo y perder menos tiempo diciendo cómo hacerlo. El «cómo» se 8932
ha resuelto una vez y para todo y está oculto en el código del algoritmo, listo 8933
para ser reutilizado cuando se necesite. 8934
Vea aquí un ejemplo de cómo utilizar el algoritmo copy: 8935
//: C06:CopyInts.cpp 8936
// Copies ints without an explicit loop. 8937
#include <algorithm> 8938
#include <cassert> 8939
#include <cstddef> // For size_t 8940
using namespace std; 8941
8942
int main() { 8943
int a[] = { 10, 20, 30 }; 8944
const size_t SIZE = sizeof a / sizeof a[0]; 8945
int b[SIZE]; 8946
copy(a, a + SIZE, b); 8947
for(size_t i = 0; i < SIZE; ++i) 8948
assert(a[i] == b[i]); 8949
} ///:~ 8950
342
Listado 7.1. C06/CopyInts.cpp 8951
8952
Los dos primeros parámetros de copy representan el rango de la secuencia 8953
de entrada —en este caso del array a. Los rangos se especifican con un par 8954
de punteros. El primero apunta al primer elemento de la secuencia, y el 8955
segungo apunta una posición después del final del array (justo después del 8956
último elemento). Esto puede parecer extraño al principio, pero es una 8957
antigua expresión idiomática de C que resulta bastante práctica. Por ejemplo, 8958
la diferencia entre esos dos punteros devuelve el número de elementos de la 8959
secuencia. Más importante, en la implementación de copy(), el segundo 8960
puntero puede actual como un centinela para para la iteración a través de la 8961
secuencia. El tercer argumento hace referencia al comienzo de la secuencia 8962
de salida, que es el array b en el ejemplo. Se asume que el array b tiene 8963
suficiente espacio para recibir los elementos copiados. 8964
El algotirmo copy() no parece muy excitante if solo puediera procesar 8965
enteros. Puede copiar cualquier tipo de secuencia. El siguiente ejemplo copia 8966
objetos string. 8967
//: C06:CopyStrings.cpp 8968
// Copies strings. 8969
#include <algorithm> 8970
#include <cassert> 8971
#include <cstddef> 8972
#include <string> 8973
using namespace std; 8974
8975
int main() { 8976
string a[] = {"read", "my", "lips"}; 8977
const size_t SIZE = sizeof a / sizeof a[0]; 8978
string b[SIZE]; 8979
343
copy(a, a + SIZE, b); 8980
assert(equal(a, a + SIZE, b)); 8981
} ///:~ 8982
Listado 7.2. C06/CopyStrings.cpp 8983
8984
Este ejmeplo presenta otro algoritmo, equal(), que devuelve cierto solo si 8985
cada elemento de la primera secuencia es igual (usando su operator==()) a 8986
su elemento correspondiente en la segunda secuencia. Este ejemplo recorre 8987
cada secuencia 2 veces, una para copiar, y otra para comparar, sin ningún 8988
bucle explícito. 8989
Los algoritmos genéricos consiguen esta flexibilidad porque son funciones 8990
parametrizadas (plantillas). Si piensa en la implementación de copy() verá 8991
que es algo como lo siguiente, que es «casi» correcto: 8992
template<typename T> 8993
void copy(T* begin, T* end, T* dest) { 8994
while (begin != end) 8995
*dest++ = *begin++; 8996
} 8997
Decimos «casi» porque copy() puede procesar secuencias delimitadas por 8998
cualquier cosa que actúe como un puntero, tal como un iterador. De ese 8999
modo, copy() se puede utilizar para duplicar un vector, como en el siguiente 9000
ejemplo. 9001
//: C06:CopyVector.cpp 9002
// Copies the contents of a vector. 9003
#include <algorithm> 9004
344
#include <cassert> 9005
#include <cstddef> 9006
#include <vector> 9007
using namespace std; 9008
9009
int main() { 9010
int a[] = { 10, 20, 30 }; 9011
const size_t SIZE = sizeof a / sizeof a[0]; 9012
vector<int> v1(a, a + SIZE); 9013
vector<int> v2(SIZE); 9014
copy(v1.begin(), v1.end(), v2.begin()); 9015
assert(equal(v1.begin(), v1.end(), v2.begin())); 9016
} ///:~ 9017
Listado 7.3. C06/CopyVector.cpp 9018
9019
El primer vector, v1, es inicializado a partir de una secuencia de enteros en 9020
el array a. La definición del vector v2 usa un contructor diferente de vector 9021
que reserva sitio para SIZE elementos, inicializados a cero (el valor por 9022
defecto para enteros). 9023
Igual que con el ejemplo anterior con el array, es importante que v2 tenga 9024
suficiente espacio para recibir una copia de los contenidos de v1. Por 9025
conveniencia, una función de librería especial, back_inserter(), retorna un 9026
tipo especial de iterador que inserta elementos en lugar de sobre-escribirlos, 9027
de modo que la memoria del contenedor se expande conforme se necesita. 9028
El siguiente ejemplo usa back_inserter(), y por eso no hay que establecer 9029
el tamaño del vector de salida, v2, antes de tiempo. 9030
//: C06:InsertVector.cpp 9031
345
// Appends the contents of a vector to another. 9032
#include <algorithm> 9033
#include <cassert> 9034
#include <cstddef> 9035
#include <iterator> 9036
#include <vector> 9037
using namespace std; 9038
9039
int main() { 9040
int a[] = { 10, 20, 30 }; 9041
const size_t SIZE = sizeof a / sizeof a[0]; 9042
vector<int> v1(a, a + SIZE); 9043
vector<int> v2; // v2 is empty here 9044
copy(v1.begin(), v1.end(), back_inserter(v2)); 9045
assert(equal(v1.begin(), v1.end(), v2.begin())); 9046
} ///:~ 9047
Listado 7.4. C06/InsertVector.cpp 9048
9049
La función back_inserter() está definida en el fichero de cabecera 9050
<iterator>. Explicaremos los iteradores de inserción en profundidad en el 9051
próximo capítulo. 9052
Dado que los iteradores son idénticos a punteros en todos los sentidos 9053
importantes, puede escribir los algoritmos de la librería estándar de modo 9054
que los argumentos puedan ser tanto punteros como iteradores. Por esta 9055
razón, la implementación de copy() se parece más al siguiente código: 9056
template<typename Iterator> 9057
346
void copy(Iterator begin, Iterator end, Iterator dest) { 9058
while (begin != end) 9059
*begin++ = *dest++; 9060
} 9061
Para cualquier tipo de argumento que use en la llamada, copy() asume 9062
que implementa adecuadamente la indirección y los operadores de 9063
incremento. Si no lo hace, obtendrás un error de compilación. 9064
7.1.1. Predicados 9065
A veces, podría querer copiar solo un subconjunto bien definido de una 9066
secuencia a otra; solo aquellos elementos que satisfagan una condición 9067
particular. Para conseguir esta flexibilidad, muchos algoritmos tienen una 9068
forma alternativa de llamada que permite proporcionar un predicado, que es 9069
simplemente una función que retorna un valor booleano basado en algún 9070
criterio. Suponga por ejemplo, que solo quiere extraer de una secuencia de 9071
enteros, aquellos que son menores o iguales de 15. Una versión de copy() 9072
llamada remove_copy_if() puede hacer el trabajo, tal que así: 9073
//: C06:CopyInts2.cpp 9074
// Ignores ints that satisfy a predicate. 9075
#include <algorithm> 9076
#include <cstddef> 9077
#include <iostream> 9078
using namespace std; 9079
9080
// You supply this predicate 9081
bool gt15(int x) { return 15 < x; } 9082
9083
347
int main() { 9084
int a[] = { 10, 20, 30 }; 9085
const size_t SIZE = sizeof a / sizeof a[0]; 9086
int b[SIZE]; 9087
int* endb = remove_copy_if(a, a+SIZE, b, gt15); 9088
int* beginb = b; 9089
while(beginb != endb) 9090
cout << *beginb++ << endl; // Prints 10 only 9091
} ///:~ 9092
Listado 7.5. C06/CopyInts2.cpp 9093
9094
La función remove_copy_if() acepta los rangos definidos por punteros 9095
habituales, seguidos de un predicado de su elección. El predicado debe ser 9096
un puntero a función[FIXME] que toma un argumento simple del mismo tipo 9097
que los elementos de la secuencia, y que debe retornar un booleano. Aquí, la 9098
función gt15 returna verdadero si su argumento es mayor que 15. El 9099
algoritmo remove_copy_if() aplica gt15() a cada elemento en la secuencia 9100
de entrada e ignora aquellos elementos para los cuales el predicado 9101
devuelve verdad cuando escribe la secuencia de salida. 9102
El siguiente programa ilustra otra variación más del algoritmo de copia. 9103
//: C06:CopyStrings2.cpp 9104
// Replaces strings that satisfy a predicate. 9105
#include <algorithm> 9106
#include <cstddef> 9107
#include <iostream> 9108
#include <string> 9109
348
using namespace std; 9110
9111
// The predicate 9112
bool contains_e(const string& s) { 9113
return s.find('e') != string::npos; 9114
} 9115
9116
int main() { 9117
string a[] = {"read", "my", "lips"}; 9118
const size_t SIZE = sizeof a / sizeof a[0]; 9119
string b[SIZE]; 9120
string* endb = replace_copy_if(a, a + SIZE, b, 9121
contains_e, string("kiss")); 9122
string* beginb = b; 9123
while(beginb != endb) 9124
cout << *beginb++ << endl; 9125
} ///:~ 9126
Listado 7.6. C06/CopyStrings2.cpp 9127
9128
En lugar de simplemente ignorar elementos que no satisfagan el 9129
predicado, replace_copy_if() substituye un valor fijo para esos elementos 9130
cuando escribe la secuencia de salida. La salida es: 9131
kiss 9132 my 9133
lips 9134
349
como la ocurrencia original de «read», la única cadena de entrada que 9135
contiene la letra «e», es reemplazada por la palabra «kiss», como se 9136
especificó en el último argumento en la llamada a replace_copy_if(). 9137
El algoritmo replace_if() cambia la secuencia original in situ, en lugar de 9138
escribir en una secuencia de salida separada, tal como muestra el siguiente 9139
programa: 9140
//: C06:ReplaceStrings.cpp 9141
// Replaces strings in-place. 9142
#include <algorithm> 9143
#include <cstddef> 9144
#include <iostream> 9145
#include <string> 9146
using namespace std; 9147
9148
bool contains_e(const string& s) { 9149
return s.find('e') != string::npos; 9150
} 9151
9152
int main() { 9153
string a[] = {"read", "my", "lips"}; 9154
const size_t SIZE = sizeof a / sizeof a[0]; 9155
replace_if(a, a + SIZE, contains_e, string("kiss")); 9156
string* p = a; 9157
while(p != a + SIZE) 9158
cout << *p++ << endl; 9159
350
} ///:~ 9160
Listado 7.7. C06/ReplaceStrings.cpp 9161
9162
7.1.2. Iteradores de flujo 9163
Como cualquier otra buena librería, la Librería Estándar de C++ intenta 9164
proporcionar modos convenientes de automatizar tareas comunes. 9165
Mencionamos al principio de este capítulo puede usar algoritmos genéricos 9166
en lugar de bucles. Hasta el momento, sin embargo, nuestros ejemplos 9167
siguen usando un bucle explícito para imprimir su salida. Dado que imprimir 9168
la salida es una de las tareas más comunes, es de esperar que haya una 9169
forma de automatizar eso también. 9170
Ahí es donde los iteradores de flujo entran en juego. Un iterador de flujo 9171
usa un flujo como secuencia de entrada o salida. Para eliminar el bucle de 9172
salida en el programa CopyInts2.cpp, puede hacer algo como lo siguiente: 9173
//: C06:CopyInts3.cpp 9174
// Uses an output stream iterator. 9175
#include <algorithm> 9176
#include <cstddef> 9177
#include <iostream> 9178
#include <iterator> 9179
using namespace std; 9180
9181
bool gt15(int x) { return 15 < x; } 9182
9183
int main() { 9184
351
int a[] = { 10, 20, 30 }; 9185
const size_t SIZE = sizeof a / sizeof a[0]; 9186
remove_copy_if(a, a + SIZE, 9187
ostream_iterator<int>(cout, "\n"), gt15); 9188
} ///:~ 9189
Listado 7.8. C06/CopyInts3.cpp 9190
9191
En este ejemplo, reemplazaremos la secuencia de salida b en el tercer 9192
argumento de remove_copy_if() con un iterador de flujo de salida, que es 9193
una instancia de la clase ostream_iterator declarada en el fichero 9194
<iterator>. Los iteradores de flujo de salida sobrecargan sus operadores de 9195
copia-asignación para escribir a sus flujos. Esta instancia en particular de 9196
ostream_iterator está vinculada al flujo de salida cout. Cada vez que 9197
remove_copy_if() asigna un entero de la secuencia a a cout a través de este 9198
iterador, el iterador escribe el entero a cout y automáticamente escribe 9199
también una instancia de la cada de separador indicada en su segundo 9200
argumento, que en este caso contiene el carácter de nueva linea. 9201
Es igual de fácil escribir en un fichero proporcionando un flujo de salida 9202
asociado a un fichero en lugar de cout. 9203
//: C06:CopyIntsToFile.cpp 9204
// Uses an output file stream iterator. 9205
#include <algorithm> 9206
#include <cstddef> 9207
#include <fstream> 9208
#include <iterator> 9209
using namespace std; 9210
9211
352
bool gt15(int x) { return 15 < x; } 9212
9213
int main() { 9214
int a[] = { 10, 20, 30 }; 9215
const size_t SIZE = sizeof a / sizeof a[0]; 9216
ofstream outf("ints.out"); 9217
remove_copy_if(a, a + SIZE, 9218
ostream_iterator<int>(outf, "\n"), gt15); 9219
} ///:~ 9220
Listado 7.9. C06/CopyIntsToFile.cpp 9221
9222
Un iterador de flujo de entrada permite a un algoritmo leer su secuencia de 9223
entrada desde un flujo de entrada. Esto se consigue haciendo que tanto el 9224
constructor como operator++() lean el siguiente elemento del flujo 9225
subyacente y sobrecargando operator*() para conseguir el valor leído 9226
previamente. Dado que los algoritmos requieren dos punteros para delimitar 9227
la secuencia de entrada, puede construir un istream_iterator de dos 9228
formas, como puede ver en el siguiente programa. 9229
//: C06:CopyIntsFromFile.cpp 9230
// Uses an input stream iterator. 9231
#include <algorithm> 9232
#include <fstream> 9233
#include <iostream> 9234
#include <iterator> 9235
#include "../require.h" 9236
using namespace std; 9237
353
9238
bool gt15(int x) { return 15 < x; } 9239
9240
int main() { 9241
ofstream ints("someInts.dat"); 9242
ints << "1 3 47 5 84 9"; 9243
ints.close(); 9244
ifstream inf("someInts.dat"); 9245
assure(inf, "someInts.dat"); 9246
remove_copy_if(istream_iterator<int>(inf), 9247
istream_iterator<int>(), 9248
ostream_iterator<int>(cout, "\n"), gt15); 9249
} ///:~ 9250
Listado 7.10. C06/CopyIntsFromFile.cpp 9251
9252
El primer argumento de replace_copy_if() en este programa asocia un 9253
objeto istream_iterator al fichero de entrada que contiene enteros. El 9254
segundo argumento usa el constructor por defecto de la clase 9255
istream_iterator. Esta llamada construye un valor especial de 9256
istream_iterator que indica el fin de fichero, de modo que cuando el primer 9257
iterador encuentra el final del fichero físico, se compara con el valor de 9258
istream_iterator<int>(), permitiendo al algoritmo terminar correctamente. 9259
Fíjese que este ejemplo evita usar un array explícito. 9260
7.1.3. Complejidad algorítmica 9261
Usar una librería es una cuestión de confianza. Debe confiar en que los 9262
desarrolladores no solo proporcionan la funcionalidad correcta, sino también 9263
esperar que las funciones se ejecutan tan eficientemente como sea posible. 9264
354
Es mejor escribir sus propios bucles que usar algoritmos que degradan el 9265
rendimiento. 9266
Para garantizar la calidad de las implementaciones de la librería, la 9267
estándar de C++ no solo especifica lo que debería hacer un algoritmo, 9268
también cómo de rápido debería hacerlo y a veces cuánto espacio debería 9269
usar. Cualquier algoritmo que no cumpla con los requisitos de rendimiento no 9270
es conforma al estándar. La medida de la eficiencia operacional de un 9271
algoritmo se llama complejidad. 9272
Cuando es posible, el estándar especifica el número exacto de 9273
operaciones que un algoritmo debería usar. El algoritmo count_if(), por 9274
ejemplo, retorna el número de elementos de una secuencia que cumplan el 9275
predicado especificado. La siguiente llamada a count_if(), si se aplica a una 9276
secuencia de enteros similar a los ejemplos anteriores de este capítulo, 9277
devuelve el número de elementos mayores que 15: 9278
size_t n = count_if(a, a + SIZE, gt15); 9279
Dado que count_if() debe comprobar cada elemento exactamente una 9280
vez, se especificó hacer un número de comprobaciones que sea 9281
exactamente igual que el número de elementos en la secuencia. El algoritmo 9282
copy() tiene la misma especificación. 9283
Otros algoritmos pueden estar especificados para realizar cierto número 9284
máximo de operaciones. El algoritmo find() busca a través de una 9285
secuencia hasta encontrar un elemento igual a su tercer argumento. 9286
int* p = find(a, a + SIZE, 20); 9287
Para tan pronto como encuentre el elemento y devuelve un puntero a la 9288
primera ocurrencia. Si no encuentra ninguno, retorna un puntero a una 9289
posición pasado el final de la secuencia (a+SIZE en este ejemplo). De modo 9290
que find() realiza como máximo tantas comparaciones como elementos 9291
tenga la secuencia. 9292
355
A veces el número de operaciones que realiza un algoritmo no se puede 9293
medir con tanta precisión. En esos casos, el estándar especifica la 9294
complejidad asintótica del algoritmo, que es una medida de cómo se 9295
comportará el algoritmo con secuencias largas comparadas con formulas 9296
bien conocidas. Un buen ejemplo es el algoritmo sort(), del que el estándar 9297
dice que requiere «aproximadamente n log n comparaciones de media» (n es 9298
el número de elementos de la secuencia). [FIXME]. Esta medida de 9299
complejidad da una idea del coste de un algoritmo y al menos le da una base 9300
fiable para comparar algoritmos. Como verá en el siguiente capítulo, el 9301
método find() para el contendor set tiene complejidad logarítmica, que 9302
implica que el coste de una búsqueda de un elemento en un set será, para 9303
conjuntos grandes, proporcional al logaritmo del número de elementos. Eso 9304
es mucho menor que el número de elementos para un n grande, de modo 9305
que siempre es mejor buscar en un set utilizando el método en lugar del 9306
algoritmo genérico. 9307
7.2. Objetos-función 9308
//: C06:GreaterThanN.cpp 9309
#include <iostream> 9310
using namespace std; 9311
9312
class gt_n { 9313
int value; 9314
public: 9315
gt_n(int val) : value(val) {} 9316
bool operator()(int n) { return n > value; } 9317
}; 9318
9319
int main() { 9320
356
gt_n f(4); 9321
cout << f(3) << endl; // Prints 0 (for false) 9322
cout << f(5) << endl; // Prints 1 (for true) 9323
} ///:~ 9324
Listado 7.11. C06/GreaterThanN.cpp 9325
9326
7.2.1. Clasificación de objetos-función 9327
7.2.2. Creación automática de objetos-función 9328
//: C06:CopyInts4.cpp 9329
// Uses a standard function object and adaptor. 9330
#include <algorithm> 9331
#include <cstddef> 9332
#include <functional> 9333
#include <iostream> 9334
#include <iterator> 9335
using namespace std; 9336
9337
int main() { 9338
int a[] = { 10, 20, 30 }; 9339
const size_t SIZE = sizeof a / sizeof a[0]; 9340
remove_copy_if(a, a + SIZE, 9341
ostream_iterator<int>(cout, "\n"), 9342
bind2nd(greater<int>(), 15)); 9343
357
} ///:~ 9344
Listado 7.12. C06/CopyInts4.cpp 9345
9346
//: C06:CountNotEqual.cpp 9347
// Count elements not equal to 20. 9348
#include <algorithm> 9349
#include <cstddef> 9350
#include <functional> 9351
#include <iostream> 9352
using namespace std; 9353
9354
int main() { 9355
int a[] = { 10, 20, 30 }; 9356
const size_t SIZE = sizeof a / sizeof a[0]; 9357
cout << count_if(a, a + SIZE, 9358
not1(bind1st(equal_to<int>(), 20)));// 2 9359
} ///:~ 9360
Listado 7.13. C06/CountNotEqual.cpp 9361
9362
7.2.3. Objetos-función adaptables 9363
7.2.4. Más ejemplos de objetos-función 9364
//: C06:Generators.h 9365
358
// Different ways to fill sequences. 9366
#ifndef GENERATORS_H 9367
#define GENERATORS_H 9368
#include <cstring> 9369
#include <set> 9370
#include <cstdlib> 9371
9372
// A generator that can skip over numbers: 9373
class SkipGen { 9374
int i; 9375
int skp; 9376
public: 9377
SkipGen(int start = 0, int skip = 1) 9378
: i(start), skp(skip) {} 9379
int operator()() { 9380
int r = i; 9381
i += skp; 9382
return r; 9383
} 9384
}; 9385
9386
// Generate unique random numbers from 0 to mod: 9387
class URandGen { 9388
std::set<int> used; 9389
int limit; 9390
359
public: 9391
URandGen(int lim) : limit(lim) {} 9392
int operator()() { 9393
while(true) { 9394
int i = int(std::rand()) % limit; 9395
if(used.find(i) == used.end()) { 9396
used.insert(i); 9397
return i; 9398
} 9399
} 9400
} 9401
}; 9402
9403
// Produces random characters: 9404
class CharGen { 9405
static const char* source; 9406
static const int len; 9407
public: 9408
char operator()() { 9409
return source[std::rand() % len]; 9410
} 9411
}; 9412
#endif // GENERATORS_H ///:~ 9413
Listado 7.14. C06/Generators.h 9414
360
9415
//: C06:Generators.cpp {O} 9416
#include "Generators.h" 9417
const char* CharGen::source = "ABCDEFGHIJK" 9418
"LMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; 9419
const int CharGen::len = std::strlen(source); 9420
///:~ 9421
Listado 7.15. C06/Generators.cpp 9422
9423
//: C06:FunctionObjects.cpp {-bor} 9424
// Illustrates selected predefined function object 9425
// templates from the Standard C++ library. 9426
//{L} Generators 9427
#include <algorithm> 9428
#include <cstdlib> 9429
#include <ctime> 9430
#include <functional> 9431
#include <iostream> 9432
#include <iterator> 9433
#include <vector> 9434
#include "Generators.h" 9435
#include "PrintSequence.h" 9436
using namespace std; 9437
9438
361
template<typename Contain, typename UnaryFunc> 9439
void testUnary(Contain& source, Contain& dest, 9440
UnaryFunc f) { 9441
transform(source.begin(), source.end(), dest.begin(), f); 9442
} 9443
9444
template<typename Contain1, typename Contain2, 9445
typename BinaryFunc> 9446
void testBinary(Contain1& src1, Contain1& src2, 9447
Contain2& dest, BinaryFunc f) { 9448
transform(src1.begin(), src1.end(), 9449
src2.begin(), dest.begin(), f); 9450
} 9451
9452
// Executes the expression, then stringizes the 9453
// expression into the print statement: 9454
#define T(EXPR) EXPR; print(r.begin(), r.end(), \ 9455
"After " #EXPR); 9456
// For Boolean tests: 9457
#define B(EXPR) EXPR; print(br.begin(), br.end(), \ 9458
"After " #EXPR); 9459
9460
// Boolean random generator: 9461
struct BRand { 9462
bool operator()() { return rand() % 2 == 0; } 9463
362
}; 9464
9465
int main() { 9466
const int SZ = 10; 9467
const int MAX = 50; 9468
vector<int> x(SZ), y(SZ), r(SZ); 9469
// An integer random number generator: 9470
URandGen urg(MAX); 9471
srand(time(0)); // Randomize 9472
generate_n(x.begin(), SZ, urg); 9473
generate_n(y.begin(), SZ, urg); 9474
// Add one to each to guarantee nonzero divide: 9475
transform(y.begin(), y.end(), y.begin(), 9476
bind2nd(plus<int>(), 1)); 9477
// Guarantee one pair of elements is ==: 9478
x[0] = y[0]; 9479
print(x.begin(), x.end(), "x"); 9480
print(y.begin(), y.end(), "y"); 9481
// Operate on each element pair of x & y, 9482
// putting the result into r: 9483
T(testBinary(x, y, r, plus<int>())); 9484
T(testBinary(x, y, r, minus<int>())); 9485
T(testBinary(x, y, r, multiplies<int>())); 9486
T(testBinary(x, y, r, divides<int>())); 9487
T(testBinary(x, y, r, modulus<int>())); 9488
363
T(testUnary(x, r, negate<int>())); 9489
vector<bool> br(SZ); // For Boolean results 9490
B(testBinary(x, y, br, equal_to<int>())); 9491
B(testBinary(x, y, br, not_equal_to<int>())); 9492
B(testBinary(x, y, br, greater<int>())); 9493
B(testBinary(x, y, br, less<int>())); 9494
B(testBinary(x, y, br, greater_equal<int>())); 9495
B(testBinary(x, y, br, less_equal<int>())); 9496
B(testBinary(x, y, br, not2(greater_equal<int>()))); 9497
B(testBinary(x,y,br,not2(less_equal<int>()))); 9498
vector<bool> b1(SZ), b2(SZ); 9499
generate_n(b1.begin(), SZ, BRand()); 9500
generate_n(b2.begin(), SZ, BRand()); 9501
print(b1.begin(), b1.end(), "b1"); 9502
print(b2.begin(), b2.end(), "b2"); 9503
B(testBinary(b1, b2, br, logical_and<int>())); 9504
B(testBinary(b1, b2, br, logical_or<int>())); 9505
B(testUnary(b1, br, logical_not<int>())); 9506
B(testUnary(b1, br, not1(logical_not<int>()))); 9507
} ///:~ 9508
Listado 7.16. C06/FunctionObjects.cpp 9509
9510
//: C06:FBinder.cpp 9511
// Binders aren't limited to producing predicates. 9512
364
//{L} Generators 9513
#include <algorithm> 9514
#include <cstdlib> 9515
#include <ctime> 9516
#include <functional> 9517
#include <iostream> 9518
#include <iterator> 9519
#include <vector> 9520
#include "Generators.h" 9521
using namespace std; 9522
9523
int main() { 9524
ostream_iterator<int> out(cout," "); 9525
vector<int> v(15); 9526
srand(time(0)); // Randomize 9527
generate(v.begin(), v.end(), URandGen(20)); 9528
copy(v.begin(), v.end(), out); 9529
transform(v.begin(), v.end(), v.begin(), 9530
bind2nd(multiplies<int>(), 10)); 9531
copy(v.begin(), v.end(), out); 9532
} ///:~ 9533
Listado 7.17. C06/FBinder.cpp 9534
9535
//: C06:BinderValue.cpp 9536
365
// The bound argument can vary. 9537
#include <algorithm> 9538
#include <functional> 9539
#include <iostream> 9540
#include <iterator> 9541
#include <cstdlib> 9542
using namespace std; 9543
9544
int boundedRand() { return rand() % 100; } 9545
9546
int main() { 9547
const int SZ = 20; 9548
int a[SZ], b[SZ] = {0}; 9549
generate(a, a + SZ, boundedRand); 9550
int val = boundedRand(); 9551
int* end = remove_copy_if(a, a + SZ, b, 9552
bind2nd(greater<int>(), val)); 9553
// Sort for easier viewing: 9554
sort(a, a + SZ); 9555
sort(b, end); 9556
ostream_iterator<int> out(cout, " "); 9557
cout << "Original Sequence:" << endl; 9558
copy(a, a + SZ, out); cout << endl; 9559
cout << "Values <= " << val << endl; 9560
copy(b, end, out); cout << endl; 9561
366
} ///:~ 9562
Listado 7.18. C06/BinderValue.cpp 9563
9564
7.2.5. Adaptadores de puntero a función 9565
//: C06:PtrFun1.cpp 9566
// Using ptr_fun() with a unary function. 9567
#include <algorithm> 9568
#include <cmath> 9569
#include <functional> 9570
#include <iostream> 9571
#include <iterator> 9572
#include <vector> 9573
using namespace std; 9574
9575
int d[] = { 123, 94, 10, 314, 315 }; 9576
const int DSZ = sizeof d / sizeof *d; 9577
9578
bool isEven(int x) { return x % 2 == 0; } 9579
9580
int main() { 9581
vector<bool> vb; 9582
transform(d, d + DSZ, back_inserter(vb), 9583
not1(ptr_fun(isEven))); 9584
367
copy(vb.begin(), vb.end(), 9585
ostream_iterator<bool>(cout, " ")); 9586
cout << endl; 9587
// Output: 1 0 0 0 1 9588
} ///:~ 9589
Listado 7.19. C06/PtrFun1.cpp 9590
9591
//: C06:PtrFun2.cpp {-edg} 9592
// Using ptr_fun() for a binary function. 9593
#include <algorithm> 9594
#include <cmath> 9595
#include <functional> 9596
#include <iostream> 9597
#include <iterator> 9598
#include <vector> 9599
using namespace std; 9600
9601
double d[] = { 01.23, 91.370, 56.661, 9602
023.230, 19.959, 1.0, 3.14159 }; 9603
const int DSZ = sizeof d / sizeof *d; 9604
9605
int main() { 9606
vector<double> vd; 9607
transform(d, d + DSZ, back_inserter(vd), 9608
368
bind2nd(ptr_fun<double, double, double>(pow), 2.0)); 9609
copy(vd.begin(), vd.end(), 9610
ostream_iterator<double>(cout, " ")); 9611
cout << endl; 9612
} ///:~ 9613
Listado 7.20. C06/PtrFun2.cpp 9614
9615
//: C06:MemFun1.cpp 9616
// Applying pointers to member functions. 9617
#include <algorithm> 9618
#include <functional> 9619
#include <iostream> 9620
#include <vector> 9621
#include "../purge.h" 9622
using namespace std; 9623
9624
class Shape { 9625
public: 9626
virtual void draw() = 0; 9627
virtual ~Shape() {} 9628
}; 9629
9630
class Circle : public Shape { 9631
public: 9632
369
virtual void draw() { cout << "Circle::Draw()" << endl; } 9633
~Circle() { cout << "Circle::~Circle()" << endl; } 9634
}; 9635
9636
class Square : public Shape { 9637
public: 9638
virtual void draw() { cout << "Square::Draw()" << endl; } 9639
~Square() { cout << "Square::~Square()" << endl; } 9640
}; 9641
9642
int main() { 9643
vector<Shape*> vs; 9644
vs.push_back(new Circle); 9645
vs.push_back(new Square); 9646
for_each(vs.begin(), vs.end(), mem_fun(&Shape::draw)); 9647
purge(vs); 9648
} ///:~ 9649
Listado 7.21. C06/MemFun1.cpp 9650
9651
//: C06:MemFun2.cpp 9652
// Calling member functions through an object reference. 9653
#include <algorithm> 9654
#include <functional> 9655
#include <iostream> 9656
370
#include <iterator> 9657
#include <vector> 9658
using namespace std; 9659
9660
class Angle { 9661
int degrees; 9662
public: 9663
Angle(int deg) : degrees(deg) {} 9664
int mul(int times) { return degrees *= times; } 9665
}; 9666
9667
int main() { 9668
vector<Angle> va; 9669
for(int i = 0; i < 50; i += 10) 9670
va.push_back(Angle(i)); 9671
int x[] = { 1, 2, 3, 4, 5 }; 9672
transform(va.begin(), va.end(), x, 9673
ostream_iterator<int>(cout, " "), 9674
mem_fun_ref(&Angle::mul)); 9675
cout << endl; 9676
// Output: 0 20 60 120 200 9677
} ///:~ 9678
Listado 7.22. C06/MemFun2.cpp 9679
9680
371
//: C06:FindBlanks.cpp 9681
// Demonstrates mem_fun_ref() with string::empty(). 9682
#include <algorithm> 9683
#include <cassert> 9684
#include <cstddef> 9685
#include <fstream> 9686
#include <functional> 9687
#include <string> 9688
#include <vector> 9689
#include "../require.h" 9690
using namespace std; 9691
9692
typedef vector<string>::iterator LSI; 9693
9694
int main(int argc, char* argv[]) { 9695
char* fname = "FindBlanks.cpp"; 9696
if(argc > 1) fname = argv[1]; 9697
ifstream in(fname); 9698
assure(in, fname); 9699
vector<string> vs; 9700
string s; 9701
while(getline(in, s)) 9702
vs.push_back(s); 9703
vector<string> cpy = vs; // For testing 9704
LSI lsi = find_if(vs.begin(), vs.end(), 9705
372
mem_fun_ref(&string::empty)); 9706
while(lsi != vs.end()) { 9707
*lsi = "A BLANK LINE"; 9708
lsi = find_if(vs.begin(), vs.end(), 9709
mem_fun_ref(&string::empty)); 9710
} 9711
for(size_t i = 0; i < cpy.size(); i++) 9712
if(cpy[i].size() == 0) 9713
assert(vs[i] == "A BLANK LINE"); 9714
else 9715
assert(vs[i] != "A BLANK LINE"); 9716
} ///:~ 9717
Listado 7.23. C06/FindBlanks.cpp 9718
9719
7.2.6. Escribir sus propios adaptadores de objeto-función 9720
//: C06:NumStringGen.h 9721
// A random number generator that produces 9722
// strings representing floating-point numbers. 9723
#ifndef NUMSTRINGGEN_H 9724
#define NUMSTRINGGEN_H 9725
#include <cstdlib> 9726
#include <string> 9727
9728
373
class NumStringGen { 9729
const int sz; // Number of digits to make 9730
public: 9731
NumStringGen(int ssz = 5) : sz(ssz) {} 9732
std::string operator()() { 9733
std::string digits("0123456789"); 9734
const int ndigits = digits.size(); 9735
std::string r(sz, ' '); 9736
// Don't want a zero as the first digit 9737
r[0] = digits[std::rand() % (ndigits - 1)] + 1; 9738
// Now assign the rest 9739
for(int i = 1; i < sz; ++i) 9740
if(sz >= 3 && i == sz/2) 9741
r[i] = '.'; // Insert a decimal point 9742
else 9743
r[i] = digits[std::rand() % ndigits]; 9744
return r; 9745
} 9746
}; 9747
#endif // NUMSTRINGGEN_H ///:~ 9748
Listado 7.24. C06/NumStringGen.h 9749
9750
//: C06:MemFun3.cpp 9751
// Using mem_fun(). 9752
374
#include <algorithm> 9753
#include <cstdlib> 9754
#include <ctime> 9755
#include <functional> 9756
#include <iostream> 9757
#include <iterator> 9758
#include <string> 9759
#include <vector> 9760
#include "NumStringGen.h" 9761
using namespace std; 9762
9763
int main() { 9764
const int SZ = 9; 9765
vector<string> vs(SZ); 9766
// Fill it with random number strings: 9767
srand(time(0)); // Randomize 9768
generate(vs.begin(), vs.end(), NumStringGen()); 9769
copy(vs.begin(), vs.end(), 9770
ostream_iterator<string>(cout, "\t")); 9771
cout << endl; 9772
const char* vcp[SZ]; 9773
transform(vs.begin(), vs.end(), vcp, 9774
mem_fun_ref(&string::c_str)); 9775
vector<double> vd; 9776
transform(vcp, vcp + SZ, back_inserter(vd), 9777
375
std::atof); 9778
cout.precision(4); 9779
cout.setf(ios::showpoint); 9780
copy(vd.begin(), vd.end(), 9781
ostream_iterator<double>(cout, "\t")); 9782
cout << endl; 9783
} ///:~ 9784
Listado 7.25. C06/MemFun3.cpp 9785
9786
//: C06:ComposeTry.cpp 9787
// A first attempt at implementing function composition. 9788
#include <cassert> 9789
#include <cstdlib> 9790
#include <functional> 9791
#include <iostream> 9792
#include <string> 9793
using namespace std; 9794
9795
template<typename R, typename E, typename F1, typename F2> 9796
class unary_composer { 9797
F1 f1; 9798
F2 f2; 9799
public: 9800
unary_composer(F1 fone, F2 ftwo) : f1(fone), f2(ftwo) {} 9801
376
R operator()(E x) { return f1(f2(x)); } 9802
}; 9803
9804
template<typename R, typename E, typename F1, typename F2> 9805
unary_composer<R, E, F1, F2> compose(F1 f1, F2 f2) { 9806
return unary_composer<R, E, F1, F2>(f1, f2); 9807
} 9808
9809
int main() { 9810
double x = compose<double, const string&>( 9811
atof, mem_fun_ref(&string::c_str))("12.34"); 9812
assert(x == 12.34); 9813
} ///:~ 9814
Listado 7.26. C06/ComposeTry.cpp 9815
9816
//: C06:ComposeFinal.cpp {-edg} 9817
// An adaptable composer. 9818
#include <algorithm> 9819
#include <cassert> 9820
#include <cstdlib> 9821
#include <functional> 9822
#include <iostream> 9823
#include <iterator> 9824
#include <string> 9825
377
#include <vector> 9826
#include "NumStringGen.h" 9827
using namespace std; 9828
9829
template<typename F1, typename F2> class unary_composer 9830
: public unary_function<typename F2::argument_type, 9831
typename F1::result_type> { 9832
F1 f1; 9833
F2 f2; 9834
public: 9835
unary_composer(F1 f1, F2 f2) : f1(f1), f2(f2) {} 9836
typename F1::result_type 9837
operator()(typename F2::argument_type x) { 9838
return f1(f2(x)); 9839
} 9840
}; 9841
9842
template<typename F1, typename F2> 9843
unary_composer<F1, F2> compose(F1 f1, F2 f2) { 9844
return unary_composer<F1, F2>(f1, f2); 9845
} 9846
9847
int main() { 9848
const int SZ = 9; 9849
vector<string> vs(SZ); 9850
378
// Fill it with random number strings: 9851
generate(vs.begin(), vs.end(), NumStringGen()); 9852
copy(vs.begin(), vs.end(), 9853
ostream_iterator<string>(cout, "\t")); 9854
cout << endl; 9855
vector<double> vd; 9856
transform(vs.begin(), vs.end(), back_inserter(vd), 9857
compose(ptr_fun(atof), mem_fun_ref(&string::c_str))); 9858
copy(vd.begin(), vd.end(), 9859
ostream_iterator<double>(cout, "\t")); 9860
cout << endl; 9861
} ///:~ 9862
Listado 7.27. C06/ComposeFinal.cpp 9863
9864
7.3. Un catálogo de algoritmos STL 9865
7.3.1. Herramientas de soporte para la creación de ejemplos 9866
//: C06:PrintSequence.h 9867
// Prints the contents of any sequence. 9868
#ifndef PRINTSEQUENCE_H 9869
#define PRINTSEQUENCE_H 9870
#include <algorithm> 9871
#include <iostream> 9872
#include <iterator> 9873
379
9874
template<typename Iter> 9875
void print(Iter first, Iter last, const char* nm = "", 9876
const char* sep = "\n", 9877
std::ostream& os = std::cout) { 9878
if(nm != 0 && *nm != '\0') 9879
os << nm << ": " << sep; 9880
typedef typename 9881
std::iterator_traits<Iter>::value_type T; 9882
std::copy(first, last, 9883
std::ostream_iterator<T>(std::cout, sep)); 9884
os << std::endl; 9885
} 9886
#endif // PRINTSEQUENCE_H ///:~ 9887
Listado 7.28. C06/PrintSequence.h 9888
9889
Reordenación estable vs. inestable 9890
//: C06:NString.h 9891
// A "numbered string" that keeps track of the 9892
// number of occurrences of the word it contains. 9893
#ifndef NSTRING_H 9894
#define NSTRING_H 9895
#include <algorithm> 9896
#include <iostream> 9897
380
#include <string> 9898
#include <utility> 9899
#include <vector> 9900
9901
typedef std::pair<std::string, int> psi; 9902
9903
// Only compare on the first element 9904
bool operator==(const psi& l, const psi& r) { 9905
return l.first == r.first; 9906
} 9907
9908
class NString { 9909
std::string s; 9910
int thisOccurrence; 9911
// Keep track of the number of occurrences: 9912
typedef std::vector<psi> vp; 9913
typedef vp::iterator vpit; 9914
static vp words; 9915
void addString(const std::string& x) { 9916
psi p(x, 0); 9917
vpit it = std::find(words.begin(), words.end(), p); 9918
if(it != words.end()) 9919
thisOccurrence = ++it->second; 9920
else { 9921
thisOccurrence = 0; 9922
381
words.push_back(p); 9923
} 9924
} 9925
public: 9926
NString() : thisOccurrence(0) {} 9927
NString(const std::string& x) : s(x) { addString(x); } 9928
NString(const char* x) : s(x) { addString(x); } 9929
// Implicit operator= and copy-constructor are OK here. 9930
friend std::ostream& operator<<( 9931
std::ostream& os, const NString& ns) { 9932
return os << ns.s << " [" << ns.thisOccurrence << "]"; 9933
} 9934
// Need this for sorting. Notice it only 9935
// compares strings, not occurrences: 9936
friend bool 9937
operator<(const NString& l, const NString& r) { 9938
return l.s < r.s; 9939
} 9940
friend 9941
bool operator==(const NString& l, const NString& r) { 9942
return l.s == r.s; 9943
} 9944
// For sorting with greater<NString>: 9945
friend bool 9946
operator>(const NString& l, const NString& r) { 9947
382
return l.s > r.s; 9948
} 9949
// To get at the string directly: 9950
operator const std::string&() const { return s; } 9951
}; 9952
9953
// Because NString::vp is a template and we are using the 9954
// inclusion model, it must be defined in this header file: 9955
NString::vp NString::words; 9956
#endif // NSTRING_H ///:~ 9957
Listado 7.29. C06/NString.h 9958
9959
7.3.2. Relleno y generación 9960
Ejemplo 9961
//: C06:FillGenerateTest.cpp 9962
// Demonstrates "fill" and "generate." 9963
//{L} Generators 9964
#include <vector> 9965
#include <algorithm> 9966
#include <string> 9967
#include "Generators.h" 9968
#include "PrintSequence.h" 9969
using namespace std; 9970
383
9971
int main() { 9972
vector<string> v1(5); 9973
fill(v1.begin(), v1.end(), "howdy"); 9974
print(v1.begin(), v1.end(), "v1", " "); 9975
vector<string> v2; 9976
fill_n(back_inserter(v2), 7, "bye"); 9977
print(v2.begin(), v2.end(), "v2"); 9978
vector<int> v3(10); 9979
generate(v3.begin(), v3.end(), SkipGen(4,5)); 9980
print(v3.begin(), v3.end(), "v3", " "); 9981
vector<int> v4; 9982
generate_n(back_inserter(v4),15, URandGen(30)); 9983
print(v4.begin(), v4.end(), "v4", " "); 9984
} ///:~ 9985
Listado 7.30. C06/FillGenerateTest.cpp 9986
9987
7.3.3. Conteo 9988
Ejemplo 9989
//: C06:Counting.cpp 9990
// The counting algorithms. 9991
//{L} Generators 9992
#include <algorithm> 9993
384
#include <functional> 9994
#include <iterator> 9995
#include <set> 9996
#include <vector> 9997
#include "Generators.h" 9998
#include "PrintSequence.h" 9999
using namespace std; 10000
10001
int main() { 10002
vector<char> v; 10003
generate_n(back_inserter(v), 50, CharGen()); 10004
print(v.begin(), v.end(), "v", ""); 10005
// Create a set of the characters in v: 10006
set<char> cs(v.begin(), v.end()); 10007
typedef set<char>::iterator sci; 10008
for(sci it = cs.begin(); it != cs.end(); it++) { 10009
int n = count(v.begin(), v.end(), *it); 10010
cout << *it << ": " << n << ", "; 10011
} 10012
int lc = count_if(v.begin(), v.end(), 10013
bind2nd(greater<char>(), 'a')); 10014
cout << "\nLowercase letters: " << lc << endl; 10015
sort(v.begin(), v.end()); 10016
print(v.begin(), v.end(), "sorted", ""); 10017
} ///:~ 10018
385
Listado 7.31. C06/Counting.cpp 10019
10020
7.3.4. Manipulación de secuencias 10021
Ejemplo 10022
//: C06:Manipulations.cpp 10023
// Shows basic manipulations. 10024
//{L} Generators 10025
// NString 10026
#include <vector> 10027
#include <string> 10028
#include <algorithm> 10029
#include "PrintSequence.h" 10030
#include "NString.h" 10031
#include "Generators.h" 10032
using namespace std; 10033
10034
int main() { 10035
vector<int> v1(10); 10036
// Simple counting: 10037
generate(v1.begin(), v1.end(), SkipGen()); 10038
print(v1.begin(), v1.end(), "v1", " "); 10039
vector<int> v2(v1.size()); 10040
copy_backward(v1.begin(), v1.end(), v2.end()); 10041
print(v2.begin(), v2.end(), "copy_backward", " "); 10042
reverse_copy(v1.begin(), v1.end(), v2.begin()); 10043
386
print(v2.begin(), v2.end(), "reverse_copy", " "); 10044
reverse(v1.begin(), v1.end()); 10045
print(v1.begin(), v1.end(), "reverse", " "); 10046
int half = v1.size() / 2; 10047
// Ranges must be exactly the same size: 10048
swap_ranges(v1.begin(), v1.begin() + half, 10049
v1.begin() + half); 10050
print(v1.begin(), v1.end(), "swap_ranges", " "); 10051
// Start with a fresh sequence: 10052
generate(v1.begin(), v1.end(), SkipGen()); 10053
print(v1.begin(), v1.end(), "v1", " "); 10054
int third = v1.size() / 3; 10055
for(int i = 0; i < 10; i++) { 10056
rotate(v1.begin(), v1.begin() + third, v1.end()); 10057
print(v1.begin(), v1.end(), "rotate", " "); 10058
} 10059
cout << "Second rotate example:" << endl; 10060
char c[] = "aabbccddeeffgghhiijj"; 10061
const char CSZ = strlen(c); 10062
for(int i = 0; i < 10; i++) { 10063
rotate(c, c + 2, c + CSZ); 10064
print(c, c + CSZ, "", ""); 10065
} 10066
cout << "All n! permutations of abcd:" << endl; 10067
int nf = 4 * 3 * 2 * 1; 10068
387
char p[] = "abcd"; 10069
for(int i = 0; i < nf; i++) { 10070
next_permutation(p, p + 4); 10071
print(p, p + 4, "", ""); 10072
} 10073
cout << "Using prev_permutation:" << endl; 10074
for(int i = 0; i < nf; i++) { 10075
prev_permutation(p, p + 4); 10076
print(p, p + 4, "", ""); 10077
} 10078
cout << "random_shuffling a word:" << endl; 10079
string s("hello"); 10080
cout << s << endl; 10081
for(int i = 0; i < 5; i++) { 10082
random_shuffle(s.begin(), s.end()); 10083
cout << s << endl; 10084
} 10085
NString sa[] = { "a", "b", "c", "d", "a", "b", 10086
"c", "d", "a", "b", "c", "d", "a", "b", "c"}; 10087
const int SASZ = sizeof sa / sizeof *sa; 10088
vector<NString> ns(sa, sa + SASZ); 10089
print(ns.begin(), ns.end(), "ns", " "); 10090
vector<NString>::iterator it = 10091
partition(ns.begin(), ns.end(), 10092
bind2nd(greater<NString>(), "b")); 10093
388
cout << "Partition point: " << *it << endl; 10094
print(ns.begin(), ns.end(), "", " "); 10095
// Reload vector: 10096
copy(sa, sa + SASZ, ns.begin()); 10097
it = stable_partition(ns.begin(), ns.end(), 10098
bind2nd(greater<NString>(), "b")); 10099
cout << "Stable partition" << endl; 10100
cout << "Partition point: " << *it << endl; 10101
print(ns.begin(), ns.end(), "", " "); 10102
} ///:~ 10103
Listado 7.32. C06/Manipulations.cpp 10104
10105
7.3.5. Búsqueda y reemplazo 10106
Ejemplo 10107
//: C06:SearchReplace.cpp 10108
// The STL search and replace algorithms. 10109
#include <algorithm> 10110
#include <functional> 10111
#include <vector> 10112
#include "PrintSequence.h" 10113
using namespace std; 10114
10115
struct PlusOne { 10116
389
bool operator()(int i, int j) { return j == i + 1; } 10117
}; 10118
10119
class MulMoreThan { 10120
int value; 10121
public: 10122
MulMoreThan(int val) : value(val) {} 10123
bool operator()(int v, int m) { return v * m > value; } 10124
}; 10125
10126
int main() { 10127
int a[] = { 1, 2, 3, 4, 5, 6, 6, 7, 7, 7, 10128
8, 8, 8, 8, 11, 11, 11, 11, 11 }; 10129
const int ASZ = sizeof a / sizeof *a; 10130
vector<int> v(a, a + ASZ); 10131
print(v.begin(), v.end(), "v", " "); 10132
vector<int>::iterator it = find(v.begin(), v.end(), 4); 10133
cout << "find: " << *it << endl; 10134
it = find_if(v.begin(), v.end(), 10135
bind2nd(greater<int>(), 8)); 10136
cout << "find_if: " << *it << endl; 10137
it = adjacent_find(v.begin(), v.end()); 10138
while(it != v.end()) { 10139
cout << "adjacent_find: " << *it 10140
<< ", " << *(it + 1) << endl; 10141
390
it = adjacent_find(it + 1, v.end()); 10142
} 10143
it = adjacent_find(v.begin(), v.end(), PlusOne()); 10144
while(it != v.end()) { 10145
cout << "adjacent_find PlusOne: " << *it 10146
<< ", " << *(it + 1) << endl; 10147
it = adjacent_find(it + 1, v.end(), PlusOne()); 10148
} 10149
int b[] = { 8, 11 }; 10150
const int BSZ = sizeof b / sizeof *b; 10151
print(b, b + BSZ, "b", " "); 10152
it = find_first_of(v.begin(), v.end(), b, b + BSZ); 10153
print(it, it + BSZ, "find_first_of", " "); 10154
it = find_first_of(v.begin(), v.end(), 10155
b, b + BSZ, PlusOne()); 10156
print(it,it + BSZ,"find_first_of PlusOne"," "); 10157
it = search(v.begin(), v.end(), b, b + BSZ); 10158
print(it, it + BSZ, "search", " "); 10159
int c[] = { 5, 6, 7 }; 10160
const int CSZ = sizeof c / sizeof *c; 10161
print(c, c + CSZ, "c", " "); 10162
it = search(v.begin(), v.end(), c, c + CSZ, PlusOne()); 10163
print(it, it + CSZ,"search PlusOne", " "); 10164
int d[] = { 11, 11, 11 }; 10165
const int DSZ = sizeof d / sizeof *d; 10166
391
print(d, d + DSZ, "d", " "); 10167
it = find_end(v.begin(), v.end(), d, d + DSZ); 10168
print(it, v.end(),"find_end", " "); 10169
int e[] = { 9, 9 }; 10170
print(e, e + 2, "e", " "); 10171
it = find_end(v.begin(), v.end(), e, e + 2, PlusOne()); 10172
print(it, v.end(),"find_end PlusOne"," "); 10173
it = search_n(v.begin(), v.end(), 3, 7); 10174
print(it, it + 3, "search_n 3, 7", " "); 10175
it = search_n(v.begin(), v.end(), 10176
6, 15, MulMoreThan(100)); 10177
print(it, it + 6, 10178
"search_n 6, 15, MulMoreThan(100)", " "); 10179
cout << "min_element: " 10180
<< *min_element(v.begin(), v.end()) << endl; 10181
cout << "max_element: " 10182
<< *max_element(v.begin(), v.end()) << endl; 10183
vector<int> v2; 10184
replace_copy(v.begin(), v.end(), 10185
back_inserter(v2), 8, 47); 10186
print(v2.begin(), v2.end(), "replace_copy 8 -> 47", " "); 10187
replace_if(v.begin(), v.end(), 10188
bind2nd(greater_equal<int>(), 7), -1); 10189
print(v.begin(), v.end(), "replace_if >= 7 -> -1", " "); 10190
} ///:~ 10191
392
Listado 7.33. C06/SearchReplace.cpp 10192
10193
7.3.6. Comparación de rangos 10194
Ejemplo 10195
//: C06:Comparison.cpp 10196
// The STL range comparison algorithms. 10197
#include <algorithm> 10198
#include <functional> 10199
#include <string> 10200
#include <vector> 10201
#include "PrintSequence.h" 10202
using namespace std; 10203
10204
int main() { 10205
// Strings provide a convenient way to create 10206
// ranges of characters, but you should 10207
// normally look for native string operations: 10208
string s1("This is a test"); 10209
string s2("This is a Test"); 10210
cout << "s1: " << s1 << endl << "s2: " << s2 << endl; 10211
cout << "compare s1 & s1: " 10212
<< equal(s1.begin(), s1.end(), s1.begin()) << endl; 10213
cout << "compare s1 & s2: " 10214
<< equal(s1.begin(), s1.end(), s2.begin()) << endl; 10215
cout << "lexicographical_compare s1 & s1: " 10216
393
<< lexicographical_compare(s1.begin(), s1.end(), 10217
s1.begin(), s1.end()) << endl; 10218
cout << "lexicographical_compare s1 & s2: " 10219
<< lexicographical_compare(s1.begin(), s1.end(), 10220
s2.begin(), s2.end()) << endl; 10221
cout << "lexicographical_compare s2 & s1: " 10222
<< lexicographical_compare(s2.begin(), s2.end(), 10223
s1.begin(), s1.end()) << endl; 10224
cout << "lexicographical_compare shortened " 10225
"s1 & full-length s2: " << endl; 10226
string s3(s1); 10227
while(s3.length() != 0) { 10228
bool result = lexicographical_compare( 10229
s3.begin(), s3.end(), s2.begin(),s2.end()); 10230
cout << s3 << endl << s2 << ", result = " 10231
<< result << endl; 10232
if(result == true) break; 10233
s3 = s3.substr(0, s3.length() - 1); 10234
} 10235
pair<string::iterator, string::iterator> p = 10236
mismatch(s1.begin(), s1.end(), s2.begin()); 10237
print(p.first, s1.end(), "p.first", ""); 10238
print(p.second, s2.end(), "p.second",""); 10239
} ///:~ 10240
394
Listado 7.34. C06/Comparison.cpp 10241
10242
7.3.7. Eliminación de elementos 10243
Ejemplo 10244
//: C06:Removing.cpp 10245
// The removing algorithms. 10246
//{L} Generators 10247
#include <algorithm> 10248
#include <cctype> 10249
#include <string> 10250
#include "Generators.h" 10251
#include "PrintSequence.h" 10252
using namespace std; 10253
10254
struct IsUpper { 10255
bool operator()(char c) { return isupper(c); } 10256
}; 10257
10258
int main() { 10259
string v; 10260
v.resize(25); 10261
generate(v.begin(), v.end(), CharGen()); 10262
print(v.begin(), v.end(), "v original", ""); 10263
// Create a set of the characters in v: 10264
string us(v.begin(), v.end()); 10265
395
sort(us.begin(), us.end()); 10266
string::iterator it = us.begin(), cit = v.end(), 10267
uend = unique(us.begin(), us.end()); 10268
// Step through and remove everything: 10269
while(it != uend) { 10270
cit = remove(v.begin(), cit, *it); 10271
print(v.begin(), v.end(), "Complete v", ""); 10272
print(v.begin(), cit, "Pseudo v ", " "); 10273
cout << "Removed element:\t" << *it 10274
<< "\nPsuedo Last Element:\t" 10275
<< *cit << endl << endl; 10276
++it; 10277
} 10278
generate(v.begin(), v.end(), CharGen()); 10279
print(v.begin(), v.end(), "v", ""); 10280
cit = remove_if(v.begin(), v.end(), IsUpper()); 10281
print(v.begin(), cit, "v after remove_if IsUpper", " "); 10282
// Copying versions are not shown for remove() 10283
// and remove_if(). 10284
sort(v.begin(), cit); 10285
print(v.begin(), cit, "sorted", " "); 10286
string v2; 10287
v2.resize(cit - v.begin()); 10288
unique_copy(v.begin(), cit, v2.begin()); 10289
print(v2.begin(), v2.end(), "unique_copy", " "); 10290
396
// Same behavior: 10291
cit = unique(v.begin(), cit, equal_to<char>()); 10292
print(v.begin(), cit, "unique equal_to<char>", " "); 10293
} ///:~ 10294
Listado 7.35. C06/Removing.cpp 10295
10296
7.3.8. Ordenación y operación sobre rangos ordenados 10297
Ordenación 10298
Ejemplo 10299
//: C06:SortedSearchTest.cpp 10300
// Test searching in sorted ranges. 10301
// NString 10302
#include <algorithm> 10303
#include <cassert> 10304
#include <ctime> 10305
#include <cstdlib> 10306
#include <cstddef> 10307
#include <fstream> 10308
#include <iostream> 10309
#include <iterator> 10310
#include <vector> 10311
#include "NString.h" 10312
#include "PrintSequence.h" 10313
397
#include "../require.h" 10314
using namespace std; 10315
10316
int main(int argc, char* argv[]) { 10317
typedef vector<NString>::iterator sit; 10318
char* fname = "Test.txt"; 10319
if(argc > 1) fname = argv[1]; 10320
ifstream in(fname); 10321
assure(in, fname); 10322
srand(time(0)); 10323
cout.setf(ios::boolalpha); 10324
vector<NString> original; 10325
copy(istream_iterator<string>(in), 10326
istream_iterator<string>(), back_inserter(original)); 10327
require(original.size() >= 4, "Must have four elements"); 10328
vector<NString> v(original.begin(), original.end()), 10329
w(original.size() / 2); 10330
sort(v.begin(), v.end()); 10331
print(v.begin(), v.end(), "sort"); 10332
v = original; 10333
stable_sort(v.begin(), v.end()); 10334
print(v.begin(), v.end(), "stable_sort"); 10335
v = original; 10336
sit it = v.begin(), it2; 10337
// Move iterator to middle 10338
398
for(size_t i = 0; i < v.size() / 2; i++) 10339
++it; 10340
partial_sort(v.begin(), it, v.end()); 10341
cout << "middle = " << *it << endl; 10342
print(v.begin(), v.end(), "partial_sort"); 10343
v = original; 10344
// Move iterator to a quarter position 10345
it = v.begin(); 10346
for(size_t i = 0; i < v.size() / 4; i++) 10347
++it; 10348
// Less elements to copy from than to the destination 10349
partial_sort_copy(v.begin(), it, w.begin(), w.end()); 10350
print(w.begin(), w.end(), "partial_sort_copy"); 10351
// Not enough room in destination 10352
partial_sort_copy(v.begin(), v.end(), w.begin(),w.end()); 10353
print(w.begin(), w.end(), "w partial_sort_copy"); 10354
// v remains the same through all this process 10355
assert(v == original); 10356
nth_element(v.begin(), it, v.end()); 10357
cout << "The nth_element = " << *it << endl; 10358
print(v.begin(), v.end(), "nth_element"); 10359
string f = original[rand() % original.size()]; 10360
cout << "binary search: " 10361
<< binary_search(v.begin(), v.end(), f) << endl; 10362
sort(v.begin(), v.end()); 10363
399
it = lower_bound(v.begin(), v.end(), f); 10364
it2 = upper_bound(v.begin(), v.end(), f); 10365
print(it, it2, "found range"); 10366
pair<sit, sit> ip = equal_range(v.begin(), v.end(), f); 10367
print(ip.first, ip.second, "equal_range"); 10368
} ///:~ 10369
Listado 7.36. C06/SortedSearchTest.cpp 10370
10371
Mezcla de rangos ordenados 10372
Ejemplo 10373
//: C06:MergeTest.cpp 10374
// Test merging in sorted ranges. 10375
//{L} Generators 10376
#include <algorithm> 10377
#include "PrintSequence.h" 10378
#include "Generators.h" 10379
using namespace std; 10380
10381
int main() { 10382
const int SZ = 15; 10383
int a[SZ*2] = {0}; 10384
// Both ranges go in the same array: 10385
generate(a, a + SZ, SkipGen(0, 2)); 10386
400
a[3] = 4; 10387
a[4] = 4; 10388
generate(a + SZ, a + SZ*2, SkipGen(1, 3)); 10389
print(a, a + SZ, "range1", " "); 10390
print(a + SZ, a + SZ*2, "range2", " "); 10391
int b[SZ*2] = {0}; // Initialize all to zero 10392
merge(a, a + SZ, a + SZ, a + SZ*2, b); 10393
print(b, b + SZ*2, "merge", " "); 10394
// Reset b 10395
for(int i = 0; i < SZ*2; i++) 10396
b[i] = 0; 10397
inplace_merge(a, a + SZ, a + SZ*2); 10398
print(a, a + SZ*2, "inplace_merge", " "); 10399
int* end = set_union(a, a + SZ, a + SZ, a + SZ*2, b); 10400
print(b, end, "set_union", " "); 10401
} ///:~ 10402
Listado 7.37. C06/MergeTest.cpp 10403
10404
Ejemplo 10405
//: C06:SetOperations.cpp 10406
// Set operations on sorted ranges. 10407
//{L} Generators 10408
#include <algorithm> 10409
401
#include <vector> 10410
#include "Generators.h" 10411
#include "PrintSequence.h" 10412
using namespace std; 10413
10414
int main() { 10415
const int SZ = 30; 10416
char v[SZ + 1], v2[SZ + 1]; 10417
CharGen g; 10418
generate(v, v + SZ, g); 10419
generate(v2, v2 + SZ, g); 10420
sort(v, v + SZ); 10421
sort(v2, v2 + SZ); 10422
print(v, v + SZ, "v", ""); 10423
print(v2, v2 + SZ, "v2", ""); 10424
bool b = includes(v, v + SZ, v + SZ/2, v + SZ); 10425
cout.setf(ios::boolalpha); 10426
cout << "includes: " << b << endl; 10427
char v3[SZ*2 + 1], *end; 10428
end = set_union(v, v + SZ, v2, v2 + SZ, v3); 10429
print(v3, end, "set_union", ""); 10430
end = set_intersection(v, v + SZ, v2, v2 + SZ, v3); 10431
print(v3, end, "set_intersection", ""); 10432
end = set_difference(v, v + SZ, v2, v2 + SZ, v3); 10433
print(v3, end, "set_difference", ""); 10434
402
end = set_symmetric_difference(v, v + SZ, 10435
v2, v2 + SZ, v3); 10436
print(v3, end, "set_symmetric_difference",""); 10437
} ///:~ 10438
Listado 7.38. C06/SetOperations.cpp 10439
10440
7.3.9. Operaciones sobre el montículo 10441
7.3.10. Aplicando una operación a cada elemento de un rango 10442
Ejemplos 10443
//: C06:Counted.h 10444
// An object that keeps track of itself. 10445
#ifndef COUNTED_H 10446
#define COUNTED_H 10447
#include <vector> 10448
#include <iostream> 10449
10450
class Counted { 10451
static int count; 10452
char* ident; 10453
public: 10454
Counted(char* id) : ident(id) { ++count; } 10455
~Counted() { 10456
std::cout << ident << " count = " 10457
403
<< --count << std::endl; 10458
} 10459
}; 10460
10461
class CountedVector : public std::vector<Counted*> { 10462
public: 10463
CountedVector(char* id) { 10464
for(int i = 0; i < 5; i++) 10465
push_back(new Counted(id)); 10466
} 10467
}; 10468
#endif // COUNTED_H ///:~ 10469
Listado 7.39. C06/Counted.h 10470
10471
//: C06:ForEach.cpp {-mwcc} 10472
// Use of STL for_each() algorithm. 10473
//{L} Counted 10474
#include <algorithm> 10475
#include <iostream> 10476
#include "Counted.h" 10477
using namespace std; 10478
10479
// Function object: 10480
template<class T> class DeleteT { 10481
404
public: 10482
void operator()(T* x) { delete x; } 10483
}; 10484
10485
// Template function: 10486
template<class T> void wipe(T* x) { delete x; } 10487
10488
int main() { 10489
CountedVector B("two"); 10490
for_each(B.begin(), B.end(), DeleteT<Counted>()); 10491
CountedVector C("three"); 10492
for_each(C.begin(), C.end(), wipe<Counted>); 10493
} ///:~ 10494
Listado 7.40. C06/ForEach.cpp 10495
10496
//: C06:Transform.cpp {-mwcc} 10497
// Use of STL transform() algorithm. 10498
//{L} Counted 10499
#include <iostream> 10500
#include <vector> 10501
#include <algorithm> 10502
#include "Counted.h" 10503
using namespace std; 10504
10505
405
template<class T> T* deleteP(T* x) { delete x; return 0; } 10506
10507
template<class T> struct Deleter { 10508
T* operator()(T* x) { delete x; return 0; } 10509
}; 10510
10511
int main() { 10512
CountedVector cv("one"); 10513
transform(cv.begin(), cv.end(), cv.begin(), 10514
deleteP<Counted>); 10515
CountedVector cv2("two"); 10516
transform(cv2.begin(), cv2.end(), cv2.begin(), 10517
Deleter<Counted>()); 10518
} ///:~ 10519
Listado 7.41. C06/Transform.cpp 10520
10521
//: C06:Inventory.h 10522
#ifndef INVENTORY_H 10523
#define INVENTORY_H 10524
#include <iostream> 10525
#include <cstdlib> 10526
using std::rand; 10527
10528
class Inventory { 10529
406
char item; 10530
int quantity; 10531
int value; 10532
public: 10533
Inventory(char it, int quant, int val) 10534
: item(it), quantity(quant), value(val) {} 10535
// Synthesized operator= & copy-constructor OK 10536
char getItem() const { return item; } 10537
int getQuantity() const { return quantity; } 10538
void setQuantity(int q) { quantity = q; } 10539
int getValue() const { return value; } 10540
void setValue(int val) { value = val; } 10541
friend std::ostream& operator<<( 10542
std::ostream& os, const Inventory& inv) { 10543
return os << inv.item << ": " 10544
<< "quantity " << inv.quantity 10545
<< ", value " << inv.value; 10546
} 10547
}; 10548
10549
// A generator: 10550
struct InvenGen { 10551
Inventory operator()() { 10552
static char c = 'a'; 10553
int q = rand() % 100; 10554
407
int v = rand() % 500; 10555
return Inventory(c++, q, v); 10556
} 10557
}; 10558
#endif // INVENTORY_H ///:~ 10559
Listado 7.42. C06/Inventory.h 10560
10561
//: C06:CalcInventory.cpp 10562
// More use of for_each(). 10563
#include <algorithm> 10564
#include <ctime> 10565
#include <vector> 10566
#include "Inventory.h" 10567
#include "PrintSequence.h" 10568
using namespace std; 10569
10570
// To calculate inventory totals: 10571
class InvAccum { 10572
int quantity; 10573
int value; 10574
public: 10575
InvAccum() : quantity(0), value(0) {} 10576
void operator()(const Inventory& inv) { 10577
quantity += inv.getQuantity(); 10578
408
value += inv.getQuantity() * inv.getValue(); 10579
} 10580
friend ostream& 10581
operator<<(ostream& os, const InvAccum& ia) { 10582
return os << "total quantity: " << ia.quantity 10583
<< ", total value: " << ia.value; 10584
} 10585
}; 10586
10587
int main() { 10588
vector<Inventory> vi; 10589
srand(time(0)); // Randomize 10590
generate_n(back_inserter(vi), 15, InvenGen()); 10591
print(vi.begin(), vi.end(), "vi"); 10592
InvAccum ia = for_each(vi.begin(),vi.end(), InvAccum()); 10593
cout << ia << endl; 10594
} ///:~ 10595
Listado 7.43. C06/CalcInventory.cpp 10596
10597
//: C06:TransformNames.cpp 10598
// More use of transform(). 10599
#include <algorithm> 10600
#include <cctype> 10601
#include <ctime> 10602
409
#include <vector> 10603
#include "Inventory.h" 10604
#include "PrintSequence.h" 10605
using namespace std; 10606
10607
struct NewImproved { 10608
Inventory operator()(const Inventory& inv) { 10609
return Inventory(toupper(inv.getItem()), 10610
inv.getQuantity(), inv.getValue()); 10611
} 10612
}; 10613
10614
int main() { 10615
vector<Inventory> vi; 10616
srand(time(0)); // Randomize 10617
generate_n(back_inserter(vi), 15, InvenGen()); 10618
print(vi.begin(), vi.end(), "vi"); 10619
transform(vi.begin(),vi.end(),vi.begin(),NewImproved()); 10620
print(vi.begin(), vi.end(), "vi"); 10621
} ///:~ 10622
Listado 7.44. C06/TransformNames.cpp 10623
10624
//: C06:SpecialList.cpp 10625
// Using the second version of transform(). 10626
410
#include <algorithm> 10627
#include <ctime> 10628
#include <vector> 10629
#include "Inventory.h" 10630
#include "PrintSequence.h" 10631
using namespace std; 10632
10633
struct Discounter { 10634
Inventory operator()(const Inventory& inv, 10635
float discount) { 10636
return Inventory(inv.getItem(), inv.getQuantity(), 10637
int(inv.getValue() * (1 - discount))); 10638
} 10639
}; 10640
10641
struct DiscGen { 10642
float operator()() { 10643
float r = float(rand() % 10); 10644
return r / 100.0; 10645
} 10646
}; 10647
10648
int main() { 10649
vector<Inventory> vi; 10650
srand(time(0)); // Randomize 10651
411
generate_n(back_inserter(vi), 15, InvenGen()); 10652
print(vi.begin(), vi.end(), "vi"); 10653
vector<float> disc; 10654
generate_n(back_inserter(disc), 15, DiscGen()); 10655
print(disc.begin(), disc.end(), "Discounts:"); 10656
vector<Inventory> discounted; 10657
transform(vi.begin(),vi.end(), disc.begin(), 10658
back_inserter(discounted), Discounter()); 10659
print(discounted.begin(), discounted.end(),"discounted"); 10660
} ///:~ 10661
Listado 7.45. C06/SpecialList.cpp 10662
10663
7.3.11. Algoritmos numéricos 10664
Ejemplo 10665
//: C06:NumericTest.cpp 10666
#include <algorithm> 10667
#include <iostream> 10668
#include <iterator> 10669
#include <functional> 10670
#include <numeric> 10671
#include "PrintSequence.h" 10672
using namespace std; 10673
10674
412
int main() { 10675
int a[] = { 1, 1, 2, 2, 3, 5, 7, 9, 11, 13 }; 10676
const int ASZ = sizeof a / sizeof a[0]; 10677
print(a, a + ASZ, "a", " "); 10678
int r = accumulate(a, a + ASZ, 0); 10679
cout << "accumulate 1: " << r << endl; 10680
// Should produce the same result: 10681
r = accumulate(a, a + ASZ, 0, plus<int>()); 10682
cout << "accumulate 2: " << r << endl; 10683
int b[] = { 1, 2, 3, 4, 1, 2, 3, 4, 1, 2 }; 10684
print(b, b + sizeof b / sizeof b[0], "b", " "); 10685
r = inner_product(a, a + ASZ, b, 0); 10686
cout << "inner_product 1: " << r << endl; 10687
// Should produce the same result: 10688
r = inner_product(a, a + ASZ, b, 0, 10689
plus<int>(), multiplies<int>()); 10690
cout << "inner_product 2: " << r << endl; 10691
int* it = partial_sum(a, a + ASZ, b); 10692
print(b, it, "partial_sum 1", " "); 10693
// Should produce the same result: 10694
it = partial_sum(a, a + ASZ, b, plus<int>()); 10695
print(b, it, "partial_sum 2", " "); 10696
it = adjacent_difference(a, a + ASZ, b); 10697
print(b, it, "adjacent_difference 1"," "); 10698
// Should produce the same result: 10699
413
it = adjacent_difference(a, a + ASZ, b, minus<int>()); 10700
print(b, it, "adjacent_difference 2"," "); 10701
} ///:~ 10702
Listado 7.46. C06/NumericTest.cpp 10703
10704
7.3.12. Utilidades generales 10705
7.4. Creando sus propios algoritmos tipo STL 10706
// Assumes pred is the incoming condition 10707
replace_copy_if(begin, end, not1(pred)); 10708
//: C06:copy_if.h 10709
// Create your own STL-style algorithm. 10710
#ifndef COPY_IF_H 10711
#define COPY_IF_H 10712
10713
template<typename ForwardIter, 10714
typename OutputIter, typename UnaryPred> 10715
OutputIter copy_if(ForwardIter begin, ForwardIter end, 10716
OutputIter dest, UnaryPred f) { 10717
while(begin != end) { 10718
if(f(*begin)) 10719
*dest++ = *begin; 10720
++begin; 10721
414
} 10722
return dest; 10723
} 10724
#endif // COPY_IF_H ///:~ 10725
Listado 7.47. C06/copy_if.h 10726
10727
7.5. Resumen 10728
7.6. Ejercicios 10729
10730
10731
[17] N. de T.: Librería Estándar de Plantillas. 10732
Parte III. Temas especiales 10733
La marca de un profesional aparece en su atención a los detalles más 10734
finos del oficio. En esta sección del libro veremos características 10735
avanzadas de C++ junto con técnicas de desarrollo usadas por 10736
profesionales brillantes de C++. 10737
A veces necesita salir de los convencionalismos que suenan a diseño 10738
orientado a objetos, inspeccionando el tipo de un objeto en tiempo de 10739
ejecución. La mayoría de las veces debería dejar que las funciones virtuales 10740
hagan ese trabajo por usted, pero cuando escriba herramientas software 10741
para propósitos especiales, tales como depuradores, visualizadores de bases 10742
de datos, o navegadores de clases, necesitará determinar la información de 10743
tipado en tiempo de ejecución. Ahí es cuando el mecanismo de identificación 10744
de tipo en tiempo de ejecución (RTTI) resulta útil. RTTI es el tema del 10745
Capítulo 8. 10746
415
La herencia múltiple ha sido maltratado a lo largo de los años, y algunos 10747
lenguajes incluso no la permiten. No obstante, cuando se usa 10748
adecuadamente, puede ser una herramienta potente para conseguir código 10749
eficiente y elegante. Un buen número de prácticas estándar que involucran 10750
herencia múltiple han evolucionado con el tiempo; las veremos en el Capítulo 10751
9. 10752
Quizás la innovación más notable en el desarrollo de software desde las 10753
técnicas de orientación a objetos es el uso de los patrones de diseño. Un 10754
patrón de diseño describe soluciones para muchos problemas comunes del 10755
diseño de software, y se puede aplicar a muchas situaciones e 10756
implementación en cualquier lenguaje. En el Capítulo 10 describiremos una 10757
selección de patrones de diseño y los implementaremos en C++. 10758
El Capítulo 11 explica los beneficios y desafíos de la programación 10759
multihilo. La versión actual de C++ Estándar no especifica soporte para hilos, 10760
aunque la mayoría de los sistema operativos los ofrecen. Usaremos un 10761
librería portable y disponible libremente para ilustrar cómo los programadores 10762
pueden sacar provecho de los hilos para construir aplicaciones más usables 10763
y receptivas. 10764
Tabla de contenidos 10765
8. Herencia múltiple 10766
8.1. Perspectiva 10767
8.2. Herencia de interfaces 10768
8.3. Herencia de implementación 10769
8.4. Subobjetos duplicados 10770
8.5. Clases base virtuales 10771
8.6. Cuestión sobre búsqueda de nombres 10772
8.7. Evitar la MI 10773
8.8. Extender una interface 10774
8.9. Resumen 10775
8.10. Ejercicios 10776
9. Patrones de Diseño 10777
9.1. El Concepto de Patrón 10778
9.2. Clasificación de los patrones 10779
9.3. Simplificación de modismos 10780
9.4. Singleton 10781
9.5. Comando: elegir la operación 10782
9.6. Desacoplamiento de objetos 10783
9.7. Adaptador 10784
9.8. Template Method 10785
9.9. Estrategia: elegir el algoritno en tiempo de ejecución 10786
416
9.10. Cadena de Responsabilidad: intentar una secuencia de 10787
estrategias 10788
9.11. Factorías: encapsular la creación de objetos 10789
9.12. Builder: creación de objetos complejos 10790
9.13. Observador 10791
9.14. Despachado múltiple 10792
9.15. Resumen 10793
9.16. Ejercicios 10794
10. Concurrencia 10795
10.1. Motivación 10796
10.2. Concurrencia en C++ 10797
10.3. Utilización de los hilos 10798
10.4. Comparición de recursos limitados 10799
10.5. Finalización de tareas 10800
10.6. Cooperación entre hilos 10801
10.7. Bloqueo letal 10802
10.8. Resumen 10803
10.9. Ejercicios 10804
8: Herencia múltiple 10805
Tabla de contenidos 10806
8.1. Perspectiva 10807
8.2. Herencia de interfaces 10808
8.3. Herencia de implementación 10809
8.4. Subobjetos duplicados 10810
8.5. Clases base virtuales 10811
8.6. Cuestión sobre búsqueda de nombres 10812
8.7. Evitar la MI 10813
8.8. Extender una interface 10814
8.9. Resumen 10815
8.10. Ejercicios 10816
El concepto básico de la herencia múltiple (HM) suena bastante simple: 10817
puede crear un nuevo tipo heredando de más una una clase base. La 10818
sintaxis es exactamente la que espera, y en la medida en que los diagramas 10819
de herencia sean simples, la HM puede ser simple también. 10820
Sin embargo, la HM puede presentar un buen número de situaciones 10821
ambiguas y extrañas, que se cubren en este capítulo. Pero primero, es útil 10822
tener algo de perspectiva sobre el asunto. 10823
8.1. Perspectiva 10824
Antes de C++, el lenguaje orientado a objetos más popular era Smaltalk. 10825
Smaltalk fue creado desde cero como un lenguaje orientado a objetos. A 10826
417
menudo se dice que es puro, mientras que a C++ se le llama lenguaje híbrido 10827
porque soporta múltiples paradigmas de programación, no sólo el paradigma 10828
orientado a objeto. Uno de las decisiones de diseño de Smalltalk fue que 10829
todas las clases tendrían solo herencia simple, empezando en una clase 10830
base (llamada Object - ese es el modelo para la jerarquía basada en 10831
objetos) [18] En Smalltalk no puede crear una nueva clase sin derivar de un 10832
clase existente, que es la razón por la que lleva cierto tiempo ser productivo 10833
con Smalltalk: debe aprender la librería de clases antes de empezar a hacer 10834
clases nuevas. La jerarquía de clases de Smalltalk es por tanto un único 10835
árbol monolítico. 10836
Las clases de Smalltalk normalmente tienen ciertas cosas en común, y 10837
siempre tienen algunas cosas en común (las características y el 10838
comportamiento de Object), de modo que no suelen aparecer situaciones en 10839
las que se necesite heredad de más de una clase base. Sin embargo, con 10840
C++ puede crear tantos árboles de herencia distintos como quiera. Por 10841
completitud lógica el lenguaje debe ser capaz de combinar más de una clase 10842
a la vez - por eso la necesidad de herencia múltiple. 10843
No fue obvio, sin embargo, que los programadores requiriesen herencia 10844
múltiple, y había (y sigue habiendo) mucha discrepancia sobre si es algo 10845
esencial en C++. La HM fue añadida en cfront release 2.0 de AT&T en 1989 10846
y fue el primer cambio significativo en el lenguaje desde la versión 1.0. [19] 10847
Desde entonces, se han añadido muchas características al Estándar C++ 10848
(las plantillas son dignas de mención) que cambian la manera de pensar al 10849
programar y le dan a la HM un papel mucho menos importante. Puede 10850
pensar en la HM como una prestación menor del lenguaje que raramente 10851
está involucrada en las decisiones de diseño diarias. 10852
Uno de los argumentos más convincentes para la HM involucra a los 10853
contenedores. Suponga que quiere crear un contenedor que todo el mundo 10854
pueda usar fácilmente. Una propuesta es usar void* como tipo para el 10855
contenido. La propuesta de Smalltalk, sin embargo, es hacer un contenedor 10856
que aloja Object, dado que Object es el tipo base de la jerarquía de 10857
Smalltalk. Como todo en Smalltalk está derivado de Object, un contenedor 10858
que aloja Objects puede contener cualquier cosa. 10859
418
Ahora considere la situación en C++. Suponga que el fabricante A crea 10860
una jerarquía basada-en-objetos que incluye un conjunto de contenedores 10861
incluye uno que desea usar llamado Holder. Después, se da cuenta de que 10862
la jerarquía de clases del fabricante B contiene alguna clase que también es 10863
importante para usted, una clase BitImage, por ejemplo, que contiene 10864
imágenes. La única forma de hacer que un Holder de BitImage es derivar de 10865
una nueva clase que derive también de Object, y así poder almacenarlos en 10866
el Holder, y BitImage: 10867
10868
Figura 8.1. 10869
10870
Éste fue un motivo importante para la HM, y muchas librerías de clases 10871
están hechas con este model. Sin embargo, tal como se vio en el Capítulo 5, 10872
la aportación de las plantillas ha cambiado la forma de crear contenedores, y 10873
por eso esta situación ya no es un asunto crucial en favor de la HM. 10874
El otro motivo por el que se necesita la HM está relacionado con el diseño. 10875
Puede usar la HM intencionadamente para hacer un diseño más flexible y útil 10876
(o al menos aparentarlo). Un ejemplo de esto es el diseño de la librería 10877
original iostream (que persiste hoy día, como vio en el Capítulo 4. 10878
10879
Figura 8.2. 10880
10881
Tanto iostream como ostream son clases útiles por si mismas, pero se 10882
pueden derivar simultáneamente por una clase que combina sus 10883
características y comportamientos. La clase ios proporciona una 10884
combinación de las dos clases, y por eso en este caso la HM es un 10885
mecanismo de FIXME:code-factoring. 10886
Sin importar lo que le motive a usar HM, debe saber que es más difícil de 10887
usar de lo que podría parecer. 10888
8.2. Herencia de interfaces 10889
419
Un uso no controvertido de la herencia múltiple es la herencia de interfaz. 10890
En C++, toda herencia lo es de implementación, dado que todo en una clase 10891
base, interface e implentación, pasa a formar parte de la clase derivada. No 10892
es posible heredar solo una parte de una clase (es decir, la interface 10893
únicamente). Tal como se explica en el [FIXME:enlace en la versión web] 10894
Capítulo 14 del volumen 1, es posible hacer herencia privada y protegida 10895
para restringir el acceso a los miembros heredados desde las clases base 10896
cuando se usa por clientes de instancias de una clase derivada, pero esto no 10897
afecta a la propia clase derivada; esa clase sigue conteniendo todos los 10898
datos de la clase base y puede acceder a todos los miembros no-privados de 10899
la clase base. 10900
La herencia de interfaces. por otra parte, sólo añade declaraciones de 10901
miembros a la interfaz de la clase derivada, algo que no está soportado 10902
directamente en C++. La técnica habitual para simular la herencia de interfaz 10903
en C++ es derivar de una clase interfaz, que es una clase que sólo contiene 10904
declaraciones (ni datos ni cuerpos de funciones). Estas declaraciones serán 10905
funciones virtuales puras, excepto el destructor. Aquí hay un ejemplo: 10906
//: C09:Interfaces.cpp 10907
// Multiple interface inheritance. 10908
#include <iostream> 10909
#include <sstream> 10910
#include <string> 10911
using namespace std; 10912
10913
class Printable { 10914
public: 10915
virtual ~Printable() {} 10916
virtual void print(ostream&) const = 0; 10917
}; 10918
420
10919
class Intable { 10920
public: 10921
virtual ~Intable() {} 10922
virtual int toInt() const = 0; 10923
}; 10924
10925
class Stringable { 10926
public: 10927
virtual ~Stringable() {} 10928
virtual string toString() const = 0; 10929
}; 10930
10931
class Able : public Printable, public Intable, 10932
public Stringable { 10933
int myData; 10934
public: 10935
Able(int x) { myData = x; } 10936
void print(ostream& os) const { os << myData; } 10937
int toInt() const { return myData; } 10938
string toString() const { 10939
ostringstream os; 10940
os << myData; 10941
return os.str(); 10942
} 10943
421
}; 10944
10945
void testPrintable(const Printable& p) { 10946
p.print(cout); 10947
cout << endl; 10948
} 10949
10950
void testIntable(const Intable& n) { 10951
cout << n.toInt() + 1 << endl; 10952
} 10953
10954
void testStringable(const Stringable& s) { 10955
cout << s.toString() + "th" << endl; 10956
} 10957
10958
int main() { 10959
Able a(7); 10960
testPrintable(a); 10961
testIntable(a); 10962
testStringable(a); 10963
} ///:~ 10964
Listado 8.1. C09/Interfaces.cpp 10965
10966
La clase Able «implementa» las interfaces Printable, Intable y 10967
Stringable dado que proporciona implementaciones para las funciones que 10968
éstas declaran. Dado que Able deriva de las tres clases, los objetos Able 10969
422
tienen múltiples relaciones «es-un». Por ejemplo, el objeto a puede actuar 10970
como un objeto Printable dado que su clase, Able, deriva públicamente de 10971
Printable y proporciona una implementación para print(). Las funciones de 10972
prueba no necesitan saber el tipo más derivado de su parámetro; sólo 10973
necesitan un objeto que sea substituible por el tipo de su parámetro. 10974
Como es habitual, una plantilla es una solución más compacta: 10975
//: C09:Interfaces2.cpp 10976
// Implicit interface inheritance via templates. 10977
#include <iostream> 10978
#include <sstream> 10979
#include <string> 10980
using namespace std; 10981
10982
class Able { 10983
int myData; 10984
public: 10985
Able(int x) { myData = x; } 10986
void print(ostream& os) const { os << myData; } 10987
int toInt() const { return myData; } 10988
string toString() const { 10989
ostringstream os; 10990
os << myData; 10991
return os.str(); 10992
} 10993
}; 10994
10995
423
template<class Printable> 10996
void testPrintable(const Printable& p) { 10997
p.print(cout); 10998
cout << endl; 10999
} 11000
11001
template<class Intable> 11002
void testIntable(const Intable& n) { 11003
cout << n.toInt() + 1 << endl; 11004
} 11005
11006
template<class Stringable> 11007
void testStringable(const Stringable& s) { 11008
cout << s.toString() + "th" << endl; 11009
} 11010
11011
int main() { 11012
Able a(7); 11013
testPrintable(a); 11014
testIntable(a); 11015
testStringable(a); 11016
} ///:~ 11017
Listado 8.2. C09/Interfaces2.cpp 11018
11019
424
Los nombres Printable, Intable y Stringable ahora no son mas que 11020
parámetros de la plantilla que asume la existencia de las operaciones 11021
indicadas en sus respectivos argumentos. En otras palabras, las funciones 11022
de prueba pueden aceptar argumentos de cualquier tipo que proporciona una 11023
definición de método con la signatura y tipo de retorno correctos. Hay gente 11024
que encuentra más cómoda la primera versión porque los nombres de tipo 11025
garantizan que las interfaces esperadas están implementadas. Otros están 11026
contentos con el hecho de que si las operaciones requeridas por las 11027
funciones de prueba no se satisfacen por los argumentos de la plantilla, el 11028
error puede ser capturado en la compilación. Esta segunda es una forma de 11029
comprobación de tipos técnicamente más débil que el primer enfoque 11030
(herencia), pero el efecto para el programador (y el programa) es el mismo. 11031
Se trata de una forma de comprobación débil de tipo que es aceptable para 11032
muchos de los programadores C++ de hoy en día. 11033
8.3. Herencia de implementación 11034
//: C09:Database.h 11035
// A prototypical resource class. 11036
#ifndef DATABASE_H 11037
#define DATABASE_H 11038
#include <iostream> 11039
#include <stdexcept> 11040
#include <string> 11041
11042
struct DatabaseError : std::runtime_error { 11043
DatabaseError(const std::string& msg) 11044
: std::runtime_error(msg) {} 11045
}; 11046
11047
425
class Database { 11048
std::string dbid; 11049
public: 11050
Database(const std::string& dbStr) : dbid(dbStr) {} 11051
virtual ~Database() {} 11052
void open() throw(DatabaseError) { 11053
std::cout << "Connected to " << dbid << std::endl; 11054
} 11055
void close() { 11056
std::cout << dbid << " closed" << std::endl; 11057
} 11058
// Other database functions... 11059
}; 11060
#endif // DATABASE_H ///:~ 11061
Listado 8.3. C09/Database.h 11062
11063
//: C09:UseDatabase.cpp 11064
#include "Database.h" 11065
11066
int main() { 11067
Database db("MyDatabase"); 11068
db.open(); 11069
// Use other db functions... 11070
db.close(); 11071
426
} 11072
/* Output: 11073
connected to MyDatabase 11074
MyDatabase closed 11075
*/ ///:~ 11076
Listado 8.4. C09/UseDatabase.cpp 11077
11078
//: C09:Countable.h 11079
// A "mixin" class. 11080
#ifndef COUNTABLE_H 11081
#define COUNTABLE_H 11082
#include <cassert> 11083
11084
class Countable { 11085
long count; 11086
protected: 11087
Countable() { count = 0; } 11088
virtual ~Countable() { assert(count == 0); } 11089
public: 11090
long attach() { return ++count; } 11091
long detach() { 11092
return (--count > 0) ? count : (delete this, 0); 11093
} 11094
long refCount() const { return count; } 11095
427
}; 11096
#endif // COUNTABLE_H ///:~ 11097
Listado 8.5. C09/Countable.h 11098
11099
//: C09:DBConnection.h 11100
// Uses a "mixin" class. 11101
#ifndef DBCONNECTION_H 11102
#define DBCONNECTION_H 11103
#include <cassert> 11104
#include <string> 11105
#include "Countable.h" 11106
#include "Database.h" 11107
using std::string; 11108
11109
class DBConnection : public Database, public Countable { 11110
DBConnection(const DBConnection&); // Disallow copy 11111
DBConnection& operator=(const DBConnection&); 11112
protected: 11113
DBConnection(const string& dbStr) throw(DatabaseError) 11114
: Database(dbStr) { open(); } 11115
~DBConnection() { close(); } 11116
public: 11117
static DBConnection* 11118
create(const string& dbStr) throw(DatabaseError) { 11119
428
DBConnection* con = new DBConnection(dbStr); 11120
con->attach(); 11121
assert(con->refCount() == 1); 11122
return con; 11123
} 11124
// Other added functionality as desired... 11125
}; 11126
#endif // DBCONNECTION_H ///:~ 11127
Listado 8.6. C09/DBConnection.h 11128
11129
//: C09:UseDatabase2.cpp 11130
// Tests the Countable "mixin" class. 11131
#include <cassert> 11132
#include "DBConnection.h" 11133
11134
class DBClient { 11135
DBConnection* db; 11136
public: 11137
DBClient(DBConnection* dbCon) { 11138
db = dbCon; 11139
db->attach(); 11140
} 11141
~DBClient() { db->detach(); } 11142
// Other database requests using db... 11143
429
}; 11144
11145
int main() { 11146
DBConnection* db = DBConnection::create("MyDatabase"); 11147
assert(db->refCount() == 1); 11148
DBClient c1(db); 11149
assert(db->refCount() == 2); 11150
DBClient c2(db); 11151
assert(db->refCount() == 3); 11152
// Use database, then release attach from original create 11153
db->detach(); 11154
assert(db->refCount() == 2); 11155
} ///:~ 11156
Listado 8.7. C09/UseDatabase2.cpp 11157
11158
//: C09:DBConnection2.h 11159
// A parameterized mixin. 11160
#ifndef DBCONNECTION2_H 11161
#define DBCONNECTION2_H 11162
#include <cassert> 11163
#include <string> 11164
#include "Database.h" 11165
using std::string; 11166
11167
430
template<class Counter> 11168
class DBConnection : public Database, public Counter { 11169
DBConnection(const DBConnection&); // Disallow copy 11170
DBConnection& operator=(const DBConnection&); 11171
protected: 11172
DBConnection(const string& dbStr) throw(DatabaseError) 11173
: Database(dbStr) { open(); } 11174
~DBConnection() { close(); } 11175
public: 11176
static DBConnection* create(const string& dbStr) 11177
throw(DatabaseError) { 11178
DBConnection* con = new DBConnection(dbStr); 11179
con->attach(); 11180
assert(con->refCount() == 1); 11181
return con; 11182
} 11183
// Other added functionality as desired... 11184
}; 11185
#endif // DBCONNECTION2_H ///:~ 11186
Listado 8.8. C09/DBConnection2.h 11187
11188
//: C09:UseDatabase3.cpp 11189
// Tests a parameterized "mixin" class. 11190
#include <cassert> 11191
431
#include "Countable.h" 11192
#include "DBConnection2.h" 11193
11194
class DBClient { 11195
DBConnection<Countable>* db; 11196
public: 11197
DBClient(DBConnection<Countable>* dbCon) { 11198
db = dbCon; 11199
db->attach(); 11200
} 11201
~DBClient() { db->detach(); } 11202
}; 11203
11204
int main() { 11205
DBConnection<Countable>* db = 11206
DBConnection<Countable>::create("MyDatabase"); 11207
assert(db->refCount() == 1); 11208
DBClient c1(db); 11209
assert(db->refCount() == 2); 11210
DBClient c2(db); 11211
assert(db->refCount() == 3); 11212
db->detach(); 11213
assert(db->refCount() == 2); 11214
} ///:~ 11215
432
Listado 8.9. C09/UseDatabase3.cpp 11216
11217
template<class Mixin1, class Mixin2, ?? , class MixinK> 11218
class Subject : public Mixin1, 11219
public Mixin2, 11220
?? 11221
public MixinK {??}; 11222
8.4. Subobjetos duplicados 11223
//: C09:Offset.cpp 11224
// Illustrates layout of subobjects with MI. 11225
#include <iostream> 11226
using namespace std; 11227
11228
class A { int x; }; 11229
class B { int y; }; 11230
class C : public A, public B { int z; }; 11231
11232
int main() { 11233
cout << "sizeof(A) == " << sizeof(A) << endl; 11234
cout << "sizeof(B) == " << sizeof(B) << endl; 11235
cout << "sizeof(C) == " << sizeof(C) << endl; 11236
C c; 11237
cout << "&c == " << &c << endl; 11238
433
A* ap = &c; 11239
B* bp = &c; 11240
cout << "ap == " << static_cast<void*>(ap) << endl; 11241
cout << "bp == " << static_cast<void*>(bp) << endl; 11242
C* cp = static_cast<C*>(bp); 11243
cout << "cp == " << static_cast<void*>(cp) << endl; 11244
cout << "bp == cp? " << boolalpha << (bp == cp) << endl; 11245
cp = 0; 11246
bp = cp; 11247
cout << bp << endl; 11248
} 11249
/* Output: 11250
sizeof(A) == 4 11251
sizeof(B) == 4 11252
sizeof(C) == 12 11253
&c == 1245052 11254
ap == 1245052 11255
bp == 1245056 11256
cp == 1245052 11257
bp == cp? true 11258
0 11259
*/ ///:~ 11260
Listado 8.10. C09/Offset.cpp 11261
11262
434
//: C09:Duplicate.cpp 11263
// Shows duplicate subobjects. 11264
#include <iostream> 11265
using namespace std; 11266
11267
class Top { 11268
int x; 11269
public: 11270
Top(int n) { x = n; } 11271
}; 11272
11273
class Left : public Top { 11274
int y; 11275
public: 11276
Left(int m, int n) : Top(m) { y = n; } 11277
}; 11278
11279
class Right : public Top { 11280
int z; 11281
public: 11282
Right(int m, int n) : Top(m) { z = n; } 11283
}; 11284
11285
class Bottom : public Left, public Right { 11286
int w; 11287
435
public: 11288
Bottom(int i, int j, int k, int m) 11289
: Left(i, k), Right(j, k) { w = m; } 11290
}; 11291
11292
int main() { 11293
Bottom b(1, 2, 3, 4); 11294
cout << sizeof b << endl; // 20 11295
} ///:~ 11296
Listado 8.11. C09/Duplicate.cpp 11297
11298
8.5. Clases base virtuales 11299
//: C09:VirtualBase.cpp 11300
// Shows a shared subobject via a virtual base. 11301
#include <iostream> 11302
using namespace std; 11303
11304
class Top { 11305
protected: 11306
int x; 11307
public: 11308
Top(int n) { x = n; } 11309
virtual ~Top() {} 11310
436
friend ostream& 11311
operator<<(ostream& os, const Top& t) { 11312
return os << t.x; 11313
} 11314
}; 11315
11316
class Left : virtual public Top { 11317
protected: 11318
int y; 11319
public: 11320
Left(int m, int n) : Top(m) { y = n; } 11321
}; 11322
11323
class Right : virtual public Top { 11324
protected: 11325
int z; 11326
public: 11327
Right(int m, int n) : Top(m) { z = n; } 11328
}; 11329
11330
class Bottom : public Left, public Right { 11331
int w; 11332
public: 11333
Bottom(int i, int j, int k, int m) 11334
: Top(i), Left(0, j), Right(0, k) { w = m; } 11335
437
friend ostream& 11336
operator<<(ostream& os, const Bottom& b) { 11337
return os << b.x << ',' << b.y << ',' << b.z 11338
<< ',' << b.w; 11339
} 11340
}; 11341
11342
int main() { 11343
Bottom b(1, 2, 3, 4); 11344
cout << sizeof b << endl; 11345
cout << b << endl; 11346
cout << static_cast<void*>(&b) << endl; 11347
Top* p = static_cast<Top*>(&b); 11348
cout << *p << endl; 11349
cout << static_cast<void*>(p) << endl; 11350
cout << dynamic_cast<void*>(p) << endl; 11351
} ///:~ 11352
Listado 8.12. C09/VirtualBase.cpp 11353
11354
36 11355 1,2,3,4 11356 1245032 11357
1 11358 1245060 11359 1245032 11360
//: C09:VirtualBase2.cpp 11361
// How NOT to implement operator<<. 11362
438
#include <iostream> 11363
using namespace std; 11364
11365
class Top { 11366
int x; 11367
public: 11368
Top(int n) { x = n; } 11369
virtual ~Top() {} 11370
friend ostream& operator<<(ostream& os, const Top& t) { 11371
return os << t.x; 11372
} 11373
}; 11374
11375
class Left : virtual public Top { 11376
int y; 11377
public: 11378
Left(int m, int n) : Top(m) { y = n; } 11379
friend ostream& operator<<(ostream& os, const Left& l) { 11380
return os << static_cast<const Top&>(l) << ',' << l.y; 11381
} 11382
}; 11383
11384
class Right : virtual public Top { 11385
int z; 11386
public: 11387
439
Right(int m, int n) : Top(m) { z = n; } 11388
friend ostream& operator<<(ostream& os, const Right& r) { 11389
return os << static_cast<const Top&>(r) << ',' << r.z; 11390
} 11391
}; 11392
11393
class Bottom : public Left, public Right { 11394
int w; 11395
public: 11396
Bottom(int i, int j, int k, int m) 11397
: Top(i), Left(0, j), Right(0, k) { w = m; } 11398
friend ostream& operator<<(ostream& os, const Bottom& b){ 11399
return os << static_cast<const Left&>(b) 11400
<< ',' << static_cast<const Right&>(b) 11401
<< ',' << b.w; 11402
} 11403
}; 11404
11405
int main() { 11406
Bottom b(1, 2, 3, 4); 11407
cout << b << endl; // 1,2,1,3,4 11408
} ///:~ 11409
Listado 8.13. C09/VirtualBase2.cpp 11410
11411
440
//: C09:VirtualBase3.cpp 11412
// A correct stream inserter. 11413
#include <iostream> 11414
using namespace std; 11415
11416
class Top { 11417
int x; 11418
public: 11419
Top(int n) { x = n; } 11420
virtual ~Top() {} 11421
friend ostream& operator<<(ostream& os, const Top& t) { 11422
return os << t.x; 11423
} 11424
}; 11425
11426
class Left : virtual public Top { 11427
int y; 11428
protected: 11429
void specialPrint(ostream& os) const { 11430
// Only print Left's part 11431
os << ','<< y; 11432
} 11433
public: 11434
Left(int m, int n) : Top(m) { y = n; } 11435
friend ostream& operator<<(ostream& os, const Left& l) { 11436
441
return os << static_cast<const Top&>(l) << ',' << l.y; 11437
} 11438
}; 11439
11440
class Right : virtual public Top { 11441
int z; 11442
protected: 11443
void specialPrint(ostream& os) const { 11444
// Only print Right's part 11445
os << ','<< z; 11446
} 11447
public: 11448
Right(int m, int n) : Top(m) { z = n; } 11449
friend ostream& operator<<(ostream& os, const Right& r) { 11450
return os << static_cast<const Top&>(r) << ',' << r.z; 11451
} 11452
}; 11453
11454
class Bottom : public Left, public Right { 11455
int w; 11456
public: 11457
Bottom(int i, int j, int k, int m) 11458
: Top(i), Left(0, j), Right(0, k) { w = m; } 11459
friend ostream& operator<<(ostream& os, const Bottom& b){ 11460
os << static_cast<const Top&>(b); 11461
442
b.Left::specialPrint(os); 11462
b.Right::specialPrint(os); 11463
return os << ',' << b.w; 11464
} 11465
}; 11466
11467
int main() { 11468
Bottom b(1, 2, 3, 4); 11469
cout << b << endl; // 1,2,3,4 11470
} ///:~ 11471
Listado 8.14. C09/VirtualBase3.cpp 11472
11473
//: C09:VirtInit.cpp 11474
// Illustrates initialization order with virtual bases. 11475
#include <iostream> 11476
#include <string> 11477
using namespace std; 11478
11479
class M { 11480
public: 11481
M(const string& s) { cout << "M " << s << endl; } 11482
}; 11483
11484
class A { 11485
443
M m; 11486
public: 11487
A(const string& s) : m("in A") { 11488
cout << "A " << s << endl; 11489
} 11490
virtual ~A() {} 11491
}; 11492
11493
class B { 11494
M m; 11495
public: 11496
B(const string& s) : m("in B") { 11497
cout << "B " << s << endl; 11498
} 11499
virtual ~B() {} 11500
}; 11501
11502
class C { 11503
M m; 11504
public: 11505
C(const string& s) : m("in C") { 11506
cout << "C " << s << endl; 11507
} 11508
virtual ~C() {} 11509
}; 11510
444
11511
class D { 11512
M m; 11513
public: 11514
D(const string& s) : m("in D") { 11515
cout << "D " << s << endl; 11516
} 11517
virtual ~D() {} 11518
}; 11519
11520
class E : public A, virtual public B, virtual public C { 11521
M m; 11522
public: 11523
E(const string& s) : A("from E"), B("from E"), 11524
C("from E"), m("in E") { 11525
cout << "E " << s << endl; 11526
} 11527
}; 11528
11529
class F : virtual public B, virtual public C, public D { 11530
M m; 11531
public: 11532
F(const string& s) : B("from F"), C("from F"), 11533
D("from F"), m("in F") { 11534
cout << "F " << s << endl; 11535
445
} 11536
}; 11537
11538
class G : public E, public F { 11539
M m; 11540
public: 11541
G(const string& s) : B("from G"), C("from G"), 11542
E("from G"), F("from G"), m("in G") { 11543
cout << "G " << s << endl; 11544
} 11545
}; 11546
11547
int main() { 11548
G g("from main"); 11549
} ///:~ 11550
Listado 8.15. C09/VirtInit.cpp 11551
11552
M in B 11553 B from G 11554 M in C 11555
C from G 11556 M in A 11557
A from E 11558 M in E 11559
E from G 11560 M in D 11561
D from F 11562 M in F 11563
F from G 11564 M in G 11565
G from main 11566
446
8.6. Cuestión sobre búsqueda de nombres 11567
//: C09:AmbiguousName.cpp {-xo} 11568
11569
class Top { 11570
public: 11571
virtual ~Top() {} 11572
}; 11573
11574
class Left : virtual public Top { 11575
public: 11576
void f() {} 11577
}; 11578
11579
class Right : virtual public Top { 11580
public: 11581
void f() {} 11582
}; 11583
11584
class Bottom : public Left, public Right {}; 11585
11586
int main() { 11587
Bottom b; 11588
b.f(); // Error here 11589
} ///:~ 11590
447
Listado 8.16. C09/AmbiguousName.cpp 11591
11592
//: C09:BreakTie.cpp 11593
11594
class Top { 11595
public: 11596
virtual ~Top() {} 11597
}; 11598
11599
class Left : virtual public Top { 11600
public: 11601
void f() {} 11602
}; 11603
11604
class Right : virtual public Top { 11605
public: 11606
void f() {} 11607
}; 11608
11609
class Bottom : public Left, public Right { 11610
public: 11611
using Left::f; 11612
}; 11613
11614
int main() { 11615
448
Bottom b; 11616
b.f(); // Calls Left::f() 11617
} ///:~ 11618
Listado 8.17. C09/BreakTie.cpp 11619
11620
//: C09:Dominance.cpp 11621
11622
class Top { 11623
public: 11624
virtual ~Top() {} 11625
virtual void f() {} 11626
}; 11627
11628
class Left : virtual public Top { 11629
public: 11630
void f() {} 11631
}; 11632
11633
class Right : virtual public Top {}; 11634
11635
class Bottom : public Left, public Right {}; 11636
11637
int main() { 11638
Bottom b; 11639
449
b.f(); // Calls Left::f() 11640
} ///:~ 11641
Listado 8.18. C09/Dominance.cpp 11642
11643
//: C09:Dominance2.cpp 11644
#include <iostream> 11645
using namespace std; 11646
11647
class A { 11648
public: 11649
virtual ~A() {} 11650
virtual void f() { cout << "A::f\n"; } 11651
}; 11652
11653
class B : virtual public A { 11654
public: 11655
void f() { cout << "B::f\n"; } 11656
}; 11657
11658
class C : public B {}; 11659
class D : public C, virtual public A {}; 11660
11661
int main() { 11662
B* p = new D; 11663
450
p->f(); // Calls B::f() 11664
delete p; 11665
} ///:~ 11666
Listado 8.19. C09/Dominance2.cpp 11667
11668
8.7. Evitar la MI 11669
8.8. Extender una interface 11670
//: C09:Vendor.h 11671
// Vendor-supplied class header 11672
// You only get this & the compiled Vendor.obj. 11673
#ifndef VENDOR_H 11674
#define VENDOR_H 11675
11676
class Vendor { 11677
public: 11678
virtual void v() const; 11679
void f() const; // Might want this to be virtual... 11680
~Vendor(); // Oops! Not virtual! 11681
}; 11682
11683
class Vendor1 : public Vendor { 11684
public: 11685
void v() const; 11686
451
void f() const; 11687
~Vendor1(); 11688
}; 11689
11690
void A(const Vendor&); 11691
void B(const Vendor&); 11692
// Etc. 11693
#endif // VENDOR_H ///:~ 11694
Listado 8.20. C09/Vendor.h 11695
11696
//: C09:Vendor.cpp {O} 11697
// Assume this is compiled and unavailable to you. 11698
#include "Vendor.h" 11699
#include <iostream> 11700
using namespace std; 11701
11702
void Vendor::v() const { cout << "Vendor::v()" << endl; } 11703
11704
void Vendor::f() const { cout << "Vendor::f()" << endl; } 11705
11706
Vendor::~Vendor() { cout << "~Vendor()" << endl; } 11707
11708
void Vendor1::v() const { cout << "Vendor1::v()" << endl; } 11709
11710
452
void Vendor1::f() const { cout << "Vendor1::f()" << endl; } 11711
11712
Vendor1::~Vendor1() { cout << "~Vendor1()" << endl; } 11713
11714
void A(const Vendor& v) { 11715
// ... 11716
v.v(); 11717
v.f(); 11718
// ... 11719
} 11720
11721
void B(const Vendor& v) { 11722
// ... 11723
v.v(); 11724
v.f(); 11725
// ... 11726
} ///:~ 11727
Listado 8.21. C09/Vendor.cpp 11728
11729
//: C09:Paste.cpp 11730
//{L} Vendor 11731
// Fixing a mess with MI. 11732
#include <iostream> 11733
#include "Vendor.h" 11734
453
using namespace std; 11735
11736
class MyBase { // Repair Vendor interface 11737
public: 11738
virtual void v() const = 0; 11739
virtual void f() const = 0; 11740
// New interface function: 11741
virtual void g() const = 0; 11742
virtual ~MyBase() { cout << "~MyBase()" << endl; } 11743
}; 11744
11745
class Paste1 : public MyBase, public Vendor1 { 11746
public: 11747
void v() const { 11748
cout << "Paste1::v()" << endl; 11749
Vendor1::v(); 11750
} 11751
void f() const { 11752
cout << "Paste1::f()" << endl; 11753
Vendor1::f(); 11754
} 11755
void g() const { cout << "Paste1::g()" << endl; } 11756
~Paste1() { cout << "~Paste1()" << endl; } 11757
}; 11758
11759
454
int main() { 11760
Paste1& p1p = *new Paste1; 11761
MyBase& mp = p1p; // Upcast 11762
cout << "calling f()" << endl; 11763
mp.f(); // Right behavior 11764
cout << "calling g()" << endl; 11765
mp.g(); // New behavior 11766
cout << "calling A(p1p)" << endl; 11767
A(p1p); // Same old behavior 11768
cout << "calling B(p1p)" << endl; 11769
B(p1p); // Same old behavior 11770
cout << "delete mp" << endl; 11771
// Deleting a reference to a heap object: 11772
delete ∓ // Right behavior 11773
} ///:~ 11774
Listado 8.22. C09/Paste.cpp 11775
11776
MyBase* mp = p1p; // Upcast 11777
calling f() 11778 Paste1::f() 11779 Vendor1::f() 11780 calling g() 11781 Paste1::g() 11782
calling A(p1p) 11783 Paste1::v() 11784 Vendor1::v() 11785 Vendor::f() 11786
calling B(p1p) 11787 Paste1::v() 11788
455
Vendor1::v() 11789 Vendor::f() 11790 delete mp 11791 ~Paste1() 11792 ~Vendor1() 11793 ~Vendor() 11794 ~MyBase() 11795
8.9. Resumen 11796
8.10. Ejercicios 11797
11798
11799
[18] Esto también ocurre en Java, y en otros lenguajes orientados a objetos. 11800
[19] Son números de versión internos de AT&T. 11801
9: Patrones de Diseño 11802
Tabla de contenidos 11803
9.1. El Concepto de Patrón 11804
9.2. Clasificación de los patrones 11805
9.3. Simplificación de modismos 11806
9.4. Singleton 11807
9.5. Comando: elegir la operación 11808
9.6. Desacoplamiento de objetos 11809
9.7. Adaptador 11810
9.8. Template Method 11811
9.9. Estrategia: elegir el algoritno en tiempo de ejecución 11812
9.10. Cadena de Responsabilidad: intentar una secuencia de estrategias 11813
9.11. Factorías: encapsular la creación de objetos 11814
9.12. Builder: creación de objetos complejos 11815
9.13. Observador 11816
9.14. Despachado múltiple 11817
9.15. Resumen 11818
9.16. Ejercicios 11819
"...describa un problema que sucede una y otra vez en nuestro entorno, y 11820
luego describa el núcleo de la solución a ese problema, de tal forma que 11821
pueda utilizar esa solución un millón de veces más, sin siquiera hacerlo dos 11822
veces de la misma manera." - Christopher Alexander 11823
456
Este capítulo presenta el importante y aún no tradicional enfoque de los 11824
patrones para el diseño de programas. 11825
El avance reciente más importante en el diseño orientado a objetos es 11826
probablemente el movimiento de los patrones de diseño, inicialmente narrado 11827
en "Design Patterns", por Gamma, Helm, Johnson y Vlissides (Addison 11828
Wesley, 1995), que suele llamarse el libro de la "Banda de los Cuatro" (en 11829
inglés, GoF: Gang of Four). El GoF muestra 23 soluciones para clases de 11830
problemas muy particulares. En este capítulo se discuten los conceptos 11831
básicos de los patrones de diseño y se ofrecen ejemplos de código que 11832
ilustran los patrones escogidos. Esto debería abrirle el apetito para leer más 11833
acerca de los patrones de diseño, una fuente de lo que se ha convertido en 11834
vocabulario esencial, casi obligatorio, para la programación orientada a 11835
objetos. 11836
9.1. El Concepto de Patrón 11837
En principio, puede pensar en un patrón como una manera especialmente 11838
inteligente e intuitiva de resolver una clase de problema en particular. Parece 11839
que un equipo de personas han estudiado todos los ángulos de un problema 11840
y han dado con la solución más general y flexible para ese tipo de problema. 11841
Este problema podría ser uno que usted ha visto y resuelto antes, pero su 11842
solución probablemente no tenía la clase de completitud que verá plasmada 11843
en un patrón. Es más, el patrón existe independientemente de cualquier 11844
implementación particular y puede implementarse de numerosas maneras. 11845
Aunque se llaman "patrones de diseño", en realidad no están ligados al 11846
ámbito del diseño. Un patrón parece apartarse de la manera tradicional de 11847
pensar sobre el análisis, diseño e implementación. En cambio, un patrón 11848
abarca una idea completa dentro de un programa, y por lo tanto puede 11849
también abarcar las fases de análisis y diseño de alto nivel. Sin embargo, 11850
dado que un patrón a menudo tiene una implementación directa en código, 11851
podría no mostrarse hasta el diseño de bajo nivel o la implementación (y 11852
usted no se daría cuenta de que necesita ese patrón hasta que llegase a 11853
esas fases). 11854
El concepto básico de un patrón puede verse también como el concepto 11855
básico del diseño de programas en general: añadir capas de abstracción. 11856
457
Cuando se abstrae algo, se están aislando detalles concretos, y una de las 11857
razones de mayor peso para hacerlo es separar las cosas que cambian de 11858
las cosas que no. Otra forma de verlo es que una vez que encuentra una 11859
parte de su programa que es susceptible de cambiar, querrá prevenir que 11860
esos cambios propagen efectos colaterales por su código. Si lo consigue, su 11861
código no sólo será más fácil de leer y comprender, también será más fácil 11862
de mantener, lo que a la larga, siempre redunda en menores costes. 11863
La parte más difícil de desarrollar un diseño elegante y mantenible a 11864
menudo es descubrir lo que llamamos el "vector de cambio". (Aquí "vector" 11865
se refiere al mayor gradiente tal y como se entiende en ciencias, no como la 11866
clase contenedora.) Esto implica encontrar la cosa más importante que 11867
cambia en su sistema o, dicho de otra forma, descubrir dónde están sus 11868
mayores costes. Una vez que descubra el vector de cambios, tendrá el punto 11869
focal alrededor del cual estructurar su diseño. 11870
Por lo tanto, el objetivo de los patrones de diseño es encapsular el cambio. 11871
Si lo enfoca de esta forma, ya habrá visto algunos patrones de diseño en 11872
este libro. Por ejemplo, la herencia podría verse como un patrón de diseño 11873
(aunque uno implementado por el compilador). Expresa diferencias de 11874
comportamiento (eso es lo que cambia) en objetos que tienen todos la misma 11875
interfaz (esto es lo que no cambia). La composición también podría 11876
considerarse un patrón, ya que puede cambiar dinámica o estáticamente los 11877
objetos que implementan su clase, y por lo tanto, la forma en la que funciona 11878
la clase. Normalmente, sin embargo, las características que los lenguajes de 11879
programación soportan directamente no se han clasificado como patrones de 11880
diseño. 11881
También ha visto ya otro patrón que aparece en el GoF: el «iterador». Esta 11882
es la herramienta fundamental usada en el diseño del STL, descrito en 11883
capítulos anteriores. El iterador esconde la implementación concreta del 11884
contenedor a medida que se avanza y se seleccionan los elementos uno a 11885
uno. Los iteradores le ayudan a escribir código genérico que realiza una 11886
operación en todos los elementos de un rango sin tener en cuenta el 11887
contenedor que contiene el rango. Por lo tanto, cualquier contenedor que 11888
pueda producir iteradores puede utilizar su código genérico. 11889
9.1.1. La composición es preferible a la herencia 11890
458
La contribución más importante del GoF puede que no sea un patrón, si no 11891
una máxima que introducen en el Capítulo 1: «Prefiera siempre la 11892
composición de objetos antes que la herencia de clases». Entender la 11893
herecia y el polimorfismo es un reto tal, que podría empezar a otorgarle una 11894
importancia excesiva a estas técnicas. Se ven muchos diseños 11895
excesivamente complicados (el nuestro incluído) como resultado de ser 11896
demasiado indulgentes con la herencia - por ejemplo, muchos diseños de 11897
herencia múltiple se desarrollan por insistir en usar la herencia en todas 11898
partes. 11899
Una de las directrices en la «Programación Extrema» es "haga la cosa 11900
más simple que pueda funcionar". Un diseño que parece requerir de herencia 11901
puede a menudo simplificarse drásticamente usando una composición en su 11902
lugar, y descubrirá también que el resultado es más flexible, como 11903
comprenderá al estudiar algunos de los patrones de diseño de este capítulo. 11904
Por lo tanto, al considerar un diseño, pregúntese: ¿Podría ser más simple si 11905
usara Composición? ¿De verdad necesito Herencia aquí, y qué me aporta? 11906
9.2. Clasificación de los patrones 11907
El GoF describe 23 patrones, clasificados según tres propósitos FIXME: 11908
(all of which revolve around the particular aspect that can vary): 11909
1. Creacional: Cómo se puede crear un objeto. Habitualmente esto incluye 11910
aislar los detalles de la creación del objeto, de forma que su código no 11911
dependa de los tipos de objeto que hay y por lo tantok, no tenga que 11912
cambiarlo cuando añada un nuevo tipo de objeto. Este capítulo presenta los 11913
patrones Singleton, Fábricas (Factories), y Constructor (Builder). 11914
2. Estructural: Esto afecta a la manera en que los objetos se conectan con 11915
otros objetos para asegurar que los cambios del sistema no requieren 11916
cambiar esas conexiones. Los patrones estructurales suelen imponerlos las 11917
restricciones del proyecto. En este capítulo verá el Proxy y el Adaptador 11918
(Adapter). 11919
3. Comportacional: Objetos que manejan tipos particulares de acciones 11920
dentro de un programa. Éstos encapsulan procesos que quiere que se 11921
ejecuten, como interpretar un lenguaje, completar una petición, moverse a 11922
459
través de una secuencia (como en un iterador) o implementar un algoritmo. 11923
Este capítulo contiene ejemplos de Comando (Command), Método Plantilla 11924
(Template Method), Estado (State), Estrategia (Strategy), Cadena de 11925
Responsabilidad (Chain of Responsibility), Observador (Observer), FIXME: 11926
Despachador Múltiple (Multiple Dispatching) y Visitador (Visitor). 11927
El GoF incluye una sección sobre cada uno de los 23 patrones, junto con 11928
uno o más ejemplos de cada uno, típicamente en C++ aunque a veces en 11929
SmallTalk. Este libro no repite los detalles de los patrones mostrados en 11930
GoF, ya que aquél FIXME: "stands on its own" y debería estudiarse aparte. 11931
La descripción y los ejemplos que se dan aquí intentan darle una visión de 11932
los patrones, de forma que pueda hacerse una idea de lo que tratan y de 11933
porqué son importantes. 11934
9.2.1. Características, modismos patrones 11935
El trabajo va más allá de lo que se muestra en el libro del GoF. Desde su 11936
publicación, hay más patrones y un proceso más refinado para definir 11937
patrones de diseño.[135] Esto es importante porque no es fácil identificar 11938
nuevos patrones ni describirlos adecuadamente. Hay mucha confusión en la 11939
literatura popular acerca de qué es un patrón de diseño, por ejemplo. Los 11940
patrones no son triviales, ni están representados por características 11941
implementadas en un lenguaje de programación. Los constructores y 11942
destructores, por ejemplo, podrían llamarse el patrón de inicialización 11943
garantizada y el de limpieza. Hay constructores importantes y esenciales, 11944
pero son características del lenguaje rutinarias, y no son lo suficientemente 11945
ricas como para ser consideradas patrones. 11946
Otro FIXME: (no-ejemplo? anti-ejemplo?) viene de varias formas de 11947
agregación. La agregación es un principio completamente fundamental en la 11948
programación orientada a objetos: se hacen objetos a partir de otros objetos. 11949
Aunque a veces, esta idea se clasifica erróneamente como un patrón. Esto 11950
no es bueno, porque contamina la idea del patrón de diseño, y sugiere que 11951
cualquier cosa que le sorprenda la primera vez que la ve debería convertirse 11952
en un patrón de diseño. 11953
El lenguaje Java da otro ejemplo equivocado: Los diseñadores de la 11954
especificación de JavaBeans decidieron referirse a la notación get/set como 11955
460
un patrón de diseño (por ejemplo, getInfo() devuelve una propiedad Info y 11956
setInfo() la modifica). Esto es únicamente una convención de nombrado, y de 11957
ninguna manera constituye un patrón de diseño. 11958
9.3. Simplificación de modismos 11959
Antes de adentrarnos en técnicas más complejas, es útil echar un vistazo a 11960
algunos métodos básicos de mantener el código simple y sencillo. 11961
9.3.1. Mensajero 11962
El más trivial es el Mensajero (Messenger), [136] que empaqueta 11963
información en un objeto que se envia, en lugar de ir enviando todas las 11964
piezas independientemente. Nótese que sin el Mensajero, el código para la 11965
función translate() sería mucho más confuso: 11966
//: C10:MessengerDemo.cpp 11967
#include <iostream> 11968
#include <string> 11969
using namespace std; 11970
11971
class Point { // A messenger 11972
public: 11973
int x, y, z; // Since it's just a carrier 11974
Point(int xi, int yi, int zi) : x(xi), y(yi), z(zi) {} 11975
Point(const Point& p) : x(p.x), y(p.y), z(p.z) {} 11976
Point& operator=(const Point& rhs) { 11977
x = rhs.x; 11978
y = rhs.y; 11979
z = rhs.z; 11980
461
return *this; 11981
} 11982
friend ostream& 11983
operator<<(ostream& os, const Point& p) { 11984
return os << "x=" << p.x << " y=" << p.y 11985
<< " z=" << p.z; 11986
} 11987
}; 11988
11989
class Vector { // Mathematical vector 11990
public: 11991
int magnitude, direction; 11992
Vector(int m, int d) : magnitude(m), direction(d) {} 11993
}; 11994
11995
class Space { 11996
public: 11997
static Point translate(Point p, Vector v) { 11998
// Copy-constructor prevents modifying the original. 11999
// A dummy calculation: 12000
p.x += v.magnitude + v.direction; 12001
p.y += v.magnitude + v.direction; 12002
p.z += v.magnitude + v.direction; 12003
return p; 12004
} 12005
462
}; 12006
12007
int main() { 12008
Point p1(1, 2, 3); 12009
Point p2 = Space::translate(p1, Vector(11, 47)); 12010
cout << "p1: " << p1 << " p2: " << p2 << endl; 12011
} ///:~ 12012
Listado 9.1. C10/MessengerDemo.cpp 12013
12014
El código ha sido simplificado para evitar distracciones. 12015
Como el objetivo del Mensajero es simplemente llevar datos, dichos datos 12016
se hacen públicos para facilitar el acceso. Sin embargo, podría tener razones 12017
para hacer estos campos privados. 12018
9.3.2. Parámetro de Recolección 12019
El hermano mayor del Mensajero es el parámetro de recolección, cuyo 12020
trabajo es capturar información sobre la función a la que es pasado. 12021
Generalmente se usa cuando el parámetro de recolección se pasa a 12022
múltiples funciones; es como una abeja recogiendo polen. 12023
Un contenedor (container) es un parámetro de recolección especialmente 12024
útil, ya que está configurado para añadir objetos dinámicamente: 12025
//: C10:CollectingParameterDemo.cpp 12026
#include <iostream> 12027
#include <string> 12028
#include <vector> 12029
using namespace std; 12030
463
12031
class CollectingParameter : public vector<string> {}; 12032
12033
class Filler { 12034
public: 12035
void f(CollectingParameter& cp) { 12036
cp.push_back("accumulating"); 12037
} 12038
void g(CollectingParameter& cp) { 12039
cp.push_back("items"); 12040
} 12041
void h(CollectingParameter& cp) { 12042
cp.push_back("as we go"); 12043
} 12044
}; 12045
12046
int main() { 12047
Filler filler; 12048
CollectingParameter cp; 12049
filler.f(cp); 12050
filler.g(cp); 12051
filler.h(cp); 12052
vector<string>::iterator it = cp.begin(); 12053
while(it != cp.end()) 12054
cout << *it++ << " "; 12055
464
cout << endl; 12056
} ///:~ 12057
Listado 9.2. C10/CollectingParameterDemo.cpp 12058
12059
El parámetro de recolección debe tener alguna forma de establecer o 12060
insertar valores. Nótese que por esta definición, un Mensajero podría usarse 12061
como parámetro de recolección. La clave reside en que el parámetro de 12062
recolección se pasa y es modificado por la función que lo recibe. 12063
9.4. Singleton 12064
Posiblemente, el patrón de diseño más simple del GoF es el Singleton, 12065
que es una forma de asegurar una única instancia de una clase. El siguiente 12066
programa muestra cómo implementar un Singleton en C++: 12067
//: C10:SingletonPattern.cpp 12068
#include <iostream> 12069
using namespace std; 12070
12071
class Singleton { 12072
static Singleton s; 12073
int i; 12074
Singleton(int x) : i(x) { } 12075
Singleton& operator=(Singleton&); // Disallowed 12076
Singleton(const Singleton&); // Disallowed 12077
public: 12078
static Singleton& instance() { return s; } 12079
int getValue() { return i; } 12080
465
void setValue(int x) { i = x; } 12081
}; 12082
12083
Singleton Singleton::s(47); 12084
12085
int main() { 12086
Singleton& s = Singleton::instance(); 12087
cout << s.getValue() << endl; 12088
Singleton& s2 = Singleton::instance(); 12089
s2.setValue(9); 12090
cout << s.getValue() << endl; 12091
} ///:~ 12092
Listado 9.3. C10/SingletonPattern.cpp 12093
12094
La clave para crear un Singleton es evitar que el programador cliente tenga 12095
control sobre el ciclo de vida del objeto. Para lograrlo, declare todos los 12096
constructores privados, y evite que el compilador genere implícitamente 12097
cualquier constructor. Fíjese que el FIXME: constructor de copia? y el 12098
operador de asignación (que intencionadamente carecen de implementación 12099
alguna, ya que nunca van a ser llamados) están declarados como privados, 12100
para evitar que se haga cualquier tipo de copia. 12101
También debe decidir cómo va a crear el objeto. Aquí, se crea de forma 12102
estática, pero también puede esperar a que el programador cliente pida uno 12103
y crearlo bajo demanda. Esto se llama "inicialización vaga", y sólo tiene 12104
sentido si resulta caro crear el objeto y no siempre se necesita. 12105
Si devuelve un puntero en lugar de una referencia, el usuario podría borrar 12106
el puntero sin darse cuenta, por lo que la implementación citada 12107
anteriormente es más segura (el destructor también podría declararse 12108
466
privado o protegido para solventar el problema). En cualquier caso, el objeto 12109
debería almacenarse de forma privada. 12110
Usted da acceso a través de FIXME (funciones de miembros) públicas. 12111
Aquí, instance() genera una referencia al objeto Singleton. El resto de la 12112
interfaz (getValue() y setValue()) es la interfaz regular de la clase. 12113
Fíjese en que no está restringido a crear un único objeto. Esta técnica 12114
también soporta la creacion de un pool limitado de objetos. En este caso, sin 12115
embargo, puede enfrentarse al problema de compartir objetos del pool. Si 12116
esto supone un problema, puede crear una solución que incluya un check-12117
out y un check-in de los objetos compartidos. 12118
9.4.1. Variantes del Singleton 12119
Cualquier miembro static dentro de una clase es una forma de Singleton 12120
se hará uno y sólo uno. En cierto modo, el lenguaje da soporte directo a esta 12121
idea; se usa de forma regular. Sin embargo, los objetos estáticos tienen un 12122
problema (ya miembros o no): el orden de inicialización, tal y como se 12123
describe en el volumen 1 de este libro. Si un objeto static depende de otro, 12124
es importante que los objetos se inicializen en el orden correcto. 12125
En el volumen 1, se mostró cómo controlar el orden de inicialización 12126
definiendo un objeto estático dentro de una función. Esto retrasa la 12127
inicialización del objeto hasta la primera vez que se llama a la función. Si la 12128
función devuelve una referencia al objeto estático, hace las veces de 12129
Singleton a la vez que elimina gran parte de la preocupación de la 12130
inicialización estática. Por ejemplo, suponga que quiere crear un fichero de 12131
log en la primera llamada a una función que devuelve una referencia a dicho 12132
fichero. Basta con este fichero de cabecera: 12133
//: C10:LogFile.h 12134
#ifndef LOGFILE_H 12135
#define LOGFILE_H 12136
#include <fstream> 12137
std::ofstream& logfile(); 12138
467
#endif // LOGFILE_H ///:~ 12139
Listado 9.4. C10/LogFile.h 12140
12141
La implementación no debe FIXME: hacerse en la misma línea, porque eso 12142
significaría que la función entera, incluída la definición del objeto estático que 12143
contiene, podría ser duplicada en cualquier unidad de traducción donde se 12144
incluya, lo que viola la regla de única definición de C++. [137] Con toda 12145
seguridad, esto frustraría cualquier intento de controlar el orden de 12146
inicialización (pero potencialmente de una forma sutil y difícil de detectar). De 12147
forma que la implementación debe separarse: 12148
//: C10:LogFile.cpp {O} 12149
#include "LogFile.h" 12150
std::ofstream& logfile() { 12151
static std::ofstream log("Logfile.log"); 12152
return log; 12153
} ///:~ 12154
Listado 9.5. C10/LogFile.cpp 12155
12156
Ahora el objeto log no se inicializará hasta la primera vez que se llame a 12157
logfile(). Así que, si crea una función: 12158
//: C10:UseLog1.h 12159
#ifndef USELOG1_H 12160
#define USELOG1_H 12161
void f(); 12162
#endif // USELOG1_H ///:~ 12163
468
Listado 9.6. C10/UseLog1.h 12164
12165
que use logfile() en su implementación: 12166
//: C10:UseLog1.cpp {O} 12167
#include "UseLog1.h" 12168
#include "LogFile.h" 12169
void f() { 12170
logfile() << __FILE__ << std::endl; 12171
} ///:~ 12172
Listado 9.7. C10/UseLog1.cpp 12173
12174
y utiliza logfile() otra vez en otro fichero: 12175
//: C10:UseLog2.cpp 12176
//{L} LogFile UseLog1 12177
#include "UseLog1.h" 12178
#include "LogFile.h" 12179
using namespace std; 12180
void g() { 12181
logfile() << __FILE__ << endl; 12182
} 12183
12184
int main() { 12185
f(); 12186
g(); 12187
469
} ///:~ 12188
Listado 9.8. C10/UseLog2.cpp 12189
12190
el objecto log no se crea hasta la primera llamada a f(). 12191
Puede combinar fácilmente la creación de objetos estáticos dentro de una 12192
función miembro con la clase Singleton. SingletonPattern.cpp puede 12193
modificarse para usar esta aproximación:[138] 12194
//: C10:SingletonPattern2.cpp 12195
// Meyers' Singleton. 12196
#include <iostream> 12197
using namespace std; 12198
12199
class Singleton { 12200
int i; 12201
Singleton(int x) : i(x) { } 12202
void operator=(Singleton&); 12203
Singleton(const Singleton&); 12204
public: 12205
static Singleton& instance() { 12206
static Singleton s(47); 12207
return s; 12208
} 12209
int getValue() { return i; } 12210
void setValue(int x) { i = x; } 12211
}; 12212
470
12213
int main() { 12214
Singleton& s = Singleton::instance(); 12215
cout << s.getValue() << endl; 12216
Singleton& s2 = Singleton::instance(); 12217
s2.setValue(9); 12218
cout << s.getValue() << endl; 12219
} ///:~ 12220
Listado 9.9. C10/SingletonPattern2.cpp 12221
12222
Se da un caso especialmente interesante cuando dos Singletons 12223
dependen mutuamente el uno del otro, de esta forma: 12224
//: C10:FunctionStaticSingleton.cpp 12225
12226
class Singleton1 { 12227
Singleton1() {} 12228
public: 12229
static Singleton1& ref() { 12230
static Singleton1 single; 12231
return single; 12232
} 12233
}; 12234
12235
class Singleton2 { 12236
471
Singleton1& s1; 12237
Singleton2(Singleton1& s) : s1(s) {} 12238
public: 12239
static Singleton2& ref() { 12240
static Singleton2 single(Singleton1::ref()); 12241
return single; 12242
} 12243
Singleton1& f() { return s1; } 12244
}; 12245
12246
int main() { 12247
Singleton1& s1 = Singleton2::ref().f(); 12248
} ///:~ 12249
Listado 9.10. C10/FunctionStaticSingleton.cpp 12250
12251
Cuando se llama a Singleton2::ref(), hace que se cree su único objeto 12252
Singleton2. En el proceso de esta creación, se llama a Singleton1::ref(), y 12253
esto hace que se cree su objeto único Singleton1. Como esta técnica no se 12254
basa en el orden de linkado ni el de carga, el programador tiene mucho 12255
mayor control sobre la inicialización, lo que redunda en menos problemas. 12256
Otra variación del Singleton separa la unicidad de un objeto de su 12257
implementación. Esto se logra usando el "Patrón Plantilla Curiosamente 12258
Recursivo" mencionado en el Capítulo 5: 12259
//: C10:CuriousSingleton.cpp 12260
// Separates a class from its Singleton-ness (almost). 12261
#include <iostream> 12262
472
using namespace std; 12263
12264
template<class T> class Singleton { 12265
Singleton(const Singleton&); 12266
Singleton& operator=(const Singleton&); 12267
protected: 12268
Singleton() {} 12269
virtual ~Singleton() {} 12270
public: 12271
static T& instance() { 12272
static T theInstance; 12273
return theInstance; 12274
} 12275
}; 12276
12277
// A sample class to be made into a Singleton 12278
class MyClass : public Singleton<MyClass> { 12279
int x; 12280
protected: 12281
friend class Singleton<MyClass>; 12282
MyClass() { x = 0; } 12283
public: 12284
void setValue(int n) { x = n; } 12285
int getValue() const { return x; } 12286
}; 12287
473
12288
int main() { 12289
MyClass& m = MyClass::instance(); 12290
cout << m.getValue() << endl; 12291
m.setValue(1); 12292
cout << m.getValue() << endl; 12293
} ///:~ 12294
Listado 9.11. C10/CuriousSingleton.cpp 12295
12296
MyClass se convierte en Singleton: 12297
1. Haciendo que su constructor sea private o protected. 12298
2. Haciéndose amigo de Singleton<MyClass>. 12299
3. Derivando MyClass desde Singleton<MyClass>. 12300
La auto-referencia del paso 3 podría sonar inversímil, pero tal como se 12301
explicó en el Capítulo 5, funciona porque sólo hay una dependencia estática 12302
sobre el argumento plantilla de la plantilla Singleton. En otras palabras, el 12303
código de la clase Singleton<MyClass> puede ser instanciado por el 12304
compilador porque no depende del tamaño de MyClass. Es después, cuando 12305
se a Singleton<MyClass>::instance(), cuando se necesita el tamaño de 12306
MyClass, y para entonces MyClass ya se ha compilado y su tamaño se 12307
conoce.[139] 12308
Es interesante lo intrincado que un patrón tan simple como el Singleton 12309
puede llegar a ser, y ni siquiera se han tratado todavía asuntos de seguridad 12310
de hilos. Por último, el patrón Singleton debería usarse lo justo y necesario. 12311
Los verdaderos objetos Singleton rara vez aparecen, y la última cosa para la 12312
que debe usarse un Singleton es para remplazar a una variable global. [140] 12313
9.5. Comando: elegir la operación 12314
474
El patrón Comando es estructuralmente muy sencillo, pero puede tener un 12315
impacto importante en el desacoplamiento (y, por ende, en la limpieza) de su 12316
código. 12317
En "Advanced C++: Programming Styles And Idioms (Addison Wesley, 12318
1992)", Jim Coplien acuña el término functor, que es un objeto cuyo único 12319
propósito es encapsular una función (dado que functor tiene su significado 12320
en matemáticas, usaremos el término "objeto función", que es más explícito). 12321
El quid está en desacoplar la elección de la función que hay que llamar del 12322
sitio donde se llama a dicha función. 12323
Este término se menciona en el GoF, pero no se usa. Sin embargo, el 12324
concepto de "objeto función" se repite en numerosos patrones del libro. 12325
Un Comando es un objeto función en su estado más puro: una función que 12326
tiene un objeto. Al envolver una función en un objeto, puede pasarla a otras 12327
funciones u objetos como parámetro, para decirles que realicen esta 12328
operación concreta mientras llevan a cabo su petición. Se podría decir que 12329
un Comando es un Mensajero que lleva un comportamiento. 12330
//: C10:CommandPattern.cpp 12331
#include <iostream> 12332
#include <vector> 12333
using namespace std; 12334
12335
class Command { 12336
public: 12337
virtual void execute() = 0; 12338
}; 12339
12340
class Hello : public Command { 12341
public: 12342
475
void execute() { cout << "Hello "; } 12343
}; 12344
12345
class World : public Command { 12346
public: 12347
void execute() { cout << "World! "; } 12348
}; 12349
12350
class IAm : public Command { 12351
public: 12352
void execute() { cout << "I'm the command pattern!"; } 12353
}; 12354
12355
// An object that holds commands: 12356
class Macro { 12357
vector<Command*> commands; 12358
public: 12359
void add(Command* c) { commands.push_back(c); } 12360
void run() { 12361
vector<Command*>::iterator it = commands.begin(); 12362
while(it != commands.end()) 12363
(*it++)->execute(); 12364
} 12365
}; 12366
12367
476
int main() { 12368
Macro macro; 12369
macro.add(new Hello); 12370
macro.add(new World); 12371
macro.add(new IAm); 12372
macro.run(); 12373
} ///:~ 12374
Listado 9.12. C10/CommandPattern.cpp 12375
12376
El punto principal del Comando es permitirle dar una acción deseada a una 12377
función u objeto. En el ejemplo anterior, esto provee una manera de encolar 12378
un conjunto de acciones que se deben ejecutar colectivamente. Aquí, puede 12379
crear dinámicamente nuevos comportamientos, algo que puede hacer 12380
normalmente escribiendo nuevo código, pero en el ejemplo anterior podría 12381
hacerse interpretando un script (vea el patrón Intérprete si lo que necesita 12382
hacer se vuelve demasiado complicado). 12383
Según el GoF, los Comandos son un sustituto orientado a objetos de las 12384
retrollamadas (callbacks). [141] Sin embargo, pensamos que la palabra 12385
"retro" es una parte esencial del concepto de retrollamada -una retrollamada 12386
retorna al creador de la misma. Por otro lado, un objeto Comando, 12387
simplemente se crea y se entrega a alguna función u objeto, y no se 12388
permanece conectado de por vida al objecto Comando. 12389
Un ejemplo habitual del patrón Comando es la implementación de la 12390
funcionalidad de "deshacer" en una aplicación. Cada vez que el usuario 12391
realiza una operación, se coloca el correspondiente objeto Comando de 12392
deshacer en una cola. Cada objeto Comando que se ejecuta guarda el 12393
estado del programa en el paso anterior. 12394
9.5.1. Desacoplar la gestión de eventos con Comando 12395
477
Como se verá en el siguiente capítulo, una de las razones para emplear 12396
técnicas de concurrencia es facilitar la gestión de la programación dirigida 12397
por eventos, donde los eventos pueden aparecer en el programa de forma 12398
impredecible. Por ejemplo, un usuario que pulsa un botón de "Salir" mientras 12399
se está realizando una operación espera que el programa responda 12400
rápidamente. 12401
Un motivo para usar concurrencia es que previene el aclopamiento entre 12402
los bloques del código. Es decir, si está ejecutando un hilo aparte para vigilar 12403
el botónde salida, las operaciones normales de su programa no necesitan 12404
saber nada sobre el botón ni sobe ninguna de las demás operaciones que se 12405
están vigilando. 12406
Sin embargo, una vez que comprenda que el quiz está en el acoplamiento, 12407
puede evitarlo usando el patrón Comando. Cada operación normal debe 12408
llamar periódicamente a una función para que compruebe el estado de los 12409
eventos, pero con el patrón Comando, estas operaciones normales no tienen 12410
porqué saber nada sobre lo que están comprobando, y por lo tanto, están 12411
desacopladas del código de manejo de eventos: 12412
//: C10:MulticastCommand.cpp {RunByHand} 12413
// Decoupling event management with the Command pattern. 12414
#include <iostream> 12415
#include <vector> 12416
#include <string> 12417
#include <ctime> 12418
#include <cstdlib> 12419
using namespace std; 12420
12421
// Framework for running tasks: 12422
class Task { 12423
public: 12424
478
virtual void operation() = 0; 12425
}; 12426
12427
class TaskRunner { 12428
static vector<Task*> tasks; 12429
TaskRunner() {} // Make it a Singleton 12430
TaskRunner& operator=(TaskRunner&); // Disallowed 12431
TaskRunner(const TaskRunner&); // Disallowed 12432
static TaskRunner tr; 12433
public: 12434
static void add(Task& t) { tasks.push_back(&t); } 12435
static void run() { 12436
vector<Task*>::iterator it = tasks.begin(); 12437
while(it != tasks.end()) 12438
(*it++)->operation(); 12439
} 12440
}; 12441
12442
TaskRunner TaskRunner::tr; 12443
vector<Task*> TaskRunner::tasks; 12444
12445
class EventSimulator { 12446
clock_t creation; 12447
clock_t delay; 12448
public: 12449
479
EventSimulator() : creation(clock()) { 12450
delay = CLOCKS_PER_SEC/4 * (rand() % 20 + 1); 12451
cout << "delay = " << delay << endl; 12452
} 12453
bool fired() { 12454
return clock() > creation + delay; 12455
} 12456
}; 12457
12458
// Something that can produce asynchronous events: 12459
class Button { 12460
bool pressed; 12461
string id; 12462
EventSimulator e; // For demonstration 12463
public: 12464
Button(string name) : pressed(false), id(name) {} 12465
void press() { pressed = true; } 12466
bool isPressed() { 12467
if(e.fired()) press(); // Simulate the event 12468
return pressed; 12469
} 12470
friend ostream& 12471
operator<<(ostream& os, const Button& b) { 12472
return os << b.id; 12473
} 12474
480
}; 12475
12476
// The Command object 12477
class CheckButton : public Task { 12478
Button& button; 12479
bool handled; 12480
public: 12481
CheckButton(Button & b) : button(b), handled(false) {} 12482
void operation() { 12483
if(button.isPressed() && !handled) { 12484
cout << button << " pressed" << endl; 12485
handled = true; 12486
} 12487
} 12488
}; 12489
12490
// The procedures that perform the main processing. These 12491
// need to be occasionally "interrupted" in order to 12492
// check the state of the buttons or other events: 12493
void procedure1() { 12494
// Perform procedure1 operations here. 12495
// ... 12496
TaskRunner::run(); // Check all events 12497
} 12498
12499
481
void procedure2() { 12500
// Perform procedure2 operations here. 12501
// ... 12502
TaskRunner::run(); // Check all events 12503
} 12504
12505
void procedure3() { 12506
// Perform procedure3 operations here. 12507
// ... 12508
TaskRunner::run(); // Check all events 12509
} 12510
12511
int main() { 12512
srand(time(0)); // Randomize 12513
Button b1("Button 1"), b2("Button 2"), b3("Button 3"); 12514
CheckButton cb1(b1), cb2(b2), cb3(b3); 12515
TaskRunner::add(cb1); 12516
TaskRunner::add(cb2); 12517
TaskRunner::add(cb3); 12518
cout << "Control-C to exit" << endl; 12519
while(true) { 12520
procedure1(); 12521
procedure2(); 12522
procedure3(); 12523
} 12524
482
} ///:~ 12525
Listado 9.13. C10/MulticastCommand.cpp 12526
12527
Aquí, el objeto Comando está representado por Tareas ejecutadas por el 12528
Singleton TaskRunner. EventSimulator crea un retraso aleatorio, de modo 12529
que si se llama periódicamente a la función fired() el resultado cambiará de 12530
false a true en algún momento aleatorio. Los objetos EventSimulator se 12531
utilizan dentro de los Botones para simular que ocurre un evento de usuario 12532
en un momento impredecible. CheckButton es la implementación de la Tarea 12533
que es comprobada periódicamente por todo el código "normal" del progama. 12534
Puede ver cómo ocurre al final de procedure1(), procedure2() y procedure3(). 12535
Aunque esto requiere un poco más de razonamiento para establecerlo, 12536
verá en el Capítulo 11 que utilizar hilos requiere mucho pensamiento y 12537
cuidado para prevenir las muchas dificultades inhenerentes a la 12538
programación concurrente, por lo que la solución más simple puede ser 12539
preferible. También puede crear un esquema de hilos muy simple moviendo 12540
las llamadas a TaskRunner::run() a un objeto temporizador multi-hilo. Al 12541
hacer esto, se elimina todo el acoplamiento entre las operaciones "normales" 12542
(los "procedures" en el ejemplo anterior) y el código de eventos. 12543
9.6. Desacoplamiento de objetos 12544
Tanto el Proxy como el Estado proporcionen una clase sucedánea. El 12545
código habla a esta clase sucedánea, y la verdadera clase que hace el 12546
trabajo está escondida detrás de la sucedánea. Cuando usted llama a una 12547
función en la clase sucedánea, simplemente da un rodeo y llama a la función 12548
en la clase implementadora. Estos dos patrones son tan familiares que, 12549
estructuralmente, Proxy es un caso especial de Estado. Uno está tentado de 12550
juntar ambos en un patrón llamado Sucedánea, pero la intención de los dos 12551
patrones es distinta. Puede ser fácil caer en la trampa de pensar que si la 12552
estructura es la misma, los patrones son el mismo. Debe mirar siempre la 12553
intención del patrón para tener claro lo que hace. 12554
483
La idea básica es simple: cree una clase base, la sucedánea se deriva 12555
junto con la clase o clases que aportan la siguiente implementación: 12556
Cuando se crea una clase sucedánea, se le da una implementación a la 12557
que envía las llamadas a función. 12558
Estructuralmente, la diferencia entre Proxy y Estado es simple: un Proxy 12559
sólo tiene una implementación, mientras que Estado tiene más de una. La 12560
aplicación de los patrones se considera (en el GoF) distinta: Proxy controla el 12561
acceso a su implementación, mientras que Estado cambia la implementación 12562
dinámicamente. Sin embargo, si se amplía la noción de "controlar el acceso a 12563
la implementación", entonces los dos parecen ser parte de un todo. 12564
9.6.1. Proxy: FIXME: hablando en nombre de otro objeto 12565
Si se implementa un Proxy usando el diagrama anterior, tiene esta pinta: 12566
//: C10:ProxyDemo.cpp 12567
// Simple demonstration of the Proxy pattern. 12568
#include <iostream> 12569
using namespace std; 12570
12571
class ProxyBase { 12572
public: 12573
virtual void f() = 0; 12574
virtual void g() = 0; 12575
virtual void h() = 0; 12576
virtual ~ProxyBase() {} 12577
}; 12578
12579
class Implementation : public ProxyBase { 12580
public: 12581
484
void f() { cout << "Implementation.f()" << endl; } 12582
void g() { cout << "Implementation.g()" << endl; } 12583
void h() { cout << "Implementation.h()" << endl; } 12584
}; 12585
12586
class Proxy : public ProxyBase { 12587
ProxyBase* implementation; 12588
public: 12589
Proxy() { implementation = new Implementation(); } 12590
~Proxy() { delete implementation; } 12591
// Forward calls to the implementation: 12592
void f() { implementation->f(); } 12593
void g() { implementation->g(); } 12594
void h() { implementation->h(); } 12595
}; 12596
12597
int main() { 12598
Proxy p; 12599
p.f(); 12600
p.g(); 12601
p.h(); 12602
} ///:~ 12603
Listado 9.14. C10/ProxyDemo.cpp 12604
12605
485
En algunos casos, Implementation no necesita la misma interfaz que 12606
Proxy, siempre y cuando Proxy esté de alguna forma hablando en nombre de 12607
la clase Implementación y referenciando llamadas a función hacia ella, 12608
entonces la idea básica se satisface (note que esta afirmación está reñida 12609
con la definición de Proxy del GoF). Sin embargo, con una interfaz común es 12610
posible realizar un reemplazo FIXME: drop-in del proxy en el código del 12611
cliente -el código del cliente está escrito para hablar al objeto original, y no 12612
necesita ser cambiado para aceptar el proxy (éste es probablemente el quiz 12613
principal de Proxy). Además, se fuerza a que Implementation complete, a 12614
través de la interfaz común, todas las funciones que Proxy necesita llamar. 12615
La diferencia entre Proxy y Estado está en los problemas que pueden 12616
resolver. Los usos más comunes para Proxy que describe el GoF son: 12617
1. Proxy remoto. Representan a objetos en un espacio de direcciones 12618
distinto. Lo implementan algunas tecnologías de objetos remotos. 12619
2. Proxy virtual. Proporciona inicialización FIXME: vaga para crear objetos 12620
costosos bajo demanda. 12621
3. Proxy de protección. Se usa cuando no se desea que el programador 12622
cliente tenga acceso completo al objecto representado. 12623
4. Referencia inteligente. Para añadir acciones adicionales cuando se 12624
acceda al objeto representado. El conteo de referencias es un buen ejemplo: 12625
mantiene un registro del número de referencias que se mantienen para un 12626
objeto en particular, para implementar el FIXME: copy-on-write idiom y para 12627
prevenir el FIXME: object aliasing. 12628
9.6.2. Estado: cambiar el comportamiento del objeto 12629
El patrón Estado produce un objeto que parece que cambia su clase, y le 12630
será útil cuando descubra que tiene código condicional en todas o casi todas 12631
sus funciones. Al igual que Proxy, un Estado se crea teniendo un objeto 12632
front-end que usa un objeto back-end de implementación para completar sus 12633
tareas. Sin embargo, el patrón Estado alterna entre una implementación y 12634
otra durante la vida del objeto front-end, para mostrar un comportamiento 12635
distinto ante las mismas llamadas a función. Es una forma de mejorar la 12636
implementación de su código cuando realiza un montón de pruebas en cada 12637
486
una de sus funciones antes de decidir qué hacer con esa función. Por 12638
ejemplo, el cuento del príncipe convertido en rana contiene un objeto (la 12639
criatura) que se comporta de modo distinto dependiendo del estado en el que 12640
se encuentre. Podría implementar esto comprobando un bool: 12641
//: C10:KissingPrincess.cpp 12642
#include <iostream> 12643
using namespace std; 12644
12645
class Creature { 12646
bool isFrog; 12647
public: 12648
Creature() : isFrog(true) {} 12649
void greet() { 12650
if(isFrog) 12651
cout << "Ribbet!" << endl; 12652
else 12653
cout << "Darling!" << endl; 12654
} 12655
void kiss() { isFrog = false; } 12656
}; 12657
12658
int main() { 12659
Creature creature; 12660
creature.greet(); 12661
creature.kiss(); 12662
creature.greet(); 12663
487
} ///:~ 12664
Listado 9.15. C10/KissingPrincess.cpp 12665
12666
Sin embargo, la función greet(), y cualquier otra función que tenga que 12667
comprobar isFrog antes de realizar sus operaciones, acaban con código 12668
poco elegante, especialmente cuando haya que añadir estados adicionales al 12669
sistema. Delegando las operaciones a un objeto Estado que puede 12670
cambiarse, el código se simplifica. 12671
//: C10:KissingPrincess2.cpp 12672
// The State pattern. 12673
#include <iostream> 12674
#include <string> 12675
using namespace std; 12676
12677
class Creature { 12678
class State { 12679
public: 12680
virtual string response() = 0; 12681
}; 12682
class Frog : public State { 12683
public: 12684
string response() { return "Ribbet!"; } 12685
}; 12686
class Prince : public State { 12687
public: 12688
488
string response() { return "Darling!"; } 12689
}; 12690
State* state; 12691
public: 12692
Creature() : state(new Frog()) {} 12693
void greet() { 12694
cout << state->response() << endl; 12695
} 12696
void kiss() { 12697
delete state; 12698
state = new Prince(); 12699
} 12700
}; 12701
12702
int main() { 12703
Creature creature; 12704
creature.greet(); 12705
creature.kiss(); 12706
creature.greet(); 12707
} ///:~ 12708
Listado 9.16. C10/KissingPrincess2.cpp 12709
12710
No es necesario hacer las clases FIXME: implementadoras anidadas ni 12711
privadas, pero si lo hace, el código será más limpio. 12712
489
Note que los cambios en las clases Estado se propagan automáticamente 12713
por todo su código, en lugar de requerir una edición de las clases para 12714
efectuar los cambios. 12715
9.7. Adaptador 12716
Un Adaptador coge un tipo y genera una interfaz para algún otro tipo. Es 12717
útil cuando se tiene una librería o trozo de código que tiene una interfaz 12718
particular, y otra librería o trozo de código que usa las mismas ideas básicas 12719
que la primera librería, pero se expresa de forma diferente. Si se adaptan las 12720
formas de expresión entre sí, se puede crear una solución rápidamente. 12721
Suponga que tiene una clase productora que genera los números de 12722
Fibonacci: 12723
//: C10:FibonacciGenerator.h 12724
#ifndef FIBONACCIGENERATOR_H 12725
#define FIBONACCIGENERATOR_H 12726
12727
class FibonacciGenerator { 12728
int n; 12729
int val[2]; 12730
public: 12731
FibonacciGenerator() : n(0) { val[0] = val[1] = 0; } 12732
int operator()() { 12733
int result = n > 2 ? val[0] + val[1] : n > 0 ? 1 : 0; 12734
++n; 12735
val[0] = val[1]; 12736
val[1] = result; 12737
return result; 12738
490
} 12739
int count() { return n; } 12740
}; 12741
#endif // FIBONACCIGENERATOR_H ///:~ 12742
Listado 9.17. C10/FibonacciGenerator.h 12743
12744
Como es un productor, se usa llamando al operador(), de esta forma: 12745
//: C10:FibonacciGeneratorTest.cpp 12746
#include <iostream> 12747
#include "FibonacciGenerator.h" 12748
using namespace std; 12749
12750
int main() { 12751
FibonacciGenerator f; 12752
for(int i =0; i < 20; i++) 12753
cout << f.count() << ": " << f() << endl; 12754
} ///:~ 12755
Listado 9.18. C10/FibonacciGeneratorTest.cpp 12756
12757
A lo mejor le gustaría coger este generador y realizar operaciones de 12758
algoritmos numéricos STL con él. Desafortunadamente, los algoritmos STL 12759
sólo trabajan con iteradores, así que tiene dos interfaces que no casan. La 12760
solución es crear un adaptador que coja el FibonacciGenerator y produzca 12761
un iterador para los algoritmos STL a usar. Dado que los algoritmos 12762
numéricos sólo necesitan un iterador de entrada, el Adaptador es bastante 12763
directo (para algo que produce un iterador STL, es decir): 12764
491
//: C10:FibonacciAdapter.cpp 12765
// Adapting an interface to something you already have. 12766
#include <iostream> 12767
#include <numeric> 12768
#include "FibonacciGenerator.h" 12769
#include "../C06/PrintSequence.h" 12770
using namespace std; 12771
12772
class FibonacciAdapter { // Produce an iterator 12773
FibonacciGenerator f; 12774
int length; 12775
public: 12776
FibonacciAdapter(int size) : length(size) {} 12777
class iterator; 12778
friend class iterator; 12779
class iterator : public std::iterator< 12780
std::input_iterator_tag, FibonacciAdapter, ptrdiff_t> { 12781
FibonacciAdapter& ap; 12782
public: 12783
typedef int value_type; 12784
iterator(FibonacciAdapter& a) : ap(a) {} 12785
bool operator==(const iterator&) const { 12786
return ap.f.count() == ap.length; 12787
} 12788
bool operator!=(const iterator& x) const { 12789
492
return !(*this == x); 12790
} 12791
int operator*() const { return ap.f(); } 12792
iterator& operator++() { return *this; } 12793
iterator operator++(int) { return *this; } 12794
}; 12795
iterator begin() { return iterator(*this); } 12796
iterator end() { return iterator(*this); } 12797
}; 12798
12799
int main() { 12800
const int SZ = 20; 12801
FibonacciAdapter a1(SZ); 12802
cout << "accumulate: " 12803
<< accumulate(a1.begin(), a1.end(), 0) << endl; 12804
FibonacciAdapter a2(SZ), a3(SZ); 12805
cout << "inner product: " 12806
<< inner_product(a2.begin(), a2.end(), a3.begin(), 0) 12807
<< endl; 12808
FibonacciAdapter a4(SZ); 12809
int r1[SZ] = {0}; 12810
int* end = partial_sum(a4.begin(), a4.end(), r1); 12811
print(r1, end, "partial_sum", " "); 12812
FibonacciAdapter a5(SZ); 12813
int r2[SZ] = {0}; 12814
493
end = adjacent_difference(a5.begin(), a5.end(), r2); 12815
print(r2, end, "adjacent_difference", " "); 12816
} ///:~ 12817
Listado 9.19. C10/FibonacciAdapter.cpp 12818
12819
Se inicializa un FibonacciAdapter diciéndole cuán largo puede ser la 12820
secuencia de Fibonacci. Cuando se crea un iterador, simplemente captura 12821
una referencia al FibonacciAdapter que lo contiene para que pueda acceder 12822
al FibonacciGenerator y la longitud. Observe que la comparación de 12823
equivalencia ignora el valor de la derecha, porque el único asunto importante 12824
es si el generador ha alcanzado su longitud. Además, el operator++() no 12825
modifica el iterador; la única operación que cambia el estado del 12826
FibonacciAdapter es llamar a la función operator() del generador en el 12827
FibonacciGenerator. Puede aceptarse esta versión extremadamente simple 12828
del iterador porque las restricciones de un Input Iterator son muy estrictas; 12829
concretamente, sólo se puede leer cada valor de la secuencia una vez. 12830
En main(), puede verse que los cuatro tipos distintos de algoritmos 12831
numéricos se testan satisfactoriamente con el FibonacciAdapter. 12832
9.8. Template Method 12833
El marco de trabajo de una aplicación nos permite heredar de una clase o 12834
conjunto de ellas y crear una nueva aplicación, reutilizando la mayoría del 12835
código de las clases existentes y sobreescribiendo una o más funciones para 12836
adaptar la aplicación a nuestras necesidades. 12837
Una característica importante de Template Method es que está definido en 12838
la clase base (a veces como una función privada) y no puede cambiarse -el 12839
Template Method es lo que permanece invariable. Llama a otras funciones 12840
de clase base (las que se sobreescriben) para hacer su trabajo, pero el 12841
programador cliente no es necesariamente capaz de llamarlo directamente, 12842
como puede verse aquí: 12843
494
//: C10:TemplateMethod.cpp 12844
// Simple demonstration of Template Method. 12845
#include <iostream> 12846
using namespace std; 12847
12848
class ApplicationFramework { 12849
protected: 12850
virtual void customize1() = 0; 12851
virtual void customize2() = 0; 12852
public: 12853
void templateMethod() { 12854
for(int i = 0; i < 5; i++) { 12855
customize1(); 12856
customize2(); 12857
} 12858
} 12859
}; 12860
12861
// Create a new "application": 12862
class MyApp : public ApplicationFramework { 12863
protected: 12864
void customize1() { cout << "Hello "; } 12865
void customize2() { cout << "World!" << endl; } 12866
}; 12867
12868
495
int main() { 12869
MyApp app; 12870
app.templateMethod(); 12871
} ///:~ 12872
Listado 9.20. C10/TemplateMethod.cpp 12873
12874
El motor que ejecuta la aplicación es el Template Method. En una 12875
aplicación gráfica, este motor sería el bucle principal de eventos. El 12876
programador cliente simplemente proporciona las definiciones para 12877
customize1() y customize2(), y la aplicación está lista para ejecutarse. 12878
9.9. Estrategia: elegir el algoritno en tiempo de 12879
ejecución 12880
Observe que el Template Method es el código que no cambia, y las 12881
funciones que sobreescribe son el código cambiante. Sin embargo, este 12882
cambio está fijado en tiempo de compilación, a través de la herencia. 12883
Siguiendo la máxima de preferir composición a herencia, se puede usar una 12884
composición para aproximar el problema de separar código que cambia de 12885
código que permanece, y generar el patrón Estrategia. Esta aproximación 12886
tiene un beneficio único: en tiempo de ejecución se puede insertar el código 12887
que cambia. Estrategia también añade un Contexto que puede ser una clase 12888
sucedánea que controla la selección y uso del objeto estrategia -¡igual que 12889
Estado!. 12890
Estrategia significa exactamente eso: se puede resolver un problema de 12891
muchas maneras. Imagine que ha olvidado el nombre de alguien. Estas son 12892
las diferentes maneras para lidiar con esa situación: 12893
//: C10:Strategy.cpp 12894
// The Strategy design pattern. 12895
#include <iostream> 12896
496
using namespace std; 12897
12898
class NameStrategy { 12899
public: 12900
virtual void greet() = 0; 12901
}; 12902
12903
class SayHi : public NameStrategy { 12904
public: 12905
void greet() { 12906
cout << "Hi! How's it going?" << endl; 12907
} 12908
}; 12909
12910
class Ignore : public NameStrategy { 12911
public: 12912
void greet() { 12913
cout << "(Pretend I don't see you)" << endl; 12914
} 12915
}; 12916
12917
class Admission : public NameStrategy { 12918
public: 12919
void greet() { 12920
cout << "I'm sorry. I forgot your name." << endl; 12921
497
} 12922
}; 12923
12924
// The "Context" controls the strategy: 12925
class Context { 12926
NameStrategy& strategy; 12927
public: 12928
Context(NameStrategy& strat) : strategy(strat) {} 12929
void greet() { strategy.greet(); } 12930
}; 12931
12932
int main() { 12933
SayHi sayhi; 12934
Ignore ignore; 12935
Admission admission; 12936
Context c1(sayhi), c2(ignore), c3(admission); 12937
c1.greet(); 12938
c2.greet(); 12939
c3.greet(); 12940
} ///:~ 12941
Listado 9.21. C10/Strategy.cpp 12942
12943
Normalmente, Context::greet() sería más complejo; es el análogo de 12944
Template Method porque contiene el código que no cambia. Pero puede ver 12945
en main() que la elección de la estrategia puede realizarse en tiempo de 12946
498
ejecución. Llendo un paso más allá, se puede combinar esto con el patrón 12947
Estado y cambiar la Estrategia durante el tiempo de vida del objeto Contexto. 12948
9.10. Cadena de Responsabilidad: intentar una 12949
secuencia de estrategias 12950
Debe pensarse en Cadena de Responsabilidad como en una 12951
generalización dinámica de la recursión, usando objetos Estrategia. Se hace 12952
una llamada y cada Estrategia de la secuencia intenta satisfacer la llamada. 12953
El proceso termina cuando una de las Estrategias tiene éxito o la cadena 12954
termina. En la recursión, una función se llama a sí misma una y otra vez 12955
hasta que se alcanza una condición de finalización; con Cadena de 12956
Responsabilidad, una función se llama a sí misma, la cual (moviendo la 12957
cadena de Estrategias) llama a una implementación diferente de la función, 12958
etc, hasta que se alcanza la condición de finalización. Dicha condición puede 12959
ser que se ha llegado al final de la cadena (lo que devuelve un objeto por 12960
defecto; puede que no sea capaz de proporcionar un resultado por defecto, 12961
así que debe ser capaz de determinar el éxito o fracaso de la cadena) o que 12962
una de las Estrategias ha tenido éxito. 12963
En lugar de llamar a una única función para satisfacer una petición, hay 12964
múltiples funciones en la cadetna que tienen la oportunidad de hacerlo, de 12965
manera que tiene el aspecto de un sistema experto. Dado que la cadena es 12966
en la práctica una lista, puede crearse dinámicamente, así que podría verse 12967
como una sentencia switch más general y construida dinámicamente. 12968
En el GoF, hay bastante discusión sobre cómo crear la cadena de 12969
responsabilidad como una lista enlazada. Sin embargo, cuando se estudia el 12970
patrón, no debería importar cómo se crea la cadena; eso es un detalle de 12971
implementación. Como el GoF se escribió antes de que los contenedores 12972
STL estuvieran disponibles en la mayoría de los compiladores de C++, las 12973
razones más probables son (1) que no había listas incluídas y por lo tanto 12974
tenían que crear una y (2) que las estructuras de datos suelen verse como 12975
una habilidad fundamental en las Escuelas (o Facultades), y a los autores del 12976
GoF no se les ocurrió la idea de que las estructuras de datos fueran 12977
herramientas estándar disponibles junto con el lenguaje de programación.. 12978
Los detalles del contenedor usado para implementar la Cadena de 12979
499
Responsabilidad como una cadena (una lista enlazada en el GoF) no añaden 12980
nada a la solución, y puede implementarse usando un contenedor STL, como 12981
se muestra abajo. 12982
Aquí puede ver una Cadena de Responsabilidad que encuentra 12983
automáticamente una solución usando un mecanismo para recorrer 12984
automática y recursivamente cada Estrategia de la cadena: 12985
//: C10:ChainOfReponsibility.cpp 12986
// The approach of the five-year-old. 12987
#include <iostream> 12988
#include <vector> 12989
#include "../purge.h" 12990
using namespace std; 12991
12992
enum Answer { NO, YES }; 12993
12994
class GimmeStrategy { 12995
public: 12996
virtual Answer canIHave() = 0; 12997
virtual ~GimmeStrategy() {} 12998
}; 12999
13000
class AskMom : public GimmeStrategy { 13001
public: 13002
Answer canIHave() { 13003
cout << "Mooom? Can I have this?" << endl; 13004
return NO; 13005
500
} 13006
}; 13007
13008
class AskDad : public GimmeStrategy { 13009
public: 13010
Answer canIHave() { 13011
cout << "Dad, I really need this!" << endl; 13012
return NO; 13013
} 13014
}; 13015
13016
class AskGrandpa : public GimmeStrategy { 13017
public: 13018
Answer canIHave() { 13019
cout << "Grandpa, is it my birthday yet?" << endl; 13020
return NO; 13021
} 13022
}; 13023
13024
class AskGrandma : public GimmeStrategy { 13025
public: 13026
Answer canIHave() { 13027
cout << "Grandma, I really love you!" << endl; 13028
return YES; 13029
} 13030
501
}; 13031
13032
class Gimme : public GimmeStrategy { 13033
vector<GimmeStrategy*> chain; 13034
public: 13035
Gimme() { 13036
chain.push_back(new AskMom()); 13037
chain.push_back(new AskDad()); 13038
chain.push_back(new AskGrandpa()); 13039
chain.push_back(new AskGrandma()); 13040
} 13041
Answer canIHave() { 13042
vector<GimmeStrategy*>::iterator it = chain.begin(); 13043
while(it != chain.end()) 13044
if((*it++)->canIHave() == YES) 13045
return YES; 13046
// Reached end without success... 13047
cout << "Whiiiiinnne!" << endl; 13048
return NO; 13049
} 13050
~Gimme() { purge(chain); } 13051
}; 13052
13053
int main() { 13054
Gimme chain; 13055
502
chain.canIHave(); 13056
} ///:~ 13057
Listado 9.22. C10/ChainOfReponsibility.cpp 13058
13059
Observe que la clase de Contexto Gimme y todas las clases Estrategia 13060
derivan de la misma clase base, GimmeStrategy. 13061
Si estudia la sección sobre Cadena de Responsabilidad del GoF, verá que 13062
la estructura difiere significativamente de la que se muestra más arriba, 13063
porque ellos se centran en crear su propia lista enlazada. Sin embargo, si 13064
mantiene en mente que la esencia de Cadena de Responsabilidad es probar 13065
muchas soluciones hasta que encuentre la que funciona, se dará cuenta de 13066
que la implementación del mecanismo de secuenciación no es parte esencial 13067
del patrón. 13068
9.11. Factorías: encapsular la creación de objetos 13069
Cuando se descubre que se necesitan añadir nuevos tipos a un sistema, el 13070
primer paso más sensato es usar polimorfismo para crear una interfaz común 13071
para esos nuevos tipos. Así, se separa el resto del código en el sistema del 13072
conocimiento de los tipos específicos que se están añadiendo. Los tipos 13073
nuevos pueden añadirse sin "molestar" al código existente, o eso parece. A 13074
primera vista, podría parecer que hace falta cambiar el código únicamente en 13075
los lugares donde se hereda un tipo nuevo, pero esto no es del todo cierto. 13076
Todavía hay que crear un objeto de este nuevo tipo, y en el momento de la 13077
creación hay que especificar qué constructor usar. Por lo tanto, si el codigo 13078
que crea objetos está distribuido por toda la aplicación, se obtiene el mismo 13079
problema que cuando se añaden tipos -hay que localizar todos los puntos del 13080
código donde el tipo tiene importancia. Lo que imoporta es la creación del 13081
tipo, más que el uso del mismo (de eso se encarga el polimorfismo), pero el 13082
efecto es el mismo: añadir un nuevo tipo puede causar problemas. 13083
La solución es forzar a que la creación de objetos se lleve a cabo a través 13084
de una factoría común, en lugar de permitir que el código creacional se 13085
503
disperse por el sistema. Si todo el código del programa debe ir a esta factoría 13086
cada vez que necesita crear uno de esos objetos, todo lo que hay que hacer 13087
para añadir un objeto es modificar la factoría. Este diseño es una variación 13088
del patrón conocido comúnmente como Factory Method. Dado que todo 13089
programa orientado a objetos crea objetos, y como es probable que haya que 13090
extender el programa añadiendo nuevos tipos, las factorías pueden ser el 13091
más útil de todos los patrones de diseño. 13092
Como ejemplo, considere el ampliamente usado ejemplo de figura 13093
(Shape). Una aproximación para implementar una factoría es definir una 13094
función miembro estática en la clase base: 13095
//: C10:ShapeFactory1.cpp 13096
#include <iostream> 13097
#include <stdexcept> 13098
#include <cstddef> 13099
#include <string> 13100
#include <vector> 13101
#include "../purge.h" 13102
using namespace std; 13103
13104
class Shape { 13105
public: 13106
virtual void draw() = 0; 13107
virtual void erase() = 0; 13108
virtual ~Shape() {} 13109
class BadShapeCreation : public logic_error { 13110
public: 13111
BadShapeCreation(string type) 13112
504
: logic_error("Cannot create type " + type) {} 13113
}; 13114
static Shape* factory(const string& type) 13115
throw(BadShapeCreation); 13116
}; 13117
13118
class Circle : public Shape { 13119
Circle() {} // Private constructor 13120
friend class Shape; 13121
public: 13122
void draw() { cout << "Circle::draw" << endl; } 13123
void erase() { cout << "Circle::erase" << endl; } 13124
~Circle() { cout << "Circle::~Circle" << endl; } 13125
}; 13126
13127
class Square : public Shape { 13128
Square() {} 13129
friend class Shape; 13130
public: 13131
void draw() { cout << "Square::draw" << endl; } 13132
void erase() { cout << "Square::erase" << endl; } 13133
~Square() { cout << "Square::~Square" << endl; } 13134
}; 13135
13136
Shape* Shape::factory(const string& type) 13137
505
throw(Shape::BadShapeCreation) { 13138
if(type == "Circle") return new Circle; 13139
if(type == "Square") return new Square; 13140
throw BadShapeCreation(type); 13141
} 13142
13143
char* sl[] = { "Circle", "Square", "Square", 13144
"Circle", "Circle", "Circle", "Square" }; 13145
13146
int main() { 13147
vector<Shape*> shapes; 13148
try { 13149
for(size_t i = 0; i < sizeof sl / sizeof sl[0]; i++) 13150
shapes.push_back(Shape::factory(sl[i])); 13151
} catch(Shape::BadShapeCreation e) { 13152
cout << e.what() << endl; 13153
purge(shapes); 13154
return EXIT_FAILURE; 13155
} 13156
for(size_t i = 0; i < shapes.size(); i++) { 13157
shapes[i]->draw(); 13158
shapes[i]->erase(); 13159
} 13160
purge(shapes); 13161
} ///:~ 13162
506
Listado 9.23. C10/ShapeFactory1.cpp 13163
13164
La función factory() toma un argumento que le permite determinar qué tipo 13165
de figura crear. Aquí, el argumento es una cadena, pero podría ser cualquier 13166
conjunto de datos. El método factory() es el único código del sistema que hay 13167
que cambiar cuando se añade un nuevo tipo de figura. (Los datos de 13168
inicialización para los objetos vendrán supuestamente de algún sitio fuera del 13169
sistema y no serán un FIXME: hard-coded array como en el ejemplo.) 13170
Para asegurar que la creación sólo puede realizarse en factory(), los 13171
constructores de cada tipo específico de figura se hacen privados, y Shape 13172
se declara como friend de forma que factory() tiene acceso a los mismos. 13173
(También se podría declarar sólamente Shape::factory() como friend, pero 13174
parece razonablemente inocuo declarar la clase base entera.) Hay otra 13175
implicación importante de este diseño -la clase base, Shape, debe conocer 13176
ahora los detalles de todas las clases derivadas -una propiedad que el 13177
diseño orientado a objetos intenta evitar. Para frameworks o cualquier librería 13178
de clases que deban poder extenderse, esto hace que se convierta 13179
rápidamente en algo difícil de manejar, ya que la clase base debe 13180
actualizarse en cuanto se añada un tipo nuevo a la jerarquía. Las factorías 13181
polimórficas, descritas en la siguiente subsección, se pueden usar para evitar 13182
esta dependencia circular tan poco deseada. 13183
9.11.1. Factorías polimórficas 13184
La función estática factory() en el ejemplo anterior fuerza que las 13185
operaciones de creación se centren en un punto, de forma que sea el único 13186
sitio en el que haya que cambiar código. Esto es, sin duda, una solución 13187
razonable, ya que encapsula amablemente el proceso de crear objetos. Sin 13188
embargo, el GoF enfatiza que la razón de ser del patrón Factory Method es 13189
que diferentes tipos de factorías se puedan derivar de la factoría básica. 13190
Factory Method es, de hecho, un tipo especial de factoría polimórfica. Esto 13191
es ShapeFactory1.cpp modificado para que los Factory Methods estén en 13192
una clase aparte como funciones virtuales. 13193
//: C10:ShapeFactory2.cpp 13194
507
// Polymorphic Factory Methods. 13195
#include <iostream> 13196
#include <map> 13197
#include <string> 13198
#include <vector> 13199
#include <stdexcept> 13200
#include <cstddef> 13201
#include "../purge.h" 13202
using namespace std; 13203
13204
class Shape { 13205
public: 13206
virtual void draw() = 0; 13207
virtual void erase() = 0; 13208
virtual ~Shape() {} 13209
}; 13210
13211
class ShapeFactory { 13212
virtual Shape* create() = 0; 13213
static map<string, ShapeFactory*> factories; 13214
public: 13215
virtual ~ShapeFactory() {} 13216
friend class ShapeFactoryInitializer; 13217
class BadShapeCreation : public logic_error { 13218
public: 13219
508
BadShapeCreation(string type) 13220
: logic_error("Cannot create type " + type) {} 13221
}; 13222
static Shape* 13223
createShape(const string& id) throw(BadShapeCreation) { 13224
if(factories.find(id) != factories.end()) 13225
return factories[id]->create(); 13226
else 13227
throw BadShapeCreation(id); 13228
} 13229
}; 13230
13231
// Define the static object: 13232
map<string, ShapeFactory*> ShapeFactory::factories; 13233
13234
class Circle : public Shape { 13235
Circle() {} // Private constructor 13236
friend class ShapeFactoryInitializer; 13237
class Factory; 13238
friend class Factory; 13239
class Factory : public ShapeFactory { 13240
public: 13241
Shape* create() { return new Circle; } 13242
friend class ShapeFactoryInitializer; 13243
}; 13244
509
public: 13245
void draw() { cout << "Circle::draw" << endl; } 13246
void erase() { cout << "Circle::erase" << endl; } 13247
~Circle() { cout << "Circle::~Circle" << endl; } 13248
}; 13249
13250
class Square : public Shape { 13251
Square() {} 13252
friend class ShapeFactoryInitializer; 13253
class Factory; 13254
friend class Factory; 13255
class Factory : public ShapeFactory { 13256
public: 13257
Shape* create() { return new Square; } 13258
friend class ShapeFactoryInitializer; 13259
}; 13260
public: 13261
void draw() { cout << "Square::draw" << endl; } 13262
void erase() { cout << "Square::erase" << endl; } 13263
~Square() { cout << "Square::~Square" << endl; } 13264
}; 13265
13266
// Singleton to initialize the ShapeFactory: 13267
class ShapeFactoryInitializer { 13268
static ShapeFactoryInitializer si; 13269
510
ShapeFactoryInitializer() { 13270
ShapeFactory::factories["Circle"]= new Circle::Factory; 13271
ShapeFactory::factories["Square"]= new Square::Factory; 13272
} 13273
~ShapeFactoryInitializer() { 13274
map<string, ShapeFactory*>::iterator it = 13275
ShapeFactory::factories.begin(); 13276
while(it != ShapeFactory::factories.end()) 13277
delete it++->second; 13278
} 13279
}; 13280
13281
// Static member definition: 13282
ShapeFactoryInitializer ShapeFactoryInitializer::si; 13283
13284
char* sl[] = { "Circle", "Square", "Square", 13285
"Circle", "Circle", "Circle", "Square" }; 13286
13287
int main() { 13288
vector<Shape*> shapes; 13289
try { 13290
for(size_t i = 0; i < sizeof sl / sizeof sl[0]; i++) 13291
shapes.push_back(ShapeFactory::createShape(sl[i])); 13292
} catch(ShapeFactory::BadShapeCreation e) { 13293
cout << e.what() << endl; 13294
511
return EXIT_FAILURE; 13295
} 13296
for(size_t i = 0; i < shapes.size(); i++) { 13297
shapes[i]->draw(); 13298
shapes[i]->erase(); 13299
} 13300
purge(shapes); 13301
} ///:~ 13302
Listado 9.24. C10/ShapeFactory2.cpp 13303
13304
Ahora, Factory Method aparece en su propia clase, ShapeFactory, como 13305
virtual create(). Es una función miembro privada, lo que significa que no 13306
puede ser llamada directametne, pero puede ser sobreescrita. Las subclases 13307
de Shape deben crear cada una su propias subclases de ShapeFactory y 13308
sobreescribir el método create para crear un objeto de su propio tipe. Estas 13309
factorías son privadas, de forma que sólo pueden ser accedidas desde el 13310
Factory Method principal. De esta forma, todo el código cliente debe pasar a 13311
través del Factory Method para crear objetos. 13312
La verdadera creación de figuras se realiza llamando a 13313
ShapeFactory::createShape( ), que es una función estática que usa el mapa 13314
en ShapeFactory para encontrar la objeto factoría apropiado basándose en el 13315
identificador que se le pasa. La factoría crea el objeto figura directamente, 13316
pero podría imaginarse un problema más complejo en el que el objeto 13317
factoría apropiado se devuelve y luego lo usa quien lo ha llamado para crear 13318
un objeto de una manera más sofisticada. Sin embargo, parece que la 13319
mayoría del tiempo no hacen falta las complejidades del Factory Method 13320
polimórfico, y bastará con una única función estática en la clase base (como 13321
se muestra en ShapeFactory1.cpp). 13322
Observe que el ShapeFactory debe ser inicializado cargando su mapa con 13323
objetos factory, lo que tiene lugar en el Singleton ShapeFactoryInitializer. Así 13324
512
que para añadir un nuevo tipo a este diseño debe definir el tipo, crear una 13325
factoría, y modificar ShapeFactoryInitializer para que se inserte una instancia 13326
de su factoría en el mapa. Esta complejidad extra, sugiere de nuevo el uso 13327
de un Factory Method estático si no necesita crear objetos factoría 13328
individuales. 13329
9.11.2. Factorías abstractas 13330
El patrón Factoría Abstracta se parece a las factorías que hemos visto 13331
anteriormente, pero con varios Factory Methods. Cada uno de los Factory 13332
Method crea una clase distinta de objeto. Cuando se crea el objecto factoría, 13333
se decide cómo se usarán todos los objetos creados con esa factoría. El 13334
ejemplo del GoF implementa la portabilidad a través de varias interfaces 13335
gráficas de usuario (GUI): se crea el objeto factoría apropiado para la GUI 13336
con la que se está trabajando y desde ahí en adelante, cuando le pida un 13337
menú, botón, barra deslizante y demás, creará automáticamente la versión 13338
apropiada para la GUI de ese elemento. Por lo tanto, es posible aislar, en un 13339
solo lugar, el efecto de cambiar de una GUI a otra. 13340
Por ejemplo, suponga que está creando un entorno para juegos de 13341
propósito general y quiere ser capaz de soportar diferentes tipos de juegos. 13342
Así es como sería usando una Factoría Abstracta: 13343
//: C10:AbstractFactory.cpp 13344
// A gaming environment. 13345
#include <iostream> 13346
using namespace std; 13347
13348
class Obstacle { 13349
public: 13350
virtual void action() = 0; 13351
}; 13352
513
13353
class Player { 13354
public: 13355
virtual void interactWith(Obstacle*) = 0; 13356
}; 13357
13358
class Kitty: public Player { 13359
virtual void interactWith(Obstacle* ob) { 13360
cout << "Kitty has encountered a "; 13361
ob->action(); 13362
} 13363
}; 13364
13365
class KungFuGuy: public Player { 13366
virtual void interactWith(Obstacle* ob) { 13367
cout << "KungFuGuy now battles against a "; 13368
ob->action(); 13369
} 13370
}; 13371
13372
class Puzzle: public Obstacle { 13373
public: 13374
void action() { cout << "Puzzle" << endl; } 13375
}; 13376
13377
514
class NastyWeapon: public Obstacle { 13378
public: 13379
void action() { cout << "NastyWeapon" << endl; } 13380
}; 13381
13382
// The abstract factory: 13383
class GameElementFactory { 13384
public: 13385
virtual Player* makePlayer() = 0; 13386
virtual Obstacle* makeObstacle() = 0; 13387
}; 13388
13389
// Concrete factories: 13390
class KittiesAndPuzzles : public GameElementFactory { 13391
public: 13392
virtual Player* makePlayer() { return new Kitty; } 13393
virtual Obstacle* makeObstacle() { return new Puzzle; } 13394
}; 13395
13396
class KillAndDismember : public GameElementFactory { 13397
public: 13398
virtual Player* makePlayer() { return new KungFuGuy; } 13399
virtual Obstacle* makeObstacle() { 13400
return new NastyWeapon; 13401
} 13402
515
}; 13403
13404
class GameEnvironment { 13405
GameElementFactory* gef; 13406
Player* p; 13407
Obstacle* ob; 13408
public: 13409
GameEnvironment(GameElementFactory* factory) 13410
: gef(factory), p(factory->makePlayer()), 13411
ob(factory->makeObstacle()) {} 13412
void play() { p->interactWith(ob); } 13413
~GameEnvironment() { 13414
delete p; 13415
delete ob; 13416
delete gef; 13417
} 13418
}; 13419
13420
int main() { 13421
GameEnvironment 13422
g1(new KittiesAndPuzzles), 13423
g2(new KillAndDismember); 13424
g1.play(); 13425
g2.play(); 13426
} 13427
516
/* Output: 13428
Kitty has encountered a Puzzle 13429
KungFuGuy now battles against a NastyWeapon */ ///:~ 13430
Listado 9.25. C10/AbstractFactory.cpp 13431
13432
En este entorno, los objetos Player interactúan con objetos Obstacle, pero 13433
los tipos de los jugadores y los obstáculos dependen del juego. El tipo de 13434
juego se determina eligiendo un GameElementFactory concreto, y luego el 13435
GameEnvironment controla la configuración y ejecución del juego. En este 13436
ejemplo, la configuración y ejecución son simples, pero dichas actividades 13437
(las condiciones iniciales y los cambios de estado) pueden determinar gran 13438
parte del resultado del juego. Aquí, GameEnvironment no está diseñado para 13439
ser heredado, aunque puede tener sentido hacerlo. 13440
Este ejemplo también ilustra el despachado doble, que se explicará más 13441
adelante. 13442
9.11.3. Constructores virtuales 13443
//: C10:VirtualConstructor.cpp 13444
#include <iostream> 13445
#include <string> 13446
#include <stdexcept> 13447
#include <stdexcept> 13448
#include <cstddef> 13449
#include <vector> 13450
#include "../purge.h" 13451
using namespace std; 13452
13453
517
class Shape { 13454
Shape* s; 13455
// Prevent copy-construction & operator= 13456
Shape(Shape&); 13457
Shape operator=(Shape&); 13458
protected: 13459
Shape() { s = 0; } 13460
public: 13461
virtual void draw() { s->draw(); } 13462
virtual void erase() { s->erase(); } 13463
virtual void test() { s->test(); } 13464
virtual ~Shape() { 13465
cout << "~Shape" << endl; 13466
if(s) { 13467
cout << "Making virtual call: "; 13468
s->erase(); // Virtual call 13469
} 13470
cout << "delete s: "; 13471
delete s; // The polymorphic deletion 13472
// (delete 0 is legal; it produces a no-op) 13473
} 13474
class BadShapeCreation : public logic_error { 13475
public: 13476
BadShapeCreation(string type) 13477
: logic_error("Cannot create type " + type) {} 13478
518
}; 13479
Shape(string type) throw(BadShapeCreation); 13480
}; 13481
13482
class Circle : public Shape { 13483
Circle(Circle&); 13484
Circle operator=(Circle&); 13485
Circle() {} // Private constructor 13486
friend class Shape; 13487
public: 13488
void draw() { cout << "Circle::draw" << endl; } 13489
void erase() { cout << "Circle::erase" << endl; } 13490
void test() { draw(); } 13491
~Circle() { cout << "Circle::~Circle" << endl; } 13492
}; 13493
13494
class Square : public Shape { 13495
Square(Square&); 13496
Square operator=(Square&); 13497
Square() {} 13498
friend class Shape; 13499
public: 13500
void draw() { cout << "Square::draw" << endl; } 13501
void erase() { cout << "Square::erase" << endl; } 13502
void test() { draw(); } 13503
519
~Square() { cout << "Square::~Square" << endl; } 13504
}; 13505
13506
Shape::Shape(string type) throw(Shape::BadShapeCreation) { 13507
if(type == "Circle") 13508
s = new Circle; 13509
else if(type == "Square") 13510
s = new Square; 13511
else throw BadShapeCreation(type); 13512
draw(); // Virtual call in the constructor 13513
} 13514
13515
char* sl[] = { "Circle", "Square", "Square", 13516
"Circle", "Circle", "Circle", "Square" }; 13517
13518
int main() { 13519
vector<Shape*> shapes; 13520
cout << "virtual constructor calls:" << endl; 13521
try { 13522
for(size_t i = 0; i < sizeof sl / sizeof sl[0]; i++) 13523
shapes.push_back(new Shape(sl[i])); 13524
} catch(Shape::BadShapeCreation e) { 13525
cout << e.what() << endl; 13526
purge(shapes); 13527
return EXIT_FAILURE; 13528
520
} 13529
for(size_t i = 0; i < shapes.size(); i++) { 13530
shapes[i]->draw(); 13531
cout << "test" << endl; 13532
shapes[i]->test(); 13533
cout << "end test" << endl; 13534
shapes[i]->erase(); 13535
} 13536
Shape c("Circle"); // Create on the stack 13537
cout << "destructor calls:" << endl; 13538
purge(shapes); 13539
} ///:~ 13540
Listado 9.26. C10/VirtualConstructor.cpp 13541
13542
9.12. Builder: creación de objetos complejos 13543
//: C10:Bicycle.h 13544
// Defines classes to build bicycles; 13545
// Illustrates the Builder design pattern. 13546
#ifndef BICYCLE_H 13547
#define BICYCLE_H 13548
#include <iostream> 13549
#include <string> 13550
#include <vector> 13551
521
#include <cstddef> 13552
#include "../purge.h" 13553
using std::size_t; 13554
13555
class BicyclePart { 13556
public: 13557
enum BPart { FRAME, WHEEL, SEAT, DERAILLEUR, 13558
HANDLEBAR, SPROCKET, RACK, SHOCK, NPARTS }; 13559
private: 13560
BPart id; 13561
static std::string names[NPARTS]; 13562
public: 13563
BicyclePart(BPart bp) { id = bp; } 13564
friend std::ostream& 13565
operator<<(std::ostream& os, const BicyclePart& bp) { 13566
return os << bp.names[bp.id]; 13567
} 13568
}; 13569
13570
class Bicycle { 13571
std::vector<BicyclePart*> parts; 13572
public: 13573
~Bicycle() { purge(parts); } 13574
void addPart(BicyclePart* bp) { parts.push_back(bp); } 13575
friend std::ostream& 13576
522
operator<<(std::ostream& os, const Bicycle& b) { 13577
os << "{ "; 13578
for(size_t i = 0; i < b.parts.size(); ++i) 13579
os << *b.parts[i] << ' '; 13580
return os << '}'; 13581
} 13582
}; 13583
13584
class BicycleBuilder { 13585
protected: 13586
Bicycle* product; 13587
public: 13588
BicycleBuilder() { product = 0; } 13589
void createProduct() { product = new Bicycle; } 13590
virtual void buildFrame() = 0; 13591
virtual void buildWheel() = 0; 13592
virtual void buildSeat() = 0; 13593
virtual void buildDerailleur() = 0; 13594
virtual void buildHandlebar() = 0; 13595
virtual void buildSprocket() = 0; 13596
virtual void buildRack() = 0; 13597
virtual void buildShock() = 0; 13598
virtual std::string getBikeName() const = 0; 13599
Bicycle* getProduct() { 13600
Bicycle* temp = product; 13601
523
product = 0; // Relinquish product 13602
return temp; 13603
} 13604
}; 13605
13606
class MountainBikeBuilder : public BicycleBuilder { 13607
public: 13608
void buildFrame(); 13609
void buildWheel(); 13610
void buildSeat(); 13611
void buildDerailleur(); 13612
void buildHandlebar(); 13613
void buildSprocket(); 13614
void buildRack(); 13615
void buildShock(); 13616
std::string getBikeName() const { return "MountainBike";} 13617
}; 13618
13619
class TouringBikeBuilder : public BicycleBuilder { 13620
public: 13621
void buildFrame(); 13622
void buildWheel(); 13623
void buildSeat(); 13624
void buildDerailleur(); 13625
void buildHandlebar(); 13626
524
void buildSprocket(); 13627
void buildRack(); 13628
void buildShock(); 13629
std::string getBikeName() const { return "TouringBike"; } 13630
}; 13631
13632
class RacingBikeBuilder : public BicycleBuilder { 13633
public: 13634
void buildFrame(); 13635
void buildWheel(); 13636
void buildSeat(); 13637
void buildDerailleur(); 13638
void buildHandlebar(); 13639
void buildSprocket(); 13640
void buildRack(); 13641
void buildShock(); 13642
std::string getBikeName() const { return "RacingBike"; } 13643
}; 13644
13645
class BicycleTechnician { 13646
BicycleBuilder* builder; 13647
public: 13648
BicycleTechnician() { builder = 0; } 13649
void setBuilder(BicycleBuilder* b) { builder = b; } 13650
void construct(); 13651
525
}; 13652
#endif // BICYCLE_H ///:~ 13653
Listado 9.27. C10/Bicycle.h 13654
13655
//: C10:Bicycle.cpp {O} {-mwcc} 13656
#include "Bicycle.h" 13657
#include <cassert> 13658
#include <cstddef> 13659
using namespace std; 13660
13661
std::string BicyclePart::names[NPARTS] = { 13662
"Frame", "Wheel", "Seat", "Derailleur", 13663
"Handlebar", "Sprocket", "Rack", "Shock" }; 13664
13665
// MountainBikeBuilder implementation 13666
void MountainBikeBuilder::buildFrame() { 13667
product->addPart(new BicyclePart(BicyclePart::FRAME)); 13668
} 13669
void MountainBikeBuilder::buildWheel() { 13670
product->addPart(new BicyclePart(BicyclePart::WHEEL)); 13671
} 13672
void MountainBikeBuilder::buildSeat() { 13673
product->addPart(new BicyclePart(BicyclePart::SEAT)); 13674
} 13675
526
void MountainBikeBuilder::buildDerailleur() { 13676
product->addPart( 13677
new BicyclePart(BicyclePart::DERAILLEUR)); 13678
} 13679
void MountainBikeBuilder::buildHandlebar() { 13680
product->addPart( 13681
new BicyclePart(BicyclePart::HANDLEBAR)); 13682
} 13683
void MountainBikeBuilder::buildSprocket() { 13684
product->addPart(new BicyclePart(BicyclePart::SPROCKET)); 13685
} 13686
void MountainBikeBuilder::buildRack() {} 13687
void MountainBikeBuilder::buildShock() { 13688
product->addPart(new BicyclePart(BicyclePart::SHOCK)); 13689
} 13690
13691
// TouringBikeBuilder implementation 13692
void TouringBikeBuilder::buildFrame() { 13693
product->addPart(new BicyclePart(BicyclePart::FRAME)); 13694
} 13695
void TouringBikeBuilder::buildWheel() { 13696
product->addPart(new BicyclePart(BicyclePart::WHEEL)); 13697
} 13698
void TouringBikeBuilder::buildSeat() { 13699
product->addPart(new BicyclePart(BicyclePart::SEAT)); 13700
527
} 13701
void TouringBikeBuilder::buildDerailleur() { 13702
product->addPart( 13703
new BicyclePart(BicyclePart::DERAILLEUR)); 13704
} 13705
void TouringBikeBuilder::buildHandlebar() { 13706
product->addPart( 13707
new BicyclePart(BicyclePart::HANDLEBAR)); 13708
} 13709
void TouringBikeBuilder::buildSprocket() { 13710
product->addPart(new BicyclePart(BicyclePart::SPROCKET)); 13711
} 13712
void TouringBikeBuilder::buildRack() { 13713
product->addPart(new BicyclePart(BicyclePart::RACK)); 13714
} 13715
void TouringBikeBuilder::buildShock() {} 13716
13717
// RacingBikeBuilder implementation 13718
void RacingBikeBuilder::buildFrame() { 13719
product->addPart(new BicyclePart(BicyclePart::FRAME)); 13720
} 13721
void RacingBikeBuilder::buildWheel() { 13722
product->addPart(new BicyclePart(BicyclePart::WHEEL)); 13723
} 13724
void RacingBikeBuilder::buildSeat() { 13725
528
product->addPart(new BicyclePart(BicyclePart::SEAT)); 13726
} 13727
void RacingBikeBuilder::buildDerailleur() {} 13728
void RacingBikeBuilder::buildHandlebar() { 13729
product->addPart( 13730
new BicyclePart(BicyclePart::HANDLEBAR)); 13731
} 13732
void RacingBikeBuilder::buildSprocket() { 13733
product->addPart(new BicyclePart(BicyclePart::SPROCKET)); 13734
} 13735
void RacingBikeBuilder::buildRack() {} 13736
void RacingBikeBuilder::buildShock() {} 13737
13738
// BicycleTechnician implementation 13739
void BicycleTechnician::construct() { 13740
assert(builder); 13741
builder->createProduct(); 13742
builder->buildFrame(); 13743
builder->buildWheel(); 13744
builder->buildSeat(); 13745
builder->buildDerailleur(); 13746
builder->buildHandlebar(); 13747
builder->buildSprocket(); 13748
builder->buildRack(); 13749
builder->buildShock(); 13750
529
} ///:~ 13751
Listado 9.28. C10/Bicycle.cpp 13752
13753
//: C10:BuildBicycles.cpp 13754
//{L} Bicycle 13755
// The Builder design pattern. 13756
#include <cstddef> 13757
#include <iostream> 13758
#include <map> 13759
#include <vector> 13760
#include "Bicycle.h" 13761
#include "../purge.h" 13762
using namespace std; 13763
13764
// Constructs a bike via a concrete builder 13765
Bicycle* buildMeABike( 13766
BicycleTechnician& t, BicycleBuilder* builder) { 13767
t.setBuilder(builder); 13768
t.construct(); 13769
Bicycle* b = builder->getProduct(); 13770
cout << "Built a " << builder->getBikeName() << endl; 13771
return b; 13772
} 13773
13774
530
int main() { 13775
// Create an order for some bicycles 13776
map <string, size_t> order; 13777
order["mountain"] = 2; 13778
order["touring"] = 1; 13779
order["racing"] = 3; 13780
13781
// Build bikes 13782
vector<Bicycle*> bikes; 13783
BicycleBuilder* m = new MountainBikeBuilder; 13784
BicycleBuilder* t = new TouringBikeBuilder; 13785
BicycleBuilder* r = new RacingBikeBuilder; 13786
BicycleTechnician tech; 13787
map<string, size_t>::iterator it = order.begin(); 13788
while(it != order.end()) { 13789
BicycleBuilder* builder; 13790
if(it->first == "mountain") 13791
builder = m; 13792
else if(it->first == "touring") 13793
builder = t; 13794
else if(it->first == "racing") 13795
builder = r; 13796
for(size_t i = 0; i < it->second; ++i) 13797
bikes.push_back(buildMeABike(tech, builder)); 13798
++it; 13799
531
} 13800
delete m; 13801
delete t; 13802
delete r; 13803
13804
// Display inventory 13805
for(size_t i = 0; i < bikes.size(); ++i) 13806
cout << "Bicycle: " << *bikes[i] << endl; 13807
purge(bikes); 13808
} 13809
13810
/* Output: 13811
Built a MountainBike 13812
Built a MountainBike 13813
Built a RacingBike 13814
Built a RacingBike 13815
Built a RacingBike 13816
Built a TouringBike 13817
Bicycle: { 13818
Frame Wheel Seat Derailleur Handlebar Sprocket Shock } 13819
Bicycle: { 13820
Frame Wheel Seat Derailleur Handlebar Sprocket Shock } 13821
Bicycle: { Frame Wheel Seat Handlebar Sprocket } 13822
Bicycle: { Frame Wheel Seat Handlebar Sprocket } 13823
Bicycle: { Frame Wheel Seat Handlebar Sprocket } 13824
532
Bicycle: { 13825
Frame Wheel Seat Derailleur Handlebar Sprocket Rack } 13826
*/ ///:~ 13827
Listado 9.29. C10/BuildBicycles.cpp 13828
13829
9.13. Observador 13830
//: C10:Observer.h 13831
// The Observer interface. 13832
#ifndef OBSERVER_H 13833
#define OBSERVER_H 13834
13835
class Observable; 13836
class Argument {}; 13837
13838
class Observer { 13839
public: 13840
// Called by the observed object, whenever 13841
// the observed object is changed: 13842
virtual void update(Observable* o, Argument* arg) = 0; 13843
virtual ~Observer() {} 13844
}; 13845
#endif // OBSERVER_H ///:~ 13846
Listado 9.30. C10/Observer.h 13847
533
13848
//: C10:Observable.h 13849
// The Observable class. 13850
#ifndef OBSERVABLE_H 13851
#define OBSERVABLE_H 13852
#include <set> 13853
#include "Observer.h" 13854
13855
class Observable { 13856
bool changed; 13857
std::set<Observer*> observers; 13858
protected: 13859
virtual void setChanged() { changed = true; } 13860
virtual void clearChanged() { changed = false; } 13861
public: 13862
virtual void addObserver(Observer& o) { 13863
observers.insert(&o); 13864
} 13865
virtual void deleteObserver(Observer& o) { 13866
observers.erase(&o); 13867
} 13868
virtual void deleteObservers() { 13869
observers.clear(); 13870
} 13871
virtual int countObservers() { 13872
534
return observers.size(); 13873
} 13874
virtual bool hasChanged() { return changed; } 13875
// If this object has changed, notify all 13876
// of its observers: 13877
virtual void notifyObservers(Argument* arg = 0) { 13878
if(!hasChanged()) return; 13879
clearChanged(); // Not "changed" anymore 13880
std::set<Observer*>::iterator it; 13881
for(it = observers.begin();it != observers.end(); it++) 13882
(*it)->update(this, arg); 13883
} 13884
virtual ~Observable() {} 13885
}; 13886
#endif // OBSERVABLE_H ///:~ 13887
Listado 9.31. C10/Observable.h 13888
13889
//: C10:InnerClassIdiom.cpp 13890
// Example of the "inner class" idiom. 13891
#include <iostream> 13892
#include <string> 13893
using namespace std; 13894
13895
class Poingable { 13896
535
public: 13897
virtual void poing() = 0; 13898
}; 13899
13900
void callPoing(Poingable& p) { 13901
p.poing(); 13902
} 13903
13904
class Bingable { 13905
public: 13906
virtual void bing() = 0; 13907
}; 13908
13909
void callBing(Bingable& b) { 13910
b.bing(); 13911
} 13912
13913
class Outer { 13914
string name; 13915
// Define one inner class: 13916
class Inner1; 13917
friend class Outer::Inner1; 13918
class Inner1 : public Poingable { 13919
Outer* parent; 13920
public: 13921
536
Inner1(Outer* p) : parent(p) {} 13922
void poing() { 13923
cout << "poing called for " 13924
<< parent->name << endl; 13925
// Accesses data in the outer class object 13926
} 13927
} inner1; 13928
// Define a second inner class: 13929
class Inner2; 13930
friend class Outer::Inner2; 13931
class Inner2 : public Bingable { 13932
Outer* parent; 13933
public: 13934
Inner2(Outer* p) : parent(p) {} 13935
void bing() { 13936
cout << "bing called for " 13937
<< parent->name << endl; 13938
} 13939
} inner2; 13940
public: 13941
Outer(const string& nm) 13942
: name(nm), inner1(this), inner2(this) {} 13943
// Return reference to interfaces 13944
// implemented by the inner classes: 13945
operator Poingable&() { return inner1; } 13946
537
operator Bingable&() { return inner2; } 13947
}; 13948
13949
int main() { 13950
Outer x("Ping Pong"); 13951
// Like upcasting to multiple base types!: 13952
callPoing(x); 13953
callBing(x); 13954
} ///:~ 13955
Listado 9.32. C10/InnerClassIdiom.cpp 13956
13957
9.13.1. El ejemplo de observador 13958
//: C10:ObservedFlower.cpp 13959
// Demonstration of "observer" pattern. 13960
#include <algorithm> 13961
#include <iostream> 13962
#include <string> 13963
#include <vector> 13964
#include "Observable.h" 13965
using namespace std; 13966
13967
class Flower { 13968
bool isOpen; 13969
public: 13970
538
Flower() : isOpen(false), 13971
openNotifier(this), closeNotifier(this) {} 13972
void open() { // Opens its petals 13973
isOpen = true; 13974
openNotifier.notifyObservers(); 13975
closeNotifier.open(); 13976
} 13977
void close() { // Closes its petals 13978
isOpen = false; 13979
closeNotifier.notifyObservers(); 13980
openNotifier.close(); 13981
} 13982
// Using the "inner class" idiom: 13983
class OpenNotifier; 13984
friend class Flower::OpenNotifier; 13985
class OpenNotifier : public Observable { 13986
Flower* parent; 13987
bool alreadyOpen; 13988
public: 13989
OpenNotifier(Flower* f) : parent(f), 13990
alreadyOpen(false) {} 13991
void notifyObservers(Argument* arg = 0) { 13992
if(parent->isOpen && !alreadyOpen) { 13993
setChanged(); 13994
Observable::notifyObservers(); 13995
539
alreadyOpen = true; 13996
} 13997
} 13998
void close() { alreadyOpen = false; } 13999
} openNotifier; 14000
class CloseNotifier; 14001
friend class Flower::CloseNotifier; 14002
class CloseNotifier : public Observable { 14003
Flower* parent; 14004
bool alreadyClosed; 14005
public: 14006
CloseNotifier(Flower* f) : parent(f), 14007
alreadyClosed(false) {} 14008
void notifyObservers(Argument* arg = 0) { 14009
if(!parent->isOpen && !alreadyClosed) { 14010
setChanged(); 14011
Observable::notifyObservers(); 14012
alreadyClosed = true; 14013
} 14014
} 14015
void open() { alreadyClosed = false; } 14016
} closeNotifier; 14017
}; 14018
14019
class Bee { 14020
540
string name; 14021
// An "inner class" for observing openings: 14022
class OpenObserver; 14023
friend class Bee::OpenObserver; 14024
class OpenObserver : public Observer { 14025
Bee* parent; 14026
public: 14027
OpenObserver(Bee* b) : parent(b) {} 14028
void update(Observable*, Argument *) { 14029
cout << "Bee " << parent->name 14030
<< "'s breakfast time!" << endl; 14031
} 14032
} openObsrv; 14033
// Another "inner class" for closings: 14034
class CloseObserver; 14035
friend class Bee::CloseObserver; 14036
class CloseObserver : public Observer { 14037
Bee* parent; 14038
public: 14039
CloseObserver(Bee* b) : parent(b) {} 14040
void update(Observable*, Argument *) { 14041
cout << "Bee " << parent->name 14042
<< "'s bed time!" << endl; 14043
} 14044
} closeObsrv; 14045
541
public: 14046
Bee(string nm) : name(nm), 14047
openObsrv(this), closeObsrv(this) {} 14048
Observer& openObserver() { return openObsrv; } 14049
Observer& closeObserver() { return closeObsrv;} 14050
}; 14051
14052
class Hummingbird { 14053
string name; 14054
class OpenObserver; 14055
friend class Hummingbird::OpenObserver; 14056
class OpenObserver : public Observer { 14057
Hummingbird* parent; 14058
public: 14059
OpenObserver(Hummingbird* h) : parent(h) {} 14060
void update(Observable*, Argument *) { 14061
cout << "Hummingbird " << parent->name 14062
<< "'s breakfast time!" << endl; 14063
} 14064
} openObsrv; 14065
class CloseObserver; 14066
friend class Hummingbird::CloseObserver; 14067
class CloseObserver : public Observer { 14068
Hummingbird* parent; 14069
public: 14070
542
CloseObserver(Hummingbird* h) : parent(h) {} 14071
void update(Observable*, Argument *) { 14072
cout << "Hummingbird " << parent->name 14073
<< "'s bed time!" << endl; 14074
} 14075
} closeObsrv; 14076
public: 14077
Hummingbird(string nm) : name(nm), 14078
openObsrv(this), closeObsrv(this) {} 14079
Observer& openObserver() { return openObsrv; } 14080
Observer& closeObserver() { return closeObsrv;} 14081
}; 14082
14083
int main() { 14084
Flower f; 14085
Bee ba("A"), bb("B"); 14086
Hummingbird ha("A"), hb("B"); 14087
f.openNotifier.addObserver(ha.openObserver()); 14088
f.openNotifier.addObserver(hb.openObserver()); 14089
f.openNotifier.addObserver(ba.openObserver()); 14090
f.openNotifier.addObserver(bb.openObserver()); 14091
f.closeNotifier.addObserver(ha.closeObserver()); 14092
f.closeNotifier.addObserver(hb.closeObserver()); 14093
f.closeNotifier.addObserver(ba.closeObserver()); 14094
f.closeNotifier.addObserver(bb.closeObserver()); 14095
543
// Hummingbird B decides to sleep in: 14096
f.openNotifier.deleteObserver(hb.openObserver()); 14097
// Something changes that interests observers: 14098
f.open(); 14099
f.open(); // It's already open, no change. 14100
// Bee A doesn't want to go to bed: 14101
f.closeNotifier.deleteObserver( 14102
ba.closeObserver()); 14103
f.close(); 14104
f.close(); // It's already closed; no change 14105
f.openNotifier.deleteObservers(); 14106
f.open(); 14107
f.close(); 14108
} ///:~ 14109
Listado 9.33. C10/ObservedFlower.cpp 14110
14111
9.14. Despachado múltiple 14112
//: C10:PaperScissorsRock.cpp 14113
// Demonstration of multiple dispatching. 14114
#include <algorithm> 14115
#include <iostream> 14116
#include <iterator> 14117
#include <vector> 14118
544
#include <ctime> 14119
#include <cstdlib> 14120
#include "../purge.h" 14121
using namespace std; 14122
14123
class Paper; 14124
class Scissors; 14125
class Rock; 14126
14127
enum Outcome { WIN, LOSE, DRAW }; 14128
14129
ostream& operator<<(ostream& os, const Outcome out) { 14130
switch(out) { 14131
default: 14132
case WIN: return os << "win"; 14133
case LOSE: return os << "lose"; 14134
case DRAW: return os << "draw"; 14135
} 14136
} 14137
14138
class Item { 14139
public: 14140
virtual Outcome compete(const Item*) = 0; 14141
virtual Outcome eval(const Paper*) const = 0; 14142
virtual Outcome eval(const Scissors*) const= 0; 14143
545
virtual Outcome eval(const Rock*) const = 0; 14144
virtual ostream& print(ostream& os) const = 0; 14145
virtual ~Item() {} 14146
friend ostream& operator<<(ostream& os, const Item* it) { 14147
return it->print(os); 14148
} 14149
}; 14150
14151
class Paper : public Item { 14152
public: 14153
Outcome compete(const Item* it) { return it->eval(this);} 14154
Outcome eval(const Paper*) const { return DRAW; } 14155
Outcome eval(const Scissors*) const { return WIN; } 14156
Outcome eval(const Rock*) const { return LOSE; } 14157
ostream& print(ostream& os) const { 14158
return os << "Paper "; 14159
} 14160
}; 14161
14162
class Scissors : public Item { 14163
public: 14164
Outcome compete(const Item* it) { return it->eval(this);} 14165
Outcome eval(const Paper*) const { return LOSE; } 14166
Outcome eval(const Scissors*) const { return DRAW; } 14167
Outcome eval(const Rock*) const { return WIN; } 14168
546
ostream& print(ostream& os) const { 14169
return os << "Scissors"; 14170
} 14171
}; 14172
14173
class Rock : public Item { 14174
public: 14175
Outcome compete(const Item* it) { return it->eval(this);} 14176
Outcome eval(const Paper*) const { return WIN; } 14177
Outcome eval(const Scissors*) const { return LOSE; } 14178
Outcome eval(const Rock*) const { return DRAW; } 14179
ostream& print(ostream& os) const { 14180
return os << "Rock "; 14181
} 14182
}; 14183
14184
struct ItemGen { 14185
Item* operator()() { 14186
switch(rand() % 3) { 14187
default: 14188
case 0: return new Scissors; 14189
case 1: return new Paper; 14190
case 2: return new Rock; 14191
} 14192
} 14193
547
}; 14194
14195
struct Compete { 14196
Outcome operator()(Item* a, Item* b) { 14197
cout << a << "\t" << b << "\t"; 14198
return a->compete(b); 14199
} 14200
}; 14201
14202
int main() { 14203
srand(time(0)); // Seed the random number generator 14204
const int sz = 20; 14205
vector<Item*> v(sz*2); 14206
generate(v.begin(), v.end(), ItemGen()); 14207
transform(v.begin(), v.begin() + sz, 14208
v.begin() + sz, 14209
ostream_iterator<Outcome>(cout, "\n"), 14210
Compete()); 14211
purge(v); 14212
} ///:~ 14213
Listado 9.34. C10/PaperScissorsRock.cpp 14214
14215
9.14.1. Despachado múltiple con Visitor 14216
//: C10:BeeAndFlowers.cpp 14217
548
// Demonstration of "visitor" pattern. 14218
#include <algorithm> 14219
#include <iostream> 14220
#include <string> 14221
#include <vector> 14222
#include <ctime> 14223
#include <cstdlib> 14224
#include "../purge.h" 14225
using namespace std; 14226
14227
class Gladiolus; 14228
class Renuculus; 14229
class Chrysanthemum; 14230
14231
class Visitor { 14232
public: 14233
virtual void visit(Gladiolus* f) = 0; 14234
virtual void visit(Renuculus* f) = 0; 14235
virtual void visit(Chrysanthemum* f) = 0; 14236
virtual ~Visitor() {} 14237
}; 14238
14239
class Flower { 14240
public: 14241
virtual void accept(Visitor&) = 0; 14242
549
virtual ~Flower() {} 14243
}; 14244
14245
class Gladiolus : public Flower { 14246
public: 14247
virtual void accept(Visitor& v) { 14248
v.visit(this); 14249
} 14250
}; 14251
14252
class Renuculus : public Flower { 14253
public: 14254
virtual void accept(Visitor& v) { 14255
v.visit(this); 14256
} 14257
}; 14258
14259
class Chrysanthemum : public Flower { 14260
public: 14261
virtual void accept(Visitor& v) { 14262
v.visit(this); 14263
} 14264
}; 14265
14266
// Add the ability to produce a string: 14267
550
class StringVal : public Visitor { 14268
string s; 14269
public: 14270
operator const string&() { return s; } 14271
virtual void visit(Gladiolus*) { 14272
s = "Gladiolus"; 14273
} 14274
virtual void visit(Renuculus*) { 14275
s = "Renuculus"; 14276
} 14277
virtual void visit(Chrysanthemum*) { 14278
s = "Chrysanthemum"; 14279
} 14280
}; 14281
14282
// Add the ability to do "Bee" activities: 14283
class Bee : public Visitor { 14284
public: 14285
virtual void visit(Gladiolus*) { 14286
cout << "Bee and Gladiolus" << endl; 14287
} 14288
virtual void visit(Renuculus*) { 14289
cout << "Bee and Renuculus" << endl; 14290
} 14291
virtual void visit(Chrysanthemum*) { 14292
551
cout << "Bee and Chrysanthemum" << endl; 14293
} 14294
}; 14295
14296
struct FlowerGen { 14297
Flower* operator()() { 14298
switch(rand() % 3) { 14299
default: 14300
case 0: return new Gladiolus; 14301
case 1: return new Renuculus; 14302
case 2: return new Chrysanthemum; 14303
} 14304
} 14305
}; 14306
14307
int main() { 14308
srand(time(0)); // Seed the random number generator 14309
vector<Flower*> v(10); 14310
generate(v.begin(), v.end(), FlowerGen()); 14311
vector<Flower*>::iterator it; 14312
// It's almost as if I added a virtual function 14313
// to produce a Flower string representation: 14314
StringVal sval; 14315
for(it = v.begin(); it != v.end(); it++) { 14316
(*it)->accept(sval); 14317
552
cout << string(sval) << endl; 14318
} 14319
// Perform "Bee" operation on all Flowers: 14320
Bee bee; 14321
for(it = v.begin(); it != v.end(); it++) 14322
(*it)->accept(bee); 14323
purge(v); 14324
} ///:~ 14325
Listado 9.35. C10/BeeAndFlowers.cpp 14326
14327
9.15. Resumen 14328
9.16. Ejercicios 14329
10: Concurrencia 14330
Tabla de contenidos 14331
10.1. Motivación 14332
10.2. Concurrencia en C++ 14333
10.3. Utilización de los hilos 14334
10.4. Comparición de recursos limitados 14335
10.5. Finalización de tareas 14336
10.6. Cooperación entre hilos 14337
10.7. Bloqueo letal 14338
10.8. Resumen 14339
10.9. Ejercicios 14340
Los objetos ofrecen una forma de dividir un programa en diferentes 14341
secciones. A menudo, también es necesario dividir un programa, 14342
independientemente de las subtareas en ejecución. 14343
Utilizando multihilado, un hilo de ejecución dirige cada una esas subtareas 14344
independientes, y puedes programar como si cada hilo tuviera su propia 14345
553
CPU. Un mecanismo interno reparte el tiempo de CPU por ti, pero en 14346
general, no necesitas pensar acerca de eso, lo que ayuda a simplificar la 14347
programación con múltiples hilos. 14348
Un proceso es un programa autocontenido en ejecución con su propio 14349
espacio de direcciones. Un sistema operativo multitarea puede ejecutar más 14350
de un proceso (programa) en el mismo tiempo, mientras ....., por medio de 14351
cambios periódicos de CPU de una tarea a otra. Un hilo es una simple flujo 14352
de control secuencial con un proceso. Un proceso puede tener de este modo 14353
múltiples hilos en ejecución concurrentes. Puesto que los hilos se ejecutan 14354
con un proceso simple, pueden compartir memoria y otros recursos. La 14355
dificultad fundamental de escribir programas multihilados está en coordinar el 14356
uso de esos recursos entre los diferentes hilos. 14357
Hay muchas aplicaciones posibles para el multihilado, pero lo más usual 14358
es querer usarlo cuando tienes alguna parte de tu programa vinculada a un 14359
evento o recurso particular. Para evitar bloquear el resto de tu programa, 14360
creas un hilo asociado a ese evento o recurso y le permites ejecutarse 14361
independientemente del programa principal. 14362
La programación concurrente se como caminar en un mundo 14363
completamente nuevo y aprender un nuevo lenguaje de programación, o por 14364
lo menos un nuevo conjunto de conceptos del lenguaje. Con la aparición del 14365
soporte para los hilos en la mayoría de los sistemas operativos para 14366
microcomputadores, han aparecido también en los lenguajes de 14367
programación o librerías extensiones para los hilos. En cualquier caso, 14368
programación hilada: 14369
1. Parece misteriosa y requiere un esfuerzo en la forma de pensar acerca 14370
de la programación. 14371
2. En otros lenguajes el soporte a los hilos es similar. Cuando entiendas 14372
los hilos, comprenderás una jerga común. 14373
Comprender la programación concurrente está al mismo nivel de dificultad 14374
que comprender el polimorfismo. Si pones un poco de esfuerzo, podrás 14375
entender el mecanismo básico, pero generalmente necesitará de un 14376
entendimiento y estudio profundo para desarrollar una comprensión auténtica 14377
sobre el tema. La meta de este capítulo es darte una base sólida en los 14378
principios de concurrencia para que puedas entender los conceptos y escribir 14379
554
programas multihilados razonables. Sé consciente de que puedes confiarte 14380
fácilmente. Si vas a escribir algo complejo, necesitarás estudiar libros 14381
específicos sobre el tema. 14382
10.1. Motivación 14383
Una de las razones más convincentes para usar concurrencia es crear una 14384
interfaz sensible al usuario. Considera un programa que realiza una 14385
operación de CPU intensiva y, de esta forma, termina ignorando la entrada 14386
del usuario y comienza a no responder. El programa necesita continuar 14387
controlandor sus operaciones, y al mismo tiempo necesita devolver el control 14388
al botón de la interfaz de usuario para que el programa pueda responder al 14389
usuario. Si tienes un botón de "Salir", no querrás estar forzado a sondearlo 14390
en todas las partes de código que escribas en tu programa. (Esto acoplaría 14391
tu botón de salir a lo largo del programa y sería un quebradero de cabeza a 14392
la hora de mantenerlo). ..... 14393
Una función convencional no puede continuar realizando sus operaciones 14394
y al mismo tiempo devolver el control al resto del programa. De hecho, suena 14395
a imposible, como si la CPU estuviera en dos lugares a la vez, pero esto es 14396
precisamente la "ilusión" que la concurrencia permite (en el caso de un 14397
sistema multiprocesador, debe haber más de una "ilusión"). 14398
También puedes usar concurrencia para optimizar la carga de trabajo. Por 14399
ejemplo, podrías necesitar hacer algo importante mientras estás estancado 14400
esperando la llegada de una entrada del puerto I/O. Sin hilos, la única 14401
solución razonable es sondear los puertos I/O, que es costoso y puede ser 14402
difícil. 14403
Si tienes una máquina multiprocesador, los múltiples hilos pueden ser 14404
distribuídos a lo largo de los múltiples procesadores, pudiendo mejorar 14405
considerablemente la carga de trabajo. Este es el típico caso de los potentes 14406
servidores web multiprocesdor, que pueden distribuir un gran número de 14407
peticiones de usuario por todas las CPUs en un programa que asigna un hilo 14408
por petición. 14409
Un programa que usa hilos en una máquina monoprocesador hará una 14410
cosa en un tiempo dado, por lo que es teóricamente posible escribir el mismo 14411
programa sin el uso de hilos. Sin embargo, el multihilado proporciona un 14412
555
beneficio de optimización importante: El diseño de un programa puede ser 14413
maravillosamente simple. Algunos tipos de problemas, como la simulación - 14414
un video juego, por ejemplo - son difíciles de resolver sin el soporte de la 14415
concurrencia. 14416
El modelo hilado es una comodidad de la programación para simplificar el 14417
manejo de muchas operaciones al mismo tiempo con un simple programa: La 14418
CPU desapilará y dará a cada hilo algo de su tiempo. Cada hilo tiene 14419
consciencia de que tiene un tiempo constante de uso de CPU, pero el tiempo 14420
de CPU está actualmente repartido entre todo los hilos. La excepción es un 14421
programa que se ejecuta sobre múltiples CPU's. Pero una de las cosas 14422
fabulosas que tiene el hilado es que te abstrae de esta capa, por lo que tu 14423
código no necesita saber si está ejecutándose sobre una sóla CPU o sobre 14424
varias.[149] De este modo, usar hilos es una manera de crear programas 14425
escalables de forma transparente - si un programa se está ejecutando 14426
demasiado despacio, puedes acelerarlo fácilmente añadiendo CPUs a tu 14427
ordenador. La multitarea y el multihilado tienden a ser las mejores opciones a 14428
utilizar en un sistema multiprocesador. 14429
El uso de hilos puede reducir la eficiencia computacional un poco, pero el 14430
aumento neto en el diseño del programa, balanceo de recursos, y la 14431
comodidad del usuario a menudo es más valorado. En general, los hilos te 14432
permiten crear diseñor más desacoplados; de lo contrario, las partes de tu 14433
código estaría obligadas a prestar atención a tareas que podrías manejarlas 14434
con hilos normalmente. 14435
10.2. Concurrencia en C++ 14436
Cuando el Comité de Estándares de C++ estaba creando el estándar 14437
inicial de C++, el mecanismo de concurrencia fue excluído de forma explícita 14438
porque C no tenía uno y también porque había varíos enfoques rivales 14439
acerca de su implementación. Parecía demasiado restrictivo forzar a los 14440
programadores a usar una sola alternativa. 14441
Sin embargo, la alternativa resultó ser peor. Para usar concurriencia, 14442
tenías que encontrar y aprender una librería y ocuparte de su indiosincrasia y 14443
las incertidumbres de trabajar con un vendedor particular. Además, no había 14444
garantía de que una librería funcionaría en diferentes compiladores o en 14445
556
distintas plataformas. También, desde que la concurrencia no formaba parte 14446
del estándar del lenguaje, fue más difícil encontrar programadores C++ que 14447
también entendieran la programación concurrente. 14448
Otra influencia pudo ser el lenguaje Java, que incluyó concurrencia en el 14449
núcleo del lenguaje. Aunque el multihilado is aún complicado, los 14450
programadores de Java tienden a empezar a aprenderlo y usarlo desde el 14451
principio. 14452
El Comité de Estándares de C++ está considerando incluir el soporte a la 14453
concurrencia en la siguiente iteración de C++, pero en el momento de este 14454
escrito no estaba claro qué aspecto tendrá la librería. Decidimos usar la 14455
librería ZThread como base para este capítulo. La escogimos por su diseño, 14456
y es open-source y gratuitamente descargable desde 14457
http://zthread.sourceforge.net. Eric Crahen de IBM, el autor de la librería 14458
ZThread, fue decisivo para crear este capítulo.[150] 14459
Este capítulo utiliza sólo un subgrupo de la librería ZThread, de acuerdo 14460
con el convenio de ideas fundamentales sobre los hilos. La librería ZThread 14461
contiene un soporte a los hilos significativamente más sofisticado que el que 14462
se muestra aquí, y deberías estudiar esa librería más profundamente para 14463
comprender completamente sus posibilidades. 14464
10.2.1. Instalación de ZThreads 14465
Por favor, note que la librería ZThread es un proyecto independiente y no 14466
está soportada por el autor de este libro; simplemente estamos usando la 14467
librería en este capítulo y no podemos dar soporte técnico a las 14468
características de la instalación. Mira el sitio web de ZThread para obtener 14469
soporte en la instalación y reporte de errores. 14470
La librería ZThread se distribuye como código fuente. Después de 14471
descargarla (versión 2.3 o superior) desde la web de ZThread, debes 14472
compilar la librería primero, y después configurar tu proyecto para que use la 14473
librería. --> 14474
El método habitual para compilar la librería ZThreads para los distintos 14475
sabores de UNIX (Linux, SunOS, Cygwin, etc) es usar un script de 14476
configuración. Después de desempaquetar los archivos (usando tar), 14477
simplemente ejecuta: ./configure && make install 14478
557
en el directorio principal de ZThreads para compilar e instalar una copia de 14479
la librería en directorio /usr/local. Puedes personalizar algunas opciones 14480
cuando uses el script, incluída la localización de los ficheros. Para más 14481
detalles, utiliza este comando: ./configure ?help 14482
El código de ZThreads está estructurado para simplificar la compilación 14483
para otras plataformas (como Borland, Microsoft y Metrowerks). Para hacer 14484
esto, crea un nuevo proyecto y añade todos los archivos .cxx en el directorio 14485
src de ZThreads a la lista de archivos a compilar. Además, asegúrate de 14486
incluir el directorio incluído del archivo en la ruta de búsqueda de la cabecera 14487
para tu proyecto???. Los detalles exactos variarán de compilador en 14488
compilador, por lo que necesitarás estar algo familiarizado con tu conjunto de 14489
herramientas para ser capaz de utilizar esta opción. 14490
Una vez la compilación ha finalizado con éxito, el siguiente paso es crear 14491
un proyecto que use la nueva librería compilada. Primero, permite al 14492
compilador saber donde están localizadas las cabeceras, por lo que tu 14493
instrucción #include funcionará correctamente. Habitualmente, necesitarás 14494
en tu proyecto una opción como se muestra: 14495
-I/path/to/installation/include 14496
Si utilizaste el script de configuración, la ruta de instalación será el prefijo 14497
de la que definiste (por defecto, /usr/local). Si utilizaste uno de los archivos 14498
de proyecto en la creación del directorio, la ruta instalación debería ser 14499
simplemente la ruta al directorio principal del archivo ZThreads. 14500
Después, necesitarás añadir una opción a tu proyecto que permitirá al 14501
enlazador saber donde está la librería. Si usaste el script de configuración, se 14502
parecerá a lo siguiente: 14503
-L/path/to/installation/lib ?lZThread 14504
Si usaste uno de los archivos del proyecto proporcionados, será similar a: 14505
558
-L/path/to/installation/Debug ZThread.lib 14506
De nuevo, si usaste el script de configuración, la ruta de instalación s será 14507
el prefijo de la que definistes. Si su utilizaste un archivo del proyecto, la ruta 14508
será la misma que la del directorio principal de ZThreads. 14509
Nota que si estás utilizando Linux, o Cygwin (www.cygwin.com) bajo 14510
Windows, no deberías necesitar modificar la ruta de include o de la librería; el 14511
proceso por defecto de instalación tendrá cuidado para hacerlo por ti, 14512
normalmente. 14513
En GNU/Linux, es posible que necesites añadir lo siguiente a tu .bashrc 14514
para que el sistema pueda encontrar la el archivo de la librería compartida 14515
LibZThread-x.x.so.0 cuando ejecute programas de este capítulo. 14516
export LD_LIBRARY_PATH=/usr/local/lib:${LD_LIBRARY_PATH} 14517
(Asumiendo que utilizas el proceso de instalación por defecto y la librería 14518
compartida acaba en /user/local/lib/; en otro caso, cambia la ruta por tu 14519
localización. 14520
10.2.2. Definición de tareas 14521
Un hilo cumple con una tarea, por lo que necesitas un manera de describir 14522
esa tarea. La clase Runnable proporciona unas interfaces comunes a 14523
ejecutar para cualquier tarea. Aquí está el núcleo de las clase Runnable de 14524
ZThread, que la encontrarás en el archivo Runnable.h dentro del directorio 14525
incluído, después de instalar la librería ZThread: 14526
class Runnable { 14527
public: 14528
virtual void run() = 0; 14529
virtual ~Runnable() {} 14530
559
}; 14531
Al hacerla una clase base abstracta, Runnable es fácilmente combinable 14532
con una clase básica u otras clases. 14533
Para definir una tarea, simplemente hereda de la clase Runnable y 14534
sobreescribe run( ) para que la tarea haga lo que quieres. 14535
Por ejecomplo, la tarea LiftOff siguiente muestra la cuenta atrás antes de 14536
despegar: 14537
//: C11:LiftOff.h 14538
// Demonstration of the Runnable interface. 14539
#ifndef LIFTOFF_H 14540
#define LIFTOFF_H 14541
#include <iostream> 14542
#include "zthread/Runnable.h" 14543
14544
class LiftOff : public ZThread::Runnable { 14545
int countDown; 14546
int id; 14547
public: 14548
LiftOff(int count, int ident = 0) : 14549
countDown(count), id(ident) {} 14550
~LiftOff() { 14551
std::cout << id << " completed" << std::endl; 14552
} 14553
void run() { 14554
while(countDown--) 14555
560
std::cout << id << ":" << countDown << std::endl; 14556
std::cout << "Liftoff!" << std::endl; 14557
} 14558
}; 14559
#endif // LIFTOFF_H ///:~ 14560
Listado 10.1. C11/LiftOff.h 14561
14562
El identificador id sirve como distinción entre multiples instancias de la 14563
tarea. Si sólo quieres hacer una instancia, debes utilizar el valor por defecto 14564
para identificarla. El destructor te permitirá ver que tarea está destruída 14565
correctamente. 14566
En el siguiente ejemplo, las tareas de run( ) no están dirigidas por hilos 14567
separados; directamente es una simple llamada en main( ): 14568
//: C11:NoThread.cpp 14569
#include "LiftOff.h" 14570
14571
int main() { 14572
LiftOff launch(10); 14573
launch.run(); 14574
} ///:~ 14575
Listado 10.2. C11/NoThread.cpp 14576
14577
Cuando una clase deriva de Runnable, debe tene una función run( ), pero 14578
no tiene nada de especial - no produce ninguna habibilidad innata en el hilo. 14579
Para llevar a cabo el funcionamiento de los hilos, debes utilizas la clase 14580
Thread. 14581
561
10.3. Utilización de los hilos 14582
Para controlar un objeto Runnable con un hilo, crea un objeto Thread 14583
separado y utiliza un pontero Runnable al constructor de Thread. Esto lleva a 14584
cabo la inicialización del hilo y, después, llama a run ( ) de Runnable como 14585
un hilo capaz de ser interrumpido. Manejando LiftOff con un hilo, el ejemplo 14586
siguiente muestra como cualquier tarea puede ser ejecutada en el contexto 14587
de cualquier otro hilo: 14588
//: C11:BasicThreads.cpp 14589
// The most basic use of the Thread class. 14590
//{L} ZThread 14591
#include <iostream> 14592
#include "LiftOff.h" 14593
#include "zthread/Thread.h" 14594
using namespace ZThread; 14595
using namespace std; 14596
14597
int main() { 14598
try { 14599
Thread t(new LiftOff(10)); 14600
cout << "Waiting for LiftOff" << endl; 14601
} catch(Synchronization_Exception& e) { 14602
cerr << e.what() << endl; 14603
} 14604
} ///:~ 14605
Listado 10.3. C11/BasicThreads.cpp 14606
562
14607
Synchronization_Exception forma parte de la librería ZThread y la clase 14608
base para todas las excepciones de ZThread. Se lanzará si hay un error al 14609
crear o usar un hilo. 14610
Un constructor de Thread sólo necesita un puntero a un objeto Runnable. 14611
Al crear un objeto Thread se efectuará la incialización necesaria del hilo y 14612
después se llamará a Runnable::run() 14613
Puede añadir más hilos fácilmente para controlar más tareas. A 14614
continuación, puede ver cómo los hilos se ejecutan con algún otro: 14615
//: C11:MoreBasicThreads.cpp 14616
// Adding more threads. 14617
//{L} ZThread 14618
#include <iostream> 14619
#include "LiftOff.h" 14620
#include "zthread/Thread.h" 14621
using namespace ZThread; 14622
using namespace std; 14623
14624
int main() { 14625
const int SZ = 5; 14626
try { 14627
for(int i = 0; i < SZ; i++) 14628
Thread t(new LiftOff(10, i)); 14629
cout << "Waiting for LiftOff" << endl; 14630
} catch(Synchronization_Exception& e) { 14631
cerr << e.what() << endl; 14632
} 14633
563
} ///:~ 14634
Listado 10.4. C11/MoreBasicThreads.cpp 14635
14636
El segundo argumento del constructor de LiftOff identifica cada tarea. 14637
Cuando ejecute el programa, verá que la ejecución de las distintas tareas se 14638
mezclan a medida que los hilos entran y salen de su ejecución. Este 14639
intercambio está controlado automáticamente por el planificador de hilos. Si 14640
tiene múltiples procesadores en su máquina, el planificador de hilos 14641
distribuirá los hilos entre los procesadores de forma transparente. 14642
El bucle for puede parecer un poco extraño a priori ya que se crea 14643
localmente dentro del bucle for e inmediatamente después sale del ámbito y 14644
es destruído. Esto hace que parezca que el hilo propiamente dicho pueda 14645
perderse inmediatamente, pero puede ver por la salida que los hilos, en 14646
efecto, están en ejecución hasta su finalización. Cuando crea un objeto 14647
Thread, el hilo asociado se registra en el sistema de hilos, que lo mantiene 14648
vivo. A pesar de que el objeto Thread local se pierde, el hilo sigue vivo hasta 14649
que su tarea asociada termina. Aunque puede ser poco intuitivo desde el 14650
punto de vista de C++, el concepto de hilos es la excepción de la regla: un 14651
hilo crea un hilo de ejecución separado que persiste después de que la 14652
llamada a función finalice. Esta excepción se refleja en la persistencia del hilo 14653
subyacente después de que el objeto desaparezca. 14654
10.3.1. Creación de interfaces de usuarios interactivas 14655
Como se dijo anteriormente, uno de las motivaciones para usar hilos es 14656
crear interfaces de usuario interactivas. Aunque en este libro no cubriremos 14657
las interfaces gráficas de usuario, verá un ejemplo sencillo de una interfaz de 14658
usuario basada en consola. 14659
El siguiente ejemplo lee líneas de un archivo y las imprime a la consola, 14660
durmiéndose (suspender el hilo actual) durante un segundo después de que 14661
cada línea sea mostrada. (Aprenderá más sobre el proceso de dormir hilos 14662
en el capítulo.) Durante este proceso, el programa no busca la entrada del 14663
usuario, por lo que la IU no es interactiva: 14664
564
//: C11:UnresponsiveUI.cpp {RunByHand} 14665
// Lack of threading produces an unresponsive UI. 14666
//{L} ZThread 14667
#include <iostream> 14668
#include <fstream> 14669
#include <string> 14670
#include "zthread/Thread.h" 14671
using namespace std; 14672
using namespace ZThread; 14673
14674
int main() { 14675
cout << "Press <Enter> to quit:" << endl; 14676
ifstream file("UnresponsiveUI.cpp"); 14677
string line; 14678
while(getline(file, line)) { 14679
cout << line << endl; 14680
Thread::sleep(1000); // Time in milliseconds 14681
} 14682
// Read input from the console 14683
cin.get(); 14684
cout << "Shutting down..." << endl; 14685
} ///:~ 14686
Listado 10.5. C11/UnresponsiveUI.cpp 14687
14688
565
Para hacer este programa interactivo, puede ejecutar una tarea que 14689
muestre el archivo en un hilo separado. De esta forma, el hilo principal puede 14690
leer la entrada del usuario, por lo que el programa se vuelve interactivo: 14691
//: C11:ResponsiveUI.cpp {RunByHand} 14692
// Threading for a responsive user interface. 14693
//{L} ZThread 14694
#include <iostream> 14695
#include <fstream> 14696
#include <string> 14697
#include "zthread/Thread.h" 14698
using namespace ZThread; 14699
using namespace std; 14700
14701
class DisplayTask : public Runnable { 14702
ifstream in; 14703
string line; 14704
bool quitFlag; 14705
public: 14706
DisplayTask(const string& file) : quitFlag(false) { 14707
in.open(file.c_str()); 14708
} 14709
~DisplayTask() { in.close(); } 14710
void run() { 14711
while(getline(in, line) && !quitFlag) { 14712
cout << line << endl; 14713
566
Thread::sleep(1000); 14714
} 14715
} 14716
void quit() { quitFlag = true; } 14717
}; 14718
14719
int main() { 14720
try { 14721
cout << "Press <Enter> to quit:" << endl; 14722
DisplayTask* dt = new DisplayTask("ResponsiveUI.cpp"); 14723
Thread t(dt); 14724
cin.get(); 14725
dt->quit(); 14726
} catch(Synchronization_Exception& e) { 14727
cerr << e.what() << endl; 14728
} 14729
cout << "Shutting down..." << endl; 14730
} ///:~ 14731
Listado 10.6. C11/ResponsiveUI.cpp 14732
14733
Ahora el hilo main() puede responder inmediatamente cuando pulse Return 14734
e invocar quit() sobre DisplayTask. 14735
Este ejemplo también muestra la necesidad de una comunicación entre 14736
tareas - la tarea en el hilo main() necesita parar al DisplayTask. Dado que 14737
tenemos un puntero a DisplayTask, puede pensar que bastaría con llamar al 14738
destructor de ese puntero para matar la tarea, pero esto hace que los 14739
567
programas sean poco fiables. El problema es que la tarea podría estar en 14740
mitad de algo importante cuando lo destruye y, por lo tanto, es probable que 14741
ponga el programa en un estado inestable. En este sentido, la propia tarea 14742
decide cuando es seguro terminar. La manera más sencilla de hacer esto es 14743
simplemente notificar a la tarea que desea detener mediante una bandera 14744
booleana. Cuando la tarea se encuentre en un punto estable puede consultar 14745
esa bandera y hacer lo que sea necesario para limpiar el estado después de 14746
regresar de run(). Cuando la tarea vuelve de run(), Thread sabe que la tarea 14747
se ha completado. 14748
Aunque este programa es lo suficientemente simple para que no haya 14749
problemas, hay algunos pequeños defectos respecto a la comunicación entre 14750
pilas. Es un tema importante que se cubrirá más tarde en este capítulo. 14751
10.3.2. Simplificación con Ejecutores 14752
Utilizando los Ejecutores de ZThread, puede simplificar su código. Los 14753
Ejecutores proporcionan una capa de indirección entre un cliente y la 14754
ejecución de una tarea; a diferencia de un cliente que ejecuta una tarea 14755
directamente, un objeto intermediario ejecuta la tarea. 14756
Podemos verlo utilizando un Ejecutor en vez de la creación explícita de 14757
objetos Thread en MoreBasicThreads.cpp. Un objeto LiftOff conoce cómo 14758
ejecutar una tarea específica; como el patrón Command, expone una única 14759
función a ejecutar. Un objeto Executor conoce como construir el contexto 14760
apropiado para lanzar objetos Runnable. En el siguiente ejemplo, 14761
ThreadedExecutor crea un hilo por tarea: 14762
//: c11:ThreadedExecutor.cpp 14763
//{L} ZThread 14764
#include <iostream> 14765
#include "zthread/ThreadedExecutor.h" 14766
#include "LiftOff.h" 14767
using namespace ZThread; 14768
568
using namespace std; 14769
14770
int main() { 14771
try { 14772
ThreadedExecutor executor; 14773
for(int i = 0; i < 5; i++) 14774
executor.execute(new LiftOff(10, i)); 14775
} catch(Synchronization_Exception& e) { 14776
cerr << e.what() << endl; 14777
} 14778
} ///:~ 14779
Listado 10.7. C11/ThreadedExecutor.cpp 14780
14781
Note que algunos casos un Executor individual puede ser usado para crear 14782
y gestionar todo los hilos en su sistema. Debe colocar el código 14783
correspondiente a los hilos dentro de un bloque try porque el método 14784
execute() de un Executor puede lanzar una Synchronization_Exception si 14785
algo va mal. Esto es válido para cualquier función que implique cambiar el 14786
estado de un objeto de sincronización (arranque de hilos, la adquisición de 14787
mutexes, esperas en condiciones, etc.), tal y como aprenderá más adelante 14788
en este capítulo. 14789
El programa finalizará cuando todas las tareas en el Executor hayan 14790
concluido. 14791
En el siguiente ejemplo, ThreadedExecutor crea un hilo para cada tarea 14792
que quiera ejecutar, pero puede cambiar fácilmente la forma en la que esas 14793
tareas son ejecutadas reemplazando el ThreadedExecutor por un tipo 14794
diferente de Executor. En este capítulo, usar un ThreadedExecutor está bien, 14795
pero para código en producción puede resultar excesivamente costoso para 14796
la creación de muchos hilos. En ese caso, puede reemplazarlo por un 14797
569
PoolExecutor, que utilizará un conjunto limitado de hilos para lanzar las 14798
tareas registradas en paralelo: 14799
//: C11:PoolExecutor.cpp 14800
//{L} ZThread 14801
#include <iostream> 14802
#include "zthread/PoolExecutor.h" 14803
#include "LiftOff.h" 14804
using namespace ZThread; 14805
using namespace std; 14806
14807
int main() { 14808
try { 14809
// Constructor argument is minimum number of threads: 14810
PoolExecutor executor(5); 14811
for(int i = 0; i < 5; i++) 14812
executor.execute(new LiftOff(10, i)); 14813
} catch(Synchronization_Exception& e) { 14814
cerr << e.what() << endl; 14815
} 14816
} ///:~ 14817
Listado 10.8. C11/PoolExecutor.cpp 14818
14819
Con PoolExecutor puede realizar una asignación inicial de hilos costosa de 14820
una sola vez, por adelantado, y los hilos se reutilizan cuando sea posible. 14821
Esto ahorra tiempo porque no está pagando el gasto de la creación de hilos 14822
por cada tarea individual de forma constante. Además, en un sistema dirigido 14823
570
por eventos, los eventos que requieren hilos para manejarlos puede ser 14824
generados tan rápido como quiera, basta con traerlos del pool. No excederá 14825
los recursos disponibles porque PoolExecutor utiliza un número limitado de 14826
objetos Thread. Así, aunque en este libro se utilizará ThreadedExecutors, 14827
tenga en cuenta utilizar PoolExecutor para código en producción. 14828
ConcurrentExecutor es como PoolExecutor pero con un tamaño fijo de 14829
hilos. Es útil para cualquier cosa que quiera lanzar en otro hilo de forma 14830
continua (una tarea de larga duración), como una tare que escucha 14831
conexiones entrantes en un socket. También es útil para tareas cortas que 14832
quiera lanzar en un hilo, por ejemplo, pequeñas tareas que actualizar un log 14833
local o remoto, o para un hilo que atienda a eventos. 14834
Si hay más de una tarea registrada en un ConcurrentExecutor, cada una 14835
de ellas se ejecutará completamente hasta que la siguiente empiece; todas 14836
utilizando el mismo hilo. En el ejemplo siguiente, verá que cada tarea se 14837
completa, en el orden en el que fue registrada, antes de que la siguiente 14838
comience. De esta forma, un ConcurrentExecutor serializa las tareas que le 14839
fueron asignadas. 14840
//: C11:ConcurrentExecutor.cpp 14841
//{L} ZThread 14842
#include <iostream> 14843
#include "zthread/ConcurrentExecutor.h" 14844
#include "LiftOff.h" 14845
using namespace ZThread; 14846
using namespace std; 14847
14848
int main() { 14849
try { 14850
ConcurrentExecutor executor; 14851
for(int i = 0; i < 5; i++) 14852
571
executor.execute(new LiftOff(10, i)); 14853
} catch(Synchronization_Exception& e) { 14854
cerr << e.what() << endl; 14855
} 14856
} ///:~ 14857
Listado 10.9. C11/ConcurrentExecutor.cpp 14858
14859
Como un ConcurrentExecutor, un SynchronousExecutor se usa cuando 14860
quiera una única tarea se ejecute al mismo tiempo, en serie en lugar de 14861
concurrente. A diferencia de ConcurrentExecutor, un SynchronousExecutor 14862
no crea ni gestiona hilos sobre si mismo. Utiliza el hilo que añadió la tarea y, 14863
así, únicamente actúa como un punto focal para la sincronización. Si tiene n 14864
tareas registradas en un SynchronousExecutor, nunca habrá 2 tareas que se 14865
ejecuten a la vez. En lugar de eso, cada una se ejecutará hasta su 14866
finalización y la siguiente en la cola comenzará. 14867
Por ejemplo, suponga que tiene un número de hilos ejecutando tareas que 14868
usan un sistema de archivos, pero está escribiendo código portable luego no 14869
quiere utilizar flock() u otra llamada al sistema operativo específica para 14870
bloquear un archivo. Puede lanzar esas tareas con un SynchronousExecutor 14871
para asegurar que solamente una de ellas, en un tiempo determinado, está 14872
ejecutándose desde cualquier hilo. De esta manera, no necesita preocuparse 14873
por la sincronización del recurso compartido (y, de paso, no se cargará el 14874
sistema de archivos). Una mejor solución pasa por sincronizar el recurso (lo 14875
cual aprenderá más adelante en este capítulo), sin embargo un 14876
SynchronousExecutor le permite evitar las molestias de obtener una 14877
coordinación adecuada para prototipar algo. 14878
//: C11:SynchronousExecutor.cpp 14879
//{L} ZThread 14880
#include <iostream> 14881
572
#include "zthread/SynchronousExecutor.h" 14882
#include "LiftOff.h" 14883
using namespace ZThread; 14884
using namespace std; 14885
14886
int main() { 14887
try { 14888
SynchronousExecutor executor; 14889
for(int i = 0; i < 5; i++) 14890
executor.execute(new LiftOff(10, i)); 14891
} catch(Synchronization_Exception& e) { 14892
cerr << e.what() << endl; 14893
} 14894
} ///:~ 14895
Listado 10.10. C11/SynchronousExecutor.cpp 14896
14897
Cuando ejecuta el programa verá que las tareas son lanzadas en el orden 14898
en el que fueron registradas, y cada tarea se ejecuta completamente antes 14899
de que la siguiente empiece. ¿Qué es lo que no ve y que hace que no se 14900
creen nuevos hilos? El hilo main() se usa para cada tarea, y debido a este 14901
ejemplo, ese es el hilo que registra todas las tareas. Podría no utilizar un 14902
SynchronousExecutor en código en producción porque, principalmente, es 14903
para prototipado. 14904
10.3.3. Ceder el paso 14905
Si sabe que ha logrado realizar lo que necesita durante una pasada a 14906
través de un bucle en su función run() (la mayoría de las funciones run() 14907
suponen un periodo largo de tiempo de ejecución), puede darle un toque al 14908
573
mecanismo de planificación de hilos, decirle que ya ha hecho suficiente y que 14909
algún otro hilo puede tener la CPU. Este toque (y es un toque - no hay 14910
garantía de que su implementación vaya a escucharlo) adopta la forma de la 14911
función yield(). 14912
Podemos construir una versión modificada de los ejemplos de LiftOff 14913
cediendo el paso después de cada bucle: 14914
//: C11:YieldingTask.cpp 14915
// Suggesting when to switch threads with yield(). 14916
//{L} ZThread 14917
#include <iostream> 14918
#include "zthread/Thread.h" 14919
#include "zthread/ThreadedExecutor.h" 14920
using namespace ZThread; 14921
using namespace std; 14922
14923
class YieldingTask : public Runnable { 14924
int countDown; 14925
int id; 14926
public: 14927
YieldingTask(int ident = 0) : countDown(5), id(ident) {} 14928
~YieldingTask() { 14929
cout << id << " completed" << endl; 14930
} 14931
friend ostream& 14932
operator<<(ostream& os, const YieldingTask& yt) { 14933
return os << "#" << yt.id << ": " << yt.countDown; 14934
574
} 14935
void run() { 14936
while(true) { 14937
cout << *this << endl; 14938
if(--countDown == 0) return; 14939
Thread::yield(); 14940
} 14941
} 14942
}; 14943
14944
int main() { 14945
try { 14946
ThreadedExecutor executor; 14947
for(int i = 0; i < 5; i++) 14948
executor.execute(new YieldingTask(i)); 14949
} catch(Synchronization_Exception& e) { 14950
cerr << e.what() << endl; 14951
} 14952
} ///:~ 14953
Listado 10.11. C11/YieldingTask.cpp 14954
14955
Puede ver que la tarea del método run() es en un bucle infinito en su 14956
totalidad. Utilizando yield(), la salida se equilibra bastante que en el caso en 14957
el que no se cede el paso. Pruebe a comentar la llamada a Thread::yield() 14958
para ver la diferencia. Sin embargo, en general, yield() es útil en raras 14959
575
ocasiones, y no puede contar con ella para realizar un afinamiento serio 14960
sobre su aplicación. 14961
10.3.4. Dormido 14962
Otra forma con la que puede tener control sobre el comportamiento de su 14963
hilos es llamando a sleep() para cesar la ejecución de uno de ellos durante 14964
un número de milisegundos dado. En el ejemplo que viene a continuación, si 14965
cambia la llamada a yield() por una a sleep(), obtendrá lo siguiente: 14966
//: C11:SleepingTask.cpp 14967
// Calling sleep() to pause for awhile. 14968
//{L} ZThread 14969
#include <iostream> 14970
#include "zthread/Thread.h" 14971
#include "zthread/ThreadedExecutor.h" 14972
using namespace ZThread; 14973
using namespace std; 14974
14975
class SleepingTask : public Runnable { 14976
int countDown; 14977
int id; 14978
public: 14979
SleepingTask(int ident = 0) : countDown(5), id(ident) {} 14980
~SleepingTask() { 14981
cout << id << " completed" << endl; 14982
} 14983
friend ostream& 14984
576
operator<<(ostream& os, const SleepingTask& st) { 14985
return os << "#" << st.id << ": " << st.countDown; 14986
} 14987
void run() { 14988
while(true) { 14989
try { 14990
cout << *this << endl; 14991
if(--countDown == 0) return; 14992
Thread::sleep(100); 14993
} catch(Interrupted_Exception& e) { 14994
cerr << e.what() << endl; 14995
} 14996
} 14997
} 14998
}; 14999
15000
int main() { 15001
try { 15002
ThreadedExecutor executor; 15003
for(int i = 0; i < 5; i++) 15004
executor.execute(new SleepingTask(i)); 15005
} catch(Synchronization_Exception& e) { 15006
cerr << e.what() << endl; 15007
} 15008
} ///:~ 15009
577
Listado 10.12. C11/SleepingTask.cpp 15010
15011
Thread::sleep() puede lanzar una Interrupted_Exception(sobre las 15012
interrupciones aprenderá más adelante), y puede ver que esta excepción se 15013
captura en run(). Sin embargo, la tarea se crea y se ejecuta dentro de un 15014
bloque try en main() que captura Interrupted_Exception (la clase base para 15015
todas las excepciones de ZThread), por lo que ¿no sería posible ignorar la 15016
excepción en run() y asumir que se propagará al manejador en main()?. Esto 15017
no funcionará porque las excepciones no se propagarán a lo largo de los 15018
hilos para volver hacia main(). De esta forma, debe manejar cualquier 15019
excepción que pueda ocurrir dentro de una tarea de forma local. 15020
Notará que los hilos tienden a ejecutarse en cualquier orden, lo que quiere 15021
decir que sleep() tampoco es una forma de controlar el orden de la ejecución 15022
de los hilos. Simplemente para la ejecución del hilo durante un rato. La única 15023
garantía que tiene es que el hilo se dormirá durante, al menos, 100 15024
milisegundos (en este ejemplo), pero puede que tarde más después de que 15025
el hilo reinicie la ejecución ya que el planificador de hilos tiene que volver a él 15026
tras haber expirado el intervalo. 15027
Si debe tener control sobre el orden de la ejecución de hilos, su mejor baza 15028
es el uso de controles de sincronización (descritos más adelante) o, en 15029
algunos casos, no usar hilos en todo, FIXMEbut instead to write your own 15030
cooperative routines that hand control to each other in a specified order. 15031
10.3.5. Prioridad 15032
La prioridad de un hilo representa la importancia de ese hilo para el 15033
planificador. Pese a que el orden en que la CPU ejecuta un conjunto de hilos 15034
es indeterminado, el planificador tenderá a ejecutar el hilo con mayor 15035
prioridad de los que estén esperando. Sin embargo, no quiere decir que hilos 15036
con menos prioridad no se ejecutarán (es decir, no tendrá bloqueo de un hilo 15037
a causa de las prioridades). Simplemente, los hilos con menos prioridad 15038
tenderán a ejecutarse menos frecuentemente. 15039
Se ha modificado MoreBasicThreads.cpp para mostrar los niveles de 15040
prioridad. Las prioridades se ajustan utilizando la función setPriority() de 15041
Thread. 15042
578
//: C11:SimplePriorities.cpp 15043
// Shows the use of thread priorities. 15044
//{L} ZThread 15045
#include <iostream> 15046
#include "zthread/Thread.h" 15047
using namespace ZThread; 15048
using namespace std; 15049
15050
const double pi = 3.14159265358979323846; 15051
const double e = 2.7182818284590452354; 15052
15053
class SimplePriorities : public Runnable { 15054
int countDown; 15055
volatile double d; // No optimization 15056
int id; 15057
public: 15058
SimplePriorities(int ident=0): countDown(5), id(ident) {} 15059
~SimplePriorities() { 15060
cout << id << " completed" << endl; 15061
} 15062
friend ostream& 15063
operator<<(ostream& os, const SimplePriorities& sp) { 15064
return os << "#" << sp.id << " priority: " 15065
<< Thread().getPriority() 15066
<< " count: "<< sp.countDown; 15067
579
} 15068
void run() { 15069
while(true) { 15070
// An expensive, interruptable operation: 15071
for(int i = 1; i < 100000; i++) 15072
d = d + (pi + e) / double(i); 15073
cout << *this << endl; 15074
if(--countDown == 0) return; 15075
} 15076
} 15077
}; 15078
15079
int main() { 15080
try { 15081
Thread high(new SimplePriorities); 15082
high.setPriority(High); 15083
for(int i = 0; i < 5; i++) { 15084
Thread low(new SimplePriorities(i)); 15085
low.setPriority(Low); 15086
} 15087
} catch(Synchronization_Exception& e) { 15088
cerr << e.what() << endl; 15089
} 15090
} ///:~ 15091
580
Listado 10.13. C11/SimplePriorities.cpp 15092
15093
En este ejemplo, el operador <<() se sobreescribe para mostrar el 15094
identificador, la prioridad y el valor de countDown de la tarea. 15095
Puede ver que el nivel de prioridad del hilo es el más alto, y que el resto de 15096
hilos tienen el nivel más bajo. No utilizamos Executor en este ejemplo porque 15097
necesitamos acceder directamente al hilo para configurar sus propiedades. 15098
Dentro de SimplePriorities::run() se ejecutan 100,000 veces un costoso 15099
conjunto de cálculos en punto flotante. La variable d es volátil para intentar 15100
garantizar que ningún compilador hace optimizaciones. Sin este cálculo, no 15101
comprobará el efecto de la configuración de los niveles de prioridad. 15102
(Pruébelo: comente el bucle for que contiene los cálculos en doble precisión.) 15103
Con el cálculo puede ver que el planificador de hilos al hilo high se le da más 15104
preferencia. (Al menos, este fue el comportamiento sobre una máquina 15105
Windows). El cálculo tarda lo suficiente para que el mecanismo de 15106
planificación de hilos lo salte, cambie hilos y tome en cuenta las prioridades 15107
para que el hilo high tenga preferencia. 15108
También, puede leer la prioridad de un hilo existente con getPriority() y 15109
cambiarla en cualquier momento (no sólo antes de que el hilo se ejecute, 15110
como en SimplePriorities.cpp) con setPriority(). 15111
La correspondencia de las prioridades con el sistema operativo es un 15112
problema. Por ejemplo, Windows 2000 tiene siete niveles de prioridades, 15113
mientras que Solaris de Sun tiene 231. El único enfoque portable es ceñirse 15114
a los niveles discretos de prioridad, como Low, Medium y High utilizados en 15115
la librería ZThread. 15116
10.4. Comparición de recursos limitados 15117
Piense en un programa con un único hilo como una solitaria entidad 15118
moviéndose a lo largo del espacio de su problema y haciendo una cosa en 15119
cada instante. Debido a que sólo hay una entidad, no tiene que preocuparse 15120
por el problema de dos entidades intentando usar el mismo recurso al mismo 15121
tiempo: problemas como dos personas intentando aparcar en el mismo sitio, 15122
pasar por una puerta al mismo tiempo o incluso hablar al mismo tiempo. 15123
581
Con multihilado las cosas ya no son solitarias, pero ahora tiene la 15124
posibilidad de tener dos o más hilos intentando utilizar un mismo recurso a la 15125
vez. Esto puede causar dos problemas distintos. El primero es que el recurso 15126
necesario podría no existir. En C++, el programador tiene un control total 15127
sobre la vida de los objetos, y es fácil crear hilos que intenten usar objetos 15128
que han sido destruidos antes de que esos hilos hayan finalizado. 15129
El segundo problema es que dos o más hilos podrían chocar cuando 15130
intenten acceder al mismo dispositivo al mismo tiempo. Si no previene esta 15131
colisión, tendrá dos hilos intentando acceder a la misma cuenta bancaria al 15132
mismo tiempo, imprimir en la misma impresora, ajustar la misma válvula, etc. 15133
Esta sección presenta el problema de los objetos que desaparecen 15134
mientras las tareas aún están usándolos y el problema del choque entre 15135
tareas sobre recursos compartidos. Aprenderá sobre las herramientas que se 15136
usan para solucionar esos problemas. 15137
10.4.1. Aseguramiento de la existencia de objetos 15138
La gestión de memoria y recursos son las principales preocupaciones en 15139
C++. Cuando crea cualquier programa en C++, tiene la opción de crear 15140
objetos en la pila o en el heap (utilizando new). En un programa con un solo 15141
hilo, normalmente es sencillo seguir la vida de los objetos con el fin de que 15142
no tenga que utilizar objetos que ya están destruidos. 15143
Los ejemplos mostrados en este capítulo crean objetos Runnable en el 15144
heap utilizando new, se dará cuenta que esos objetos nunca son destruidos 15145
explícitamente. Sin embargo, podrá por la salida cuando ejecuta el programa 15146
que la biblioteca de hilos sigue la pista a cada tarea y, eventualmente, las 15147
destruye. Esto ocurre cuando el método Runnable::run() finaliza - volver de 15148
run() indica que la tarea ha finalizado. 15149
Recargar el hilo al destruir una tarea es un problema. Ese hilo sabe 15150
necesariamente si otro necesita hacer referencia a ese Runnable, y por ello 15151
el Runnable podría ser destruido prematuramente. Para ocuparse de este 15152
problema, el mecanismo de la biblioteca ZThread mantiene un conteo de 15153
referencias sobre las tareas. Una tarea se mantiene viva hasta que su 15154
contador de referencias se pone a cero, en este punto la tarea se destruye. 15155
Esto quiere decir que las tareas tienen que ser destruidas dinámicamente 15156
582
siempre, por lo que no pueden ser creadas en la pila. En vez de eso, las 15157
tareas deben ser creadas utilizando new, tal y como puede ver en todos los 15158
ejemplos de este capítulo. tareas in ZThreads 15159
Además, también debe asegurar que los objetos que no son tareas estarán 15160
vivos tanto tiempo como el que las tareas necesiten de ellos. Por otro lado, 15161
resulta sencillo para los objetos utilizados por las tareas salir del ámbito 15162
antes de que las tareas hayan concluido. Si ocurre esto, las tareas intentarán 15163
acceder zonas de almacenamiento ilegales y provocará que el programa 15164
falle. He aquí un simple ejemplo: 15165
//: C11:Incrementer.cpp {RunByHand} 15166
// Destroying objects while threads are still 15167
// running will cause serious problems. 15168
//{L} ZThread 15169
#include <iostream> 15170
#include "zthread/Thread.h" 15171
#include "zthread/ThreadedExecutor.h" 15172
using namespace ZThread; 15173
using namespace std; 15174
15175
class Count { 15176
enum { SZ = 100 }; 15177
int n[SZ]; 15178
public: 15179
void increment() { 15180
for(int i = 0; i < SZ; i++) 15181
n[i]++; 15182
} 15183
583
}; 15184
15185
class Incrementer : public Runnable { 15186
Count* count; 15187
public: 15188
Incrementer(Count* c) : count(c) {} 15189
void run() { 15190
for(int n = 100; n > 0; n--) { 15191
Thread::sleep(250); 15192
count->increment(); 15193
} 15194
} 15195
}; 15196
15197
int main() { 15198
cout << "This will cause a segmentation fault!" << endl; 15199
Count count; 15200
try { 15201
Thread t0(new Incrementer(&count)); 15202
Thread t1(new Incrementer(&count)); 15203
} catch(Synchronization_Exception& e) { 15204
cerr << e.what() << endl; 15205
} 15206
} ///:~ 15207
584
Listado 10.14. C11/Incrementer.cpp 15208
15209
Podría parecer a priori que la clase Count es excesiva, pero si únicamente 15210
n es un int (en lugar de una matriz), el compilador puede ponerlo dentro de 15211
un registro y ese almacenamiento seguirá estando disponible (aunque 15212
técnicamente es ilegal) después de que el objeto Count salga del ámbito. Es 15213
difícil detectar la violación de memoria en este caso. Sus resultados podrían 15214
variar dependiendo de su compilador y de su sistema operativo, pero pruebe 15215
a que n sea un int y verá qué ocurre. En cualquier evento, si Count contiene 15216
una matriz de ints y como antes, el compilador está obligado a ponerlo en la 15217
pila y no en un registro. 15218
Incrementer es una tarea sencilla que utiliza un objeto Count. En main(), 15219
puede ver que las tareas Incrementer se ejecutan el tiempo suficiente para 15220
que el salga del ámbito, por lo que la tarea intentará acceder a un objeto que 15221
no existe. Esto produce un fallo en el programa. objeto Count 15222
Para solucionar este problema, debemos garantizar que cualquiera de los 15223
objetos compartidos entre tareas estarán accesibles tanto tiempo como las 15224
tareas los necesiten. (Si los objetos no fueran compartidos, podrían estar 15225
directamente dentro de las clases de las tareas y, así, unir su tiempo de vida 15226
a la tarea.) Dado que no queremos que el propio ámbito estático del 15227
programa controle el tiempo de vida del objeto, pondremos el en heap. Y 15228
para asegurar que el objeto no se destruye hasta que no haya objetos 15229
(tareas, en este caso) que lo estén utilizando, utilizaremos el conteo de 15230
referencias. 15231
El conteo de referencias se ha explicado a lo largo del volumen uno de 15232
este libro y además se revisará en este volumen. La librería ZThread incluye 15233
una plantilla llamada CountedPtr que automáticamente realiza el conteo de 15234
referencias y destruye un objeto cuando su contador de referencias vale 15235
cero. A continuación, se ha modificado el programa para que utilice 15236
CountedPtr para evitar el fallo: 15237
//: C11:ReferenceCounting.cpp 15238
// A CountedPtr prevents too-early destruction. 15239
585
//{L} ZThread 15240
#include <iostream> 15241
#include "zthread/Thread.h" 15242
#include "zthread/CountedPtr.h" 15243
using namespace ZThread; 15244
using namespace std; 15245
15246
class Count { 15247
enum { SZ = 100 }; 15248
int n[SZ]; 15249
public: 15250
void increment() { 15251
for(int i = 0; i < SZ; i++) 15252
n[i]++; 15253
} 15254
}; 15255
15256
class Incrementer : public Runnable { 15257
CountedPtr<Count> count; 15258
public: 15259
Incrementer(const CountedPtr<Count>& c ) : count(c) {} 15260
void run() { 15261
for(int n = 100; n > 0; n--) { 15262
Thread::sleep(250); 15263
count->increment(); 15264
586
} 15265
} 15266
}; 15267
15268
int main() { 15269
CountedPtr<Count> count(new Count); 15270
try { 15271
Thread t0(new Incrementer(count)); 15272
Thread t1(new Incrementer(count)); 15273
} catch(Synchronization_Exception& e) { 15274
cerr << e.what() << endl; 15275
} 15276
} ///:~ 15277
Listado 10.15. C11/ReferenceCounting.cpp 15278
15279
Ahora Incrementer contiene un objeto CountedPtr, que gestiona un Count. 15280
En la función main(), los objetos CountedPtr se pasan a los dos objetos 15281
Incrementer por valor, por lo que se llama el constructor de copia, 15282
incrementando el conteo de referencias. Mientras la tarea esté ejecutándose, 15283
el contador de referencias no valdrá cero, por lo que el objeto Count utilizado 15284
por CountedPtr no será destruído. Solamente cuando todas las tareas que 15285
utilice el Count terminen se llamará al destructor (automáticamente) sobre el 15286
objeto Count por el CountedPtr. 15287
Siempre que tenga una tarea que utilice más de un objeto, casi siempre 15288
necesitará controlar aquellos objetos utilizando la plantilla CountedPtr para 15289
evitar problemas derivados del tiempo de vida de los objetos. 15290
10.4.2. Acceso no apropiado a recursos 15291
587
Considere el siguiente ejemplo, donde una tarea genera números 15292
constantes y otras tareas consumen esos números. Ahora, el único trabajo 15293
de los hilos consumidores es probar la validez de los números constantes. 15294
Primeramente, definiremos EvenChecker, el hilo consumidor, puesto que 15295
será reutilizado en todos los ejemplos siguientes. Para desacoplar 15296
EvenChecker de los varios tipos de generadores con los que 15297
experimentaremos, crearemos una interfaz llamada Generator que contiene 15298
el número mínimo de funciones que EvenChecker necesita conocer: por lo 15299
que tiene una función nextValue() y que puede ser cancelada. 15300
//: C11:EvenChecker.h 15301
#ifndef EVENCHECKER_H 15302
#define EVENCHECKER_H 15303
#include <iostream> 15304
#include "zthread/CountedPtr.h" 15305
#include "zthread/Thread.h" 15306
#include "zthread/Cancelable.h" 15307
#include "zthread/ThreadedExecutor.h" 15308
15309
class Generator : public ZThread::Cancelable { 15310
bool canceled; 15311
public: 15312
Generator() : canceled(false) {} 15313
virtual int nextValue() = 0; 15314
void cancel() { canceled = true; } 15315
bool isCanceled() { return canceled; } 15316
}; 15317
15318
588
class EvenChecker : public ZThread::Runnable { 15319
ZThread::CountedPtr<Generator> generator; 15320
int id; 15321
public: 15322
EvenChecker(ZThread::CountedPtr<Generator>& g, int ident) 15323
: generator(g), id(ident) {} 15324
~EvenChecker() { 15325
std::cout << "~EvenChecker " << id << std::endl; 15326
} 15327
void run() { 15328
while(!generator->isCanceled()) { 15329
int val = generator->nextValue(); 15330
if(val % 2 != 0) { 15331
std::cout << val << " not even!" << std::endl; 15332
generator->cancel(); // Cancels all EvenCheckers 15333
} 15334
} 15335
} 15336
// Test any type of generator: 15337
template<typename GenType> static void test(int n = 10) { 15338
std::cout << "Press Control-C to exit" << std::endl; 15339
try { 15340
ZThread::ThreadedExecutor executor; 15341
ZThread::CountedPtr<Generator> gp(new GenType); 15342
for(int i = 0; i < n; i++) 15343
589
executor.execute(new EvenChecker(gp, i)); 15344
} catch(ZThread::Synchronization_Exception& e) { 15345
std::cerr << e.what() << std::endl; 15346
} 15347
} 15348
}; 15349
#endif // EVENCHECKER_H ///:~ 15350
Listado 10.16. C11/EvenChecker.h 15351
15352
La clase Generator presenta la clase abstracta Cancelable, que es parte 15353
de la biblioteca de ZThread. El propósito de Cancelable es proporcionar una 15354
interfaz consistente para cambiar el estado de un objeto via cancel() y ver si 15355
el objeto ha sido cancelado con la función isCanceled(). Aquí utilizamos el 15356
enfoque simple de una bandera de cancelación booleana similar a quitFlag, 15357
vista previamente en ResponsiveUI.cpp. Note que en este ejemplo la clase 15358
que es Cancelable no es Runnable. En su lugar, toda tarea EvenChecker que 15359
dependa de un objeto Cancelable (el Generator) lo comprueba para ver que 15360
ha sido cancelado, como puede ver en run(). De esta manera, las tareas que 15361
comparten recursos comunes (el Cancelable Generator) estén atentos a la 15362
señal de ese recurso para terminar. Esto elimina la también conocida 15363
condición de carrera, donde dos o más tareas compiten por responder una 15364
condición y, así, colisionar o producir resultados inconsistentes. Debe pensar 15365
sobre esto cuidadosamente y protegerse de todas las formas posible de los 15366
fallos de un sistema concurrente. Por ejemplo, una tarea no puede depender 15367
de otra porque el orden de finalización de las tareas no está garantizado. En 15368
este sentido, eliminamos la potencial condición de carrera haciendo que las 15369
tareas dependan de objetos que no son tareas (que son contados 15370
referencialmente utilizando CountedPtr. 15371
En las secciones posteriores, verá que la librería ZThread contiene más 15372
mecanismos generales para la terminación de hilos. 15373
590
Debido a que muchos objetos EvenChecker podrían terminar compartiendo 15374
un Generator, la plantilla CountedPtr se usa para contar las referencias de 15375
los objetos Generator. 15376
El último método en EvenChecker es un miembro estático de la plantilla que 15377
configura y realiza una comprobación de los tipos de Generator creando un 15378
CountedPtr dentro y, seguidamente, lanzar un número de EvenCheckers que 15379
usan ese Generator. Si el Generator provoca un fallo, test() lo reportará y 15380
volverá; en otro caso, deberá pulsar Control-C para finalizarlo. 15381
Las tareas EvenChecker leen constantemente y comprueban que los 15382
valores de sus Generators asociados. Vea que si generator->isCanceled() es 15383
verdadero, run() retorna, con lo que se le dice al Executor de 15384
EvenChecker::test() que la tarea se ha completado. Cualquier tarea 15385
EvenChecker puede llamar a cancel() sobre su Generator asociado, lo que 15386
causará que todos los demás EvenCheckers que utilicen ese Generator 15387
finalicen con elegancia. 15388
EvenGenerator es simple - nextValue() produce el siguiente valor 15389
constante: 15390
//: C11:EvenGenerator.cpp 15391
// When threads collide. 15392
//{L} ZThread 15393
#include <iostream> 15394
#include "EvenChecker.h" 15395
#include "zthread/ThreadedExecutor.h" 15396
using namespace ZThread; 15397
using namespace std; 15398
15399
class EvenGenerator : public Generator { 15400
unsigned int currentEvenValue; // Unsigned can't overflow 15401
591
public: 15402
EvenGenerator() { currentEvenValue = 0; } 15403
~EvenGenerator() { cout << "~EvenGenerator" << endl; } 15404
int nextValue() { 15405
++currentEvenValue; // Danger point here! 15406
++currentEvenValue; 15407
return currentEvenValue; 15408
} 15409
}; 15410
15411
int main() { 15412
EvenChecker::test<EvenGenerator>(); 15413
} ///:~ 15414
Listado 10.17. C11/EvenGenerator.cpp 15415
15416
Es posible que un hilo llame a nextValue() después de el primer 15417
incremento de currentEvenValue y antes del segundo (en el lugar "Danger 15418
point here!" del código comentado), que pone el valor en un estado 15419
"incorrecto". Para probar que esto puede ocurrir, EventChecker::test() crea 15420
un grupo de objetos EventChecker para leer continuamente la salida de un 15421
EvenGenerator y ver si cada valor es constante. Si no es así, el error se 15422
reporta y el programa finaliza. 15423
Este programa podría no detectar el problema hasta que EvenGenerator ha 15424
completado varios ciclos, dependiendo de las particuliaridades de su sistema 15425
operativo y otros detalles de implementación. Si quiere ver que falla mucho 15426
más rápido, pruebe a poner una llamada a yield() entre el primero y segundo 15427
incremento. En algún evento, fallará puntualmente a causa de que los hilos 15428
592
EvenChecker pueden acceder a la información en EvenGenerator mientras se 15429
encuentra en un estado "incorrecto". 15430
10.4.3. Control de acceso 15431
En el ejemplo anterior se muestra el problema fundamental a la hora de 15432
utilizar hilos: nunca sabrá cuándo un hilo puede ser ejecutado. Imagínese 15433
sentado en la mesa con un tenedor, a punto de coger el último pedazo de 15434
comida de un plato y tan pronto como su tenedor lo alcanza, de repente, la 15435
comida se desvanece (debido a que su hilo fue suspendido y otro vino y se 15436
comió la comida). Ese es el problema que estamos tratando a la hora de 15437
escribir programas concurrentes. 15438
En ocasiones no le importará si un recurso está siendo accedido a la vez 15439
que intenta usarlo. Pero en la mayoría de los casos sí, y para trabajar con 15440
múltiples hilos necesitará alguna forma de evitar que dos hilos accedan al 15441
mismo recurso, al menos durante períodos críticos. 15442
Para prevenir este tipo de colisiones existe una manera sencilla que 15443
consiste en poner un bloqueo sobre un recursos cuando un hilo trata de 15444
usarlo. El primer hilo que accede al recursos lo bloquea y, así, otro hilo no 15445
puede acceder al recurso hasta que no sea desbloqueado, momento en el 15446
que este hilo lo vuelve a bloquear y lo vuelve a usar, y así sucesivamente. 15447
De esta forma, tenemos que ser capaces de evitar cualquier tarea de 15448
acceso a memoria mientras ese almacenamiento no esté en un estado 15449
adecuado. Esto es, necesitamos tener un mecanismo que excluya una 15450
segunda tarea sobre el acceso a memoria cuando una primera tarea ya está 15451
usándola. Esta idea es fundamental para todo sistema multihilado y se 15452
conoce como exclusión mutua; abreviado como mutex. La biblioteca ZThread 15453
tiene un mecanismo de mutex en el fichero de cabecera Mutex.h. 15454
Para solucionar el problema en el programa anterior, identificaremos las 15455
secciones críticas donde debe aplicarse la exclusión mutua; posteriormente, 15456
antes de entrar en la sección crítica adquiriremos el mutex y lo liberaremos 15457
cuando finalice la sección crítica. Únicamente un hilo podrá adquirir el mutex 15458
al mismo tiempo, por lo que se logra exclusión mutua: 15459
593
//: C11:MutexEvenGenerator.cpp {RunByHand} 15460
// Preventing thread collisions with mutexes. 15461
//{L} ZThread 15462
#include <iostream> 15463
#include "EvenChecker.h" 15464
#include "zthread/ThreadedExecutor.h" 15465
#include "zthread/Mutex.h" 15466
using namespace ZThread; 15467
using namespace std; 15468
15469
class MutexEvenGenerator : public Generator { 15470
unsigned int currentEvenValue; 15471
Mutex lock; 15472
public: 15473
MutexEvenGenerator() { currentEvenValue = 0; } 15474
~MutexEvenGenerator() { 15475
cout << "~MutexEvenGenerator" << endl; 15476
} 15477
int nextValue() { 15478
lock.acquire(); 15479
++currentEvenValue; 15480
Thread::yield(); // Cause failure faster 15481
++currentEvenValue; 15482
int rval = currentEvenValue; 15483
lock.release(); 15484
594
return rval; 15485
} 15486
}; 15487
15488
int main() { 15489
EvenChecker::test<MutexEvenGenerator>(); 15490
} ///:~ 15491
Listado 10.18. C11/MutexEvenGenerator.cpp 15492
15493
MutexEvenGenerator añade un Mutex llamado lock y utiliza acquire() y 15494
release() para crear una sección crítica con nextValue(). Además, se ha 15495
insertado una llamada a Thread::yield() entre los dos incrementos, para 15496
aumentar la probabilidad de que haya un cambio de contexto mientras 15497
currentEvenValue se encuentra en un estado extraño. Este hecho no 15498
producirá un fallo ya que el mutex evita que más de un hilo esté en la sección 15499
crítica al mismo tiempo, pero llamar a yield() es una buena forma de provocar 15500
un fallo si este ocurriera. 15501
Note que nextValue() debe capturar el valor de retorno dentro de la 15502
sección crítica porque si lo devolviera dentro de la sección critica no liberaría 15503
lock y así evitar que fuera adquirido. (Normalmente, esto conllevaría un 15504
interbloqueo, de lo cual aprenderá sobre ello al final de este capítulo.) 15505
El primer hilo que entre en nextValue() adquirirá lock y cualquier otro hilo 15506
que intente adquirirlo será bloqueado hasta que el primer hilo libere lock. En 15507
ese momento, el mecanismo de planificación selecciona otro hilo que esté 15508
esperando en lock. De esta manera, solo un hilo puede pasar a través del 15509
código custodiado por el mutex al mismo tiempo. 15510
10.4.4. Código simplificado mediante guardas 15511
El uso de mutexes se convierte rápidamente complicado cuando se 15512
introducen excepciones. Para estar seguro de que el mutes siempre se 15513
595
libera, debe asegurar que cualquier camino a una excepción incluya una 15514
llamada a release(). Además, cualquier función que tenga múltiples caminos 15515
para retornar debe asegurar cuidadosamente que se llama a release() en el 15516
momento adecuado. 15517
Esos problemas pueden se fácilmente solucionados utilizando el hecho de 15518
que los objetos de la pila (automáticos) tiene un destructor que siempre se 15519
llama sea cual sea la forma en que salga del ámbito de la función. En la 15520
librería ZThread, esto se implementa en la plantilla Guard. La plantilla Guard 15521
crea objetos que adquieren un objeto Lockable cuando se construyen y lo 15522
liberan cuando son destruidos. Los objetos Guard creados en la pila local 15523
serán eliminados automáticamente independientemente de la forma en el 15524
que la función finalice y siempre desbloqueará el objeto Lockable. A 15525
continuación, el ejemplo anterior reimplementado para utilizar Guards: 15526
//: C11:GuardedEvenGenerator.cpp {RunByHand} 15527
// Simplifying mutexes with the Guard template. 15528
//{L} ZThread 15529
#include <iostream> 15530
#include "EvenChecker.h" 15531
#include "zthread/ThreadedExecutor.h" 15532
#include "zthread/Mutex.h" 15533
#include "zthread/Guard.h" 15534
using namespace ZThread; 15535
using namespace std; 15536
15537
class GuardedEvenGenerator : public Generator { 15538
unsigned int currentEvenValue; 15539
Mutex lock; 15540
public: 15541
596
GuardedEvenGenerator() { currentEvenValue = 0; } 15542
~GuardedEvenGenerator() { 15543
cout << "~GuardedEvenGenerator" << endl; 15544
} 15545
int nextValue() { 15546
Guard<Mutex> g(lock); 15547
++currentEvenValue; 15548
Thread::yield(); 15549
++currentEvenValue; 15550
return currentEvenValue; 15551
} 15552
}; 15553
15554
int main() { 15555
EvenChecker::test<GuardedEvenGenerator>(); 15556
} ///:~ 15557
Listado 10.19. C11/GuardedEvenGenerator.cpp 15558
15559
Note que el valor de retorno temporal ya no es necesario en nextValue(). 15560
En general, hay menos código que escribir y la probabilidad de errores por 15561
parte del usuario se reduce en gran medida. 15562
Una característica interesante de la plantilla Guard es que puede ser usada 15563
para manipular otros elementos de seguridad. Por ejemplo, un segundo 15564
Guard puede ser utilizado temporalmente para desbloquear un elemento de 15565
seguridad: 15566
//: C11:TemporaryUnlocking.cpp 15567
597
// Temporarily unlocking another guard. 15568
//{L} ZThread 15569
#include "zthread/Thread.h" 15570
#include "zthread/Mutex.h" 15571
#include "zthread/Guard.h" 15572
using namespace ZThread; 15573
15574
class TemporaryUnlocking { 15575
Mutex lock; 15576
public: 15577
void f() { 15578
Guard<Mutex> g(lock); 15579
// lock is acquired 15580
// ... 15581
{ 15582
Guard<Mutex, UnlockedScope> h(g); 15583
// lock is released 15584
// ... 15585
// lock is acquired 15586
} 15587
// ... 15588
// lock is released 15589
} 15590
}; 15591
15592
598
int main() { 15593
TemporaryUnlocking t; 15594
t.f(); 15595
} ///:~ 15596
Listado 10.20. C11/TemporaryUnlocking.cpp 15597
15598
Un Guard también puede utilizarse para adquirir un lock durante un 15599
determinado tiempo y, después, liberarlo: 15600
//: C11:TimedLocking.cpp 15601
// Limited time locking. 15602
//{L} ZThread 15603
#include "zthread/Thread.h" 15604
#include "zthread/Mutex.h" 15605
#include "zthread/Guard.h" 15606
using namespace ZThread; 15607
15608
class TimedLocking { 15609
Mutex lock; 15610
public: 15611
void f() { 15612
Guard<Mutex, TimedLockedScope<500> > g(lock); 15613
// ... 15614
} 15615
}; 15616
599
15617
int main() { 15618
TimedLocking t; 15619
t.f(); 15620
} ///:~ 15621
Listado 10.21. C11/TimedLocking.cpp 15622
15623
En este ejemplo, se lanzará una Timeout_Exception si el lock no puede ser 15624
adquirido en 500 milisegundos. 15625
Sincronización de clases completas 15626
La librería ZThread también proporciona la plantilla GuardedClass para 15627
crear automáticamente un recubrimiento de sincronización para toda una 15628
clase. Esto quiere decir que cualquier método de una clase estará 15629
automáticamente protegido: 15630
//: C11:SynchronizedClass.cpp {-dmc} 15631
//{L} ZThread 15632
#include "zthread/GuardedClass.h" 15633
using namespace ZThread; 15634
15635
class MyClass { 15636
public: 15637
void func1() {} 15638
void func2() {} 15639
}; 15640
15641
600
int main() { 15642
MyClass a; 15643
a.func1(); // Not synchronized 15644
a.func2(); // Not synchronized 15645
GuardedClass<MyClass> b(new MyClass); 15646
// Synchronized calls, only one thread at a time allowed: 15647
b->func1(); 15648
b->func2(); 15649
} ///:~ 15650
Listado 10.22. C11/SynchronizedClass.cpp 15651
15652
El objeto a no está sincronizado, por lo que func1() y func2() pueden ser 15653
llamadas en cualquier momento por cualquier número de hilos. El objeto b 15654
está protegido por el recubrimiento GuardedClass, así que cada método se 15655
sincroniza automáticamente y solo se puede llamar a una función por objeto 15656
en cualquier instante. 15657
El recubrimiento bloquea un tipo de nivel de granularidad, que podría 15658
afectar al rendimiento.[151] Si una clase contiene funciones no vinculadas, 15659
puede ser mejor sincronizarlas internamente con 2 locks diferentes. Sin 15660
embargo, si se encuentra haciendo esto, significa que la clase contiene 15661
grupos de datos que puede no estar fuertemente asociados. Considere 15662
dividir la clase en dos. 15663
Proteger todos los métodos de una clase con un mutex no hace que esa 15664
clase sea segura automáticamente cuando se utilicen hilos. Debe tener 15665
cuidado con estas cuestiones para garantizar la seguridad cuando se usan 15666
hilos. 15667
10.4.5. Almacenamiento local al hilo 15668
601
Una segunda forma de eliminar el problema de colisión de tareas sobre 15669
recursos compartidos es la eliminación de las variables compartidas, lo cual 15670
puede realizarse mediante la creación de diferentes almacenamientos para la 15671
misma variable, uno por cada hilo que use el objeto. De esta forma, si tiene 15672
cinco hilos que usan un objeto con una variable x, el almacenamiento local al 15673
hilo genera automáticamente cinco porciones de memoria distintas para 15674
almacenar x. Afortunadamente, la creación y gestión del almacenamiento 15675
local al hilo la lleva a cabo una plantilla de ZThread llamada ThreadLocal, tal 15676
y como se puede ver aquí: 15677
//: C11:ThreadLocalVariables.cpp {RunByHand} 15678
// Automatically giving each thread its own storage. 15679
//{L} ZThread 15680
#include <iostream> 15681
#include "zthread/Thread.h" 15682
#include "zthread/Mutex.h" 15683
#include "zthread/Guard.h" 15684
#include "zthread/ThreadedExecutor.h" 15685
#include "zthread/Cancelable.h" 15686
#include "zthread/ThreadLocal.h" 15687
#include "zthread/CountedPtr.h" 15688
using namespace ZThread; 15689
using namespace std; 15690
15691
class ThreadLocalVariables : public Cancelable { 15692
ThreadLocal<int> value; 15693
bool canceled; 15694
Mutex lock; 15695
602
public: 15696
ThreadLocalVariables() : canceled(false) { 15697
value.set(0); 15698
} 15699
void increment() { value.set(value.get() + 1); } 15700
int get() { return value.get(); } 15701
void cancel() { 15702
Guard<Mutex> g(lock); 15703
canceled = true; 15704
} 15705
bool isCanceled() { 15706
Guard<Mutex> g(lock); 15707
return canceled; 15708
} 15709
}; 15710
15711
class Accessor : public Runnable { 15712
int id; 15713
CountedPtr<ThreadLocalVariables> tlv; 15714
public: 15715
Accessor(CountedPtr<ThreadLocalVariables>& tl, int idn) 15716
: id(idn), tlv(tl) {} 15717
void run() { 15718
while(!tlv->isCanceled()) { 15719
tlv->increment(); 15720
603
cout << *this << endl; 15721
} 15722
} 15723
friend ostream& 15724
operator<<(ostream& os, Accessor& a) { 15725
return os << "#" << a.id << ": " << a.tlv->get(); 15726
} 15727
}; 15728
15729
int main() { 15730
cout << "Press <Enter> to quit" << endl; 15731
try { 15732
CountedPtr<ThreadLocalVariables> 15733
tlv(new ThreadLocalVariables); 15734
const int SZ = 5; 15735
ThreadedExecutor executor; 15736
for(int i = 0; i < SZ; i++) 15737
executor.execute(new Accessor(tlv, i)); 15738
cin.get(); 15739
tlv->cancel(); // All Accessors will quit 15740
} catch(Synchronization_Exception& e) { 15741
cerr << e.what() << endl; 15742
} 15743
} ///:~ 15744
604
Listado 10.23. C11/ThreadLocalVariables.cpp 15745
15746
Cuando crea un objeto ThreadLocal instanciando la plantilla, únicamente 15747
puede acceder al contenido del objeto utilizando los métodos set() y get(). El 15748
método get() devuelve una copia del objeto que está asociado a ese hilo, y 15749
set() inserta su argumento dentro del objeto almacenado para ese hilo, 15750
devolviendo el objeto antiguo que se encontraba almacenado. Puede 15751
comprobar que esto se utiliza en increment() y get() de 15752
ThreadLocalVariables. 15753
Ya que tlv se comparte en múltiples objetos Accessor, está escrito como un 15754
Cancelable, por lo que los Accessors puede recibir señales cuando queramos 15755
parar el sistema. 15756
Cuando ejecute este programa se evidenciará que se reserva para cada 15757
hilo su propio almacenamiento. 15758
10.5. Finalización de tareas 15759
En los ejemplos anteriores, hemos visto el uso de una "bandera de 15760
terminación" o de la interfaz Cancelable para finalizar una tarea. Este es un 15761
enfoque razonable para el problema. Sin embargo, en algunas ocasiones la 15762
tarea tiene que ser finalizada más abruptamente. En esta sección, aprenderá 15763
sobre las cuestiones y problemas de este tipo de finalización. 15764
Primeramente, veamos en un ejemplo que no sólo demuestra el problema 15765
de la finalización sino que, además, es un ejemplo adicional de comparición 15766
de recursos. Para mostrar el ejemplo, primero necesitaremos resolver el 15767
problema de la colisión de iostream. 15768
10.5.1. Prevención de coliciones en iostream 15769
FIXME: dos versiones: Podría haberse dado cuenta en los anteriores 15770
ejemplos que la salida es confusa en algunas ocasiones. Los iostreams de 15771
C++ no fueron creados pensando en el sistema de hilos, por lo que no hay 15772
Puede haberse dado cuenta en los ejemplos anteriores que la salida es 15773
FIXMEconfusa. El sistema iostream de C++ no fue creado con el sistema de 15774
hilos en mente, por no lo que no hay nada que prevenga que la salida de un 15775
605
hilo interfiera con la salida de otro hilo. Por ello, debe escribir aplicaciones de 15776
tal forma que sincronicen el uso de iostreams. 15777
Para solucionar el problema, primero necesitamos crear un paquete 15778
completo de salida y, después, decidir explícitamente cuando intentamos 15779
mandarlo a la consola. Una sencilla solución pasa por escribir la informacion 15780
en un ostringstream y posteriormente utilizar un único objeto con un mutex 15781
como punto de salida de todos los hilos, para evitar que más de un hilo 15782
escriba al mismo tiempo: 15783
//: C11:Display.h 15784
// Prevents ostream collisions. 15785
#ifndef DISPLAY_H 15786
#define DISPLAY_H 15787
#include <iostream> 15788
#include <sstream> 15789
#include "zthread/Mutex.h" 15790
#include "zthread/Guard.h" 15791
15792
class Display { // Share one of these among all threads 15793
ZThread::Mutex iolock; 15794
public: 15795
void output(std::ostringstream& os) { 15796
ZThread::Guard<ZThread::Mutex> g(iolock); 15797
std::cout << os.str(); 15798
} 15799
}; 15800
#endif // DISPLAY_H ///:~ 15801
606
Listado 10.24. C11/Display.h 15802
15803
De esta manera, predefinimos la función estandar operator<<() y el objeto 15804
puede ser construido en memoria utilizando operadores habituales de 15805
ostream. Cuando una tarea quiere mostrar una salida, crea un objeto 15806
ostringstream temporal que utiliza FIXME. Cuando llama a output(), el mutex 15807
evita que varios hilos escriban a este objeto Display. (Debe usar solo un 15808
objeto Display en su programa, tal y como verá en los siguientes ejemplos.) 15809
Todo esto muestra la idea básica pero, si es necesario, puede construir un 15810
entorno más elaborado. Por ejemplo, podría forzar el requisito de que solo 15811
haya un objeto Display en un programa haciéndolo Singleton. (La librería 15812
ZThread tiene una plantilla Singleton para dar soporte a Singletons). 15813
10.5.2. El jardín ornamental 15814
En esta simulación, al comité del jardín le gustaría saber cuanta gente 15815
entra en el jardín cada día a través de distintas puertas. Cada puerta tiene un 15816
FIXMEturnstile o algún otro tipo de contador, y después de que el contador 15817
FIXMEturnstile se incrementa, aumenta una cuenta compartida que 15818
representa el número total de gente en el jardín. 15819
//: C11:OrnamentalGarden.cpp {RunByHand} 15820
//{L} ZThread 15821
#include <vector> 15822
#include <cstdlib> 15823
#include <ctime> 15824
#include "Display.h" 15825
#include "zthread/Thread.h" 15826
#include "zthread/FastMutex.h" 15827
#include "zthread/Guard.h" 15828
#include "zthread/ThreadedExecutor.h" 15829
607
#include "zthread/CountedPtr.h" 15830
using namespace ZThread; 15831
using namespace std; 15832
15833
class Count : public Cancelable { 15834
FastMutex lock; 15835
int count; 15836
bool paused, canceled; 15837
public: 15838
Count() : count(0), paused(false), canceled(false) {} 15839
int increment() { 15840
// Comment the following line to see counting fail: 15841
Guard<FastMutex> g(lock); 15842
int temp = count ; 15843
if(rand() % 2 == 0) // Yield half the time 15844
Thread::yield(); 15845
return (count = ++temp); 15846
} 15847
int value() { 15848
Guard<FastMutex> g(lock); 15849
return count; 15850
} 15851
void cancel() { 15852
Guard<FastMutex> g(lock); 15853
canceled = true; 15854
608
} 15855
bool isCanceled() { 15856
Guard<FastMutex> g(lock); 15857
return canceled; 15858
} 15859
void pause() { 15860
Guard<FastMutex> g(lock); 15861
paused = true; 15862
} 15863
bool isPaused() { 15864
Guard<FastMutex> g(lock); 15865
return paused; 15866
} 15867
}; 15868
15869
class Entrance : public Runnable { 15870
CountedPtr<Count> count; 15871
CountedPtr<Display> display; 15872
int number; 15873
int id; 15874
bool waitingForCancel; 15875
public: 15876
Entrance(CountedPtr<Count>& cnt, 15877
CountedPtr<Display>& disp, int idn) 15878
: count(cnt), display(disp), number(0), id(idn), 15879
609
waitingForCancel(false) {} 15880
void run() { 15881
while(!count->isPaused()) { 15882
++number; 15883
{ 15884
ostringstream os; 15885
os << *this << " Total: " 15886
<< count->increment() << endl; 15887
display->output(os); 15888
} 15889
Thread::sleep(100); 15890
} 15891
waitingForCancel = true; 15892
while(!count->isCanceled()) // Hold here... 15893
Thread::sleep(100); 15894
ostringstream os; 15895
os << "Terminating " << *this << endl; 15896
display->output(os); 15897
} 15898
int getValue() { 15899
while(count->isPaused() && !waitingForCancel) 15900
Thread::sleep(100); 15901
return number; 15902
} 15903
friend ostream& 15904
610
operator<<(ostream& os, const Entrance& e) { 15905
return os << "Entrance " << e.id << ": " << e.number; 15906
} 15907
}; 15908
15909
int main() { 15910
srand(time(0)); // Seed the random number generator 15911
cout << "Press <ENTER> to quit" << endl; 15912
CountedPtr<Count> count(new Count); 15913
vector<Entrance*> v; 15914
CountedPtr<Display> display(new Display); 15915
const int SZ = 5; 15916
try { 15917
ThreadedExecutor executor; 15918
for(int i = 0; i < SZ; i++) { 15919
Entrance* task = new Entrance(count, display, i); 15920
executor.execute(task); 15921
// Save the pointer to the task: 15922
v.push_back(task); 15923
} 15924
cin.get(); // Wait for user to press <Enter> 15925
count->pause(); // Causes tasks to stop counting 15926
int sum = 0; 15927
vector<Entrance*>::iterator it = v.begin(); 15928
while(it != v.end()) { 15929
611
sum += (*it)->getValue(); 15930
++it; 15931
} 15932
ostringstream os; 15933
os << "Total: " << count->value() << endl 15934
<< "Sum of Entrances: " << sum << endl; 15935
display->output(os); 15936
count->cancel(); // Causes threads to quit 15937
} catch(Synchronization_Exception& e) { 15938
cerr << e.what() << endl; 15939
} 15940
} ///:~ 15941
Listado 10.25. C11/OrnamentalGarden.cpp 15942
15943
Count es la clase que conserva el contador principal de los visitantes del 15944
jardín. El objeto único Count definido en main() como contador FIXME is held 15945
como un CountedPtr en Entrance y, así, se comparte entre todos los objetos 15946
Entrance. En este ejemplo, se utiliza un FastMutex llamado lock en vez de un 15947
Mutex ordinario ya que un FastMutex usa el mutex nativo del sistema 15948
operativo y, por ello, aportará resultados más interesantes. 15949
Se utiliza un Guard con bloqueo en increment() para sincronizar el acceso a 15950
count. Esta función usa rand() para realizar una carga de trabajo alta 15951
(mediante yield()) FIXME la mitad del tiempo, .... 15952
La clase Entrance también mantiene una variable local numbre con el 15953
número de visitantes que han pasado a través de una entrada concreta. Esto 15954
proporciona un chequeo doble contra el objeto count para asegurar que el 15955
verdadero número de visitantes es el que se está almacenando. 15956
612
Entrance::run() simplemente incrementa number y el objeto count y se 15957
duerme durante 100 milisegundos. 15958
En main, se carga un vector<Entrance*> con cada Entrance que se crean. 15959
Después de que el usuario pulse Enter, el vector se utiliza para iterar sobre el 15960
valor de cada Entrance y calcular el total. 15961
FIXMEThis program goes to quite a bit of extra trouble to shut everything 15962
down in a stable fashion. 15963
Toda la comunicación entre los objetos Entrance ocurre a través de un 15964
único objeto Count. Cuando el usuario pulsa Enter, main() manda el mensaje 15965
pause() a count. Como cada Entrance::run() está vigilando a que el objeto 15966
count esté pausado, esto hace que cada Entrance se mueva al estado 15967
waitingForCancel, donde no se cuenta más, pero aún sigue vivo. Esto es 15968
esencial porque main() debe poder seguir iterando de forma segura sobre los 15969
objetos del vector<Entrace*>. Note que debido a que existe una 15970
FIXMEpequeña posibilidad que la iteración pueda ocurrir antes de que un 15971
Entrance haya terminado de contar y haya ido al estado de 15972
waitingForCancel, la función getValue() itera a lo largo de las llamadas a 15973
sleep() hasta que el objeto vaya al estado de waitingForCancel. (Esta es una 15974
forma, que se conoce como espera activa, y es indeseable. Verá un enfoque 15975
más apropiado utilizando wait(), más adelante en el capítulo). Una vez que 15976
main() completa una iteración a lo largo del vector<Entrance*>, se manda el 15977
mensaje de cancel() al objeto count, y de nuevo todos los objetos Entrance 15978
esperan a este cambio de estado. En ese instante, imprimen un mensaje de 15979
finalización y salen de run(), por lo que el mecanismo de hilado destruye 15980
cada tarea. 15981
Tal y como este programa se ejecuta, verá que la cuenta total y la de cada 15982
una de las entradas se muestran a la vez que la gente pasa a través de un 15983
FIXMEturnstile. Si comenta el objeto Guard en Count::increment(), se dará 15984
cuenta que el número total de personas no es el que espera que sea. El 15985
número de personas contadas por cada FIXMEturnstile será diferente del 15986
valor de count. Tan pronto como haya un Mutex para sincronizar el acceso al 15987
Counter, las cosas funcionarán correctamente. Tenga en cuenta que 15988
Count::increment() exagera la situación potencial de fallo que supone utilizar 15989
temp y yield(). En problemas reales de hilado, la probabilidad de fallo puede 15990
613
ser estadísticamente menor, por lo que puede caer fácilmente en la trampa 15991
de creer que las cosas funcionan correctamente. Tal y como muestra el 15992
problema anterior, existen FIXMElikely problemas ocultos que no le han 15993
ocurrido, por lo que debe ser excepcionalmente diligente cuando revise 15994
código concurrente. 15995
Operaciones atómicas 15996
Note que Count::value() devuelve el valor de count utilizando un objeto 15997
Guard para la sincronización. Esto ofrece un aspecto interesante porque este 15998
código probablemente funcionará bien con la mayoría de los compiladores y 15999
sistemas sin sincronización. El motivo es que, en general, una operación 16000
simple como devolver un int será una operación atómica, que quiere decir 16001
que probablemente se llevará a cabo con una única instrucción de 16002
microprocesador y no será interrumpida. (El mecanismo de multihilado no 16003
puede parar un hilo en mitad de una instrucción de microprocesador.) Esto 16004
es, las operaciones atómicas no son interrumpibles por el mecanismo de 16005
hilado y, así, no necesitan ser protegidas.[152] De hecho, si elimináramos la 16006
asignación de count en temp y quitáramos yield(), y en su lugar simplemente 16007
incrementáramos count directamente, probablemente no necesitaríamos un 16008
lock ya que la operación de incremento es, normalmente, atómica. [153] 16009
El problema es que el estándar de C++ no garantiza la atomicidad para 16010
ninguna de esas operaciones. Sin embargo, pese a que operaciones como 16011
devolver un int e incrementar un int son atómicas en la mayoría de las 16012
máquinas no hay garantías. Y puesto que no hay garantía, debe asumir lo 16013
peor. En algunas ocasiones podría investigar el funcionamiento de la 16014
atomicidad para una máquina en particular (normalmente mirando el lenguaje 16015
ensamblador) y escribir código basado en esas asunciones. Esto es siempre 16016
peligroso y FIXMEill-advised. Es muy fácil que esta información se pierda o 16017
esté oculta, y la siguiente persona que venga podría asumir que el código 16018
puede ser portado a otra máquina y, por ello, volverse loco siguiendo la pista 16019
al FIXMEoccsional glitch provocado por la colisión de hilos. 16020
Por ello, aunque quitar el guarda en Count::value() parezca que funciona 16021
no es FIXMEairtight y, así, en algunas máquinas puede ver un 16022
comportamiento aberrante. 16023
614
10.5.3. FIXME:Teminación al bloquear 16024
En el ejemplo anterior, Entrance::run() incluye una llamada a sleep() en el 16025
bucle principal. Sabemos que sleep() se despertará eventualmente y que la 16026
tarea llegará al principio del bucle donde tiene una oportunidad para salir de 16027
ese bucle chequeando el estado isPaused(). Sin embargo, sleep() es 16028
simplemente una situación donde un hilo en ejecución se bloquea, y a veces 16029
necesita terminar una tarea que está bloqueada. 16030
Un hilo puede estar en alguno de los cuatro estados: 16031
1. Nuevo: Un hilo permanece en este estado solamente de forma 16032
momentánea, tan solo cuando se crea. Reserva todos los recursos del 16033
sistema necesarios y ejecuta la inicialización. En este momento se convierte 16034
en FIXMEcandidato para recibir tiempo de CPU. A continuación, el 16035
planificador llevará a este hilo al estado de ejecución o de bloqueo. 16036
2. Ejecutable: Esto significa que un hilo puede ser ejecutado cuando el 16037
mecanismo de fraccionador de tiempo tenga ciclos de CPU disponibles para 16038
el hilo. Así, el hilo podría o no ejecutarse en cualquier momento, pero no hay 16039
nada que evite FIXME 16040
3. Bloqueado: El hilo pudo ser ejecutado, pero algo lo impidió. (Podría 16041
estar esperando a que se complete una operación de entrada/salida, por 16042
ejemplo.) Mientras un hilo esté en el estado de bloqueo, el planificador 16043
simplemente lo ignorará y no le dará tiempo de CPU. Hasta que un hilo no 16044
vuelva a entrar en el estado de ejecución, no ejecutará ninguna operación. 16045
4. FIXMEMuerte: Un hilo en el estado de muerto no será planificable y no 16046
recibirá tiempo de CPU. Sus tareas han finalizado, y no será ejecutable 16047
nunca más. La forma normal que un hilo tiene para morir es volviendo de su 16048
función run(). 16049
Un hilo está bloqueado cuando no puede continuar su ejecución. Un hilo 16050
puede bloquearse debido a los siguientes motivos: 16051
Puso el hilo a dormir llamando a sleep(milisegundos), en cuyo caso no 16052
será ejecutado durante el tiempo especificado. 16053
Suspendió la ejecución del hilo con wait(). No volverá a ser ejecutable 16054
hasta que el hilo no obtenga el mensaje signal() o broadcast(). Estudiaremos 16055
esto en una sección más adelante. 16056
615
El hilo está esperando a que una operación de entrada/salida finalice. 16057
El hilo está intentando entrar en un bloque de código controlado por un 16058
mutex, y el mutex ha sido ya adquirido por otro hilo. 16059
El problema que tenemos ahora es el siguiente: algunas veces quiere 16060
terminar un hilo que está en el estado de bloqueo. Si no puede esperar a que 16061
el hilo llegue a un punto en el código donde pueda comprobar el valor del 16062
estado y decidir si terminar por sus propios medios, debe forzar a que el hilo 16063
salga de su estado de bloqueo. 16064
10.5.4. Interrupción 16065
Tal y como podría imaginar, es mucho más FIXMEmessier salir de forma 16066
brusca en mitad de la función Runnable::run() que si espera a que esa 16067
función llegue al test de isCanceled() (o a algún otro lugar donde el 16068
programador esté preparado para salir de la función). Cuando sale de una 16069
tarea bloqueada, podría necesitar destruir objetos y liberar recursos. Debido 16070
a esto, salir en mitad de un run() de una tarea, más que otra cosa, consiste 16071
en lanzar una excepción, por lo que en ZThreads, las excepciones se utilizan 16072
para este tipo de terminación. (Esto roza el límite de un uso inapropiado de 16073
las excepciones, porque significa que las utiliza para el control de flujo.)[154] 16074
Para volver de esta manera a un buen estado conocido a la hora de terminar 16075
una tarea, tenga en cuenta cuidadosamente los caminos de ejecución de su 16076
código y libere todo correctamente dentro de los bloques catch. Veremos 16077
esta técnica en la presente sección. 16078
Para finalizar un hilo bloqueado, la librería ZThread proporciona la función 16079
Thread::interrupted(). Esta configura el estado de interrupción para ese hilo. 16080
Un hilo con su estado de interrupción configurado lanzará una 16081
Interrupted_Exception si está bloqueado o si espera una operación 16082
bloqueante. El estado de interrupción será restaurado cuando se haya 16083
lanzado la excepción o si la tarea llama a Thread::interrupted(). Como puede 16084
ver, Thread::interrupted() proporciona otra forma de salir de su bucle run(), 16085
sin lanzar una excepción. 16086
A continuación, un ejemplo que ilustra las bases de interrupt(): 16087
616
//: C11:Interrupting.cpp 16088
// Interrupting a blocked thread. 16089
//{L} ZThread 16090
#include <iostream> 16091
#include "zthread/Thread.h" 16092
using namespace ZThread; 16093
using namespace std; 16094
16095
class Blocked : public Runnable { 16096
public: 16097
void run() { 16098
try { 16099
Thread::sleep(1000); 16100
cout << "Waiting for get() in run():"; 16101
cin.get(); 16102
} catch(Interrupted_Exception&) { 16103
cout << "Caught Interrupted_Exception" << endl; 16104
// Exit the task 16105
} 16106
} 16107
}; 16108
16109
int main(int argc, char* argv[]) { 16110
try { 16111
Thread t(new Blocked); 16112
617
if(argc > 1) 16113
Thread::sleep(1100); 16114
t.interrupt(); 16115
} catch(Synchronization_Exception& e) { 16116
cerr << e.what() << endl; 16117
} 16118
} ///:~ 16119
Listado 10.26. C11/Interrupting.cpp 16120
16121
Puede ver que, además de la inserción de cout, run() tiene dos puntos 16122
donde puede ocurrir el bloqueo: la llamada a Thread::sleep(1000) y la 16123
llamada a cin.get(). Dando cualquier argumento por línea de comandos al 16124
programa, dirá a main() que se duerma lo suficiente para que la tarea finalice 16125
su sleep() y llame a cin.get().[155] Si no le da un argumento, el sleep() de 16126
main() se ignora. Ahora, la llamada a interrupt() ocurrirá mientras la tarea 16127
está dormida, y verá que esto provoca que una Interrupted_Exception se 16128
lance. Si le da un argumento por línea de comandos al programa, descubrirá 16129
que la tarea no puede ser interrumpida si está bloqueada en la 16130
entrada/salida. Esto es, puede interrumpir cualquier operación bloqueante a 16131
excepción de una entrada/salida.[156] 16132
Esto es un poco desconcertante si está creando un hilo que ejecuta 16133
entrada/salida porque quiere decir que la entrada/salida tiene posibilidades 16134
de bloquear su programa multihilado. El problema es que, de nuevo, C++ no 16135
fue diseñado con el sistema de hilos en mente; muy al contrario, 16136
FIXMEpresupone que el hilado no existe. Por ello, la librería iostream no ses 16137
thread-friendly. Si el nuevo estándar de C++ decide añadir soporte a hilos, la 16138
librería iostream podría necesitar ser reconsiderada en el proceso. Bloqueo 16139
debido a un mutex. 16140
Si intenta llamar a una función cuyo mutes ha sido adquirido, la tarea que 16141
llama será suspendida hasta que el mutex esté accesible. El siguiente 16142
ejemplo comprueba si este tipo de bloqueo es interrumpible: 16143
618
//: C11:Interrupting2.cpp 16144
// Interrupting a thread blocked 16145
// with a synchronization guard. 16146
//{L} ZThread 16147
#include <iostream> 16148
#include "zthread/Thread.h" 16149
#include "zthread/Mutex.h" 16150
#include "zthread/Guard.h" 16151
using namespace ZThread; 16152
using namespace std; 16153
16154
class BlockedMutex { 16155
Mutex lock; 16156
public: 16157
BlockedMutex() { 16158
lock.acquire(); 16159
} 16160
void f() { 16161
Guard<Mutex> g(lock); 16162
// This will never be available 16163
} 16164
}; 16165
16166
class Blocked2 : public Runnable { 16167
BlockedMutex blocked; 16168
619
public: 16169
void run() { 16170
try { 16171
cout << "Waiting for f() in BlockedMutex" << endl; 16172
blocked.f(); 16173
} catch(Interrupted_Exception& e) { 16174
cerr << e.what() << endl; 16175
// Exit the task 16176
} 16177
} 16178
}; 16179
16180
int main(int argc, char* argv[]) { 16181
try { 16182
Thread t(new Blocked2); 16183
t.interrupt(); 16184
} catch(Synchronization_Exception& e) { 16185
cerr << e.what() << endl; 16186
} 16187
} ///:~ 16188
Listado 10.27. C11/Interrupting2.cpp 16189
16190
La clase BlockedMutex tiene un constructor que adquiere su propio objeto 16191
Mutex y nunca lo libera. Por esa razón, si intenta llamara a f(), siempre será 16192
bloqueado porque el Mutex no puede ser adquirido. En Blocked2, la función 16193
run() se parará en la llamada blocked.f(). Cuando ejecute el programa verá 16194
620
que, a diferencia de la llamada a iostream, interrupt() puede salir de una 16195
llamada que está bloqueada por un mutex.[157] Comprobación de una una 16196
interrupción. 16197
Note que cuando llama a interrupt() sobre un hilo, la única vez que ocurre 16198
la interrupción es cuando la tarea entra, o ya está dentro, de una operación 16199
bloqueante (a excepción, como ya ha visto, del caso de la entrada/salida, 16200
donde simplemente 16201
//: C11:Interrupting3.cpp {RunByHand} 16202
// General idiom for interrupting a task. 16203
//{L} ZThread 16204
#include <iostream> 16205
#include "zthread/Thread.h" 16206
using namespace ZThread; 16207
using namespace std; 16208
16209
const double PI = 3.14159265358979323846; 16210
const double E = 2.7182818284590452354; 16211
16212
class NeedsCleanup { 16213
int id; 16214
public: 16215
NeedsCleanup(int ident) : id(ident) { 16216
cout << "NeedsCleanup " << id << endl; 16217
} 16218
~NeedsCleanup() { 16219
cout << "~NeedsCleanup " << id << endl; 16220
621
} 16221
}; 16222
16223
class Blocked3 : public Runnable { 16224
volatile double d; 16225
public: 16226
Blocked3() : d(0.0) {} 16227
void run() { 16228
try { 16229
while(!Thread::interrupted()) { 16230
point1: 16231
NeedsCleanup n1(1); 16232
cout << "Sleeping" << endl; 16233
Thread::sleep(1000); 16234
point2: 16235
NeedsCleanup n2(2); 16236
cout << "Calculating" << endl; 16237
// A time-consuming, non-blocking operation: 16238
for(int i = 1; i < 100000; i++) 16239
d = d + (PI + E) / (double)i; 16240
} 16241
cout << "Exiting via while() test" << endl; 16242
} catch(Interrupted_Exception&) { 16243
cout << "Exiting via Interrupted_Exception" << endl; 16244
} 16245
622
} 16246
}; 16247
16248
int main(int argc, char* argv[]) { 16249
if(argc != 2) { 16250
cerr << "usage: " << argv[0] 16251
<< " delay-in-milliseconds" << endl; 16252
exit(1); 16253
} 16254
int delay = atoi(argv[1]); 16255
try { 16256
Thread t(new Blocked3); 16257
Thread::sleep(delay); 16258
t.interrupt(); 16259
} catch(Synchronization_Exception& e) { 16260
cerr << e.what() << endl; 16261
} 16262
} ///:~ 16263
Listado 10.28. C11/Interrupting3.cpp 16264
16265
10.6. Cooperación entre hilos 16266
10.6.1. Wait y signal 16267
//: C11:WaxOMatic.cpp {RunByHand} 16268
623
// Basic thread cooperation. 16269
//{L} ZThread 16270
#include <iostream> 16271
#include <string> 16272
#include "zthread/Thread.h" 16273
#include "zthread/Mutex.h" 16274
#include "zthread/Guard.h" 16275
#include "zthread/Condition.h" 16276
#include "zthread/ThreadedExecutor.h" 16277
using namespace ZThread; 16278
using namespace std; 16279
16280
class Car { 16281
Mutex lock; 16282
Condition condition; 16283
bool waxOn; 16284
public: 16285
Car() : condition(lock), waxOn(false) {} 16286
void waxed() { 16287
Guard<Mutex> g(lock); 16288
waxOn = true; // Ready to buff 16289
condition.signal(); 16290
} 16291
void buffed() { 16292
Guard<Mutex> g(lock); 16293
624
waxOn = false; // Ready for another coat of wax 16294
condition.signal(); 16295
} 16296
void waitForWaxing() { 16297
Guard<Mutex> g(lock); 16298
while(waxOn == false) 16299
condition.wait(); 16300
} 16301
void waitForBuffing() { 16302
Guard<Mutex> g(lock); 16303
while(waxOn == true) 16304
condition.wait(); 16305
} 16306
}; 16307
16308
class WaxOn : public Runnable { 16309
CountedPtr<Car> car; 16310
public: 16311
WaxOn(CountedPtr<Car>& c) : car(c) {} 16312
void run() { 16313
try { 16314
while(!Thread::interrupted()) { 16315
cout << "Wax On!" << endl; 16316
Thread::sleep(200); 16317
car->waxed(); 16318
625
car->waitForBuffing(); 16319
} 16320
} catch(Interrupted_Exception&) { /* Exit */ } 16321
cout << "Ending Wax On process" << endl; 16322
} 16323
}; 16324
16325
class WaxOff : public Runnable { 16326
CountedPtr<Car> car; 16327
public: 16328
WaxOff(CountedPtr<Car>& c) : car(c) {} 16329
void run() { 16330
try { 16331
while(!Thread::interrupted()) { 16332
car->waitForWaxing(); 16333
cout << "Wax Off!" << endl; 16334
Thread::sleep(200); 16335
car->buffed(); 16336
} 16337
} catch(Interrupted_Exception&) { /* Exit */ } 16338
cout << "Ending Wax Off process" << endl; 16339
} 16340
}; 16341
16342
int main() { 16343
626
cout << "Press <Enter> to quit" << endl; 16344
try { 16345
CountedPtr<Car> car(new Car); 16346
ThreadedExecutor executor; 16347
executor.execute(new WaxOff(car)); 16348
executor.execute(new WaxOn(car)); 16349
cin.get(); 16350
executor.interrupt(); 16351
} catch(Synchronization_Exception& e) { 16352
cerr << e.what() << endl; 16353
} 16354
} ///:~ 16355
Listado 10.29. C11/WaxOMatic.cpp 16356
16357
10.6.2. Relación de productor/consumidor 16358
//: C11:ToastOMatic.cpp {RunByHand} 16359
// Problems with thread cooperation. 16360
//{L} ZThread 16361
#include <iostream> 16362
#include <cstdlib> 16363
#include <ctime> 16364
#include "zthread/Thread.h" 16365
#include "zthread/Mutex.h" 16366
627
#include "zthread/Guard.h" 16367
#include "zthread/Condition.h" 16368
#include "zthread/ThreadedExecutor.h" 16369
using namespace ZThread; 16370
using namespace std; 16371
16372
// Apply jam to buttered toast: 16373
class Jammer : public Runnable { 16374
Mutex lock; 16375
Condition butteredToastReady; 16376
bool gotButteredToast; 16377
int jammed; 16378
public: 16379
Jammer() : butteredToastReady(lock) { 16380
gotButteredToast = false; 16381
jammed = 0; 16382
} 16383
void moreButteredToastReady() { 16384
Guard<Mutex> g(lock); 16385
gotButteredToast = true; 16386
butteredToastReady.signal(); 16387
} 16388
void run() { 16389
try { 16390
while(!Thread::interrupted()) { 16391
628
{ 16392
Guard<Mutex> g(lock); 16393
while(!gotButteredToast) 16394
butteredToastReady.wait(); 16395
++jammed; 16396
} 16397
cout << "Putting jam on toast " << jammed << endl; 16398
{ 16399
Guard<Mutex> g(lock); 16400
gotButteredToast = false; 16401
} 16402
} 16403
} catch(Interrupted_Exception&) { /* Exit */ } 16404
cout << "Jammer off" << endl; 16405
} 16406
}; 16407
16408
// Apply butter to toast: 16409
class Butterer : public Runnable { 16410
Mutex lock; 16411
Condition toastReady; 16412
CountedPtr<Jammer> jammer; 16413
bool gotToast; 16414
int buttered; 16415
public: 16416
629
Butterer(CountedPtr<Jammer>& j) 16417
: toastReady(lock), jammer(j) { 16418
gotToast = false; 16419
buttered = 0; 16420
} 16421
void moreToastReady() { 16422
Guard<Mutex> g(lock); 16423
gotToast = true; 16424
toastReady.signal(); 16425
} 16426
void run() { 16427
try { 16428
while(!Thread::interrupted()) { 16429
{ 16430
Guard<Mutex> g(lock); 16431
while(!gotToast) 16432
toastReady.wait(); 16433
++buttered; 16434
} 16435
cout << "Buttering toast " << buttered << endl; 16436
jammer->moreButteredToastReady(); 16437
{ 16438
Guard<Mutex> g(lock); 16439
gotToast = false; 16440
} 16441
630
} 16442
} catch(Interrupted_Exception&) { /* Exit */ } 16443
cout << "Butterer off" << endl; 16444
} 16445
}; 16446
16447
class Toaster : public Runnable { 16448
CountedPtr<Butterer> butterer; 16449
int toasted; 16450
public: 16451
Toaster(CountedPtr<Butterer>& b) : butterer(b) { 16452
toasted = 0; 16453
} 16454
void run() { 16455
try { 16456
while(!Thread::interrupted()) { 16457
Thread::sleep(rand()/(RAND_MAX/5)*100); 16458
// ... 16459
// Create new toast 16460
// ... 16461
cout << "New toast " << ++toasted << endl; 16462
butterer->moreToastReady(); 16463
} 16464
} catch(Interrupted_Exception&) { /* Exit */ } 16465
cout << "Toaster off" << endl; 16466
631
} 16467
}; 16468
16469
int main() { 16470
srand(time(0)); // Seed the random number generator 16471
try { 16472
cout << "Press <Return> to quit" << endl; 16473
CountedPtr<Jammer> jammer(new Jammer); 16474
CountedPtr<Butterer> butterer(new Butterer(jammer)); 16475
ThreadedExecutor executor; 16476
executor.execute(new Toaster(butterer)); 16477
executor.execute(butterer); 16478
executor.execute(jammer); 16479
cin.get(); 16480
executor.interrupt(); 16481
} catch(Synchronization_Exception& e) { 16482
cerr << e.what() << endl; 16483
} 16484
} ///:~ 16485
Listado 10.30. C11/ToastOMatic.cpp 16486
16487
10.6.3. Resolución de problemas de hilos mediante colas 16488
//: C11:TQueue.h 16489
632
#ifndef TQUEUE_H 16490
#define TQUEUE_H 16491
#include <deque> 16492
#include "zthread/Thread.h" 16493
#include "zthread/Condition.h" 16494
#include "zthread/Mutex.h" 16495
#include "zthread/Guard.h" 16496
16497
template<class T> class TQueue { 16498
ZThread::Mutex lock; 16499
ZThread::Condition cond; 16500
std::deque<T> data; 16501
public: 16502
TQueue() : cond(lock) {} 16503
void put(T item) { 16504
ZThread::Guard<ZThread::Mutex> g(lock); 16505
data.push_back(item); 16506
cond.signal(); 16507
} 16508
T get() { 16509
ZThread::Guard<ZThread::Mutex> g(lock); 16510
while(data.empty()) 16511
cond.wait(); 16512
T returnVal = data.front(); 16513
data.pop_front(); 16514
633
return returnVal; 16515
} 16516
}; 16517
#endif // TQUEUE_H ///:~ 16518
Listado 10.31. C11/TQueue.h 16519
16520
//: C11:TestTQueue.cpp {RunByHand} 16521
//{L} ZThread 16522
#include <string> 16523
#include <iostream> 16524
#include "TQueue.h" 16525
#include "zthread/Thread.h" 16526
#include "LiftOff.h" 16527
using namespace ZThread; 16528
using namespace std; 16529
16530
class LiftOffRunner : public Runnable { 16531
TQueue<LiftOff*> rockets; 16532
public: 16533
void add(LiftOff* lo) { rockets.put(lo); } 16534
void run() { 16535
try { 16536
while(!Thread::interrupted()) { 16537
LiftOff* rocket = rockets.get(); 16538
634
rocket->run(); 16539
} 16540
} catch(Interrupted_Exception&) { /* Exit */ } 16541
cout << "Exiting LiftOffRunner" << endl; 16542
} 16543
}; 16544
16545
int main() { 16546
try { 16547
LiftOffRunner* lor = new LiftOffRunner; 16548
Thread t(lor); 16549
for(int i = 0; i < 5; i++) 16550
lor->add(new LiftOff(10, i)); 16551
cin.get(); 16552
lor->add(new LiftOff(10, 99)); 16553
cin.get(); 16554
t.interrupt(); 16555
} catch(Synchronization_Exception& e) { 16556
cerr << e.what() << endl; 16557
} 16558
} ///:~ 16559
Listado 10.32. C11/TestTQueue.cpp 16560
16561
//: C11:ToastOMaticMarkII.cpp {RunByHand} 16562
635
// Solving the problems using TQueues. 16563
//{L} ZThread 16564
#include <iostream> 16565
#include <string> 16566
#include <cstdlib> 16567
#include <ctime> 16568
#include "zthread/Thread.h" 16569
#include "zthread/Mutex.h" 16570
#include "zthread/Guard.h" 16571
#include "zthread/Condition.h" 16572
#include "zthread/ThreadedExecutor.h" 16573
#include "TQueue.h" 16574
using namespace ZThread; 16575
using namespace std; 16576
16577
class Toast { 16578
enum Status { DRY, BUTTERED, JAMMED }; 16579
Status status; 16580
int id; 16581
public: 16582
Toast(int idn) : status(DRY), id(idn) {} 16583
#ifdef __DMC__ // Incorrectly requires default 16584
Toast() { assert(0); } // Should never be called 16585
#endif 16586
void butter() { status = BUTTERED; } 16587
636
void jam() { status = JAMMED; } 16588
string getStatus() const { 16589
switch(status) { 16590
case DRY: return "dry"; 16591
case BUTTERED: return "buttered"; 16592
case JAMMED: return "jammed"; 16593
default: return "error"; 16594
} 16595
} 16596
int getId() { return id; } 16597
friend ostream& operator<<(ostream& os, const Toast& t) { 16598
return os << "Toast " << t.id << ": " << t.getStatus(); 16599
} 16600
}; 16601
16602
typedef CountedPtr< TQueue<Toast> > ToastQueue; 16603
16604
class Toaster : public Runnable { 16605
ToastQueue toastQueue; 16606
int count; 16607
public: 16608
Toaster(ToastQueue& tq) : toastQueue(tq), count(0) {} 16609
void run() { 16610
try { 16611
while(!Thread::interrupted()) { 16612
637
int delay = rand()/(RAND_MAX/5)*100; 16613
Thread::sleep(delay); 16614
// Make toast 16615
Toast t(count++); 16616
cout << t << endl; 16617
// Insert into queue 16618
toastQueue->put(t); 16619
} 16620
} catch(Interrupted_Exception&) { /* Exit */ } 16621
cout << "Toaster off" << endl; 16622
} 16623
}; 16624
16625
// Apply butter to toast: 16626
class Butterer : public Runnable { 16627
ToastQueue dryQueue, butteredQueue; 16628
public: 16629
Butterer(ToastQueue& dry, ToastQueue& buttered) 16630
: dryQueue(dry), butteredQueue(buttered) {} 16631
void run() { 16632
try { 16633
while(!Thread::interrupted()) { 16634
// Blocks until next piece of toast is available: 16635
Toast t = dryQueue->get(); 16636
t.butter(); 16637
638
cout << t << endl; 16638
butteredQueue->put(t); 16639
} 16640
} catch(Interrupted_Exception&) { /* Exit */ } 16641
cout << "Butterer off" << endl; 16642
} 16643
}; 16644
16645
// Apply jam to buttered toast: 16646
class Jammer : public Runnable { 16647
ToastQueue butteredQueue, finishedQueue; 16648
public: 16649
Jammer(ToastQueue& buttered, ToastQueue& finished) 16650
: butteredQueue(buttered), finishedQueue(finished) {} 16651
void run() { 16652
try { 16653
while(!Thread::interrupted()) { 16654
// Blocks until next piece of toast is available: 16655
Toast t = butteredQueue->get(); 16656
t.jam(); 16657
cout << t << endl; 16658
finishedQueue->put(t); 16659
} 16660
} catch(Interrupted_Exception&) { /* Exit */ } 16661
cout << "Jammer off" << endl; 16662
639
} 16663
}; 16664
16665
// Consume the toast: 16666
class Eater : public Runnable { 16667
ToastQueue finishedQueue; 16668
int counter; 16669
public: 16670
Eater(ToastQueue& finished) 16671
: finishedQueue(finished), counter(0) {} 16672
void run() { 16673
try { 16674
while(!Thread::interrupted()) { 16675
// Blocks until next piece of toast is available: 16676
Toast t = finishedQueue->get(); 16677
// Verify that the toast is coming in order, 16678
// and that all pieces are getting jammed: 16679
if(t.getId() != counter++ || 16680
t.getStatus() != "jammed") { 16681
cout << ">>>> Error: " << t << endl; 16682
exit(1); 16683
} else 16684
cout << "Chomp! " << t << endl; 16685
} 16686
} catch(Interrupted_Exception&) { /* Exit */ } 16687
640
cout << "Eater off" << endl; 16688
} 16689
}; 16690
16691
int main() { 16692
srand(time(0)); // Seed the random number generator 16693
try { 16694
ToastQueue dryQueue(new TQueue<Toast>), 16695
butteredQueue(new TQueue<Toast>), 16696
finishedQueue(new TQueue<Toast>); 16697
cout << "Press <Return> to quit" << endl; 16698
ThreadedExecutor executor; 16699
executor.execute(new Toaster(dryQueue)); 16700
executor.execute(new Butterer(dryQueue,butteredQueue)); 16701
executor.execute( 16702
new Jammer(butteredQueue, finishedQueue)); 16703
executor.execute(new Eater(finishedQueue)); 16704
cin.get(); 16705
executor.interrupt(); 16706
} catch(Synchronization_Exception& e) { 16707
cerr << e.what() << endl; 16708
} 16709
} ///:~ 16710
Listado 10.33. C11/ToastOMaticMarkII.cpp 16711
641
16712
10.6.4. Broadcast 16713
//: C11:CarBuilder.cpp {RunByHand} 16714
// How broadcast() works. 16715
//{L} ZThread 16716
#include <iostream> 16717
#include <string> 16718
#include "zthread/Thread.h" 16719
#include "zthread/Mutex.h" 16720
#include "zthread/Guard.h" 16721
#include "zthread/Condition.h" 16722
#include "zthread/ThreadedExecutor.h" 16723
#include "TQueue.h" 16724
using namespace ZThread; 16725
using namespace std; 16726
16727
class Car { 16728
int id; 16729
bool engine, driveTrain, wheels; 16730
public: 16731
Car(int idn) : id(idn), engine(false), 16732
driveTrain(false), wheels(false) {} 16733
// Empty Car object: 16734
Car() : id(-1), engine(false), 16735
driveTrain(false), wheels(false) {} 16736
642
// Unsynchronized -- assumes atomic bool operations: 16737
int getId() { return id; } 16738
void addEngine() { engine = true; } 16739
bool engineInstalled() { return engine; } 16740
void addDriveTrain() { driveTrain = true; } 16741
bool driveTrainInstalled() { return driveTrain; } 16742
void addWheels() { wheels = true; } 16743
bool wheelsInstalled() { return wheels; } 16744
friend ostream& operator<<(ostream& os, const Car& c) { 16745
return os << "Car " << c.id << " [" 16746
<< " engine: " << c.engine 16747
<< " driveTrain: " << c.driveTrain 16748
<< " wheels: " << c.wheels << " ]"; 16749
} 16750
}; 16751
16752
typedef CountedPtr< TQueue<Car> > CarQueue; 16753
16754
class ChassisBuilder : public Runnable { 16755
CarQueue carQueue; 16756
int counter; 16757
public: 16758
ChassisBuilder(CarQueue& cq) : carQueue(cq),counter(0) {} 16759
void run() { 16760
try { 16761
643
while(!Thread::interrupted()) { 16762
Thread::sleep(1000); 16763
// Make chassis: 16764
Car c(counter++); 16765
cout << c << endl; 16766
// Insert into queue 16767
carQueue->put(c); 16768
} 16769
} catch(Interrupted_Exception&) { /* Exit */ } 16770
cout << "ChassisBuilder off" << endl; 16771
} 16772
}; 16773
16774
class Cradle { 16775
Car c; // Holds current car being worked on 16776
bool occupied; 16777
Mutex workLock, readyLock; 16778
Condition workCondition, readyCondition; 16779
bool engineBotHired, wheelBotHired, driveTrainBotHired; 16780
public: 16781
Cradle() 16782
: workCondition(workLock), readyCondition(readyLock) { 16783
occupied = false; 16784
engineBotHired = true; 16785
wheelBotHired = true; 16786
644
driveTrainBotHired = true; 16787
} 16788
void insertCar(Car chassis) { 16789
c = chassis; 16790
occupied = true; 16791
} 16792
Car getCar() { // Can only extract car once 16793
if(!occupied) { 16794
cerr << "No Car in Cradle for getCar()" << endl; 16795
return Car(); // "Null" Car object 16796
} 16797
occupied = false; 16798
return c; 16799
} 16800
// Access car while in cradle: 16801
Car* operator->() { return &c; } 16802
// Allow robots to offer services to this cradle: 16803
void offerEngineBotServices() { 16804
Guard<Mutex> g(workLock); 16805
while(engineBotHired) 16806
workCondition.wait(); 16807
engineBotHired = true; // Accept the job 16808
} 16809
void offerWheelBotServices() { 16810
Guard<Mutex> g(workLock); 16811
645
while(wheelBotHired) 16812
workCondition.wait(); 16813
wheelBotHired = true; // Accept the job 16814
} 16815
void offerDriveTrainBotServices() { 16816
Guard<Mutex> g(workLock); 16817
while(driveTrainBotHired) 16818
workCondition.wait(); 16819
driveTrainBotHired = true; // Accept the job 16820
} 16821
// Tell waiting robots that work is ready: 16822
void startWork() { 16823
Guard<Mutex> g(workLock); 16824
engineBotHired = false; 16825
wheelBotHired = false; 16826
driveTrainBotHired = false; 16827
workCondition.broadcast(); 16828
} 16829
// Each robot reports when their job is done: 16830
void taskFinished() { 16831
Guard<Mutex> g(readyLock); 16832
readyCondition.signal(); 16833
} 16834
// Director waits until all jobs are done: 16835
void waitUntilWorkFinished() { 16836
646
Guard<Mutex> g(readyLock); 16837
while(!(c.engineInstalled() && c.driveTrainInstalled() 16838
&& c.wheelsInstalled())) 16839
readyCondition.wait(); 16840
} 16841
}; 16842
16843
typedef CountedPtr<Cradle> CradlePtr; 16844
16845
class Director : public Runnable { 16846
CarQueue chassisQueue, finishingQueue; 16847
CradlePtr cradle; 16848
public: 16849
Director(CarQueue& cq, CarQueue& fq, CradlePtr cr) 16850
: chassisQueue(cq), finishingQueue(fq), cradle(cr) {} 16851
void run() { 16852
try { 16853
while(!Thread::interrupted()) { 16854
// Blocks until chassis is available: 16855
cradle->insertCar(chassisQueue->get()); 16856
// Notify robots car is ready for work 16857
cradle->startWork(); 16858
// Wait until work completes 16859
cradle->waitUntilWorkFinished(); 16860
// Put car into queue for further work 16861
647
finishingQueue->put(cradle->getCar()); 16862
} 16863
} catch(Interrupted_Exception&) { /* Exit */ } 16864
cout << "Director off" << endl; 16865
} 16866
}; 16867
16868
class EngineRobot : public Runnable { 16869
CradlePtr cradle; 16870
public: 16871
EngineRobot(CradlePtr cr) : cradle(cr) {} 16872
void run() { 16873
try { 16874
while(!Thread::interrupted()) { 16875
// Blocks until job is offered/accepted: 16876
cradle->offerEngineBotServices(); 16877
cout << "Installing engine" << endl; 16878
(*cradle)->addEngine(); 16879
cradle->taskFinished(); 16880
} 16881
} catch(Interrupted_Exception&) { /* Exit */ } 16882
cout << "EngineRobot off" << endl; 16883
} 16884
}; 16885
16886
648
class DriveTrainRobot : public Runnable { 16887
CradlePtr cradle; 16888
public: 16889
DriveTrainRobot(CradlePtr cr) : cradle(cr) {} 16890
void run() { 16891
try { 16892
while(!Thread::interrupted()) { 16893
// Blocks until job is offered/accepted: 16894
cradle->offerDriveTrainBotServices(); 16895
cout << "Installing DriveTrain" << endl; 16896
(*cradle)->addDriveTrain(); 16897
cradle->taskFinished(); 16898
} 16899
} catch(Interrupted_Exception&) { /* Exit */ } 16900
cout << "DriveTrainRobot off" << endl; 16901
} 16902
}; 16903
16904
class WheelRobot : public Runnable { 16905
CradlePtr cradle; 16906
public: 16907
WheelRobot(CradlePtr cr) : cradle(cr) {} 16908
void run() { 16909
try { 16910
while(!Thread::interrupted()) { 16911
649
// Blocks until job is offered/accepted: 16912
cradle->offerWheelBotServices(); 16913
cout << "Installing Wheels" << endl; 16914
(*cradle)->addWheels(); 16915
cradle->taskFinished(); 16916
} 16917
} catch(Interrupted_Exception&) { /* Exit */ } 16918
cout << "WheelRobot off" << endl; 16919
} 16920
}; 16921
16922
class Reporter : public Runnable { 16923
CarQueue carQueue; 16924
public: 16925
Reporter(CarQueue& cq) : carQueue(cq) {} 16926
void run() { 16927
try { 16928
while(!Thread::interrupted()) { 16929
cout << carQueue->get() << endl; 16930
} 16931
} catch(Interrupted_Exception&) { /* Exit */ } 16932
cout << "Reporter off" << endl; 16933
} 16934
}; 16935
16936
650
int main() { 16937
cout << "Press <Enter> to quit" << endl; 16938
try { 16939
CarQueue chassisQueue(new TQueue<Car>), 16940
finishingQueue(new TQueue<Car>); 16941
CradlePtr cradle(new Cradle); 16942
ThreadedExecutor assemblyLine; 16943
assemblyLine.execute(new EngineRobot(cradle)); 16944
assemblyLine.execute(new DriveTrainRobot(cradle)); 16945
assemblyLine.execute(new WheelRobot(cradle)); 16946
assemblyLine.execute( 16947
new Director(chassisQueue, finishingQueue, cradle)); 16948
assemblyLine.execute(new Reporter(finishingQueue)); 16949
// Start everything running by producing chassis: 16950
assemblyLine.execute(new ChassisBuilder(chassisQueue)); 16951
cin.get(); 16952
assemblyLine.interrupt(); 16953
} catch(Synchronization_Exception& e) { 16954
cerr << e.what() << endl; 16955
} 16956
} ///:~ 16957
Listado 10.34. C11/CarBuilder.cpp 16958
16959
10.7. Bloqueo letal 16960
651
//: C11:DiningPhilosophers.h 16961
// Classes for Dining Philosophers. 16962
#ifndef DININGPHILOSOPHERS_H 16963
#define DININGPHILOSOPHERS_H 16964
#include <string> 16965
#include <iostream> 16966
#include <cstdlib> 16967
#include "zthread/Condition.h" 16968
#include "zthread/Guard.h" 16969
#include "zthread/Mutex.h" 16970
#include "zthread/Thread.h" 16971
#include "Display.h" 16972
16973
class Chopstick { 16974
ZThread::Mutex lock; 16975
ZThread::Condition notTaken; 16976
bool taken; 16977
public: 16978
Chopstick() : notTaken(lock), taken(false) {} 16979
void take() { 16980
ZThread::Guard<ZThread::Mutex> g(lock); 16981
while(taken) 16982
notTaken.wait(); 16983
taken = true; 16984
} 16985
652
void drop() { 16986
ZThread::Guard<ZThread::Mutex> g(lock); 16987
taken = false; 16988
notTaken.signal(); 16989
} 16990
}; 16991
16992
class Philosopher : public ZThread::Runnable { 16993
Chopstick& left; 16994
Chopstick& right; 16995
int id; 16996
int ponderFactor; 16997
ZThread::CountedPtr<Display> display; 16998
int randSleepTime() { 16999
if(ponderFactor == 0) return 0; 17000
return rand()/(RAND_MAX/ponderFactor) * 250; 17001
} 17002
void output(std::string s) { 17003
std::ostringstream os; 17004
os << *this << " " << s << std::endl; 17005
display->output(os); 17006
} 17007
public: 17008
Philosopher(Chopstick& l, Chopstick& r, 17009
ZThread::CountedPtr<Display>& disp, int ident,int ponder) 17010
653
: left(l), right(r), id(ident), ponderFactor(ponder), 17011
display(disp) {} 17012
virtual void run() { 17013
try { 17014
while(!ZThread::Thread::interrupted()) { 17015
output("thinking"); 17016
ZThread::Thread::sleep(randSleepTime()); 17017
// Hungry 17018
output("grabbing right"); 17019
right.take(); 17020
output("grabbing left"); 17021
left.take(); 17022
output("eating"); 17023
ZThread::Thread::sleep(randSleepTime()); 17024
right.drop(); 17025
left.drop(); 17026
} 17027
} catch(ZThread::Synchronization_Exception& e) { 17028
output(e.what()); 17029
} 17030
} 17031
friend std::ostream& 17032
operator<<(std::ostream& os, const Philosopher& p) { 17033
return os << "Philosopher " << p.id; 17034
} 17035
654
}; 17036
#endif // DININGPHILOSOPHERS_H ///:~ 17037
Listado 10.35. C11/DiningPhilosophers.h 17038
17039
//: C11:DeadlockingDiningPhilosophers.cpp {RunByHand} 17040
// Dining Philosophers with Deadlock. 17041
//{L} ZThread 17042
#include <ctime> 17043
#include "DiningPhilosophers.h" 17044
#include "zthread/ThreadedExecutor.h" 17045
using namespace ZThread; 17046
using namespace std; 17047
17048
int main(int argc, char* argv[]) { 17049
srand(time(0)); // Seed the random number generator 17050
int ponder = argc > 1 ? atoi(argv[1]) : 5; 17051
cout << "Press <ENTER> to quit" << endl; 17052
enum { SZ = 5 }; 17053
try { 17054
CountedPtr<Display> d(new Display); 17055
ThreadedExecutor executor; 17056
Chopstick c[SZ]; 17057
for(int i = 0; i < SZ; i++) { 17058
executor.execute( 17059
655
new Philosopher(c[i], c[(i+1) % SZ], d, i,ponder)); 17060
} 17061
cin.get(); 17062
executor.interrupt(); 17063
executor.wait(); 17064
} catch(Synchronization_Exception& e) { 17065
cerr << e.what() << endl; 17066
} 17067
} ///:~ 17068
Listado 10.36. C11/DeadlockingDiningPhilosophers.cpp 17069
17070
//: C11:FixedDiningPhilosophers.cpp {RunByHand} 17071
// Dining Philosophers without Deadlock. 17072
//{L} ZThread 17073
#include <ctime> 17074
#include "DiningPhilosophers.h" 17075
#include "zthread/ThreadedExecutor.h" 17076
using namespace ZThread; 17077
using namespace std; 17078
17079
int main(int argc, char* argv[]) { 17080
srand(time(0)); // Seed the random number generator 17081
int ponder = argc > 1 ? atoi(argv[1]) : 5; 17082
cout << "Press <ENTER> to quit" << endl; 17083
656
enum { SZ = 5 }; 17084
try { 17085
CountedPtr<Display> d(new Display); 17086
ThreadedExecutor executor; 17087
Chopstick c[SZ]; 17088
for(int i = 0; i < SZ; i++) { 17089
if(i < (SZ-1)) 17090
executor.execute( 17091
new Philosopher(c[i], c[i + 1], d, i, ponder)); 17092
else 17093
executor.execute( 17094
new Philosopher(c[0], c[i], d, i, ponder)); 17095
} 17096
cin.get(); 17097
executor.interrupt(); 17098
executor.wait(); 17099
} catch(Synchronization_Exception& e) { 17100
cerr << e.what() << endl; 17101
} 17102
} ///:~ 17103
Listado 10.37. C11/FixedDiningPhilosophers.cpp 17104
17105
10.8. Resumen 17106
657
10.9. Ejercicios 17107
Top Related