Вы находитесь на странице: 1из 657

1

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31

Pensar en C++ (Volumen 2)


Traduccin (INACABADA) del libro Thinking in C++, Volumen 2

Tabla de contenidos

1: Introduccin
Tabla de contenidos 1.1. Objetivos 1.2. Captulos 1.3. Ejercicios 1.4. Cdigo fuente 1.5. Compiladores 1.6. Estndares del lenguaje 1.7. Seminarios, CD-ROMs y consultora 1.8. Errores 1.9. Sobre la portada 1.10. Agradecimientos En el volumen 1 de este libro, aprendi los fundamentos de C y C++. En este volumen, veremos caractersticas ms avanzadas, con miras hacia tcnicas e ideas para realizar programas robustos en C++. Asumimos que est familiarizado con el material presentado en el Volumen 1.

1.1. Objetivos
Nuestros objetivos en este libros son: 1. Presentar el material como un sencillo paso cada vez, de este modo el lector puede asimilar cada concepto antes de seguir adelante. 2. Ensear tcnicas de "programacin prctica" que puede usar en la base del da a da. 3. Darle lo que pensamos que es importante para que entienda el lenguaje, ms bien que todo lo que sabemos. Creemos que hay una "jerarqua de

2
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 importancia de la informacin", y hay algunos hechos que el 95% de los programadores nunca necesitarn conocer, sino que slo confundira a la gente y aade complejidad a su percepcin del lenguaje. Tomando un ejemplo de C, si memoriza la tabla de prioridad de operadores (nosotros nunca lo hicimos) puede escribir cdigo ingenioso. Pero si debe pensarlo, confundir al lector/mantenedor de ese cdigo. De modo que olvide las precedencias y use parntesis cuando las cosas no estn claras. 4. Mantngase suficientemente centrado en cada seccin de modo que el tiempo de lectura -y el tiempo entre ejercicios- sea pequeo. Esto no slo hace mantener las mentes de los lectores ms activas e involucradas durante un seminario prctico sino que da al lector un mayor sentido de xito. Hemos procurado no usar ninguna versin de un vendedor particular de C++. Hemos probado el cdigo sobre todas las implementaciones que pudimos (descriptas posteriormente en esta introduccin), y cuando una implementacin no funciona en absoluto porque no cumple el Estndar C++, lo hemos sealado en el ejemplo(ver las marcas en el cdigo fuente) para excluirlo del proceso de construccin. 6. Automatizar la compilacin y las pruebas del cdigo en el libro. Hemos descubierto que el cdigo que no est compilado y probado probablemente no funcione correctamente, de este modo en este volumen hemos provisto a los ejemplos con cdigo de pruebas. Adems, el cdigo que puede descargar desde http://www.MindView.net ha sido extrado directamente del texto del libro usando programas que automticamente crean makefiles para compilar y ejecutar las pruebas. De esta forma sabemos que el cdigo en el libro es correcto.

57 58 59 60 61 62 63

1.2. Captulos
Aqu est una breve descripcin de los captulos que contiene este libro: Parte 1: Construccin de sistemas estables 1. Manejar excepciones. Manejar errores ha sido siempre un problema en programacin. Incluso si obedientemente devuelve informacin del error o pone una bandera, la funcin que llama puede simplemente ignorarlo. Manejar excepciones es una cualidad primordial en C++ que soluciona este

3
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 problema permitindole "lanzar" un objeto fuera de su funcin cuando ocurre un error crtico. Tire diferentes tipos de objetos para diferentes errores, y la funcin que llama "coge" estos objetos en rutinas separadas de gestin de errores. Si lanza una excepcin, no puede ser ignorada, de modo que puede garantizar que algo ocurrir en respuesta a su error. La decisin de usar excepciones afecta al diseo del cdigo positivamente, de modo fundamental. 2. Programacin defensiva. Muchos problemas de software pueden ser prevenidos. Programar de forma defensiva es realizar cuidadosamente cdigo de tal modo que los bugs son encontrados y arreglados pronto antes que puedan daar en el campo. Usar aserciones es la nica y ms importante forma para validar su cdigo durante el desarrollo, dejando al mismo tiempo seguimiento ejecutable de la documentacin en su cdigo que muestra sus pensamientos mientras escribe el cdigo en primer lugar. Pruebe rigurosamente su cdigo antes de darlo a otros. Un marco de trabajo de pruebas unitario automatizado es una herramienta indispensable para el xito, en el desarrollo diario de software. Parte 2: La biblioteca estndar de C++ 3. Cadenas en profundidad. La actividad ms comn de programacin es el procesamiento de texto. La clase string de C++ libera al programador de los temas de gestin de memoria, mientras al mismo tiempo proporciona una fuente de recursos para el procesamiento de texto. C++ tambin facilita el uso de una gran variedad de caracteres y locales para las aplicaciones de internacionalizacin. 4. Iostreams. Una de las bibliotecas original de C++-la que proporciona la facilidad bsica de I/O-es llamada iostreams. Iostreams est destinado a reemplazar stdio.h de C con una biblioteca I/O que es ms fcil de usar, ms flexible, y extensible- que puede adaptarla para trabajar con sus nuevas clases. Este captulo le ensea cmo hacer el mejor uso de la biblioteca actual I/O, fichero I/O, y formateo en memoria. 5. Plantillas en profundidad. La distintiva cualidad del "C++ moderno" es el extenso poder de las plantillas. Las plantillas hacen algo ms que slo crear contenedores genricos. Sostienen el desarrollo de bibliotecas robustas, genricas y de alto rendimiento.Hay mucho por saber sobre plantillas-

4
98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 constituyen, como fue, un sub-lenguaje dentro del lenguaje C++, y da al programador un grado impresionante de control sobre el proceso de compilacin. No es una exageracin decir que las plantillas han revolucionado la programacin de C++. 6. Algoritmos genricos. Los algoritmos son el corazn de la informtica, y C++, por medio de la facilidad de las plantillas, facilita un impresionante entorno de poder, eficiencia, y facilidad de uso de algoritmos genricos. Los algoritmos estndar son tambin personalizables a travs de objetos de funcin. Este captulo examina cada algoritmo de la biblioteca. (Captulos 6 y 7 abarcan esa parte de la biblioteca Estndar de C++ comnmente conocida como Biblioteca de Plantilla Estndar, o STL.) 7. Contenedores genricos e Iteradores. C++ proporciona todas las estructuras comunes de datos de modo de tipado fuerte. Nunca necesita preocuparse sobre qu tiene tal contenedor. La homogenidad de sus objetos est garantizada. Separar la #FIXME traversing de un contenedor del propio contenedor, otra realizacin de plantillas, se hace posible por medio de los iteradores. Este ingenioso arreglo permite una aplicacin flexible de algoritmos a contenedores usando el ms sencillo de los diseos. Parte 3: Temas especiales 8. Identificacin de tipo en tiempo de ejecucin. La identificacin de tipo en tiempo de ejecucin (RTTI) encuentra el tipo exacto de un objeto cuando slo tiene un puntero o referencia al tipo base. Normalmente, tendr que ignorar a propsito el tipo exacto de un objeto y permitir al mecanismo de funcin virtual implementar el comportamiento correcto para ese tipo. Pero ocasionalmente (como las herramientas de escritura de software tales como los depuradores) es til para conocer el tipo exacto de un objeto-con su informacin, puede realizar con frecuencia una operacin en casos especiales de forma ms eficiente. Este captulo explica para qu es RTTT y como usarlo. 9. Herencia mltiple. Parece sencillo al principio: Una nueva clase hereda de ms de una clase existente. Sin embargo, puede terminar con copias ambiguas y mltiples de objetos de la clase base. Ese problema est resuelto con clases bases virtuales, pero la mayor cuestin continua: Cundo usarla? La herencia mltiple es slo imprescindible cuando

5
132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 necesite manipular un objeto por medio de ms de un clase base comn. Este captulo explica la sintaxis para la herencia mltiple y muestra enfoques alternativos- en particular, como las plantillas solucionan un problema tpico. Usar herencia mltiple para reparar un interfaz de clase "daada" demuestra un uso valioso de esta cualidad. 10. Patrones de diseo. El ms revolucionario avance en programacin desde los objetos es la introduccin de los patrones de diseo. Un patrn de diseo es una codificacin independiente del lenguaje de una solucin a un problema de programacin comn, expresado de tal modo que puede aplicarse a muchos contextos. Los patrones tales como Singleton, Factory Method, y Visitor ahora tienen lugar en discusiones diarias alrededor del teclado. Este captulo muestra como implementar y usar algunos de los patrones de diseo ms usados en C++. 11. Programacin concurrente. La gente ha llegado a esperar interfaces de usuario sensibles que (parece que) procesan mltiples tareas simultneamente. Los sistemas operativos modernos permiten a los procesos tener mltiples hilos que comparten el espacio de direccin del proceso. La programacin multihilo requiere una perspectiva diferente, sin embargo, y viene con su propio conjunto de dificultades. Este captulo utiliza un biblioteca disponible gratuitamente (la biblioteca ZThread por Eric Crahen de IBM) para mostrar como gestionar eficazmente aplicaciones multihilo en C++.

154 155 156 157 158 159 160 161 162 163

1.3. Ejercicios
Hemos descubierto que los ejercicios sencillos son excepcionalmente tiles durante un seminario para completar la comprensin de un estudiante. Encontrar una coleccin al final de cada captulo. Estos son bastante sencillos, de modo que puedan ser acabados en una suma de tiempo razonable en una situacin de clase mientras el profesor observa, asegurndose que todos los estudiantes estn absorbiendo el material. Algunos ejercicios son un poco ms exigentes para mantener entretenidos a los estudiantes avanzados. Estn todos diseados para ser resueltos en un tiempo corto y slo estn all para probar y refinar su

6
164 165 166 167 168 169 conocimiento ms bien que presentar retos mayores (presumiblemente, podr encontrarlos o ms probablemente ellos le encontrarn a usted).

1.3.1. Soluciones de los ejercicios


Las soluciones a los ejercicios pueden encontrarse en el documento electrnico La Gua de Soluciones Comentada de C++, Volumen 2, disponible por una cuota simblica en http://www.MindView.net.

170 171 172 173 174 175 176 177

1.4. Cdigo fuente


El cdigo fuente para este libro est autorizado como software gratuito, distribuido por medio del sitio web http://www.MindView.net. Los derechos de autor le impiden volver a publicar el cdigo impreso sin permiso. En el directorio inicial donde desempaqueta el cdigo encontrar el siguiente aviso de derechos de autor: Puede usar el cdigo en sus proyectos y en clase siempre que el aviso de los derechos de autor se conserve.

178 179 180 181 182 183 184 185 186 187 188 189 190 191 192

1.5. Compiladores
Su compilador podra no soportar todas las cualidades discutidas en este libro, especialmente si no tiene la versin ms nueva de su compilador. Implementar un lenguaje como C++ es un tarea Herclea, y puede suponer que las cualidades aparecen por partes en lugar de todas juntas. Pero si intenta uno de los ejemplos del libro y obtiene muchos errores del compilador, no es necesariamente un error en el cdigo o en el compiladorpuede que sencillamente no est implementado todava en su compilador concreto. Empleamos un nmero de compiladores para probar el cdigo de este libro, en un intento para asegurar que nuestro cdigo cumple el Estndar C++ y funcionar con todos los compiladores posibles. Desafortunadamente, no todos los compiladores cumplen el Estndar C++, y de este modo tenemos un modo de excluir ciertos ficheros de la construccin con esos compiladores. Estas exclusiones se reflejadas automticamente en los

7
193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 makefiles creados para el paquete de cdigo para este libro que puede descargar desde www.MindView.net. Puede ver las etiquetas de exclusin incrustadas en los comentarios al inicio de cada listado, de este modo sabr si exigir a un compilador concreto que funcione con ese cdigo (en pocos casos, el compilador realmente compilar el cdigo pero el comportamiento de ejecucin es errneo, y excluimos esos tambin). Aqu estn las etiquetas y los compiladores que se excluyen de la construccin. {-dmc} El Compilador de Mars Digital de Walter Bright para Windows, descargable gratuitamente en www.DigitalMars.com. Este compilador es muy tolerante y as no ver casi ninguna de estas etiquetas en todo el libro. {-g++} La versin libre Gnu C++ 3.3.1, que viene preinstalada en la mayora de los paquetes Linux y Macintosh OSX. Tambin es parte de Cygwin para Windows (ver abajo). Est disponible para la mayora de las plataformas en gcc.gnu.org. {-msc} Microsoft Version 7 con Visual C++ .NET (viene slo con Visual Studio .NET; no descargable gratuitamente). {-bor} Borland C++ Version 6 (no la versin gratuita; ste est ms actualizado). {-edg} Edison Design Group (EDG) C++. Este es el compilador de referencia para la conformidad con los estndares. Esta etiqueta existe a causa de los temas de biblioteca, y porque estbamos usando un copia gratis de la interfaz EDG con una implementacin de la biblioteca gratuita de Dinkumware, Ltd. No aparecieron errores de compilacin a causa slo del compilador. {-mwcc} Metrowerks Code Warrior para Macintosh OS X. Fjese que OS X viene con Gnu C++ preinstalado, tambin. Si descarga y desempaqueta el paquete de cdigo de este libro de www.MindView.net, encontrar los makefiles para construir el cdigo para los compiladores de ms arriba. Usabamos GNU-make disponible gratuitamente, que viene con Linux, Cygwin (una consola gratis de Unix que corre encima de Windows; ver www.Cygwin.com), o puede instalar en su plataforma, ver www.gnu.org/software/make. (Otros makes pueden o no funcionar con estos

8
226 227 228 229 230 231 232 233 234 235 236 237 ficheros, pero no estn soportados.) Una vez que instale make, si teclea make en la lnea de comando obtendr instrucciones de cmo construir el cdigo del libro para los compiladores de ms arriba. Fjese que la colocacin de estas etiquetas en los ficheros en este libro indica el estado de la versin concreta del compilador en el momento que lo probamos. Es posible y probable que el vendedor del compilador haya mejorado el compilador desde la publicacin de este libro. Es posible tambin que mientras que realizamos el libro con tantos compiladores, hayamos desconfigurado un compilador en concreto que en otro caso habra compilado el cdigo correctamente. Por consiguiente, debera probar el cdigo usted mismo con su compilador, y comprobar tambin el cdigo descargado de www.MindView.net para ver que es actual.

238 239 240 241 242 243 244 245 246 247 248 249 250

1.6. Estndares del lenguaje


A lo largo de este libro, cuando se hace referencia a la conformidad con el ANSI/ISO C estndar, estaremos haciendo referencia al estndar de 1989, y de forma general diremos solamente 'C.'. Slo si es necesario distinguir entre el Estndar de C de 1989 y anteriores, versiones pre-Estndares de C haremos la distincin. No hacemos referencia a c99 en este libro. El Comit de ANSI/ISO C++ hace mucho acab el trabajo sobre el primer Estndar C++, comunmente conocido como C++98. Usaremos el trmino Standard C++ para referirnos a este lenguaje normalizado. Si nos referimos sencillamente a C++, asuma que queremos decir "Standard C++". El Comit de Estndares de C++ continua dirigiendo cuestiones importantes para la comunidad de C++ que se convertir en C++0x, un futuro Estndar de C++ que no estar probablemente disponible durante muchos aos.

251 252 253 254 255 256

1.7. Seminarios, CD-ROMs y consultora


La compaa de Bruce Eckel, MindView, Inc., proporciona seminarios pblicos prcticos de formacin basados en el material de este libro, y tambin para temas avanzados. El material seleccionado de cada captulo representa una leccin, que es sucedida por un periodo de ejercicios guiado de tal modo que cada estudiante recibe atencin personalizada. Tambin

9
257 258 259 260 facilitamos formacin en el lugar, consultora, tutora y comprobacin de diseo y cdigo. La informacin y los formularios de inscripcin para los seminarios prximos y otra informacin de contacto se puede encontrar en http://www.MindView.net.

261 262 263 264 265 266

1.8. Errores
No importa cuantos trucos usen los escritores para detectar errores, algunos siempre pasan desapercibidos y stos a menudo destacan para un nuevo lector. Si descubre algo que cree ser un error, por favor use el sistema de respuesta incorporado en la versin electrnica de este libro, que encontrar en http://www.MindView.net. Su ayuda se valora.

267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286

Parte I. Construccin de Sistemas estables


Los ingenieros de software gastan tanto tiempo en validar cdigo como el que tardan en crearlo. La calidad es, o debera ser, el objetivo de todo programador, y se puede recorrer un largo camino hacia ese objetivo eliminando problemas antes de que aparezcan. Adems, los sistemas software deberan ser lo suficientemente robustos como para comportarse razonablemente en presencia de problemas imprevistos.

Las excepciones se introdujeron en C++ para facilitar una gestin de errores sofisticada sin trocear el cdigo con una innumerable cantidad de lgica de error. El Captulo 1 explica cmo el uso apropiado de las excepciones puede hacer software FIXME:well-behaved, y tambin introduce los principios de diseo que subyacen al cdigo seguro. En el Captulo 2 cubrimos las pruebas unitarias y las tcnicas de depuracin que prevn maximizar la calidad del cdigo antes de ser entregado. El uso de aserciones para expresar y reforzar las invariantes de un programa es una seal inequvoca de un ingeniero de software experimentado. Tambin introducimos un entorno simple para dar soporte a las pruebas unitarias. Tabla de contenidos 2. Tratamiento de excepciones

10
287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 2.1. Tratamiento tradicional de errores 2.2. Lanzar una excepcin 2.3. Capturar una excepcin 2.4. 2.5. Limpieza 2.6. Excepciones estndar 2.7. Especificaciones de excepciones 2.8. Seguridad de la excepcin 2.9. Programar con excepciones 2.10. Sobrecarga 2.11. Resumen 2.12. Ejercicios 3. Programacin defensiva 3.1. Aserciones 3.2. Un framework de pruebas unitarias sencillo 3.3. Tcnicas de depuracin 3.4. Resumen 3.5. Ejercicios

2: Tratamiento de excepciones
Tabla de contenidos 2.1. Tratamiento tradicional de errores 2.2. Lanzar una excepcin 2.3. Capturar una excepcin 2.4. 2.5. Limpieza 2.6. Excepciones estndar 2.7. Especificaciones de excepciones 2.8. Seguridad de la excepcin 2.9. Programar con excepciones 2.10. Sobrecarga 2.11. Resumen 2.12. Ejercicios Mejorar la recuperacin de errores es una de las maneras ms potentes de incrementar la robustez de su cdigo. Una de las principales caractersticas de C++ es el tratamiento o manejo de excepciones, el cual es una manera mejor de pensar acerca de los errores y su tratamiento. Con el tratamiento de excepciones: 1. El cdigo de manejo de errores no resulta tan tedioso de escribir y no se entremezcla con su cdigo normal. Usted escribe el cdigo que desea que se ejecute, y ms tarde, en una seccin aparte, el cdigo que se encarga de los problemas. Si realiza varias llamadas a la misma funcin, el manejo de errores de esa funcin se har una sola vez, en un solo lugar.

11
329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 2. Los errores no pueden ser ignorados. Si una funcin necesita enviar un mensaje de error al invocador de esa funcin, sta lanza un objeto que representa a ese error fuera de la funcin. Si el invocador no captura el error y lo trata, ste pasa al siguiente mbito abarcador, y as hasta que el error es capturado o el programa termina al no existir un manejador adecuado para ese tipo de excepcin.

2.1. Tratamiento tradicional de errores


En la mayora de ejemplos de estos volmenes, usamos la funcin assert() para lo que fue concebida: para la depuracin durante el desarrollo insertando cdigo que puede deshabilitarse con #define NDEBUG en un producto comercial. Para la comprobacin de errores en tiempo de ejecucin se utilizan las funciones de require.h (assure( ) y require( )) desarrolladas en el captulo 9 del Volumen 1 y repetidas aqu en el Apndice B. Estas funciones son un modo conveniente de decir, Hay un problema aqu que probablemente quiera manejar con un cdigo algo ms sofisticado, pero no es necesario que se distraiga con eso en este ejemplo. Las funciones de require.h pueden parecer suficientes para programas pequeos, pero para productos complicados desear escribir un cdigo de manejo de errores ms sofisticado. El tratamiento de errores es bastante sencillo cuando uno sabe exactamente qu hacer, puesto que se tiene toda la informacin necesaria en ese contexto. Simplemente se trata el error en ese punto. El problema ocurre cuando no se tiene suficiente informacin en ese contexto, y se necesita pasar la informacin sobre el error a un contexto diferente donde esa informacin s que existe. En C, esta situacin puede tratarse usando tres enfoques: 2. Usar el poco conocido sistema de manejo de seales de la biblioteca estndar de C, implementado en las funciones signal( ) (para determinar lo que ocurre cuando se presenta un evento) y raise( ) (para generar un evento). De nuevo, esta alternativa supone un alto acoplamiento debido a que requiere que el usuario de cualquier biblioteca que genere seales entienda e instale el mecanismo de manejo de seales adecuado. En

12
361 362 363 364 365 366 367 368 369 370 371 proyectos grandes los nmeros de las seales de las diferentes bibliotecas puede llegar a entrar en conflicto. Cuando se consideran los esquemas de tratamiento de errores para C++, hay un problema adicional que es crtico: Las tcnicas de C de seales y setjmp( )/longjmp( ) no llaman a los destructores, por lo que los objetos no se limpian adecuadamente. (De hecho, si longjmp( ) salta ms all del final de un mbito donde los destructores deben ser llamados, el comportamiento del programa es indefinido.) Esto hace casi imposible recuperarse efectivamente de una condicin excepcional, puesto que siempre se estn dejando objetos detrs sin limpiar y a los que ya no se tiene acceso. El siguiente ejemplo lo demuestra con setjmp/longjmp:

372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387

//: C01:Nonlocal.cpp // setjmp() & longjmp(). #include <iostream> #include <csetjmp> using namespace std;

class Rainbow { public: Rainbow() { cout << "Rainbow()" << endl; } ~Rainbow() { cout << "~Rainbow()" << endl; } };

jmp_buf kansas;

void oz() { Rainbow rb;

13

388 389 390 391 392 393 394 395 396 397 398 399 400 401 402

for(int i = 0; i < 3; i++) cout << "there's no place like home" << endl; longjmp(kansas, 47); }

int main() { if(setjmp(kansas) == 0) { cout << "tornado, witch, munchkins..." << endl; oz(); } else { cout << "Auntie Em! " << "I had the strangest dream..." << endl; } } ///:~

403 404 405 406 407 408 409 410 411

Listado 2.1. C01/Nonlocal.cpp El problema con C++ es que longjmp( ) no respeta los objetos; en particular no llama a los destructores cuando salta fuera de un mbito.[1] Puesto que las llamadas a los destructores son esenciales, esta propuesta no es vlida para C++. De hecho, el estndar de C++ aclara que saltar a un mbito con goto (pasando por alto las llamadas a los constructores), o saltar fuera de un mbito con longjmp( ) donde un objeto en la pila posee un destructor, constituye un comportamiento indefinido.

412

2.2. Lanzar una excepcin

14
413 414 415 416 417 418 Si usted se encuentra en su cdigo con una situacin excepcional-es decir, si no tiene suficiente informacin en el contexto actual para decidir lo que hacer- puede enviar informacin acerca del error a un contexto mayor creando un objeto que contenga esa informacin y lanzndolo fuera de su contexto actual. Esto es lo que se llama lanzar una excepcin. Este es el aspecto que tiene:

419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435

//: C01:MyError.cpp {RunByHand}

class MyError { const char* const data; public: MyError(const char* const msg = 0) : data(msg) {} };

void f() { // Here we "throw" an exception object: throw MyError("something bad happened"); }

int main() { // As you'll see shortly, we'll want a "try block" here: f(); } ///:~

436 437

Listado 2.2. C01/MyError.cpp

15
438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 MyError es una clase normal, que en este caso acepta un char* como argumento del constructor. Usted puede usar cualquier tipo para lanzar (incluyendo los tipos predefinidos), pero normalmente crear clases especial para lanzar excepciones. La palabra clave throw hace que suceda una serie de cosas relativamente mgicas. En primer lugar se crea una copia del objeto que se est lanzando y se devuelve desde la funcin que contiene la expresin throw, aun cuando ese tipo de objeto no es lo que normalmente la funcin est diseada para devolver. Un modo simplificado de pensar acerca del tratamiento de excepciones es como un mecanismo alternativo de retorno (aunque llegar a tener problemas si lleva esta analoga demasiado lejos). Tambin es posible salir de mbitos normales lanzando una excepcin. En cualquier caso se devuelve un valor y se sale de la funcin o mbito. Adems es posible lanzar tantos tipos de objetos diferentes como se quiera. Tpicamente, para cada categora de error se lanzar un tipo diferente. La idea es almacenar la informacin en el objeto y en el nombre de la clase con el fin de quien est en el contexto invocador pueda averiguar lo que hacer con esa excepcin.

456 457

2.3. Capturar una excepcin


2.3.1. El bloque try
try { // Code that may generate exceptions }

458 459 460

461

2.3.2. Manejadores de excepcin


try { // Code that may generate exceptions } catch(type1 id1) {

462 463 464

16

465 466 467 468 469 470 471 472 473

// Handle exceptions of type1 } catch(type2 id2) { // Handle exceptions of type2 } catch(type3 id3) // Etc... } catch(typeN idN) // Handle exceptions of typeN } // Normal execution resumes here...

474 475 476 477 478 479 480 481 482 483 484 485 486 487 488

//: C01:Nonlocal2.cpp // Illustrates exceptions. #include <iostream> using namespace std;

class Rainbow { public: Rainbow() { cout << "Rainbow()" << endl; } ~Rainbow() { cout << "~Rainbow()" << endl; } };

void oz() { Rainbow rb; for(int i = 0; i < 3; i++) cout << "there's no place like home" << endl;

17

489 490 491 492 493 494 495 496 497 498 499 500

throw 47; }

int main() { try { cout << "tornado, witch, munchkins..." << endl; oz(); } catch(int) { cout << "Auntie Em! I had the strangest dream..." << endl; } } ///:~

501 502 503

Listado 2.3. C01/Nonlocal2.cpp

2.3.3.

504

2.4.
//: C01:Autoexcp.cpp // No matching conversions. #include <iostream> using namespace std;

505 506 507 508 509 510 511

class Except1 {};

18

512 513 514 515 516 517 518 519 520 521 522 523 524 525 526

class Except2 { public: Except2(const Except1&) {} };

void f() { throw Except1(); }

int main() { try { f(); } catch(Except2&) { cout << "inside catch(Except2)" << endl; } catch(Except1&) { cout << "inside catch(Except1)" << endl; } } ///:~

527 528

Listado 2.4. C01/Autoexcp.cpp

529 530 531 532 533 534 535

//: C01:Basexcpt.cpp // Exception hierarchies. #include <iostream> using namespace std;

class X { public:

19

536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554

class Trouble {}; class Small : public Trouble {}; class Big : public Trouble {}; void f() { throw Big(); } };

int main() { X x; try { x.f(); } catch(X::Trouble&) { cout << "caught Trouble" << endl; // Hidden by previous handler: } catch(X::Small&) { cout << "caught Small Trouble" << endl; } catch(X::Big&) { cout << "caught Big Trouble" << endl; } } ///:~

555 556 557

Listado 2.5. C01/Basexcpt.cpp

2.4.1. Capturar cualquier excepcin


catch(...) { cout << "an exception was thrown" << endl;

558 559

20

560

561

2.4.2. Relanzar una excepcin


catch(...) { cout << "an exception was thrown" << endl; // Deallocate your resource here, and then rethrow throw; }

562 563 564 565 566

567

2.4.3. Excepciones no capturadas


//: C01:Terminator.cpp // Use of set_terminate(). Also shows uncaught exceptions. #include <exception> #include <iostream> using namespace std;

568 569 570 571 572 573 574 575 576 577 578 579 580 581

void terminator() { cout << "I'll be back!" << endl; exit(0); }

void (*old_terminate)() = set_terminate(terminator);

class Botch {

21

582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598

public: class Fruit {}; void f() { cout << "Botch::f()" << endl; throw Fruit(); } ~Botch() { throw 'c'; } };

int main() { try { Botch b; b.f(); } catch(...) { cout << "inside catch(...)" << endl; } } ///:~

599 600

Listado 2.6. C01/Terminator.cpp

601

2.5. Limpieza
//: C01:Cleanup.cpp // Exceptions clean up complete objects only. #include <iostream>

602 603 604

22

605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629

using namespace std;

class Trace { static int counter; int objid; public: Trace() { objid = counter++; cout << "constructing Trace #" << objid << endl; if(objid == 3) throw 3; } ~Trace() { cout << "destructing Trace #" << objid << endl; } };

int Trace::counter = 0;

int main() { try { Trace n1; // Throws exception: Trace array[5]; Trace n2; // Won't get here.

} catch(int i) {

23

630 631 632

cout << "caught " << i << endl; } } ///:~

633 634 635 636 637 638 639 640 641 642 643

Listado 2.7. C01/Cleanup.cpp

constructing Trace constructing Trace constructing Trace constructing Trace destructing Trace destructing Trace destructing Trace caught 3

#0 #1 #2 #3 #2 #1 #0

2.5.1. Gestin de recursos


//: C01:Rawp.cpp // Naked pointers. #include <iostream> #include <cstddef> using namespace std;

644 645 646 647 648 649 650 651 652 653 654 655 656 657

class Cat { public: Cat() { cout << "Cat()" << endl; } ~Cat() { cout << "~Cat()" << endl; } };

class Dog { public:

24

658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682

void* operator new(size_t sz) { cout << "allocating a Dog" << endl; throw 47; } void operator delete(void* p) { cout << "deallocating a Dog" << endl; ::operator delete(p); } };

class UseResources { Cat* bp; Dog* op; public: UseResources(int count = 1) { cout << "UseResources()" << endl; bp = new Cat[count]; op = new Dog; } ~UseResources() { cout << "~UseResources()" << endl; delete [] bp; // Array delete delete op; } };

25

683 684 685 686 687 688 689 690


int main() { try { UseResources ur(3); } catch(int) { cout << "inside handler" << endl; } } ///:~

691 692 693 694 695 696 697 698

Listado 2.8. C01/Rawp.cpp

UseResources() Cat() Cat() Cat() allocating a Dog inside handler

699

2.5.2.
//: C01:Wrapped.cpp // Safe, atomic pointers. #include <iostream> #include <cstddef> using namespace std;

700 701 702 703 704 705 706 707 708

// Simplified. Yours may have other arguments. template<class T, int sz = 1> class PWrap { T* ptr;

26

709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733

public: class RangeError {}; // Exception class PWrap() { ptr = new T[sz]; cout << "PWrap constructor" << endl; } ~PWrap() { delete[] ptr; cout << "PWrap destructor" << endl; } T& operator[](int i) throw(RangeError) { if(i >= 0 && i < sz) return ptr[i]; throw RangeError(); } };

class Cat { public: Cat() { cout << "Cat()" << endl; } ~Cat() { cout << "~Cat()" << endl; } void g() {} };

class Dog { public:

27

734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758

void* operator new[](size_t) { cout << "Allocating a Dog" << endl; throw 47; } void operator delete[](void* p) { cout << "Deallocating a Dog" << endl; ::operator delete[](p); } };

class UseResources { PWrap<Cat, 3> cats; PWrap<Dog> dog; public: UseResources() { cout << "UseResources()" << endl; } ~UseResources() { cout << "~UseResources()" << endl; } void f() { cats[1].g(); } };

int main() { try { UseResources ur; } catch(int) { cout << "inside handler" << endl; } catch(...) {

28

759 760 761

cout << "inside catch(...)" << endl; } } ///:~

762 763 764 765 766 767 768 769 770 771 772 773

Listado 2.9. C01/Wrapped.cpp

Cat() Cat() Cat() PWrap constructor allocating a Dog ~Cat() ~Cat() ~Cat() PWrap destructor inside handler

774

2.5.3. auto_ptr
//: C01:Auto_ptr.cpp // Illustrates the RAII nature of auto_ptr. #include <memory> #include <iostream> #include <cstddef> using namespace std;

775 776 777 778 779 780 781 782 783 784 785 786 787

class TraceHeap { int i; public: static void* operator new(size_t siz) { void* p = ::operator new(siz); cout << "Allocating TraceHeap object on the heap "

29

788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803

<< "at address " << p << endl; return p; } static void operator delete(void* p) { cout << "Deleting TraceHeap object at address " << p << endl; ::operator delete(p); } TraceHeap(int i) : i(i) {} int getVal() const { return i; } };

int main() { auto_ptr<TraceHeap> pMyObject(new TraceHeap(5)); cout << pMyObject->getVal() << endl; } ///:~ // Prints 5

804 805 806

Listado 2.10. C01/Auto_ptr.cpp

2.5.4. Bloques try a nivel de funcin


//: C01:InitExcept.cpp {-bor} // Handles exceptions from subobjects. #include <iostream> using namespace std;

807 808 809 810

30

811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835
int main() { class Derived : public Base { public: class DerivedExcept { const char* msg; public: DerivedExcept(const char* msg) : msg(msg) {} const char* what() const { return msg; } }; Derived(int j) try : Base(j) { // Constructor body cout << "This won't print" << endl; } catch(BaseExcept&) { throw DerivedExcept("Base subobject threw");; } }; class Base { int i; public: class BaseExcept {}; Base(int i) : i(i) { throw BaseExcept(); } };

31

836 837 838 839 840 841

try { Derived d(3); } catch(Derived::DerivedExcept& d) { cout << d.what() << endl; } } ///:~ // "Base subobject threw"

842 843

Listado 2.11. C01/InitExcept.cpp

844 845 846 847 848 849 850 851 852 853 854 855

//: C01:FunctionTryBlock.cpp {-bor} // Function-level try blocks. // {RunByHand} (Don't run automatically by the makefile) #include <iostream> using namespace std;

int main() try { throw "main"; } catch(const char* msg) { cout << msg << endl; return 1; } ///:~

856 857

Listado 2.12. C01/FunctionTryBlock.cpp

858

2.6. Excepciones estndar

32

859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876

//: C01:StdExcept.cpp // Derives an exception class from std::runtime_error. #include <stdexcept> #include <iostream> using namespace std;

class MyError : public runtime_error { public: MyError(const string& msg = "") : runtime_error(msg) {} };

int main() { try { throw MyError("my message"); } catch(MyError& x) { cout << x.what() << endl; } } ///:~

877 878

Listado 2.13. C01/StdExcept.cpp

879

2.7. Especificaciones de excepciones


void f() throw(toobig, toosmall, divzero); void f();

880 881

33

882

void f() throw();

883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905

//: C01:Unexpected.cpp // Exception specifications & unexpected(), //{-msc} (Doesn't terminate properly) #include <exception> #include <iostream> using namespace std;

class Up {}; class Fit {}; void g();

void f(int i) throw(Up, Fit) { switch(i) { case 1: throw Up(); case 2: throw Fit(); } g(); }

// void g() {}

// Version 1

void g() { throw 47; } // Version 2

void my_unexpected() {

34

906 907 908 909 910 911 912 913 914 915 916 917 918 919 920

cout << "unexpected exception thrown" << endl; exit(0); }

int main() { set_unexpected(my_unexpected); // (Ignores return value) for(int i = 1; i <=3; i++) try { f(i); } catch(Up) { cout << "Up caught" << endl; } catch(Fit) { cout << "Fit caught" << endl; } } ///:~

921 922

Listado 2.14. C01/Unexpected.cpp

923 924 925 926 927 928 929

//: C01:BadException.cpp {-bor} #include <exception> // For std::bad_exception

#include <iostream> #include <cstdio> using namespace std;

// Exception classes:

35

930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954

class A {}; class B {};

// terminate() handler void my_thandler() { cout << "terminate called" << endl; exit(0); }

// unexpected() handlers void my_uhandler1() { throw A(); } void my_uhandler2() { throw; }

// If we embed this throw statement in f or g, // the compiler detects the violation and reports // an error, so we put it in its own function. void t() { throw B(); }

void f() throw(A) { t(); } void g() throw(A, bad_exception) { t(); }

int main() { set_terminate(my_thandler); set_unexpected(my_uhandler1); try {

36

955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970

f(); } catch(A&) { cout << "caught an A from f" << endl; } set_unexpected(my_uhandler2); try { g(); } catch(bad_exception&) { cout << "caught a bad_exception from g" << endl; } try { f(); } catch(...) { cout << "This will never print" << endl; } } ///:~

971 972 973

Listado 2.15. C01/BadException.cpp

2.7.1. Mejores especificaciones de excepciones?


void f(); void f() throw(...); // Not in C++

974 975

976

2.7.2. Especificacin de excepciones y herencia

37

977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001

//: C01:Covariance.cpp {-xo} // Should cause compile error. {-mwcc}{-msc} #include <iostream> using namespace std;

class Base { public: class BaseException {}; class DerivedException : public BaseException {}; virtual void f() throw(DerivedException) { throw DerivedException(); } virtual void g() throw(BaseException) { throw BaseException(); } };

class Derived : public Base { public: void f() throw(BaseException) { throw BaseException(); } virtual void g() throw(DerivedException) { throw DerivedException(); }

38

1002

}; ///:~

1003 1004 1005

Listado 2.16. C01/Covariance.cpp

2.7.3. Cundo no usar especificaciones de excepcin


T pop() throw(logic_error);

1006

1007

2.8. Seguridad de la excepcin


void pop(); template<class T> T stack<T>::pop() { if(count == 0) throw logic_error("stack underflow"); else return data[--count]; }

1008 1009 1010 1011 1012 1013 1014

1015 1016 1017 1018 1019 1020 1021

//: C01:SafeAssign.cpp // An Exception-safe operator=. #include <iostream> #include <new> // For std::bad_alloc

#include <cstring> #include <cstddef> using namespace std;

39

1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046
// A class that has two pointer members using the heap class HasPointers { // A Handle class to hold the data struct MyData { const char* theString; const int* theInts; size_t numInts; MyData(const char* pString, const int* pInts, size_t nInts) : theString(pString), theInts(pInts), numInts(nInts) {} } *theData; // The handle

// Clone and cleanup functions: static MyData* clone(const char* otherString, const int* otherInts, size_t nInts) { char* newChars = new char[strlen(otherString)+1]; int* newInts; try { newInts = new int[nInts]; } catch(bad_alloc&) { delete [] newChars; throw; } try { // This example uses built-in types, so it won't

40

1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071

// throw, but for class types it could throw, so we // use a try block for illustration. (This is the // point of the example!) strcpy(newChars, otherString); for(size_t i = 0; i < nInts; ++i) newInts[i] = otherInts[i]; } catch(...) { delete [] newInts; delete [] newChars; throw; } return new MyData(newChars, newInts, nInts); } static MyData* clone(const MyData* otherData) { return clone(otherData->theString, otherData->theInts, otherData->numInts); } static void cleanup(const MyData* theData) { delete [] theData->theString; delete [] theData->theInts; delete theData; } public: HasPointers(const char* someString, const int* someInts, size_t numInts) {

41

1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096

theData = clone(someString, someInts, numInts); } HasPointers(const HasPointers& source) { theData = clone(source.theData); } HasPointers& operator=(const HasPointers& rhs) { if(this != &rhs) { MyData* newData = clone(rhs.theData->theString, rhs.theData->theInts, rhs.theData->numInts); cleanup(theData); theData = newData; } return *this; } ~HasPointers() { cleanup(theData); } friend ostream& operator<<(ostream& os, const HasPointers& obj) { os << obj.theData->theString << ": "; for(size_t i = 0; i < obj.theData->numInts; ++i) os << obj.theData->theInts[i] << ' '; return os; } };

int main() {

42

1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107

int someNums[] = { 1, 2, 3, 4 }; size_t someCount = sizeof someNums / sizeof someNums[0]; int someMoreNums[] = { 5, 6, 7 }; size_t someMoreCount = sizeof someMoreNums / sizeof someMoreNums[0]; HasPointers h1("Hello", someNums, someCount); HasPointers h2("Goodbye", someMoreNums, someMoreCount); cout << h1 << endl; // Hello: 1 2 3 4

h1 = h2; cout << h1 << endl; // Goodbye: 5 6 7

} ///:~

1108 1109

Listado 2.17. C01/SafeAssign.cpp

1110 1111 1112

2.9. Programar con excepciones


2.9.1. Cundo evitar las excepciones 2.9.2. Usos tpicos de excepciones

1113

2.10. Sobrecarga
//: C01:HasDestructor.cpp {O} class HasDestructor { public: ~HasDestructor() {} };

1114 1115 1116 1117 1118

43

1119 1120 1121 1122 1123 1124 1125


void f() { HasDestructor h; g(); } ///:~ void g(); // For all we know, g may throw.

1126 1127

Listado 2.18. C01/HasDestructor.cpp

1128

2.11. Resumen 2.12. Ejercicios

1129

1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145

3: Programacin defensiva
Tabla de contenidos 3.1. Aserciones 3.2. Un framework de pruebas unitarias sencillo 3.3. Tcnicas de depuracin 3.4. Resumen 3.5. Ejercicios Escribir software puede ser un objetivo difcil para desarrolladores, pero unas pocas tcnicas defensivas, aplicadas rutinariamente, pueden dirigir a un largo camino hacia la mejora de la calidad de su cdigo. Aunque la complejidad de la produccin tpica de software garantiza que los probadores tendrn siempre trabajo, esperamos que anheles producir software sin defectos. Las tcnicas de diseo orientada a objetos hacen mucho para limitar la dificultad de proyectos grandes, pero finalmente debe escribir bucles y funciones. Estos pequeos detalles de programacin se convierten en los bloques de construccin de componentes mayores

44
1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 necesarios para sus diseos. Si sus blucles fallan por uno o sus funciones calculan los valores correctos slo la mayora de las veces, tiene problemas no importa como de elaborada sea su metodologa general. En este captulo, ver prcticas que ayudan a crear cdigo robusto sin importar el tamao de su proyecto. Su cdigo es, entre otras cosas, una expresin de su intento de resolver un problema. Sera claro para el lector (incluyendo usted) exactamente lo que estaba pensando cuando diseo aquel bucle. En ciertos puntos de su programa, deber crear atreverse con sentencias que considera alguna u otra condicin. (Si no puede, no ha realmente solucionado todava el problema.) Tales sentencias se llaman invariantes, puesto que deberan ser invariablemente verdad en el punto donde aparecen en el cdigo; si no, o su diseo es defectuoso, o su cdigo no refleja con precisin su diseo. Considere un programa que juega al juego de adivinanza mayor-menor. Una persona piensa un nmero entre el 1 y 100,y la otra persona adivina el nmero. (Permitir al ordenador hacer la adivinanza.) La persona que piensa el nmero le dice al adivinador si su conjetura es mayor, menor o correcta. La mejor estrategia para el adivinador es la bsqueda binaria, que elige el punto medio del rango de los nmeros donde el nmero buscado reside. La respuesta mayor-menor dice al adivinador que mitad de la lista ocupa el nmero, y el proceso se repite, reduciendo el tamao del rango de bsqueda activo en cada iteracin. Entonces cmo escribe un bucle para realizar la repeticin correctamente? No es suficiente simplemente decir bool adivinado = false; while(!adivinado) { ... } porque un usuario malintencionado podra responder engaosamente, y podra pasarse todo el da adivinando. Qu suposicin, que sea sencilla, est haciendo cada vez que adivina? En otras palabras, qu condicin debera cumplir por diseo en cada iteracin del bucle? La suposicin sencilla es que el nmero secreto est dentro del actual rango activo de nmeros sin adivinar: [1, 100]. Suponga que etiquetamos los puntos finales del rango con las variables bajo y alto. Cada vez que pasa por el bucle necesita asegurarse que si el nmero estaba en el rango [bajo, alto]

45
1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 al principio del bucle, calcule el nuevo rango de modo que todava contenga el nmero al final de la iteracin en curso. El objetivo es expresar el invariante del bucle en cdigo de modo que una violacin pueda ser detectada en tiempo de ejecucin. Desafortunadamente, ya que el ordenador no conoce el nmero secreto, no puede expresar esta condicin directamente en cdigo, pero puede al menos hacer un comentario para este efecto: while(!adivinado) { // INVARIANTE: el nmero est en el rango [low, high] Qu ocurre cuando el usuario dice que una conjetura es demasiado alta o demasiado baja cuando no lo es? El engao excluira el nmero secreto del nuevo subrango. Porque una mentira siempre dirige a otra, finalmente su rango disminuir a nada (puesto que se reduce a la mitad cada vez y el nmero secreto no est all). Podemos expresar esta condicin en el siguiente programa:

1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206

//: C02:HiLo.cpp {RunByHand} // Plays the game of Hi-Lo to illustrate a loop invariant. #include <cstdlib> #include <iostream> #include <string> using namespace std;

int main() { cout << "Think of a number between 1 and 100" << endl << "I will make a guess; " << "tell me if I'm (H)igh or (L)ow" << endl; int low = 1, high = 100; bool guessed = false; while(!guessed) {

46

1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231

// Invariant: the number is in the range [low, high] if(low > high) { // Invariant violation

cout << "You cheated! I quit" << endl; return EXIT_FAILURE; } int guess = (low + high) / 2; cout << "My guess is " << guess << ". "; cout << "(H)igh, (L)ow, or (E)qual? "; string response; cin >> response; switch(toupper(response[0])) { case 'H': high = guess - 1; break; case 'L': low = guess + 1; break; case 'E': guessed = true; break; default: cout << "Invalid response" << endl; continue; } }

47

1232 1233 1234

cout << "I got it!" << endl; return EXIT_SUCCESS; } ///:~

1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260

Listado 3.1. C02/HiLo.cpp La violacin del invariante se detecta con la condicin if(menor > mayor), porque si el usuario siempre dice la verdad, siempre encontraremos el nmero secreto antes que agotsemos los intentos. Usamos tambin una tcnica del estndar C para informar sobre el estado de un programa al contexto llamante devolviendo diferentes valores desde main( ). Es portable para usar la sentencia return 0; para indicar xito, pero no hay un valor portable para indicar fracaso. Por esta razn usamos la macro declarada para este propsito en <cstdlib>:EXIT_FAILURE. Por consistencia, cuando usamos EXIT_FAILURE tambin usamos EXIT_SUCCESS, a pesar de que ste es siempre definido como cero.

3.1. Aserciones
La condicin en el programa mayor-menor depende de la entrada del usuario, por lo tanto no puede prevenir una violacin del invariante. Sin embargo, los invariantes normalmente dependen solo del cdigo que escribe, por eso comprobarn siempre si ha implementado su diseo correctamente. En este caso, es ms claro hacer una asercin, que es un sentencia positiva que muestra sus decisiones de diseo. Suponga que est implementando un vector de enteros: un array expandible que crece a peticin. La funcin que aade un elemento al vector debe primero verificar que hay un espacio vaco en el array subyacente que contiene los elementos; de lo contrario, necesita solicitar ms espacio en la pila y copiar los elementos existentes al nuevo espacio antes de aadir el nuevo elemento (y borrar el viejo array). Tal funcin podra ser de la siguiente forma:

48

1261 1262 1263 1264 1265 1266

void MyVector::push_back(int x) { if(nextSlot == capacity) grow(); assert(nextSlot < capacity); data[nextSlot++] = x; }

1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290

En este ejemplo, la informacin es un array dinmico de ints con capacidad espacios y espacioSiguiente espacios en uso. El propsito de grow( ) es expandir el tamao de la informacin para que el nuevo valor de capacidad sea estrictamente mayor que espacioSiguiente. El comportamiento correcto de MiVector depende de esta decisin de diseo, y nunca fallar si el resto del cdigo secundario es correcto. Afirmamos la condicin con la macro assert( ), que est definido en la cabecera <cassert>. La macro assert( ) de la biblioteca Estndar de C es breve, que resulta, portable. Si la condicin en su parmetro no evala a cero, la ejecucin contina ininterrumpidamente; si no, un mensaje contiene el texto de la expresin culpable con su nombre de fichero fuente y el nmero de lnea impreso en el canal de error estndar y el programa se suspende. Es eso tan drstico? En la prctica, es mucho ms drstico permitir que la ejecucin continue cuando un supuesto de diseo bsico ha fracasado. Su programa necesita ser arreglado. Si todo va bien, probar a conciencia su cdigo con todas las aserciones intactas hasta el momento en que se haga uso del producto final. (Diremos ms sobre pruebas ms tarde.) Depende de la naturaleza de su aplicacin, los ciclos de mquina necesarios para probar todas las aserciones en tiempo de ejecucin podran tener demasiado impacto en el rendimiento en produccin. En ese caso, puede eliminar todas las aserciones del cdigo automticamente definiendo la macro NDEBUG y reconstruir la aplicacin. Para ver como funciona esto, observe que una implementacin tpica de assert( ) se parece a esto:

49

1291 1292 1293 1294 1295 1296 1297

#ifdef NDEBUG #define assert(cond) ((void)0) #else void assertImpl(const char*, const char*, long); #define assert(cond) \ ((cond) ? (void)0 : assertImpl(???)) #endif

1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320

Cuando la macro NDEBUG est definida, el cdigo se descompone a la expresin (void) 0, todo lo que queda en la cadena de compilacin es una sentencia esencialmente vaca como un resultado de la semicolumna que aade a cada invocacin de assert( ). Si NDEBUG no est definido, assert(cond) se expande a una sentencia condicional que, cuando cond es cero, llama a una funcin dependiente del compilador (que llamamos assertImpl( )) con argumento string representando el texto de cond, junto con el nombre de fichero y el nmero de lnea donde aparece la asercin. (Usamos como un marcador de posicin en el ejemplo, pero la cadena mencionada es de hecho computada all, junto con el nombre del fichero y el nmero de lnea donde la macro aparece en ese fichero. Como estos valores se obtienen es irrelevante para nuestra discusin.) Si quiere activar y desactivar aserciones en diferentes puntos de su programa, no debe solo #define o #undef NDEBUG, sino que debe tambin reincluir <cassert>. Las macros son evaluadas cuando el preprocesador los encuentra y as usa cualquier estado NDEBUG se aplica en el punto de inclusin. El camino ms comn define NDEBUG una vez para todo el programa es como una opcin del compilador, o mediante la configuracin del proyecto en su entorno visual o mediante la lnea de comandos, como en:
mycc NDEBUG myfile.cpp

La mayora de los compiladores usan la bandera para definir los nombres de las macros. (Substituya el nombre del ejecutable de su compiladores por mycc arriba.) La ventaja de este enfoque es que puede dejar sus aserciones

50
1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 en el cdigo fuente como un inapreciable parte de documentacin, y no hay an castigo en tiempo de ejecucin. Porque el cdigo en una asercin desaparece cuando NDEBUG est definido, es importante que no haga trabajo en una asercin. Slo las condiciones de prueba que no cambien el estado de su programa. Si usar NDEBUG para liberar cdigo es una buena idea queda un tema de debate. Tony Hoare, una de los ms influyentes expertos en informtica de todos los tiempos,[15] ha sugerido que desactivando las comprobaciones en tiempo de ejecucin como las aserciones es similar a un entusiasta de navegacin que lleva un chaleco salvavidas mientras entrena en tierra y luego se deshace de l cuando va al mar.[16] Si una asercin falla en produccin, tiene un problema mucho peor que la degradacin en rendimiento, as que elija sabiamente. No todas las condiciones deberan ser cumplidas por aserciones. Los errores de usuario y los fallos de los recursos en tiempos de ejecucin deberan ser sealados lanzando excepciones, como explicamos en detalle en el Captulo 1. Es tentador usar aserciones para la mayora de las condiciones de error mientras esbozamos cdigo, con el propsito de remplazar muchos de ellos despus con un manejador de excepciones robusto. Como cualquier otra tentacin, sese con moderacin, pues podra olvidar hacer todos los cambios necesarios ms tarde. Recuerde: las aserciones tienen la intencin de verificar decisiones de diseo que fallarn slo por lgica defectuosa del programador. Lo ideal es solucionar todas las violaciones de aserciones durante el desarrollo. No use aserciones para condiciones que no estn totalmente en su control (por ejemplo, condiciones que dependen de la entrada del usuario). En particular, no querra usar aserciones para validar argumentos de funcin; lance un logic_error en su lugar. El uso de aserciones como una herramienta para asegurar la correccin de un programa fue formalizada por Bertran Meyer en su Diseo mediante metodologa de contrato.[17] Cada funcin tiene un contrato implcito con los clientes que, dadas ciertas precondiciones, garantiza ciertas postcondiciones. En otras palabras, las precondiciones son los requerimientos para usar la funcin, como los argumentos que se facilitan

51
1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 dentro de ciertos rangos, y las postcondiciones son los resultados enviados por la funcin o por retorno por valor o por efecto colateral. Cuando los programas clientes fallan al darle un entrada vlida, debe comentarles que han roto el contrato. Este no es el mejor momento para suspender el programa (aunque est justificado hacerlo desde que el contrato fue violado), pero una excepcin es desde luego apropiada. Esto es porque la librera Estndar de C++ lanza excepciones derivadas de logic_error, como out_of_range.[18] Si hay funciones que slo usted llama, no obstante, como funciones privadas en una clase de su propio diseo, la macro assert( ) es apropiada, puesto que tiene total control sobre la situacin y desde luego quiere depurar su cdigo antes de enviarlo. Una postcondicin fallada indica un error de programa, y es apropiado usar aserciones para cualquier invariante en cualquier momento, incluyendo la postcondicin de prueba al final de una funcin. Esto se aplica en particular a las funciones de una clase que mantienen el estado de un objeto. En el ejemplo MyVector previo, por ejemplo, un invariante razonable para todas las funciones sera: assert(0 <= siguienteEspacio && siguienteEspacio <= capacidad); o, si siguienteEspacio es un integer sin signo, sencillamente assert(siguienteEspacio <= capacidad); Tal tipo de invariante se llama invariante de clase y puede ser razonablemente forzada por una asercin. Las subclases juegan un papel de subcontratista para sus clases base porque deben mantener el contrato original entre la clase base y sus clientes. Por esta razn, las precondiciones en clases derivadas no deben imponer requerimientos adicionales ms all de aquellos del contrato base, y las postcondiciones deben cumplir al menos como mucho.[19] Validar resultados devueltos por el cliente, sin embargo, no es ms o menos que probar, de manera que usar aserciones de postcondicin en este caso sera duplicar trabajo. S, es buena documentacin, pero ms de un desarrollador has sido engaado usando incorrectamente las aserciones de post-condicin como un substituto para pruebas de unidad.

52
1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419

3.2. Un framework de pruebas unitarias sencillo


Escribir software es todo sobre encontrar requerimientos.[20] Crear estos requerimientos es difcil, y pueden cambiar de un da a otro; podra descubrir en una reunin de proyecto semanal que lo que ha empleado la semana haciendo no es exactamente lo que los usuarios realmente quieren. Las personas no pueden articular requerimientos de software sin muestrear un sistema de trabajo en evolucin. Es mucho mejor especificar un poco, disear un poco, codificar un poco y probar un poco. Entonces, despus de evaluar el resultado, hacerlo todo de nuevo. La habilidad para desarrollar con una moda iterativa es uno de los mejores avances del enfoque orientado a objetos, pero requiere programadores giles que pueden hacer cdigo fuerte. El cambio es duro. Otro mpetu para el cambio viene de usted, el programador. El artfice que hay en usted quiere continuamente mejorar el diseo de su cdigo. Qu programador de mantenimiento no ha maldecido el envejecimiento, el producto de la compaa insignia como un mosaico de espaguetis inmodificable, enrevesado? La reluctancia de los supervisores en permitir que uno interfiera con un sistema que funciona le roba al cdigo la flexibilidad que necesita para que perdure. Si no est roto, no arreglarlo finalmente le da el camino para, no podemos arreglarlo reescribmoslo. El cambio es necesario. Afortunadamente, nuestra industria est creciendo acostumbrada a la disciplina de refactoring, el arte de reestructura internamente cdigo para mejorar su diseo, sin cambiar su comportamiento.[21] Tales mejoras incluyen extraer una nueva funcin de otra, o de forma inversa, combinar funciones, reemplazar una funcin con un objeto; parametrizar una funcin o clase; y reemplazar condicionales con polimorfismo. Refactorizar ayuda al cdigo evolucionar. Si la fuerza para el cambio viene de los usuarios o programadores, los cambios hoy pueden destrozar lo trabajado ayer. Necesitamos un modo para construir cdigo que resista el cambio y mejoras a lo largo del tiempo. La Programacin Extrema (XP)[22] es slo uno de las muchas prcticas que motivan la agilidad. En esta seccin exploramos lo que pensamos es la

53
1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 clave para hacer un desarrollo flexible, incremental que tenga xito: un framework de pruebas unitarias automatizada fcil de usar. (Note que los probadores, profesionales de software que prueban el cdigo de otros para ganarse la vida, son todava indispensables. Aqu, estamos simplemente describiendo un modo para ayudar a los desarrolladores a escribir mejor cdigo.) Los desarrolladores escriben pruebas unitarias para conseguir confianza para decir las dos cosas ms importantes que cualquier desarrollador puede decir: 1. Entiendo los requerimientos. Mi cdigo cumple esos requerimientos (hasta donde yo s) No hay mejor modo para asegurar que sabe lo que el cdigo que est por escribir debera hacer mejor que escribir primero pruebas unitarias. Este ejercicio sencillo ayuda a centrar la mente en las tareas siguientes y probablemente guiar a cdigo que funcionalmente ms rpido mejor que slo saltar a codificar. O, expresarlo en trminos XP: Probar + programar es ms rpido que slo programar. Escribir primero pruebas slo le protegen contra condiciones lmite que podran destrozar su cdigo, por lo tanto su cdigo es ms robusto. Cuando su cdigo pasa todas sus pruebas, sabe que si el sistema no est funcionando, su cdigo no es probablemente el problema. La frase todas mis pruebas funcionan es un fuerte razonamiento.

3.2.1. Pruebas automatizadas


Por lo tanto, qu aspecto tiene una prueba unitaria? Demasiado a menudo los desarrolladores simplemente usan alguna entrada correcta para producir alguna salida esperada, que examinan visualmente. Existen dos peligros en este enfoque. Primero, los programas no siempre reciben slo entradas correctas. Todos sabemos que deberamos probar los lmites de entrada de un programa, pero es duro pensar esto cuando est intentando simplemente hacer que las cosas funcionar. Si escribe primero la prueba para una funcin antes de comenzar a codificar, puede ponerse su traje de probador y preguntarse a si mismo, qu hara posiblemente destrozar esto?

54
1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 Codificar una prueba que probar la funcin que escribir no es errneo, y luego ponerte el traje de desarrollador y hacerlo pasar. Escribir mejor cdigo que si no haba escrito la prueba primero. El segundo peligro es que esperar una salida visualmente es tedioso y propenso a error. La mayora de cualquier tipo de cosas que un humano puede hacer un ordenador puede hacerlas, pero sin el error humano. Es mejor formular pruebas como colecciones de expresiones boolean y tener un programa de prueba que informa de cualquier fallo. Por ejemplo, suponga que necesita construir una clase Fecha que tiene las siguientes propiedades: Una fecha puede estar inicializada con una cadena (AAAAMMDD), 3 enteros (A, M, D), o nada (dando la fecha de hoy). Un objecto fecha puede producir su ao, mes y da o una cadena de la forma AAAAMMDD. Todas las comparaciones relacionales estn disponibles, adems de calcular la duracin entre dos fechas (en aos, meses, y das). Las fechas para ser comparadas necesitan poder extenderse un nmero arbitrario de siglos(por ejemplo, 16002200). Su clase puede almacenar tres enteros que representan el ao, mes y da. (Slo asegrese que el ao es al menos de 16 bits de tamao para satisfacer el ltimo punto.) La interfaz de su clase Fecha se podra parecer a esto:

1473 1474 1475 1476 1477 1478 1479 1480

//: C02:Date1.h // A first pass at Date.h. #ifndef DATE1_H #define DATE1_H #include <string>

class Date { public:

55

1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504

// A struct to hold elapsed time: struct Duration { int years; int months; int days; Duration(int y, int m, int d) : years(y), months(m), days(d) {} }; Date(); Date(int year, int month, int day); Date(const std::string&); int getYear() const; int getMonth() const; int getDay() const; std::string toString() const; friend bool operator<(const Date&, const Date&); friend bool operator>(const Date&, const Date&); friend bool operator<=(const Date&, const Date&); friend bool operator>=(const Date&, const Date&); friend bool operator==(const Date&, const Date&); friend bool operator!=(const Date&, const Date&); friend Duration duration(const Date&, const Date&); }; #endif // DATE1_H ///:~

56
1505 1506 1507 1508 1509 Listado 3.2. C02/Date1.h Antes de que implemente esta clase, puede solidificar sus conocimientos de los requerimientos escribiendo el principio de un programa de prueba. Podra idear algo como lo siguiente:

1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529

//: C02:SimpleDateTest.cpp //{L} Date #include <iostream> #include "Date.h" // From Appendix B using namespace std;

// Test machinery int nPass = 0, nFail = 0; void test(bool t) { if(t) nPass++; else nFail++; }

int main() { Date mybday(1951, 10, 1); test(mybday.getYear() == 1951); test(mybday.getMonth() == 10); test(mybday.getDay() == 1); cout << "Passed: " << nPass << ", Failed: " << nFail << endl; } /* Expected output: Passed: 3, Failed: 0

57

1530

*/ ///:~

1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543

Listado 3.3. C02/SimpleDateTest.cpp En este caso trivial, la funcin test( ) mantiene las variables globales nAprobar y nSuspender. La nica revisin visual que hace es leer el resultado final. Si una prueba falla, un test( ) ms sofisticado muestra un mensaje apropiado. El framework descrito ms tarde en este captulo tiene un funcin de prueba, entre otras cosas. Puede ahora implementar la clase Fecha para hacer pasar estas pruebas, y luego puede proceder iterativamente hasta que se satisfagan todos los requerimientos. Escribiendo primero pruebas, es ms probable que piense en casos lmite que podran destrozar su prxima implementacin, y es ms probable que escriba el cdigo correctamente la primera vez. Como ejercicio podra realizar la siguiente versin de una prueba para la clase Fecha:

1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556

//: C02:SimpleDateTest2.cpp //{L} Date #include <iostream> #include "Date.h" using namespace std;

// Test machinery int nPass = 0, nFail = 0; void test(bool t) { if(t) ++nPass; else ++nFail; }

int main() { Date mybday(1951, 10, 1); Date today;

58

1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581

Date myevebday("19510930");

// Test the operators test(mybday < today); test(mybday <= today); test(mybday != today); test(mybday == mybday); test(mybday >= mybday); test(mybday <= mybday); test(myevebday < mybday); test(mybday > myevebday); test(mybday >= myevebday); test(mybday != myevebday);

// Test the functions test(mybday.getYear() == 1951); test(mybday.getMonth() == 10); test(mybday.getDay() == 1); test(myevebday.getYear() == 1951); test(myevebday.getMonth() == 9); test(myevebday.getDay() == 30); test(mybday.toString() == "19511001"); test(myevebday.toString() == "19510930");

// Test duration

59

1582 1583 1584 1585 1586 1587 1588 1589 1590 1591

Date d2(2003, 7, 4); Date::Duration dur = duration(mybday, d2); test(dur.years == 51); test(dur.months == 9); test(dur.days == 3);

// Report results: cout << "Passed: " << nPass << ", Failed: " << nFail << endl; } ///:~

1592 1593 1594 1595 1596 1597

Listado 3.4. C02/SimpleDateTest2.cpp Esta prueba puede ser desarrollada por completo. Por ejemplo, no hemos probado que duraciones grandes son manejadas correctamente. Pararemos aqu, pero coja la idea. La implementacin entera para la case Fecha est disponible en los ficheros Date.h y Date.cpp en el apndice.[23]

1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609

3.2.2. El Framework TestSuite


Algunas herramientas de pruebas unitarias automatizadas de C++ estn disponibles en la World Wide Web para descargar, como CppUnit.[24] Nuestra intencin aqu no es slo presentar un mecanismo de prueba que sea fcil de usar, sino tambin fcil de entender internamente e incluso modificar si es necesario. Por lo tanto, en el espritu de Hacer Lo Ms Simple Que Podra Posiblemente Funcionar,[25] hemos desarrollado el Framework TestSuite, un espacio de nombres llamado TestSuite que contiene dos clases principales: Test y Suite. La clase Test es una clase base abstracta de la cual deriva un objeto test. Tiene constancia del nmero de xitos y fracasos y muestra el texto de cualquier condicin de prueba que falla. Simplemente para sobreescribir la

60
1610 1611 1612 1613 funcin run( ), que debera llamar en turnos a la macro test_() para cada condicin de prueba boolean que defina. Para definir una prueba para la clase Fecha usando el framework, puede heredar de Test como se muetra en el siguiente programa:

1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634

//: C02:DateTest.h #ifndef DATETEST_H #define DATETEST_H #include "Date.h" #include "../TestSuite/Test.h"

class DateTest : public TestSuite::Test { Date mybday; Date today; Date myevebday; public: DateTest(): mybday(1951, 10, 1), myevebday("19510930") {} void run() { testOps(); testFunctions(); testDuration(); } void testOps() { test_(mybday < today); test_(mybday <= today); test_(mybday != today);

61

1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659

test_(mybday == mybday); test_(mybday >= mybday); test_(mybday <= mybday); test_(myevebday < mybday); test_(mybday > myevebday); test_(mybday >= myevebday); test_(mybday != myevebday); } void testFunctions() { test_(mybday.getYear() == 1951); test_(mybday.getMonth() == 10); test_(mybday.getDay() == 1); test_(myevebday.getYear() == 1951); test_(myevebday.getMonth() == 9); test_(myevebday.getDay() == 30); test_(mybday.toString() == "19511001"); test_(myevebday.toString() == "19510930"); } void testDuration() { Date d2(2003, 7, 4); Date::Duration dur = duration(mybday, d2); test_(dur.years == 51); test_(dur.months == 9); test_(dur.days == 3); }

62

1660 1661

}; #endif // DATETEST_H ///:~

1662 1663 1664 1665

Listado 3.5. C02/DateTest.h Ejecutar la prueba es una sencilla cuestin de instaciacin de un objeto DateTest y llamar a su funcin run( ):

1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681

//: C02:DateTest.cpp // Automated testing (with a framework). //{L} Date ../TestSuite/Test #include <iostream> #include "DateTest.h" using namespace std;

int main() { DateTest test; test.run(); return test.report(); } /* Output: Test "DateTest": Passed: 21, */ ///:~ Failed: 0

1682 1683

Listado 3.6. C02/DateTest.cpp

63
1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 La funcin Test::report( ) muestra la salida previa y devuelve el nmero de fallos, de este modo es conveniente usarlo como valor de retorno desde el main( ). La clase Test usa RTTI[26] para obtener el nombre de su clase(por ejemplo, DateTest) para el informe. Hay tambin una funcin setStream() si quiere enviar los resultados de la prueba a un fichero en lugar de la salida estndar (por defecto). Ver la implementacin de la clase Test ms tarde en este captulo. La macro test_( ) puede extraer el texto de la condicin booleana que falla, junto con el nombre del fichero y nmero de lnea.[27] Para ver lo que ocurre cuando un fallo aparece, puede insertar un error intencionado en el cdigo, por ejemplo invirtiendo la condicin en la primera llamda a test_( ) en DateTest::testOps( ) en el cdigo de ejemplo previo. La salida indica exactamente que la prueba tena un error y dnde ocurri: DateTest fallo: (mybday > hoy) , DateTest.h (lnea 31) Test "DateTest": Passados: 20 Fallados: 1 Adems de test_( ), el framework incluye las funciones succed_( ) y fail_( ), para casos donde una prueba Boolean no funcionar. Estas funciones se aplican cuando la clase que est probando podra lanzar excepciones. Durante la prueba, crear un conjunto de entrada que causar que la excepcin aparezca. Si no, es un error y puede llamar a fail_( ) explicitamente para mostrar un mensaje y actualizar el contador de fallos. Si lanza la excecpin como se esperaba, llame a succeed_( ) para actualizar el contador de xitos. Para ilustrar, suponga que modificamos la especificacin de los dos constructor no por defecto de Date para lanzar una excepcin DateError (un tipo anidado dentro de Date y derivado de std::logic_error) si los parmetros de entrada no representa un fecha vlida: Date(const string& s) throw(DateError); Date(int year, int month, int day) throw(DateError); La funcin DateTest::run( ) puede ahora llamar a la siguiente funcin para probar el manejo de excepciones:

1715

void testExceptions() {

64

1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728

try { Date d(0,0,0); // Invalid

fail_("Invalid date undetected in Date int ctor"); } catch(Date::DateError&) { succeed_(); } try { Date d(""); // Invalid

fail_("Invalid date undetected in Date string ctor"); } catch(Date::DateError&) { succeed_(); } }

1729 1730 1731

En ambos casos, si una excepcin no se lanza, es un error. Fjese que debe pasar manualmente un mensaje a fail_( ), pues no se est evaluando una expresin booleana.

1732 1733 1734 1735 1736 1737 1738 1739 1740

3.2.3. Suites de test


Los proyectos reales contienen normalmente muchas clases, por lo tanto necesita un modo para agrupar pruebas para que pueda simplemente pulsar un solo botn para probar el proyecto entero.[28] La clase Suite recoge pruebas en una unidad funcional. Aada objetos Test a Suite con la funcin addTest( ), o puede incluir una suite existente entera con addSuite( ). Para ilustrar, el siguiente ejemplo rena los programas del Captulo 3 que usa la clase Test en una sola suite. Fjese que este fichero aparecer en el subdirectorio del Captulo 3:

65

1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765

//: C03:StringSuite.cpp //{L} ../TestSuite/Test ../TestSuite/Suite //{L} TrimTest // Illustrates a test suite for code from Chapter 3 #include <iostream> #include "../TestSuite/Suite.h" #include "StringStorage.h" #include "Sieve.h" #include "Find.h" #include "Rparse.h" #include "TrimTest.h" #include "CompStr.h" using namespace std; using namespace TestSuite;

int main() { Suite suite("String Tests"); suite.addTest(new StringStorageTest); suite.addTest(new SieveTest); suite.addTest(new FindTest); suite.addTest(new RparseTest); suite.addTest(new TrimTest); suite.addTest(new CompStrTest); suite.run(); long nFail = suite.report();

66

1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786

suite.free(); return nFail; } /* Output: s1 = 62345 s2 = 12345 Suite "String Tests" ==================== Test "StringStorageTest": Passed: 2 Failed: 0

Test "SieveTest": Passed: 50 Failed: 0

Test "FindTest": Passed: 9 Failed: 0

Test "RparseTest": Passed: 8 Failed: 0

Test "TrimTest": Passed: 11 Failed: 0

Test "CompStrTest": Passed: 8 Failed: 0

*/ ///:~

1787 1788 1789 1790 1791

Listado 3.7. C03/StringSuite.cpp 5 de los tests de ms arriba estn completamente contenidos en los ficheros de cabecera. TrimTest no lo est, porque contiene datos estticos que deben estar definidos en un fichero de implementacin. Las dos

67
1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 primeras lneas de salida son trazos de la prueba StringStorage. Debe dar a la suite un nombre como argumento del constructor. La funcin Suite::run( ) llama a Test::run( ) po cada una de las pruebas que tiene. Ms de lo mismo pasa con Suite::report( ), excepto que puede enviar los informes de pruebas individuales a cadenas destinaciones diferentes mejor que el informe de la suite. Si la prueba pasa a addSuite( ) ya tiene un puntero de cadena asignado, que lo guarda. En otro caso, obtiene su cadena del objeto Suite. (Como con Test, hay un segundo argumento opcional para el constructor suite que no se presenta a std::cout.) El destructor para Suite no borra automticamente los punteros contenidos en Test porque no necesitan residir en la pila; este es el trabajo de Suite::free( ).

1803 1804 1805 1806 1807

3.2.4. El cdigo del framework de prueba


El cdigo del framework de pruebas es un subdirectorio llamado TestSuite en la distribucin de cdigo disponible en www.MindView.net. Para usarlo, incluya la ruta de bsqueda para el subdirectorio TestSuite en la ruta de bsqueda de la biblioteca. Aqu est la cabecera para Test.h:

1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819

//: TestSuite:Test.h #ifndef TEST_H #define TEST_H #include <string> #include <iostream> #include <cassert> using std::string; using std::ostream; using std::cout;

// fail_() has an underscore to prevent collision with // ios::fail(). For consistency, test_() and succeed_()

68

1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844

// also have underscores.

#define test_(cond) \ do_test(cond, #cond, __FILE__, __LINE__) #define fail_(str) \ do_fail(str, __FILE__, __LINE__)

namespace TestSuite {

class Test { ostream* osptr; long nPass; long nFail; // Disallowed: Test(const Test&); Test& operator=(const Test&); protected: void do_test(bool cond, const string& lbl, const char* fname, long lineno); void do_fail(const string& lbl, const char* fname, long lineno); public: Test(ostream* osptr = &cout) { this->osptr = osptr; nPass = nFail = 0;

69

1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858

} virtual ~Test() {} virtual void run() = 0; long getNumPassed() const { return nPass; } long getNumFailed() const { return nFail; } const ostream* getStream() const { return osptr; } void setStream(ostream* osptr) { this->osptr = osptr; } void succeed_() { ++nPass; } long report() const; virtual void reset() { nPass = nFail = 0; } };

} // namespace TestSuite #endif // TEST_H ///:~

1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871

Listado 3.8. Hay tres funciones virtuales en la clase Test: Un destructor virtual La funcin reset( ) La funcin virtual pura run( ) Como se explic en el Volumen 1, es un error eliminar un objeto derivado de la pila a travs de un puntero base a menos que la clase base tenga un destructor virtual. Cualquier clase propuesta para ser una clase base (normalmente evidenciadas por la presencia de al menos una de las otras funciones virtuales) tendra un destructor virtual. La implementacin por defecto de Test::reset( ) pone los contadores de xitos y fallos a cero. Podra querer sobreescribir esta funcin para poner el estado de los datos en su

70
1872 1873 1874 1875 1876 1877 1878 1879 1880 objeto de test derivado; slo asegrese de llamar a Test::rest( ) explcitamente en su sobreescritura de modo que los contadores se reajusten. La funcin Test::run( ) es virtual pura ya que es necesario para sobreescribirla en su clase derivada. Las macros test_( ) y fail_( ) pueden incluir la informacin disponible del nombre del fichero y el nmero de lnea del preprocesador. Originalmente omitimos el guin bajo en los nombres, pero la macro fail colisiona con ios::fail( ), provocando errores de compilacin. Aqu est la implementacin del resto de las funciones Test:

1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898

//: TestSuite:Test.cpp {O} #include "Test.h" #include <iostream> #include <typeinfo> using namespace std; using namespace TestSuite;

void Test::do_test(bool cond, const std::string& lbl, const char* fname, long lineno) { if(!cond) do_fail(lbl, fname, lineno); else succeed_(); }

void Test::do_fail(const std::string& lbl, const char* fname, long lineno) { ++nFail;

71

1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914

if(osptr) { *osptr << typeid(*this).name() << "failure: (" << lbl << ") , " << fname << " (line " << lineno << ")" << endl; } }

long Test::report() const { if(osptr) { *osptr << "Test \"" << typeid(*this).name() << "\":\n\tPassed: " << nPass << "\tFailed: " << nFail << endl; } return nFail; } ///:~

1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925

Listado 3.9. La clase Test lleva la cuenta del nmero de xitos y fracasos adems de la cadena donde quiere que Test::report( ) muestre los resultados. Las macros test_( ) y fail_() extraen la informacin del nombre del fichero actual y el nmero de lnea del preprocesador y pasa el nombre del fichero a do_test( ) y el nmero de lnea a do_fail( ), que hacen el mismo trabajo de mostrar un mensaje y actualizar el contador apropiado. No podemos pensar una buena razn para permitir copiar y asignar objetos de prueba, por lo que hemos rechazado estas operaciones para hacer sus prototipos privados y omitir el cuerpo de sus respectivas funciones.

72
1926 Aqu est el fichero de cabecera para Suite:

1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950

//: TestSuite:Suite.h #ifndef SUITE_H #define SUITE_H #include <vector> #include <stdexcept> #include "../TestSuite/Test.h" using std::vector; using std::logic_error;

namespace TestSuite {

class TestSuiteError : public logic_error { public: TestSuiteError(const string& s = "") : logic_error(s) {} };

class Suite { string name; ostream* osptr; vector<Test*> tests; void reset(); // Disallowed ops: Suite(const Suite&);

73

1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966 1967 1968

Suite& operator=(const Suite&); public: Suite(const string& name, ostream* osptr = &cout) : name(name) { this->osptr = osptr; } string getName() const { return name; } long getNumPassed() const; long getNumFailed() const; const ostream* getStream() const { return osptr; } void setStream(ostream* osptr) { this->osptr = osptr; } void addTest(Test* t) throw(TestSuiteError); void addSuite(const Suite&); void run(); // Calls Test::run() repeatedly long report() const; void free(); // Deletes tests };

} // namespace TestSuite #endif // SUITE_H ///:~

1969 1970 1971 1972 1973 1974 1975 1976

Listado 3.10. La clase Suite tiene punteros a sus objetos Test en un vector. Fjese en la especificacin de la excepcin en la funcin addTest( ). Cuando aada una prueba a una suite, Suite::addTest( ) verifique que el puntero que pasa no sea null; si es null, se lanza una excepcin TestSuiteError. Puesto que esto hace imposible aadir un puntero null a una suite, addSuite( ) afirma esta condicin en cada prueba, como hacen las otras funciones que atraviesan el

74
1977 1978 vector de pruebas (vea la siguiente implementacin). Copiar y asignar estn desestimados como estn en la clase Test.

1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001

//: TestSuite:Suite.cpp {O} #include "Suite.h" #include <iostream> #include <cassert> #include <cstddef> using namespace std; using namespace TestSuite;

void Suite::addTest(Test* t) throw(TestSuiteError) { // Verify test is valid and has a stream: if(t == 0) throw TestSuiteError("Null test in Suite::addTest"); else if(osptr && !t->getStream()) t->setStream(osptr); tests.push_back(t); t->reset(); }

void Suite::addSuite(const Suite& s) { for(size_t i = 0; i < s.tests.size(); ++i) { assert(tests[i]); addTest(s.tests[i]); }

75

2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026

void Suite::free() { for(size_t i = 0; i < tests.size(); ++i) { delete tests[i]; tests[i] = 0; } }

void Suite::run() { reset(); for(size_t i = 0; i < tests.size(); ++i) { assert(tests[i]); tests[i]->run(); } }

long Suite::report() const { if(osptr) { long totFail = 0; *osptr << "Suite \"" << name << "\"\n======="; size_t i; for(i = 0; i < name.size(); ++i) *osptr << '=';

76

2027 2028 2029 2030 2031 2032 2033 2034 2035 2036 2037 2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 2051

*osptr << "=" << endl; for(i = 0; i < tests.size(); ++i) { assert(tests[i]); totFail += tests[i]->report(); } *osptr << "======="; for(i = 0; i < name.size(); ++i) *osptr << '='; *osptr << "=" << endl; return totFail; } else return getNumFailed(); }

long Suite::getNumPassed() const { long totPass = 0; for(size_t i = 0; i < tests.size(); ++i) { assert(tests[i]); totPass += tests[i]->getNumPassed(); } return totPass; }

long Suite::getNumFailed() const {

77

2052 2053 2054 2055 2056 2057 2058 2059 2060 2061 2062 2063 2064 2065

long totFail = 0; for(size_t i = 0; i < tests.size(); ++i) { assert(tests[i]); totFail += tests[i]->getNumFailed(); } return totFail; }

void Suite::reset() { for(size_t i = 0; i < tests.size(); ++i) { assert(tests[i]); tests[i]->reset(); } } ///:~

2066 2067 2068 2069

Listado 3.11. Usaremos el framework TestSuite donde sea pertinente a lo largo del resto de este libro.

2070 2071 2072 2073 2074 2075

3.3. Tcnicas de depuracin


La mejor costumbre para eliminar fallos es usar aserciones como se explica al principio de este captulo; haciendo esto le ayudar a encontrar errores lgicos antes de que causen problemas reales. Esta seccin contiene otros consejos y tcnicas que podran ayudar durante la depuracin.

3.3.1. Macros de seguimiento

78
2076 2077 2078 2079 2080 2081 2082 2083 2084 2085 2086 2087 2088 2089 2090 2091 2092 2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 Algunas veces es til imprimir el cdigo de cada sentencia cuando es ejecutada, o cout o trazar un fichero. Aqu esta una macro de preprocesaor para llevar a cabo esto: #define TRACE(ARG) cout << #ARG << endl; ARG Ahora puede ir a travs y alrededor de las sentencias que trace con esta macro. Sin embargo, esto puede introducir problemas. Por ejemplo, si coge la sentencia: for(int i = 0; i < 100; i++) cout << i << endl; y ponga ambas lneas dentro de la macro TRACE( ), obtiene esto: TRACE(for(int i = 0; i < 100; i++)) TRACE( cout << i << endl;) que se expande a esto: cout << "for(int i = 0; i < 100; i++)" << endl; for(int i = 0; i < 100; i++) cout << "cout << i << endl;" << endl; cout << i << endl; que no es exactamente lo que quiere. Por lo tanto, debe usar esta tcnica cuidadosamente. Lo siguiente es una variacin en la macro TRACE( ): #define D(a) cout << #a "=[" << a << "]" << endl; Si quiere mostrar una expresin, simplemente pngala dentro de una llamada a D( ). La expresin se muestra, seguida de su valor ( asumiendo que hay un operador sobrecargado << para el tipo de resultado). Por ejemplo, puede decir D(a + b). Puede usar esta macro en cualquier momento que quiera comprobar un valor intermedio. Estas dos macros representan las dos cosas fundamentales que hace con un depurador: trazar la ejecucin de cdigo y mostrar valores. Un buen depurador es una herramienta de productividad excelente, pero a veces los depuradores no estn disponibles, o no es conveniente usarlos. Estas tcnicas siempre funcionan, sin tener en cuenta la situacin.

2103 2104 2105

3.3.2. Fichero de rastro


ADVERTENCIA: Esta seccin y la siguiente contienen cdigo que est oficialmente sin aprobacin por el Estndar C++. En particular, redefinimos

79
2106 2107 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 cout y new mediante macros, que puede provocar resultados sorprendentes si no tiene cuidado. Nuestros ejemplos funcionan en todos los compiladores que usamos, comoquiera, y proporcionan informacin til. Este es el nico lugar en este libro donde nos desviaremos de la inviolabilidad de la prctica de codificar cumpliendo el estndar. salo bajo tu propio riesgo! Dese cuenta que para este trabajo, usar delcaraciones debe ser realizado, para que cout no est prefijado por su nombre de espacio, p.e. std::cout no funcionar. El siguiente cdigo crea fcilmente un fichero de seguimiento y enva todas las salidas que iran normalmente a cout a ese fichero. Todo lo que debe hacer es #define TRACEON e incluir el fichero de cabecera (por supuesto, es bastante fcil slo escribir las dos lneas claves correctamente en su fichero):

2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129

//: C03:Trace.h // Creating a trace file. #ifndef TRACE_H #define TRACE_H #include <fstream>

#ifdef TRACEON std::ofstream TRACEFILE__("TRACE.OUT"); #define cout TRACEFILE__ #endif

#endif // TRACE_H ///:~

2130 2131 2132

Listado 3.12. C03/Trace.h Aqu esta una prueba sencilla del fichero anterior:

80

2133 2134 2135 2136 2137 2138 2139 2140 2141 2142 2143 2144 2145 2146

//: C03:Tracetst.cpp {-bor} #include <iostream> #include <fstream> #include "../require.h" using namespace std;

#define TRACEON #include "Trace.h"

int main() { ifstream f("Tracetst.cpp"); assure(f, "Tracetst.cpp"); cout << f.rdbuf(); // Dumps file contents to file } ///:~

2147 2148 2149 2150 2151 2152 2153

Listado 3.13. C03/Tracetst.cpp Porque cout ha sido textualmente convertido en algo ms por Trace.h, todas las sentencias cout en su programa ahora envan informacin al fichero de seguimiento. Esto es una forma conveniente de capturar su salida en un fichero, en caso de que su sistema operativo no haga una fcil redireccin de la salida.

2154 2155 2156 2157 2158

3.3.3. Encontrar agujeros en memoria


Las siguientes tcnicas sencillas de depuracin estn explicadas en el Volumen 1: 1. Para comprobar los lmites de un array, usa la plantilla Array en C16:Array3.cpp del Volumen 1 para todos los arrays. Puede desactivar la

81
2159 2160 2161 2162 2163 2164 2165 2166 2167 2168 2169 2170 2171 2172 2173 2174 2175 2176 2177 2178 2179 2180 2181 2182 2183 2184 2185 2186 2187 2188 2189 comprobacin e incrementar la eficiencia cuando est listo para enviar. (Aunque esto no trata con el caso de coger un puntero a un array.) 2. Comprobar destructores no virtuales en clases base. Seguirle la pista a new/delete y malloc/free Los problemas comunes con la asignacin de memoria incluyen llamadas por error a delete para memoria que no est libre, borrar el espacio libre ms de una vez, y ms a menudo, olvidando borrar un puntero. Esta seccin discute un sistema que puede ayudarle a localizar estos tipos de problemas. Como clusula adicional de exencin de responsabilidad ms all de la seccin precedente: por el modo que sobrecargamos new, la siguiente tcnica puede no funcionar en todas las plataformas, y funcionar slo para programas que no llaman explicitamente al operador de funcin new( ). Hemos sido bastante cuidadosos en este libro para presentar slo cdigo que se ajuste completamente al Estndar C++, pero en este ejemplo estamos haciendo una excepcin por las siguientes razones: 1. A pesar de que es tcnicamente ilegal, funciona en muchos compiladores.[29] 2. Ilustramos algunos pensamientos tiles en el trascurso del camino. Para usar el sistema de comprobacin de memoria, simplemente incluya el fichero de cabecera MemCheck.h, conecte el fichero MemCheck.obj a su aplicacin para interceptar todas las llamadas a new y delete, y llame a la macro MEM_ON( ) (se explica ms tarde en esta seccin) para iniciar el seguimiento de la memoria. Un seguimiento de todas las asignaciones y desasignaciones es impreso en la salida estndar (mediante stdout). Cuando use este sistema, todas las llamadas a new almacenan informacin sobre el fichero y la lnea donde fueron llamados. Esto est dotado usando la sintaxis de colocacin para el operador new.[30] Aunque normalmente use la sintaxis de colocacin cuando necesite colocar objetos en un punto de memoria especfico, puede tambin crear un operador new( ) con cualquier nmero de argumentos. Esto se usa en el siguiente ejemplo para almacenar los resultados de las macros __FILE__ y __LINE__ cuando se llama a new:

2190

//: C02:MemCheck.h

82

2191 2192 2193 2194 2195 2196 2197 2198 2199 2200 2201 2202 2203 2204 2205 2206 2207 2208

#ifndef MEMCHECK_H #define MEMCHECK_H #include <cstddef> // For size_t

// Usurp the new operator (both scalar and array versions) void* operator new(std::size_t, const char*, long); void* operator new[](std::size_t, const char*, long); #define new new (__FILE__, __LINE__)

extern bool traceFlag; #define TRACE_ON() traceFlag = true #define TRACE_OFF() traceFlag = false

extern bool activeFlag; #define MEM_ON() activeFlag = true #define MEM_OFF() activeFlag = false

#endif // MEMCHECK_H ///:~

2209 2210 2211 2212 2213 2214 2215 2216 2217

Listado 3.14. C02/MemCheck.h Es importante incluir este fichero en cualquier fichero fuente en el que quiera seguir la actividad de la memoria libre, pero inclyalo al final (despus de sus otras directivas #include). La mayora de las cabeceras en la biblioteca estndar son plantillas, y puesto que la mayora de los compiladores usan el modelo de inclusin de compilacin de plantilla (significa que todo el cdigo fuente est en las cabeceras), la macro que reemplaza new en MemCheck.h usurpar todas las instancias del operador

83
2218 2219 2220 2221 2222 2223 2224 2225 new en el cdigo fuente de la biblioteca (y casi resultara en errores de compilacin). Adems, est slo interesado en seguir sus propios errores de memoria, no los de la biblioteca. En el siguiente fichero, que contiene la implementacin del seguimiento de memoria, todo est hecho con C estndar I/O ms que con iostreams C++. No debera influir, puesto que no estamos interfiriendo con el uso de iostream en la memoria libre, pero cuando lo intentamos, algunos compiladores se quejaron. Todos los compiladores estaban felices con la versin <cstdio>.

2226 2227 2228 2229 2230 2231 2232 2233 2234 2235 2236 2237 2238 2239 2240 2241 2242 2243 2244

//: C02:MemCheck.cpp {O} #include <cstdio> #include <cstdlib> #include <cassert> #include <cstddef> using namespace std; #undef new

// Global flags set by macros in MemCheck.h bool traceFlag = true; bool activeFlag = false;

namespace {

// Memory map entry type struct Info { void* ptr; const char* file; long line;

84

2245 2246 2247 2248 2249 2250 2251 2252 2253 2254 2255 2256 2257 2258 2259 2260 2261 2262 2263 2264 2265 2266 2267 2268 2269

};

// Memory map data const size_t MAXPTRS = 10000u; Info memMap[MAXPTRS]; size_t nptrs = 0;

// Searches the map for an address int findPtr(void* p) { for(size_t i = 0; i < nptrs; ++i) if(memMap[i].ptr == p) return i; return -1; }

void delPtr(void* p) { int pos = findPtr(p); assert(pos >= 0); // Remove pointer from map for(size_t i = pos; i < nptrs-1; ++i) memMap[i] = memMap[i+1]; --nptrs; }

// Dummy type for static destructor

85

2270 2271 2272 2273 2274 2275 2276 2277 2278 2279 2280 2281 2282 2283 2284 2285 2286 2287 2288 2289 2290 2291 2292 2293 2294

struct Sentinel { ~Sentinel() { if(nptrs > 0) { printf("Leaked memory at:\n"); for(size_t i = 0; i < nptrs; ++i) printf("\t%p (file: %s, line %ld)\n", memMap[i].ptr, memMap[i].file, memMap[i].line); } else printf("No user memory leaks!\n"); } };

// Static dummy object Sentinel s;

} // End anonymous namespace

// Overload scalar new void* operator new(size_t siz, const char* file, long line) { void* p = malloc(siz); if(activeFlag) { if(nptrs == MAXPTRS) { printf("memory map too small (increase MAXPTRS)\n");

86

2295 2296 2297 2298 2299 2300 2301 2302 2303 2304 2305 2306 2307 2308 2309 2310 2311 2312 2313 2314 2315 2316 2317 2318 2319

exit(1); } memMap[nptrs].ptr = p; memMap[nptrs].file = file; memMap[nptrs].line = line; ++nptrs; } if(traceFlag) { printf("Allocated %u bytes at address %p ", siz, p); printf("(file: %s, line: %ld)\n", file, line); } return p; }

// Overload array new void* operator new[](size_t siz, const char* file, long line) { return operator new(siz, file, line); }

// Override scalar delete void operator delete(void* p) { if(findPtr(p) >= 0) { free(p); assert(nptrs > 0);

87

2320 2321 2322 2323 2324 2325 2326 2327 2328 2329 2330 2331

delPtr(p); if(traceFlag) printf("Deleted memory at address %p\n", p); } else if(!p && activeFlag) printf("Attempt to delete unknown pointer: %p\n", p); }

// Override array delete void operator delete[](void* p) { operator delete(p); } ///:~

2332 2333 2334 2335 2336 2337 2338 2339 2340 2341 2342 2343 2344 2345 2346 2347 2348

Listado 3.15. C02/MemCheck.cpp Las banderas booleanas de traceFalg y activeFlag son globales, por lo que pueden ser modificados en su cdigo por las macros TRACE_ON( ), TRACE_OFF( ), MEM_ON( ), y MEM_OFF( ). En general, encierre todo el cdigo en su main( ) dentro una pareja MEM_ON( )-MEM_OFF( ) de modo que la memoria sea siempre trazada. Trazar, que repite la actividad de las funciones de sustitucin por el operador new( ) y el operador delete( ), es por defecto, pero puede desactivarlo con TRACE_OFF( ). En cualquier caso, los resultados finales son siempre impresos (vea la prueba que se ejecuta ms tarde en este captulo). La facilidad MemCheck rastrea la memoria guardando todas las direcciones asignadas por el operador new( ) en un array de estructuras Info, que tambin tiene el nombre del fichero y el nmero de lnea donde la llamada new se encuentra. Para prevenir la colisin con cualquier nombre que haya colocado en el espacio de nombres global, tanta informacin como sea posible se guarda dentro del espacio de nombre annimo. La clase

88
2349 2350 2351 2352 2353 2354 2355 2356 2357 2358 2359 2360 2361 2362 2363 2364 2365 2366 Sentinel existe nicamente para llamar a un destructor de objetos con esttico cuando el programa termina. Este destructor inspecciona memMap para ver si algn puntero est esperando a ser borrado (indicando una perdida de memoria). Nuestro operador new( ) usa malloc( ) para conseguir memoria, y luego aade el puntero y su informacin de fichero asociado a memMap. La funcin de operador delete( ) deshace todo el trabajo llamando a free( ) y decrementando nptrs, pero primero se comprueba para ver si el puntero en cuestin est en el mapa en el primer lugar. Si no es as, o reintenta borrar una direccin que no est en el almacn libre, o re intenta borrar la que ya ha sido borrada y eliminada del mapa. La variable activeFlag es importante aqu porque no queremos procesar ninguna desasignacin de alguna actividad del cierre del sistema. Llamando a MEM_OFF( ) al final de su cdigo, activeFlag ser puesta a falso, y posteriores llamadas para borrar sern ignoradas. (Est mal en un programa real, pero nuestra intencin aqu es encontrar agujeros, no est depurando la biblioteca.) Por simplicidad, enviamos todo el trabajo por array new y delete a sus homlogos escalares. Lo siguiente es un test sencillo usando la facilidad MemCheck:

2367 2368 2369 2370 2371 2372 2373 2374 2375 2376 2377

//: C02:MemTest.cpp //{L} MemCheck // Test of MemCheck system. #include <iostream> #include <vector> #include <cstring> #include "MemCheck.h" // Must appear last!

using namespace std;

class Foo { char* s;

89

2378 2379 2380 2381 2382 2383 2384 2385 2386 2387 2388 2389 2390 2391 2392 2393 2394 2395 2396 2397 2398 2399

public: Foo(const char*s ) { this->s = new char[strlen(s) + 1]; strcpy(this->s, s); } ~Foo() { delete [] s; } };

int main() { MEM_ON(); cout << "hello" << endl; int* p = new int; delete p; int* q = new int[3]; delete [] q; int* r; delete r; vector<int> v; v.push_back(1); Foo s("goodbye"); MEM_OFF(); } ///:~

2400 2401

Listado 3.16. C02/MemTest.cpp

90
2402 2403 2404 2405 2406 2407 2408 2409 2410 2411 2412 2413 2414 2415 2416 2417 2418 2419 Este ejemplo verifica que puede usar MemCheck en presencia de streams, contenedores estndar, y clases que asignan memoria en constructores. Los punteros p y q son asignados y desasignados sin ningn problema, pero r no es un puntero de pila vlido, as que la salida indica el error como un intento de borrar un puntero desconocido: hola Asignados 4 bytes en la direccin 0xa010778 (fichero: memtest.cpp, lnea: 25) Deleted memory at address 0xa010778 Asignados 12 bytes en la direccin 0xa010778 (fichero: memtest.cpp, lnea: 27) Memoria borrada en la direccin 0xa010778 Intento de borrar puntero desconocido: 0x1 Asignados 8 bytes en la direccin 0xa0108c0 (fichero: memtest.cpp, lnea: 14) Memoria borrada en la direccin 0xa0108c0 No hay agujeros de memoria de usuario! A causa de la llamada a MEM_OFF( ), no se procesan posteriores llamadas al operador delete( ) por vector o ostream. Todava podra conseguir algunas llamadas a delete realizadas dsede reasignaciones por los contenedores. Si llama a TRACE_OFF( ) al principio del programa, la salida es Hola Intento de borrar puntero desconocido: 0x1 No hay agujeros de memoria de usuario!

2420 2421 2422 2423 2424 2425 2426 2427 2428 2429 2430 2431

3.4. Resumen
Muchos de los dolores de cabeza de la ingenera del software pueden ser evitados reflexionando sobre lo que est haciendo. Probablemente ha estado usando aserciones mentales cuando ha navegado por sus blucles y funciones, incluso si no ha usado rutinariamente la macro assert( ). Si usa assert( ), encontrar errores lgicos ms pronto y acabar con cdigo ms legible tambin. Recuerde usar solo aserciones para invariantes, aunque, no para el manejo de error en tiempo de ejecucin. Nada le dar ms tranquilidad que cdigo probado rigurosamente. Si ha sido un lo en el pasado, use un framework automatizado, como el que hemos presentado aqu, para integrar la rutina de pruebas en su trabajo diario. Usted (y sus usarios!) estarn contentos de que lo haga.

91
2432 2433 2434 2435 2436 2437 2438 2439 2440 2441 2442 2443 2444 2445 2446 2447 2448 2449

3.5. Ejercicios
Las soluciones para ejercicios seleccionados pueden encontrarse en el documento electrnico Pensar en C++ Volumen 2 Gua de Soluciones Comentadas disponible por una pequea cuota en www.MindView.net. 1. Escriba un programa de prueba usando el Framework TestSuite para la clase estndar vector que prueba rigurosamente prueba las siguientes funciones con un vector de enteros: push_back( ) (aade un elemento al final del vector) front( ) (devuelve el primer elemento en el vector), back( ) (devuelve el ltimo elemento en el vector), pop_back( ) (elimina el ltimo elemento sin devolverlo), at( ) (devuelve el elemento en una posicin especfica), y size( ) (devuelve el nmero de elementos). Asegrese de verificar que vector::at( ) lanza una excepcin std::out_of_range si el ndice facilitado est fuera de rango. 2. Supngase que le piden desarrollar un clase llamada Rational que da soporte a nmeros racionales (fracciones). La fraccin en un objecto Rational debera siempre almacenarse en los trminos ms bajos, y un denominador de cero es un error. Aqu est una interfaz de ejemplo para esa clase Rational:

2450 2451 2452 2453 2454 2455 2456 2457 2458 2459 2460

//: C02:Rational.h {-xo} #ifndef RATIONAL_H #define RATIONAL_H #include <iosfwd>

class Rational { public: Rational(int numerator = 0, int denominator = 1); Rational operator-() const; friend Rational operator+(const Rational&, const Rational&);

92

2461 2462 2463 2464 2465 2466 2467 2468 2469 2470 2471 2472 2473 2474 2475 2476 2477 2478 2479 2480 2481 2482 2483 2484 2485

friend Rational operator-(const Rational&, const Rational&); friend Rational operator*(const Rational&, const Rational&); friend Rational operator/(const Rational&, const Rational&); friend std::ostream& operator<<(std::ostream&, const Rational&); friend std::istream& operator>>(std::istream&, Rational&); Rational& operator+=(const Rational&); Rational& operator-=(const Rational&); Rational& operator*=(const Rational&); Rational& operator/=(const Rational&); friend bool operator<(const Rational&, const Rational&); friend bool operator>(const Rational&, const Rational&); friend bool operator<=(const Rational&, const Rational&); friend bool operator>=(const Rational&, const Rational&); friend bool operator==(const Rational&, const Rational&); friend bool operator!=(const Rational&,

93

2486 2487 2488

const Rational&); }; #endif // RATIONAL_H ///:~

2489 2490 2491 2492 2493 2494 2495 2496 2497 2498 2499 2500 2501

Listado 3.17. C02/Rational.h Escriba una especificacin completa para esta clase, incluyendo especificaciones de precondiciones, postcondiciones, y de excepcin. 3. Escriba un prueba usando el framework TestSuite que pruebe rigurosamente todas las especificaciones del ejercicio anterior, incluyendo probar las excepciones. 4. Implemente la clase Rational de modo que pase todas las pruebas del ejercicio anterior. Use aserciones slo para las invariantes. 5. El fichero BuggedSearch.cpp de abajo contiene un funcin de bsqueda binaria que busca para el rango [pedir, final). Hay algunos errores en el algoritmo. Use las tcnicas de seguimiento de este captulo para depurar la funcin de bsqueda.

2502 2503 2504 2505 2506 2507 2508 2509 2510 2511 2512 2513 2514

Parte II. La librera Estndar de C++


El C++ Estndar no solo incorpora todas las libreras de Estndar C (con pequeos aadidos y cambios para permitir tipos seguros), tambin aade sus propias libreras. Estas libreras son mucho ms potentes que las de C. La mejora al usarlas es anloga a la que se consigue al cambiar de C a C++.

Esta seccin del libro le da una introduccin en profundidad a las partes clave de la librera Estndar de C++. La referencia ms completa y tambin la ms oscura para las libreras es el propio Estndar. The C++ Programming Language, Third Edition (Addison Wesley, 2000) de Bjarne Stroustrup sigue siendo una referencia fiable tanto para el lenguaje como para la librera. La referencia ms aclamada en cuanto a la librera es The C++ Standard Library: A Tutorial and Reference, by

94
2515 2516 2517 2518 2519 2520 2521 2522 2523 2524 2525 2526 2527 2528 2529 2530 2531 2532 2533 2534 2535 2536 2537 2538 2539 2540 2541 2542 2543 2544 2545 2546 2547 2548 Nicolai Josuttis (Addison Wesley, 1999). El objetivo de los captulos de esta parte del libro es ofrecer un catlogo de descripciones y ejemplos para que disponga de un buen punto de partida para resolver cualquier problema que requiera el uso de las libreras Estndar. Sin embargo, algunas tcnicas y temas se usan poco y no se tratan aqu. Si no puede encontrar algo en estos captulos, mire en los dos libros que se citan anteriormente; esto libro no pretende reemplazarlos, ms bien completarlos. En particular, esperamos que despus de consular el material de los siguientes captulos pueda comprender mejor esos libros. El lector notar que estos captulos no contienen documentacin exhaustiva describiendo cada funcin o clase del la Librera Estndar C++. Hemos dejado las descripciones completas a otros; en particular a Dinkumware C/C++ Library Reference de P.J. Plauger. Esta es una excelente documentacin que puede puede ver con un navegador web cada vez que necesite buscar algo. Puede verla on-line o comprarla para verla en local. Contiene una referencia completa para las libreras de C y C++ (de modo que es buena para cualquier cuestin de programacin en C/C++ Estndar). La documentacin electrnica no slo es efectiva porque pueda tenerla siempre a mano, sino porque tambin puede hacer bsquedas electrnicas. Cuando usted est programando activamente, estos recursos deberan satisfacer sus necesidades de referencias (y puede usarlas para buscar algo de este captulo que no tenga claro). El Apndice A incluye referencias adicionales. El primer captulo de esta seccin introduce la clase string del Estndar C++, que es una herramienta potente que simplifica la mayora de las tareas de procesamiento de texto que podra tener que realizar. Casi cualquier cosas que tenga hecho para cadenas de caracteres en C puede hacerse con una llamada a un mtodo de la clase string. El captulo 4 cubre la librera iostreams, que contiene clases para procesar entrada y salida con ficheros, cadenas, y la consola del sistema. Aunque el Captulo 5: Las plantillas a fondo no es explcitamente un captulo de la librera, es una preparacin necesaria para los dos siguientes captulos. En el captulo 6 examinaremos los algoritmos genricos que ofrece

95
2549 2550 2551 2552 2553 2554 2555 2556 2557 2558 2559 2560 2561 2562 2563 2564 2565 2566 2567 2568 2569 2570 2571 2572 2573 2574 2575 2576 2577 2578 2579 2580 2581 2582 2583 2584 2585 2586 2587 2588 2589 2590 2591 2592 2593 la librera Estndar C++. Como estn implementados con plantillas, esos algoritmos se pueden aplicar a cualquier secuencia de objetos. El Captulo 7 cubre los contenedores estndar y sus iteradores asociados Vemos los algoritmos primero porque se pueden utilizar usando nicamente arrays y el contenedor vector (que vimos en el Volumen 1). Tambin es normal el uso de algoritmos estndar junto con contenedores, y es bueno que le resulten familiares antes de estudiar los contenedores. Tabla de contenidos 4. Las cadenas a fondo 4.1. Qu es un string? 4.2. Operaciones con cadenas 4.3. Buscar en cadenas 4.4. Una aplicacin con cadenas 4.5. Resumen 4.6. Ejercicios 5. Iostreams 5.1. Por que iostream? 5.2. Iostreams al rescate 5.3. Manejo errores de stream 5.4. Iostreams de fichero 5.5. Almacenamiento de iostream 5.6. Buscar en iostreams 5.7. Iostreams de string 5.8. Formateo de stream de salida 5.9. Manipuladores 5.10. 5.11. 5.12. 5.13. 6. Las plantillas en profundidad 6.1. 6.2. 6.3. 6.4. 6.5. 6.6. 6.7. 6.8. 7. Algoritmos genricos 7.1. Un primer vistazo 7.2. Objetos-funcin 7.3. Un catlogo de algoritmos STL 7.4. Creando sus propios algoritmos tipo STL 7.5. Resumen 7.6. Ejercicios

96

2594 2595 2596 2597 2598 2599 2600 2601 2602 2603 2604 2605 2606 2607 2608 2609 2610 2611 2612 2613 2614 2615 2616 2617 2618 2619 2620 2621 2622 2623 2624 2625 2626 2627

4: Las cadenas a fondo


Tabla de contenidos 4.1. Qu es un string? 4.2. Operaciones con cadenas 4.3. Buscar en cadenas 4.4. Una aplicacin con cadenas 4.5. Resumen 4.6. Ejercicios El procesamiento de cadenas de caracteres en C es una de las mayores
prdidas de tiempo. Las cadenas de caracteres requieren que el programador tenga en cuenta las diferencias entre cadenas estticas y las cadenas creadas en la pila y en el montn, adems del hecho que a veces pasa como argumento un char* y a veces hay que copiar el arreglo entero.

Precisamente porque la manipulacin de cadenas es muy comn, las cadenas de caracteres son una gran fuente de confusiones y errores. Es por ello que la creacin de clases de cadenas sigue siendo desde hace aos un ejercicio comn para programadores novatos. La clase string de la biblioteca estndar de C++ resuelve el problema de la manipulacin de caracteres de una vez por todas, gestionando la memoria incluso durante las asignaciones y las construcciones de copia. Simplemente no tiene que preocuparse por ello. Este captulo[1] examina la clase string del Estndar C++; empieza con un vistazo a la composicin de las string de C++ y como la versin de C++ difiere del tradicional arreglo de caracteres de C. Aprender sobre las operaciones y la manipulacin usando objetos string, y ver como stas se FIXME[acomodan a la variacin] de conjuntos de caracteres y conversin de datos. Manipular texto es una de las aplicaciones ms antiguas de la programacin, por eso no resulta sorprendente que las string de C++ estn fuertemente inspiradas en las ideas y la terminologa que ha usado continuamente en C y otros lenguajes. Conforme vaya aprendiendo sobre los
string

de C++, este hecho se debera ir viendo ms claramente. Da igual el hacer con las cadenas:

lenguaje de programacin que escoja, hay tres cosas comunes que querr

97
2628 2629 2630 2631 2632 2633 2634 2635 2636 2637 2638 2639 2640 2641 2642 2643 2644 2645 2646 2647 2648 2649 2650 2651 2652 2653 2654 2655 2656 2657 2658 2659 2660

Crear o modificar secuencias de caracteres almacenados en una cadena Detectar la presencia o ausencia de elementos dentro de la cadena Traducir entre diversos esquemas para representar cadenas de caracteres

Ver como cada una de estas tareas se resuelve usando objetos string en C++.

4.1. Qu es un string?
En C, una cadena es simplemente un arreglo de caracteres que siempre incluye un 0 binario (frecuentemente llamado terminador nulo) como elemento final del arreglo. Existen diferencias significativas entre los string de C++ y sus progenitoras en C. Primero, y ms importante, los string de C++ esconden la implementacin fsica de la secuencia de caracteres que contiene. No debe preocuparse de las dimensiones del arreglo o del terminador nulo. Un string tambin contiene cierta informacin para uso interno sobre el tamao y la localizacin en memoria de los datos. Especficamente, un objeto string de C++ conoce su localizacin en memoria, su contenido, su longitud en caracteres, y la cantidad de caracteres que puede crecer antes de que el objeto string deba redimensionar su buffer interno de datos. Las string de C++, por tanto, reducen enormemente las probabilidades de cometer uno de los tres errores de programacin en C ms comunes y destructivos: sobrescribir los lmites del arreglo, intentar acceder a un arreglo no inicializado o con valores de puntero incorrectos, y dejar punteros colgando despus de que el arreglo deje de ocupar el espacio que estaba ocupando. La implementacin exacta del esquema en memoria para una clase string no esta definida en el estndar C++. Esta arquitectura esta pensada para ser suficientemente flexible para permitir diferentes implementaciones de los fabricantes de compiladores, garantizando igualmente un comportamiento predecible por los usuarios. En particular, las condiciones exactas de cmo situar el almacenamiento para alojar los datos para un objeto string no estn definidas. FIXME: Las reglas de alojamiento de un string fueron

98
2661 2662 2663 2664 2665 2666 2667 2668 formuladas para permitir, pero no requerir, una implementacin con referencias mltiples, pero dependiendo de la implementacin usar referencias mltiples sin variar la semntica. Por decirlo de otra manera, en C, todos los arreglos de char ocupan una nica regin fsica de memoria. En C++, los objetos string individuales pueden o no ocupar regiones fsicas nicas de memoria, pero si su conjunto de referencias evita almacenar copias duplicadas de datos, los objetos individuales deben parecer y actuar como si tuvieran sus propias regiones nicas de almacenamiento.

2669 2670 2671 2672 2673 2674 2675 2676 2677 2678 2679 2680 2681 2682 2683 2684 2685 2686 2687

//: C03:StringStorage.h #ifndef STRINGSTORAGE_H #define STRINGSTORAGE_H #include <iostream> #include <string> #include "../TestSuite/Test.h" using std::cout; using std::endl; using std::string;

class StringStorageTest : public TestSuite::Test { public: void run() { string s1("12345"); // This may copy the first to the second or // use reference counting to simulate a copy: string s2 = s1; test_(s1 == s2); // Either way, this statement must ONLY modify s1:

99

2688 2689 2690 2691 2692 2693 2694

s1[0] = '6'; cout << "s1 = " << s1 << endl; cout << "s2 = " << s2 << endl; test_(s1 != s2); } }; #endif // STRINGSTORAGE_H ///:~ // 62345 // 12345

2695 2696 2697 2698 2699 2700 2701 2702 2703 2704 2705

Listado 4.1. C03/StringStorage.h Decimos que cuando una implementacin solo hace una sola copia al modificar el string usa una estrategia de copiar al escribir. Esta aproximacin ahorra tiempo y espacio cuando usamos string como parmetros por valor o en otras situaciones de solo lectura. El uso de referencias mltiples en la implementacin de una librera debera ser transparente al usuario de la clase string. Desgraciadamente, esto no es siempre el caso. En programas multihilo, es prcticamente imposible usar implementaciones con mltiples referencias de forma segura[32].[2]

2706 2707 2708 2709 2710 2711 2712 2713 2714 2715

4.2. Operaciones con cadenas


Si ha programado en C, estar acostumbrado a la familia de funciones que leen, escriben, modifican y copian cadenas. Existen dos aspectos poco afortunados en la funciones de la librera estndar de C para manipular cadenas. Primero, hay dos familias pobremente organizadas: el grupo plano, y aquellos que requieren que se les suministre el nmero de caracteres para ser consideradas en la operacin a mano. La lista de funciones en la librera de cadenas de C sorprende al usuario desprevenido con una larga lista de nombres crpticos y mayoritariamente impronunciables. Aunque el tipo y nmero de argumentos es algo consistente, para usarlas adecuadamente

100
2716 2717 2718 2719 2720 2721 2722 2723 2724 2725 2726 2727 2728 2729 2730 2731 2732 2733 2734 2735 2736 2737 2738 2739 2740 2741 2742 2743 2744 debe estar atento a los detalles de nombres de la funcin y a los parmetros que le pasas. La segunda trampa inherente a las herramientas para cadenas del estndar de C es que todas ellas explcitamente confan en la asuncin de que cada cadena incluye un terminador nulo. Si por confusin o error el terminador nulo es omitido o sobrescrito, poco se puede hacer para impedir que las funciones de cadena de C manipulen la memoria ms all de los lmites del espacio de alojamiento, a veces con resultados desastrosos. C++ aporta una vasta mejora en cuanto a conveniencia y seguridad de los objetos string. Para los propsitos de las actuales operaciones de manipulacin, existe el mismo nmero de funciones que la librera de C, pero gracias a la sobrecarga, la funcionalidad es mucho mayor. Adems, con una nomenclatura ms sensata y un acertado uso de los argumentos por defecto, estas caractersticas se combinan para hacer de la clase string mucho ms fcil de usar que la biblioteca de funciones de cadena de C.

4.2.1. Aadiendo, insertando y concatenando cadenas


Uno de los aspectos ms valiosos y convenientes de los string en C++ es que crecen cuando lo necesitan, sin intervencin por parte del programador. No solo hace el cdigo de manejo del string sea inherentemente mas confiable, adems elimina por completo las tediosas funciones "caseras" para controlar los limites del almacenamiento en donde nuestra cadena reside. Por ejemplo, si crea un objeto string e inicializa este string con 50 copias de "X", y despus copia en el 50 copias de "Zowie", el objeto, por s mismo, readecua suficiente almacenamiento para acomodar el crecimiento de los datos. Quizs en ningn otro lugar es ms apreciada esta propiedad que cuando las cadenas manipuladas por su cdigo cambian de tamao y no sabe cuan grande puede ser este cambio. La funcin miembro append() e
insert()

de string reubican de manera transparente el almacenamiento cuando un string crece:

2745 2746

//: C03:StrSize.cpp #include <string>

101

2747 2748 2749 2750 2751 2752 2753 2754 2755 2756 2757 2758 2759 2760 2761 2762 2763 2764 2765 2766 2767 2768 2769 2770

#include <iostream> using namespace std;

int main() { string bigNews("I saw Elvis in a UFO. "); cout << bigNews << endl; // How much data have we actually got? cout << "Size = " << bigNews.size() << endl; // How much can we store without reallocating? cout << "Capacity = " << bigNews.capacity() << endl; // Insert this string in bigNews immediately // before bigNews[1]: bigNews.insert(1, " thought I"); cout << bigNews << endl; cout << "Size = " << bigNews.size() << endl; cout << "Capacity = " << bigNews.capacity() << endl; // Make sure that there will be this much space bigNews.reserve(500); // Add this to the end of the string: bigNews.append("I've been working too hard."); cout << bigNews << endl; cout << "Size = " << bigNews.size() << endl; cout << "Capacity = " << bigNews.capacity() << endl; } ///:~

102
2771 2772 2773 2774 2775 2776 2777 2778 2779 2780 2781 2782 2783 2784 2785 2786 2787 2788 2789 2790 2791 2792 2793 2794 2795 2796 2797 2798 2799 2800 2801 2802 2803 2804 2805 2806 2807 Listado 4.2. C03/StrSize.cpp Aqu la salida desde un compilador cualquiera:
I saw Elvis in a UFO. Size = 22 Capacity = 31 I thought I saw Elvis in a UFO. Size = 32 Capacity = 47 I thought I saw Elvis in a UFO. I've been working too hard. Size = 59 Capacity = 511

Este ejemplo demuestra que aunque puede ignorar con seguridad muchas de las responsabilidades de reserva y gestin de la memoria que tus string ocupan, C++ provee a los string con varias herramientas para monitorizar y gestionar su tamao. Ntese la facilidad con la que hemos cambiado el tamao de la memoria reservada para los string. La funcin size() retorna el numero de caracteres actualmente almacenados en el string y es idntico a la funcin miembro lenght(). La funcin capacity() retorna el tamao de la memoria subyacente actual, es decir, el nmero de caracteres que el
string

puede almacenar sin tener que reservar ms memoria. La funcin es una optimizacin del mecanismo que indica su intencin de

reserve()

especificar cierta cantidad de memoria para un futuro uso; capacity() siempre retorna un valor al menos tan largo como la ultima llamada a
reserve().

La funcin resize() aade espacio si el nuevo tamao es mayor puede especificar una adicin diferente de caracteres).

que el tamao actual del string; sino trunca el string. (Una sobrecarga de
resize()

La manera exacta en que las funciones miembro de string reservan espacio para sus datos depende de la implementacin de la librera. Cuando testeamos una implementacin con el ejemplo anterior, parece que se hacia una reserva de una palabra de memoria (esto es, un entero) dejando un byte en blanco entre cada una de ellas. Los arquitectos de la clase string se esforzaron para poder mezclar el uso de las cadenas de caracteres de C y los objetos string, por lo que es probable por lo que se puede observar en StrSize.cpp, en esta implementacin en particular, el byte est aadido para acomodar fcilmente la insercin de un terminador nulo.

103
2808 2809 2810 2811 2812 2813 2814 2815 2816 2817 2818 2819 2820 2821

4.2.2. Reemplazar caracteres en cadenas


La funcin insert() es particularmente til por que te evita el tener que estar seguro de que la insercin de caracteres en un string no sobrepasa el espacio reservado o sobrescribe los caracteres que inmediatamente siguientes al punto de insercin. El espacio crece y los caracteres existentes se mueven graciosamente para acomodar a los nuevos elementos. A veces, puede que no sea esto exactamente lo que quiere. Si quiere que el tamao del string permanezca sin cambios, use la funcin replace() para sobrescribir los caracteres. Existe un nmero de versiones sobrecargadas de
replace(),

pero la ms simple toma tres argumentos: un entero indicando

donde empezar en el string, un entero indicando cuantos caracteres para eliminar del string original, y el string con el que reemplazaremos (que puede ser diferente en numero de caracteres que la cantidad eliminada). Aqu un ejemplo simple:

2822 2823 2824 2825 2826 2827 2828 2829 2830 2831 2832 2833 2834 2835

//: C03:StringReplace.cpp // Simple find-and-replace in strings. #include <cassert> #include <string> using namespace std;

int main() { string s("A piece of text"); string tag("$tag$"); s.insert(8, tag + ' '); assert(s == "A piece $tag$ of text"); int start = s.find(tag); assert(start == 8); assert(tag.size() == 5);

104

2836 2837 2838

s.replace(start, tag.size(), "hello there"); assert(s == "A piece hello there of text"); } ///:~

2839 2840 2841 2842 2843 2844 2845 2846 2847


Tag

Listado 4.3. C03/StringReplace.cpp es insertada en s (notese que la insercin ocurre antes de que el valor despues de Tag), y entonces es encontrada y reemplazada. Debera cerciorarse de que ha encontrado algo antes de realizar el
replace().

indicando el punto de insercin y de que el espacio extra haya sido aadido

En los ejemplos anteriores se reemplaza con un char*, pero

existe una versin sobrecargada que reemplaza con un string. Aqui hay un ejempl ms completo de demostracin de replace():

2848 2849 2850 2851 2852 2853 2854 2855 2856 2857 2858 2859 2860 2861

//: C03:Replace.cpp #include <cassert> #include <cstddef> // For size_t

#include <string> using namespace std;

void replaceChars(string& modifyMe, const string& findMe, const string& newChars) { // Look in modifyMe for the "find string" // starting at position 0: size_t i = modifyMe.find(findMe, 0); // Did we find the string to replace? if(i != string::npos) // Replace the find string with newChars:

105

2862 2863 2864 2865 2866 2867 2868 2869 2870 2871 2872 2873 2874

modifyMe.replace(i, findMe.size(), newChars); }

int main() { string bigNews = "I thought I saw Elvis in a UFO. " "I have been working too hard."; string replacement("wig"); string findMe("UFO"); // Find "UFO" in bigNews and overwrite it: replaceChars(bigNews, findMe, replacement); assert(bigNews == "I thought I saw Elvis in a " "wig. I have been working too hard."); } ///:~

2875 2876 2877 2878 2879 2880 2881 2882 2883 2884 2885

Listado 4.4. C03/Replace.cpp Si replace() no encuentra la cadena buscada, retorna un string::npos. El dato miembro npos es una constante estatica de la clase string que representa una posicin de carcter que no existe[33]. [3] A diferencia de insert(), replace() no aumentar el espacio de alamcenamiento de string si copia nuevos caracteres en el medio de una serie de elementos de array existentes. Sin embargo, s que cerecer su espacio si es necesario, por ejemplo, cuando hace un "reemplazamiento" que pueda expandir el string ms all del final de la memoria reservada actual. Aqu un ejemplo:

2886 2887

//: C03:ReplaceAndGrow.cpp #include <cassert>

106

2888 2889 2890 2891 2892 2893 2894 2895 2896 2897 2898 2899 2900

#include <string> using namespace std;

int main() { string bigNews("I have been working the grave."); string replacement("yard shift."); // The first argument says "replace chars // beyond the end of the existing string": bigNews.replace(bigNews.size() - 1, replacement.size(), replacement); assert(bigNews == "I have been working the " "graveyard shift."); } ///:~

2901 2902 2903 2904 2905 2906 2907 2908 2909 2910 2911 2912 2913 2914

Listado 4.5. C03/ReplaceAndGrow.cpp La llamada a replace() empieza "reemplazando" ms all del final del array existente, que es equivalente a la operacin append(). Ntese que en este ejemplo replace() expande el array coherentemente. Puede que haya estado buscando a travs del captulo; intentando hacer algo relativamente fcil como reemplazar todas las ocurrencias de un carcter con diferentes caracteres. Al buscar el material previo sobre reemplazar, puede que haya encontrado la respuesta, pero entonces ha empezaro viendo grupos de caracteres y contadores y otras cosas que parecen un poco demasiado complejas. No tiene string una manera para reemplazar un carcter con otro simplemente? Puede escribir fcilmente cada funcin usando las funciones miembro
find()

y replace() como se muestra acontinuacion.

107

2915 2916 2917 2918 2919 2920 2921 2922

//: C03:ReplaceAll.h #ifndef REPLACEALL_H #define REPLACEALL_H #include <string>

std::string& replaceAll(std::string& context, const std::string& from, const std::string& to); #endif // REPLACEALL_H ///:~

2923 2924

Listado 4.6. C03/ReplaceAll.h

2925 2926 2927 2928 2929 2930 2931 2932 2933 2934 2935 2936 2937 2938

//: C03:ReplaceAll.cpp {O} #include <cstddef> #include "ReplaceAll.h" using namespace std;

string& replaceAll(string& context, const string& from, const string& to) { size_t lookHere = 0; size_t foundHere; while((foundHere = context.find(from, lookHere)) != string::npos) { context.replace(foundHere, from.size(), to); lookHere = foundHere + to.size(); }

108

2939 2940

return context; } ///:~

2941 2942 2943 2944 2945 2946 2947

Listado 4.7. C03/ReplaceAll.cpp La versin de find() usada aqu toma como segundo argumento la posicin donde empezar a buscar y retorna string::npos si no lo encuentra. Es importante avanzar en la posicin contenida por la variable lookHere pasada como subcadena, en caso de que from es una subcadena de to. El siguiente programa comprueba la funcion replaceAll():

2948 2949 2950 2951 2952 2953 2954 2955 2956 2957 2958 2959 2960

//: C03:ReplaceAllTest.cpp //{L} ReplaceAll #include <cassert> #include <iostream> #include <string> #include "ReplaceAll.h" using namespace std;

int main() { string text = "a man, a plan, a canal, Panama"; replaceAll(text, "an", "XXX"); assert(text == "a mXXX, a plXXX, a cXXXal, PXXXama"); } ///:~

2961 2962

Listado 4.8. C03/ReplaceAllTest.cpp

109
2963 2964 2965 2966 2967 2968 2969 2970 2971 2972 2973 Como puede comprobar, la clase string por ella sola no resuelve todos los posibles problemas. Muchas soluciones se han dejado en los algoritmos de la librera estndar[4] por que la clase string puede parece justamente como una secuencia STL(gracias a los iteradores descritos antes). Todos los algoritmos genricos funcionan en un "rango" de elementos dentro de un contenedor. Generalmente este rango es justamente desde el principio del contenedor hasta el final. Un objeto string se parece a un contenedor de caracteres: para obtener el principio de este rango use string::begin(), y para obtener el final del rango use string::end(). El siguiente ejemplomuestra el uso del algoritmo replace() para reemplazar todas las instancias de un determinado carcter "X" con "Y"

2974 2975 2976 2977 2978 2979 2980 2981 2982 2983 2984

//: C03:StringCharReplace.cpp #include <algorithm> #include <cassert> #include <string> using namespace std;

int main() { string s("aaaXaaaXXaaXXXaXXXXaaa"); replace(s.begin(), s.end(), 'X', 'Y'); assert(s == "aaaYaaaYYaaYYYaYYYYaaa"); } ///:~

2985 2986 2987 2988 2989 2990

Listado 4.9. C03/StringCharReplace.cpp Ntese que esta funcin replace() no es llamada como funcin miembro de string. Adems, a diferencia de la funcin string::replace(), que solo realiza un reemplazo, el algoritmo replace() reemplaza todas las instancias de un carcter con otro.

110
2991 2992 2993 2994 2995 El algoritmo replace() solo funciona con objetos individuales (en este caso, objetos char) y no reemplazar arreglos constantes o objetos string. Desde que un string se copmporta como una secuencia STL, un conjunto de algoritmos pueden serle aplicados, que resolvern otros problemas que las funciones miembro de string no resuelven.

2996 2997 2998 2999 3000 3001 3002

4.2.3. Concatenacin usando operadores no-miembro sobrecargados


Uno de los descubrimientos ms deliciosos que esperan al programador de C que est aprendiendo sobre el manejo de cadenas en C++, es lo simple que es combinar y aadir string usando los operadores operator+ y
operator+=.

Estos operadores hacen combinaciones de cadenas

sintacticamente parecidas a la suma de datos numricos:

3003 3004 3005 3006 3007 3008 3009 3010 3011 3012 3013 3014 3015 3016 3017

//: C03:AddStrings.cpp #include <string> #include <cassert> using namespace std;

int main() { string s1("This "); string s2("That "); string s3("The other "); // operator+ concatenates strings s1 = s1 + s2; assert(s1 == "This That "); // Another way to concatenates strings s1 += s3; assert(s1 == "This That The other ");

111

3018 3019 3020 3021

// You can index the string on the right s1 += s3 + s3[4] + "ooh lala"; assert(s1 == "This That The other The other oooh lala"); } ///:~

3022 3023 3024 3025 3026 3027

Listado 4.10. C03/AddStrings.cpp Usar los operadores operator+ y operator+= es una manera flexible y conveniente de combinar los datos de las cadenas. En la parte derecha de la sentencia, puede usar casi cualquier tipo que evale a un grupo de uno o ms caracteres.

3028 3029 3030 3031 3032 3033 3034 3035 3036 3037 3038 3039 3040 3041 3042 3043 3044 3045

4.3. Buscar en cadenas


La familia de funciones miembro de string find localiza un carcter o grupo de caracteres en una cadena dada. Aqu los miembros de la familia
find()

y su uso general:

Funcin miembro de bsqueda en un string Qu/Cmo lo encuentra?


find()

Busca en un string un carcter determinado o un grupo de caracteres y retorna la posicin de inicio de la primera ocurrencia o npos si ha sido encontrado.
find_first_of()

Busca en un string y retorna la posicin de la primera ocurrencia de cualquier carcter en un grupo especifico. Si no encuentra ocurrencias, retorna npos.
find_last_of()

Busca en un string y retorna la posicin de la ltima ocurrencia de cualquier carcter en un grupo especfico. Si no encuentra ocurrencias, retorna npos.

112
3046 3047 3048 3049 3050 3051 3052 3053 3054 3055 3056 3057 3058 3059 3060 3061 3062 3063 3064 3065 3066 3067 3068 3069 3070
find_first_not_of( )

Busca en un string y retorna la posicin de la primera ocurrencia que no pertenece a un grupo especfico. Si no encontramos ningn elemento, retorna un npos
find_last_not_of( )

Busca en un string y retorna la posicin del elemento con el indice mayor que no pertenece a un grupo especfico. Si no encontramos ningn elemento, retorna un npos
rfind()

Busca en un string, desde el final hasta el origen, un carcter o grupo de caracteres y retorna la posicin inicial de la ocurrencia si se ha encontrado alguna. Si no encuentra ocurrencias, retorna npos. El uso ms simple de find(), busca uno o ms caracteres en un string. La versin sobrecargada de find() toma un parmetro que especifica el/los carcter(es) que buscar y opcionalmente un parmetro que dice donde empezar a buscar en el string la primera ocurrencia. (Por defecto la posicin de incio es 0). Insertando la llamada a la funcin find() dentro de un bucle puede buscar fcilmente todas las ocurrencias de un carcter dado o un grupo de caracteres dentro de un string. El siguiente programa usa el mtodo del Tamiz de Eratostenes para hallar los nmeros primos menores de 50. Este mtodo empieza con el nmero 2, marca todos los subsecuentes mltiplos de 2 ya que no son primos, y repite el proceso para el siguiente candidato a primo. El constructor de sieveTest inicializa sieveChars poniendo el tamao inicial del arreglo de carcter y escribiendo el valor 'P' para cada miembro.

3071 3072 3073 3074 3075

//: C03:Sieve.h #ifndef SIEVE_H #define SIEVE_H #include <cmath> #include <cstddef>

113

3076 3077 3078 3079 3080 3081 3082 3083 3084 3085 3086 3087 3088 3089 3090 3091 3092 3093 3094 3095 3096 3097 3098 3099 3100

#include <string> #include "../TestSuite/Test.h" using std::size_t; using std::sqrt; using std::string;

class SieveTest : public TestSuite::Test { string sieveChars; public: // Create a 50 char string and set each // element to 'P' for Prime: SieveTest() : sieveChars(50, 'P') {} void run() { findPrimes(); testPrimes(); } bool isPrime(int p) { if(p == 0 || p == 1) return false; int root = int(sqrt(double(p))); for(int i = 2; i <= root; ++i) if(p % i == 0) return false; return true; } void findPrimes() { // By definition neither 0 nor 1 is prime.

114

3101 3102 3103 3104 3105 3106 3107 3108 3109 3110 3111 3112 3113 3114 3115 3116 3117 3118 3119 3120 3121 3122 3123 3124 3125

// Change these elements to "N" for Not Prime: sieveChars.replace(0, 2, "NN"); // Walk through the array: size_t sieveSize = sieveChars.size(); int root = int(sqrt(double(sieveSize))); for(int i = 2; i <= root; ++i) // Find all the multiples: for(size_t factor = 2; factor * i < sieveSize; ++factor) sieveChars[factor * i] = 'N'; } void testPrimes() { size_t i = sieveChars.find('P'); while(i != string::npos) { test_(isPrime(i++)); i = sieveChars.find('P', i); } i = sieveChars.find_first_not_of('P'); while(i != string::npos) { test_(!isPrime(i++)); i = sieveChars.find_first_not_of('P', i); } } }; #endif // SIEVE_H ///:~

115
3126 3127 Listado 4.11. C03/Sieve.h

3128 3129 3130 3131 3132 3133 3134 3135 3136

//: C03:Sieve.cpp //{L} ../TestSuite/Test #include "Sieve.h"

int main() { SieveTest t; t.run(); return t.report(); } ///:~

3137 3138 3139 3140 3141 3142 3143 3144 3145 3146 3147

Listado 4.12. C03/Sieve.cpp La funcin find() puede recorrer el string, detectando mltiples ocurrencias de un carcter o un grupo de caracteres, y find_first_not_of() encuentra otros caracteres o subcadenas. No existen funciones en la clase string para cambiar entre maysculas/minsculas en una cadena, pero puede crear esa funcin fcilmente usando la funcin de la libreria estndar de C toupper() y
tolower(),

que cambian los caracteres entre maysculas/minsculas de uno maysculas/minsculas.

en uno. El ejemplo siguiente ilustra una bsqueda sensible a

3148 3149 3150 3151

//: C03:Find.h #ifndef FIND_H #define FIND_H #include <cctype>

116

3152 3153 3154 3155 3156 3157 3158 3159 3160 3161 3162 3163 3164 3165 3166 3167 3168 3169 3170 3171 3172 3173 3174 3175 3176

#include <cstddef> #include <string> #include "../TestSuite/Test.h" using std::size_t; using std::string; using std::tolower; using std::toupper;

// Make an uppercase copy of s inline string upperCase(const string& s) { string upper(s); for(size_t i = 0; i < s.length(); ++i) upper[i] = toupper(upper[i]); return upper; }

// Make a lowercase copy of s inline string lowerCase(const string& s) { string lower(s); for(size_t i = 0; i < s.length(); ++i) lower[i] = tolower(lower[i]); return lower; }

class FindTest : public TestSuite::Test {

117

3177 3178 3179 3180 3181 3182 3183 3184 3185 3186 3187 3188 3189 3190 3191 3192 3193 3194 3195 3196 3197 3198 3199 3200 3201

string chooseOne; public: FindTest() : chooseOne("Eenie, Meenie, Miney, Mo") {} void testUpper() { string upper = upperCase(chooseOne); const string LOWER = "abcdefghijklmnopqrstuvwxyz"; test_(upper.find_first_of(LOWER) == string::npos); } void testLower() { string lower = lowerCase(chooseOne); const string UPPER = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; test_(lower.find_first_of(UPPER) == string::npos); } void testSearch() { // Case sensitive search size_t i = chooseOne.find("een"); test_(i == 8); // Search lowercase: string test = lowerCase(chooseOne); i = test.find("een"); test_(i == 0); i = test.find("een", ++i); test_(i == 8); i = test.find("een", ++i); test_(i == string::npos);

118

3202 3203 3204 3205 3206 3207 3208 3209 3210 3211 3212 3213 3214 3215 3216 3217

// Search uppercase: test = upperCase(chooseOne); i = test.find("EEN"); test_(i == 0); i = test.find("EEN", ++i); test_(i == 8); i = test.find("EEN", ++i); test_(i == string::npos); } void run() { testUpper(); testLower(); testSearch(); } }; #endif // FIND_H ///:~

3218 3219

Listado 4.13. C03/Find.h

3220 3221 3222 3223 3224 3225

//: C03:Find.cpp //{L} ../TestSuite/Test #include "Find.h" #include "../TestSuite/Test.h"

int main() {

119

3226 3227 3228 3229

FindTest t; t.run(); return t.report(); } ///:~

3230 3231 3232 3233 3234 3235 3236 3237 3238 3239 3240

Listado 4.14. C03/Find.cpp Tanto las funciones upperCase() como lowerCase() siguen la misma forma: hacen una copia de la cadena argumento y cambian entre maysculas/minsculas. El programa Find.cpp no es la mejor solucin para el problema para las maysculas/minsculas, por lo que lo revisitaremos cuando examinemos la comparacin entre cadenas.

4.3.1. Busqueda inversa


Si necesita buscar en una cadena desde el final hasta el principio (para encontrar datos en orden "ltimo entra / primero sale"), puede usar la funcin miembro de string rfind().

3241 3242 3243 3244 3245 3246 3247 3248 3249 3250 3251

//: C03:Rparse.h #ifndef RPARSE_H #define RPARSE_H #include <cstddef> #include <string> #include <vector> #include "../TestSuite/Test.h" using std::size_t; using std::string; using std::vector;

120

3252 3253 3254 3255 3256 3257 3258 3259 3260 3261 3262 3263 3264 3265 3266 3267 3268 3269 3270 3271 3272 3273 3274 3275 3276

class RparseTest : public TestSuite::Test { // To store the words: vector<string> strings; public: void parseForData() { // The ';' characters will be delimiters string s("now.;sense;make;to;going;is;This"); // The last element of the string: int last = s.size(); // The beginning of the current word: size_t current = s.rfind(';'); // Walk backward through the string: while(current != string::npos) { // Push each word into the vector. // Current is incremented before copying // to avoid copying the delimiter: ++current; strings.push_back(s.substr(current, last - current)); // Back over the delimiter we just found, // and set last to the end of the next word: current -= 2; last = current + 1; // Find the next delimiter: current = s.rfind(';', current); }

121

3277 3278 3279 3280 3281 3282 3283 3284 3285 3286 3287 3288 3289 3290 3291 3292 3293 3294 3295 3296 3297 3298 3299 3300 3301

// Pick up the first word -- it's not // preceded by a delimiter: strings.push_back(s.substr(0, last)); } void testData() { // Test them in the new order: test_(strings[0] == "This"); test_(strings[1] == "is"); test_(strings[2] == "going"); test_(strings[3] == "to"); test_(strings[4] == "make"); test_(strings[5] == "sense"); test_(strings[6] == "now."); string sentence; for(size_t i = 0; i < strings.size() - 1; i++) sentence += strings[i] += " "; // Manually put last word in to avoid an extra space: sentence += strings[strings.size() - 1]; test_(sentence == "This is going to make sense now."); } void run() { parseForData(); testData(); } };

122

3302

#endif // RPARSE_H ///:~

3303 3304

Listado 4.15. C03/Rparse.h

3305 3306 3307 3308 3309 3310 3311 3312 3313

//: C03:Rparse.cpp //{L} ../TestSuite/Test #include "Rparse.h"

int main() { RparseTest t; t.run(); return t.report(); } ///:~

3314 3315 3316 3317 3318

Listado 4.16. C03/Rparse.cpp La funcin miembro de string rfind() vuelve por la cadena buscando elementos y reporta el indice del arreglo de las coincidencias de caracteres o
string::npos

si no tiene xito.

3319 3320 3321 3322 3323

4.3.2. Encontrar el primero/ltimo de un conjunto de caracteres


La funcin miembro find_first_of( ) y find_last_of( ) pueden ser convenientemente usadas para crear una pequea utilidad the ayude a deshechar los espacion en blanco del final e inicio de la cadena. Ntese que no se toca el string originar sino que se devuelve una nuevo string:

3324 3325

//: C03:Trim.h // General tool to strip spaces from both ends.

123

3326 3327 3328 3329 3330 3331 3332 3333 3334 3335 3336 3337 3338 3339 3340

#ifndef TRIM_H #define TRIM_H #include <string> #include <cstddef>

inline std::string trim(const std::string& s) { if(s.length() == 0) return s; std::size_t beg = s.find_first_not_of(" \a\b\f\n\r\t\v"); std::size_t end = s.find_last_not_of(" \a\b\f\n\r\t\v"); if(beg == std::string::npos) // No non-spaces return ""; return std::string(s, beg, end - beg + 1); } #endif // TRIM_H ///:~

3341 3342 3343 3344 3345 3346 3347

Listado 4.17. C03/Trim.h La primera prueba checkea si el string esta vaco; en ese caso, ya no se realizan ms test, y se retorna una copia. Ntese que una vez los puntos del final son encontrados, el constructor de string construye un nuevo string desde el viejo, dndole el contador incial y la longitud. Las pruebas de una herramienta tan general den ser cuidadosas

3348 3349 3350

//: C03:TrimTest.h #ifndef TRIMTEST_H #define TRIMTEST_H

124

3351 3352 3353 3354 3355 3356 3357 3358 3359 3360 3361 3362 3363 3364 3365 3366 3367 3368 3369 3370 3371 3372 3373 3374 3375

#include "Trim.h" #include "../TestSuite/Test.h"

class TrimTest : public TestSuite::Test { enum {NTESTS = 11}; static std::string s[NTESTS]; public: void testTrim() { test_(trim(s[0]) == "abcdefghijklmnop"); test_(trim(s[1]) == "abcdefghijklmnop"); test_(trim(s[2]) == "abcdefghijklmnop"); test_(trim(s[3]) == "a"); test_(trim(s[4]) == "ab"); test_(trim(s[5]) == "abc"); test_(trim(s[6]) == "a b c"); test_(trim(s[7]) == "a b c"); test_(trim(s[8]) == "a \t b \t c"); test_(trim(s[9]) == ""); test_(trim(s[10]) == ""); } void run() { testTrim(); } }; #endif // TRIMTEST_H ///:~

125
3376 3377 Listado 4.18. C03/TrimTest.h

3378 3379 3380 3381 3382 3383 3384 3385 3386 3387 3388 3389 3390

//: C03:TrimTest.cpp {O} #include "TrimTest.h"

// Initialize static data std::string TrimTest::s[TrimTest::NTESTS] = { " \t abcdefghijklmnop \t ", "abcdefghijklmnop \t ", " \t abcdefghijklmnop", "a", "ab", "abc", "a b c", " \t a b c \t ", " \t a \t b \t c \t ", "\t \n \r \v \f", "" // Must also test the empty string }; ///:~

3391 3392

Listado 4.19. C03/TrimTest.cpp

3393 3394 3395 3396 3397 3398 3399

//: C03:TrimTestMain.cpp //{L} ../TestSuite/Test TrimTest #include "TrimTest.h"

int main() { TrimTest t; t.run();

126

3400 3401

return t.report(); } ///:~

3402 3403 3404 3405 3406 3407 3408

Listado 4.20. C03/TrimTestMain.cpp En el arrglo de string, puede ver que los arreglos de carcter son automticamente convertidos a objetos string. Este arreglo provee casos para checkear el borrado de espacios en blanco y tabuladores en los extremos, adems de asegurar que los espacios y tabuladores no son borrados de la mitad de un string.

3409 3410 3411 3412 3413 3414 3415 3416 3417 3418 3419

4.3.3. Borrar caracteres de cadenas


Borrar caracteres es fcil y eficiente con la funcin miembro erase(), que toma dos argumentos: donde empezar a borrar caracteres (que por defecto es 0), y cuantos caracteres borrar (que por defecto es string::npos). Si especifica ms caracteres que los que quedan en el string, los caracteres restantes se borran igualmente (llamando erase() sin argumentos borra todos los caracteres del string). A veces es til abrir un fichero HTML y borrar sus etiquetas y caracteres especiales de manera que tengamos algo aproximadamente igual al texto que obtendramos en el navegador Web, slo como un fichero de texto plano. El siguiente ejemplo usa erase() para hacer el trabajo:

3420 3421 3422 3423 3424 3425 3426

//: C03:HTMLStripper.cpp {RunByHand} //{L} ReplaceAll // Filter to remove html tags and markers. #include <cassert> #include <cmath> #include <cstddef> #include <fstream>

127

3427 3428 3429 3430 3431 3432 3433 3434 3435 3436 3437 3438 3439 3440 3441 3442 3443 3444 3445 3446 3447 3448 3449 3450 3451

#include <iostream> #include <string> #include "ReplaceAll.h" #include "../require.h" using namespace std;

string& stripHTMLTags(string& s) { static bool inTag = false; bool done = false; while(!done) { if(inTag) { // The previous line started an HTML tag // but didn't finish. Must search for '>'. size_t rightPos = s.find('>'); if(rightPos != string::npos) { inTag = false; s.erase(0, rightPos + 1); } else { done = true; s.erase(); } } else { // Look for start of tag:

128

3452 3453 3454 3455 3456 3457 3458 3459 3460 3461 3462 3463 3464 3465 3466 3467 3468 3469 3470 3471 3472 3473 3474 3475 3476

size_t leftPos = s.find('<'); if(leftPos != string::npos) { // See if tag close is in this line: size_t rightPos = s.find('>'); if(rightPos == string::npos) { inTag = done = true; s.erase(leftPos); } else s.erase(leftPos, rightPos - leftPos + 1); } else done = true; } } // Remove all special HTML characters replaceAll(s, "&lt;", "<"); replaceAll(s, "&gt;", ">"); replaceAll(s, "&amp;", "&"); replaceAll(s, "&nbsp;", " "); // Etc... return s; }

int main(int argc, char* argv[]) {

129

3477 3478 3479 3480 3481 3482 3483 3484 3485

requireArgs(argc, 1, "usage: HTMLStripper InputFile"); ifstream in(argv[1]); assure(in, argv[1]); string s; while(getline(in, s)) if(!stripHTMLTags(s).empty()) cout << s << endl; } ///:~

3486 3487 3488 3489 3490 3491 3492 3493 3494 3495 3496 3497 3498 3499

Listado 4.21. C03/HTMLStripper.cpp Este ejemplo borrar incluso las etiquetas HTML que se extienden a lo largo de varias lneas.[5] Esto se cumple gracias a la bandera esttica inTag, que evala a cierto si el principio de una etiqueta es encontrada, pero la etiqueta de finalizacin correspondiente no es encontrada en la misma lnea. Todas la formas de erase() aparecen en la funcin stripHTMLFlags().[6] La versin de getline() que usamos aqu es una funcin (global) declarada en la cabecera de string y es til porque guarda una lnea arbitrariamente larga en su argumento string. No necesita preocuparse de las dimensiones de un arreglo cuando trabaja con istream::getline(). Ntese que este programa usa la funcin replaceAll() vista antes en este captulo. En el pximo capitulo, usaremos los flujos de cadena para crear una solucin ms elegante.

3500 3501 3502 3503 3504

4.3.4. Comparar cadenas


Comparar cadenas es inherentemente diferente a comparar enteros. Los nombres tienen un significado universal y constante. Para evaluar la relacin entre las magnitudes de dos cadenas, se necesita hacer una comparacin lxica. Una comparacin lxica significa que cuando se comprueba un

130
3505 3506 3507 3508 3509 3510 3511 3512 3513 3514 3515 3516 3517 3518 3519 3520 3521 3522 carcter para saber si es "mayor que" o "menor que" otro carcter, est en realidad comparando la representacin numrica de aquellos caracteres tal como estn especificados en el orden del conjunto de caracteres que est siendo usado. La ordenacin ms habitual suele ser la secuencia ASCII, que asigna a los caracteres imprimibles para el lenguaje ingls nmeros en un rango del 32 al 127 decimal. En la codificacin ASCII, el primer "carcter" en la lista es el espacio, seguido de diversas marcas de puntuacin comn, y despus las letras maysculas y minsculas. Respecto al alfabeto, esto significa que las letras cercanas al principio tienen un valor ASCII menor a aquellos ms cercanos al final. Con estos detalles en mente, se vuelve ms fcil recordar que cuando una comparaci lxica reporta que s1 es "mayor que" s2, simplemente significa que cuando fueron comparados, el primer carcter diferente en s1 estaba atrs en el alfabeto que el carcter en la misma posicin en s2. C++ provee varias maneras de comparar cadenas, y cada una tiene ventajas. La ms simple de usar son las funciones no-miembro sobrecargadas de operador: operator==, operator!= operator>, operator<,
operator>=

y operator<=.

3523 3524 3525 3526 3527 3528 3529 3530 3531 3532 3533

//: C03:CompStr.h #ifndef COMPSTR_H #define COMPSTR_H #include <string> #include "../TestSuite/Test.h" using std::string;

class CompStrTest : public TestSuite::Test { public: void run() { // Strings to compare

131

3534 3535 3536 3537 3538 3539 3540 3541 3542 3543 3544 3545 3546

string s1("This"); string s2("That"); test_(s1 == s1); test_(s1 != s2); test_(s1 > s2); test_(s1 >= s2); test_(s1 >= s1); test_(s2 < s1); test_(s2 <= s1); test_(s1 <= s1); } }; #endif // COMPSTR_H ///:~

3547 3548

Listado 4.22. C03/CompStr.h

3549 3550 3551 3552 3553 3554 3555 3556

//: C03:CompStr.cpp //{L} ../TestSuite/Test #include "CompStr.h"

int main() { CompStrTest t; t.run(); return t.report();

132

3557

} ///:~

3558 3559 3560 3561 3562 3563 3564 3565 3566 3567

Listado 4.23. C03/CompStr.cpp Los operadores de comaracin sobrecargados son tiles para comparar dos cadenas completas y elementos individuales de una cadena de caracteres. Ntese en el siguiente ejemplo la flexibilidad de los tipos de argumento ambos lados de los operadores de comparacin. Por eficiencia, la clase
string

provee operadores sobrecargados para la comparacin directa de que crear objetos string temporales.

objetos string, literales de cadena, y punteros a cadenas estilo C sin tener

3568 3569 3570 3571 3572 3573 3574 3575 3576 3577 3578 3579 3580 3581 3582

//: C03:Equivalence.cpp #include <iostream> #include <string> using namespace std;

int main() { string s2("That"), s1("This"); // The lvalue is a quoted literal // and the rvalue is a string: if("That" == s2) cout << "A match" << endl; // The left operand is a string and the right is // a pointer to a C-style null terminated string: if(s1 != s2.c_str()) cout << "No match" << endl;

133

3583

} ///:~

3584 3585 3586 3587 3588 3589 3590 3591 3592 3593 3594 3595 3596 3597 3598 3599 3600 3601 3602 3603

Listado 4.24. C03/Equivalence.cpp La funcin c_str() retorna un const char* que apunta a una cadena estilo C terminada en nulo, equivalente en contenidos al objeto string. Esto se vuelve muy til cuando se quiere pasar un strin a una funcin C, como atoi() o cualquiera de las funciones definidas en la cabecera cstring. Es un error usar el valor retornado por c_str() como un argumento constante en cualquier funcin. No encontrar el operador not (!) o los operadores de comparacin lgicos (&& y ||) entre los operadore para string. (No encontrar ninguna versin sobrecargada de los operadores de bits de C: &, |, ^, o ~.) Los operadores de conversin no miembros sobrecargados para la clases string estn limitados a un subconjunto que tiene una aplicacin clara y no ambigua para caracteres individuales o grupos de caracteres. La funcin miembro compare() le ofrece un gran modo de comparacin ms sofisticado y preciso que el conjunto de operadores nomiembro. Provee versiones sobrecargadas para comparar:

Dos string completos

Parte de un string con un string completo

Partes de dos string

3604 3605 3606 3607 3608 3609 3610

//: C03:Compare.cpp // Demonstrates compare() and swap(). #include <cassert> #include <string> using namespace std;

int main() {

134

3611 3612 3613 3614 3615 3616 3617 3618 3619 3620 3621

string first("This"); string second("That"); assert(first.compare(first) == 0); assert(second.compare(second) == 0); // Which is lexically greater? assert(first.compare(second) > 0); assert(second.compare(first) < 0); first.swap(second); assert(first.compare(second) < 0); assert(second.compare(first) > 0); } ///:~

3622 3623 3624 3625 3626 3627 3628 3629 3630 3631

Listado 4.25. C03/Compare.cpp La funcin swap() en este ejemplo hace lo que su nombre implica: cambia el contenido del objeto por el del parmetro. Para comparar un subconjunto de caracteres en un o ambos string, aada argumentos que definen donde empezar y cuantos caracteres considerar. Por ejemplo, puede usar las siguientes versiones sobrecargadas de compare():
s1.compare(s1StartPos, s1NumberChars, s2, s2StartPos, s2NumberChars);

Aqui un ejemplo:

3632 3633 3634 3635 3636

//: C03:Compare2.cpp // Illustrate overloaded compare(). #include <cassert> #include <string> using namespace std;

135

3637 3638 3639 3640 3641 3642 3643 3644 3645 3646
int main() { string first("This is a day that will live in infamy"); string second("I don't believe that this is what " "I signed up for"); // Compare "his is" in both strings: assert(first.compare(1, 7, second, 22, 7) == 0); // Compare "his is a" to "his is w": assert(first.compare(1, 9, second, 22, 9) < 0); } ///:~

3647 3648 3649 3650 3651 3652

Listado 4.26. C03/Compare2.cpp Hasta ahora, en los ejemplos, hemos usado la sintaxis de indexacin de arrays estilo C para referirnos a un carcter individual en un string. C++ provee de una alternativa a la notacin s[n]: el miembro at(). Estos dos mecanismos de indexacin producen los mismos resultados si todo va bien:

3653 3654 3655 3656 3657 3658 3659 3660 3661

//: C03:StringIndexing.cpp #include <cassert> #include <string> using namespace std;

int main() { string s("1234"); assert(s[1] == '2'); assert(s.at(1) == '2');

136

3662

} ///:~

3663 3664 3665 3666 3667 3668

Listado 4.27. C03/StringIndexing.cpp Sin embargo, existe una importante diferencia entre [ ] y at() . Cuando usted intenta referenciar el elemento de un arreglo que esta fuera de sus lmites, at() tiene la delicadeza de lanzar una excepcin, mientras que ordinariamente [ ] le dejar a su suerte.

3669 3670 3671 3672 3673 3674 3675 3676 3677 3678 3679 3680 3681 3682 3683

//: C03:BadStringIndexing.cpp #include <exception> #include <iostream> #include <string> using namespace std;

int main() { string s("1234"); // at() saves you by throwing an exception: try { s.at(5); } catch(exception& e) { cerr << e.what() << endl; } } ///:~

3684 3685

Listado 4.28. C03/BadStringIndexing.cpp

137
3686 3687 3688 3689 3690 3691 3692 3693 3694 3695 3696 3697 Los programadores responsables no usarn ndices errticos, pero puede que quiera beneficiarse de la comprobacin automtica de indices, usandoat() en el lugar de [ ] le da la oportunidad de recuperar diligentemente de las referencias a elementos de un arreglo que no existen. La ejecucin de sobre uno de nuestros compiladores le da la siguiente salida: "invalid string position" La funcin miembro at() lanza un objeto de clase out_of_class, que deriva finalmente de std::exception. Capturando este objeto en un manejador de excepciones, puede tomar las medidas adecuadas como recalcular el ndice incorrecto o hacer crecer el arreglo. Usar
string::operator[]( )

no proporciona ningn tipo de proteccin y es tan

peligroso como el procesado de arreglos de caracteres en C.[37] [7]

3698 3699 3700 3701 3702 3703 3704 3705 3706 3707 3708 3709 3710 3711 3712 3713 3714 3715 3716 3717 3718

4.3.5. Cadenas y rasgos de caracteres


El programa Find.cpp anterior en este captulo nos lleva a hacernos la pregunta obvia: por que la comparacin sensible a maysculas/minsculas no es parte de la clase estndar string? La respuesta nos brinda un interesante transfondo sobre la verdadera naturaleza de los objetos string en C++. Considere qu significa para un carcter tener "mayscula/minscula". El Hebreo escrito, el Farsi, y el Kanji no usan el concepto de "mayscula/minscula", con lo que para esas lenguas esta idea carece de significado. Esto daria a entender que si existiera una manera de designar algunos lenguages como "todo maysculas" o "todo minsculas", podriamos disear una solucin generalizada. Sin embargo, algunos leguajes que emplean el concepto de "mayscula/minscula", tambien cambian el significado de caracteres particulares con acentos diacrticos, por ejemplo la cedilla del Espaol, el circumflexo en Francs y la diresis en Alemn. Por esta razn, cualquier codificacin sensible a maysculas que intenta ser comprensiva acaba siendo una pesadilla en su uso. Aunque tratamos habitualmente el string de C++ como una clase, esto no es del todo cierto. El tipo string es una especializacin de algo ms general, la plantilla basic_string< >. Observe como est declarada string en el fichero de cabecera de C++ estndar.

138
3719 3720 3721 3722 3723 3724 3725 3726 3727 3728 3729 3730 3731 3732 3733 3734 3735 3736 3737 3738 3739 3740 3741 3742 3743 3744 3745 3746 3747 3748 3749 3750 3751 3752
typedef basic_string<char> string;

Para comprender la naturaleza de la clase string, mire la plantilla


basic_string< > template<class charT, class traits = char_traits<charT>, class allocator = allocator<charT> > class basic_string;

En el Captulo 5, examinamos las plantillas con gran detalle (mucho ms que en el Captulo 16 del volmen 1). Por ahora ntese que el tipo string es creada cuando instanciamos la plantilla basic_string con char. Dentro de la declaracin plantilla basic_string< > la lnea:
class traits = char_traits<charT<,

nos dice que el comportamiento de la clase hecha a partir de


basic_string< > char_traits< >.

es defineida por una clase basada en la plantilla

As, la plantilla basic_string< > produce clases orientadas

a string que manipulan otros tipos que char (caracteres anchos, por ejemplo). Para hacer esto, la plantilla char_traits< > controla el contenido y el comportamiento de la ordenacin de una variedad de conjuntos de caracteres usando las funciones de comparacin eq() (equal), ne() (not equal), y lt() (less than). Las funciones de comparacin de basic_string< > confian en esto. Es por esto por lo que la clase string no incluye funciones miembro sensibles a maysculas/minsculas: eso no esta en la descripcin de su trabajo. Para cambiar la forma en que la clase string trata la comparacin de caracteres, tiene que suministrar una plantilla char_traits< > diferente ya que define el comportamiento individual de las funciones miembro de comparacin carcteres. Puede usar esta informacin para hacer un nuevo tipo de string que ignora las maysculas/minsculas. Primero, definiremos una nueva plantilla no sensible a maysculas/minsculas de char_traits< > que hereda de una plantilla existente. Luego, sobrescribiremos slo los miembros que necesitamos cambiar para hacer la comparacin carcter por carcter. (Adems de los tres miembros de comparacin lxica mencionados antes, daremos una nueva implementacin para laspara las funciones de
char_traits find()

y compare()). Finalmente, haremos un typedef de una

nueva clase basada en basic_string, pero usando nuestra plantilla

139
3753 3754 insensible a maysculas/minsculas, ichar_traits, como segundo argumento:

3755 3756 3757 3758 3759 3760 3761 3762 3763 3764 3765 3766 3767 3768 3769 3770 3771 3772 3773 3774 3775 3776 3777

//: C03:ichar_traits.h // Creating your own character traits. #ifndef ICHAR_TRAITS_H #define ICHAR_TRAITS_H #include <cassert> #include <cctype> #include <cmath> #include <cstddef> #include <ostream> #include <string> using std::allocator; using std::basic_string; using std::char_traits; using std::ostream; using std::size_t; using std::string; using std::toupper; using std::tolower;

struct ichar_traits : char_traits<char> { // We'll only change character-by// character comparison functions static bool eq(char c1st, char c2nd) {

140

3778 3779 3780 3781 3782 3783 3784 3785 3786 3787 3788 3789 3790 3791 3792 3793 3794 3795 3796 3797 3798 3799 3800 3801 3802

return toupper(c1st) == toupper(c2nd); } static bool ne(char c1st, char c2nd) { return !eq(c1st, c2nd); } static bool lt(char c1st, char c2nd) { return toupper(c1st) < toupper(c2nd); } static int compare(const char* str1, const char* str2, size_t n) { for(size_t i = 0; i < n; ++i) { if(str1 == 0) return -1; else if(str2 == 0) return 1; else if(tolower(*str1) < tolower(*str2)) return -1; else if(tolower(*str1) > tolower(*str2)) return 1; assert(tolower(*str1) == tolower(*str2)); ++str1; ++str2; // Compare the other chars } return 0; } static const char*

141

3803 3804 3805 3806 3807 3808 3809 3810 3811 3812 3813 3814 3815 3816 3817 3818

find(const char* s1, size_t n, char c) { while(n-- > 0) if(toupper(*s1) == toupper(c)) return s1; else ++s1; return 0; } };

typedef basic_string<char, ichar_traits> istring;

inline ostream& operator<<(ostream& os, const istring& s) { return os << string(s.c_str(), s.length()); } #endif // ICHAR_TRAITS_H ///:~

3819 3820 3821 3822 3823 3824 3825

Listado 4.29. C03/ichar_traits.h Proporcionamos un typedef llamado istring ya que nuestra clase actuar como un string ordinario en todas sus formas, excepto que realizar todas las comparaciones sin respetar las maysculas/minsculas. Por conveniencia, damos un operador sobrecargado operator<<() para que pueda imprimir los istring. Aque hay un ejemplo:

3826 3827

//: C03:ICompare.cpp #include <cassert>

142

3828 3829 3830 3831 3832 3833 3834 3835 3836 3837 3838 3839 3840 3841 3842

#include <iostream> #include "ichar_traits.h" using namespace std;

int main() { // The same letters except for case: istring first = "tHis"; istring second = "ThIS"; cout << first << endl; cout << second << endl; assert(first.compare(second) == 0); assert(first.find('h') == 1); assert(first.find('I') == 2); assert(first.find('x') == string::npos); } ///:~

3843 3844 3845 3846 3847 3848 3849 3850 3851 3852 3853 3854

Listado 4.30. C03/ICompare.cpp Este es solo un ejemplo de prueba. Para hacer istring completamente equivalente a un string, deberiamos haber creado las otras funciones necesarias para soportar el nuevo tipo istring. La cabecera <string> provee de un string ancho [8] gracias al siguiente
typedef: typedef basic_string<wchar_t> wstring;

El soporte para string ancho se revela tambien en los streams anchos (wostream en lugar de ostream, tambien definido en <iostream>) y en la especializacin de wchar_t de los char_traits en la libreria estndar le da la posibilidad de hacer una version de carcter ancho de ichar_traits

143

3855 3856 3857 3858 3859 3860 3861 3862 3863 3864 3865 3866 3867 3868 3869 3870 3871 3872 3873 3874 3875 3876 3877 3878 3879

//: C03:iwchar_traits.h {-g++} // Creating your own wide-character traits. #ifndef IWCHAR_TRAITS_H #define IWCHAR_TRAITS_H #include <cassert> #include <cmath> #include <cstddef> #include <cwctype> #include <ostream> #include <string>

using std::allocator; using std::basic_string; using std::char_traits; using std::size_t; using std::towlower; using std::towupper; using std::wostream; using std::wstring;

struct iwchar_traits : char_traits<wchar_t> { // We'll only change character-by// character comparison functions static bool eq(wchar_t c1st, wchar_t c2nd) { return towupper(c1st) == towupper(c2nd);

144

3880 3881 3882 3883 3884 3885 3886 3887 3888 3889 3890 3891 3892 3893 3894 3895 3896 3897 3898 3899 3900 3901 3902 3903 3904

} static bool ne(wchar_t c1st, wchar_t c2nd) { return towupper(c1st) != towupper(c2nd); } static bool lt(wchar_t c1st, wchar_t c2nd) { return towupper(c1st) < towupper(c2nd); } static int compare( const wchar_t* str1, const wchar_t* str2, size_t n) { for(size_t i = 0; i < n; i++) { if(str1 == 0) return -1; else if(str2 == 0) return 1; else if(towlower(*str1) < towlower(*str2)) return -1; else if(towlower(*str1) > towlower(*str2)) return 1; assert(towlower(*str1) == towlower(*str2)); ++str1; ++str2; // Compare the other wchar_ts } return 0; } static const wchar_t* find(const wchar_t* s1, size_t n, wchar_t c) {

145

3905 3906 3907 3908 3909 3910 3911 3912 3913 3914 3915 3916 3917 3918 3919 3920

while(n-- > 0) if(towupper(*s1) == towupper(c)) return s1; else ++s1; return 0; } };

typedef basic_string<wchar_t, iwchar_traits> iwstring;

inline wostream& operator<<(wostream& os, const iwstring& s) { return os << wstring(s.c_str(), s.length()); } #endif // IWCHAR_TRAITS_H ///:~

3921 3922 3923 3924

Listado 4.31. C03/iwchar_traits.h Como puede ver, esto es principalmente un ejercicio de poner 'w' en el lugar adecuado del cdigo fuente. El programa de prueba podria ser asi:

3925 3926 3927 3928

//: C03:IWCompare.cpp {-g++} #include <cassert> #include <iostream> #include "iwchar_traits.h"

146

3929 3930 3931 3932 3933 3934 3935 3936 3937 3938 3939 3940 3941

using namespace std;

int main() { // The same letters except for case: iwstring wfirst = L"tHis"; iwstring wsecond = L"ThIS"; wcout << wfirst << endl; wcout << wsecond << endl; assert(wfirst.compare(wsecond) == 0); assert(wfirst.find('h') == 1); assert(wfirst.find('I') == 2); assert(wfirst.find('x') == wstring::npos); } ///:~

3942 3943 3944 3945

Listado 4.32. C03/IWCompare.cpp Desgraciadamente, todavia algunos compiladores siguen sin ofrecer un soporte robusto para caracteres anchos.

3946 3947 3948 3949 3950 3951 3952 3953 3954 3955

4.4. Una aplicacin con cadenas


Si ha observado atentamente los cdigos de ejemplo de este libro, habr observado que ciertos elementos en los comentarios envuelven el cdigo. Son usados por un programa en Python que escribi Bruce para extraer el cdigo en ficheros y configurar makefiles para construir el cdigo. Por ejemplo, una doble barra segida de dos puntos en el comienzo de una lnea denota la primera lnea de un fichero de cdigo . El resto de la lnea contiene informacin describiendo el nombre del fichero y su locaizacin y cuando deberia ser solo compilado en vez constituir un fichero ejecutable. Por ejemplo, la primera lnea del programa anterior contiene la cadena

147
3956 3957 3958 3959 3960 3961 3962 3963 3964 3965 3966 3967 3968 3969 3970 3971 3972 3973 3974 3975 3976 3977 3978 3979 3980 3981 3982 3983 3984 3985 C03:IWCompare.cpp, indicando que el fichero IWCompare.cpp deberia ser extraido en el directorio C03. La ltima lnea del fichero fuente contiene una triple barra seguida de dos puntos y un signo "~". Es la primera lnea tiene una exclamacin inmediatamente despus de los dos puntos, la primera y la ltima lnea del cdigo fuente no son para ser extradas en un fichero (solo es para ficheros solo de datos). (Si se est preguntando por que evitamos mostrar estos elementos, es por que no queremos romper el extractor de cdigo cuando lo aplicamos al texto del libro!). El programa en Python de Bruce hace muchas ms cosas que simplemente extraer el cdigo. Si el elemento "{O}" sigue al nombre del fichero, su entrada en el makefile solo ser configurada para compilar y no para enlazarla en un ejecutable. (El Test Framework en el Captulo 2 est contruida de esta manera). Para enlazar un fichero con otro fuente de ejemplo, el fichero fuente del ejecutable objetivo contendr una directiva "{L}", como aqu:
//{L} ../TestSuite/Test

Esta seccin le presentar un programa para extraer todo el cdigo para que pueda compilarlo e inspeccionarlo manualmente. Puede usar este programa para extraer todo el codigo de este libro salvando el fichero como un fichero de texto[9] (llammosle TICV2.txt)y ejecutando algo como la siguiente lnea de comandos: C:> extractCode TICV2.txt /TheCode Este comando lee el fichero de texto TICV2.txt y escribe todos los archivos de cdigo fuente en subdirectorios bajo el definido /TheCode. El arbol de directorios se mostrar como sigue:
TheCode/ C0B/ C01/ C02/ C03/ C04/ C05/ C06/ C07/ C08/ C09/ C10/ C11/ TestSuite/

Los ficheros de cdigo fuente que contienen los ejemplos de cada captulo estarn en el correspondiente directorio. Aqu est el programa:

3986 3987

//: C03:ExtractCode.cpp {-edg} {RunByHand} // Extracts code from text.

148

3988 3989 3990 3991 3992 3993 3994 3995 3996 3997 3998 3999 4000 4001 4002 4003 4004 4005 4006 4007 4008 4009 4010 4011 4012

#include <cassert> #include <cstddef> #include <cstdio> #include <cstdlib> #include <fstream> #include <iostream> #include <string> using namespace std;

// Legacy non-standard C header for mkdir() #if defined(__GNUC__) || defined(__MWERKS__) #include <sys/stat.h> #elif defined(__BORLANDC__) || defined(_MSC_VER) \ || defined(__DMC__) #include <direct.h> #else #error Compiler not supported #endif

// Check to see if directory exists // by attempting to open a new file // for output within it. bool exists(string fname) { size_t len = fname.length(); if(fname[len-1] != '/' && fname[len-1] != '\\')

149

4013 4014 4015 4016 4017 4018 4019 4020 4021 4022 4023 4024 4025 4026 4027 4028 4029 4030 4031 4032 4033 4034 4035 4036 4037

fname.append("/"); fname.append("000.tmp"); ofstream outf(fname.c_str()); bool existFlag = outf; if(outf) { outf.close(); remove(fname.c_str()); } return existFlag; }

int main(int argc, char* argv[]) { // See if input file name provided if(argc == 1) { cerr << "usage: extractCode file [dir]" << endl; exit(EXIT_FAILURE); } // See if input file exists ifstream inf(argv[1]); if(!inf) { cerr << "error opening file: " << argv[1] << endl; exit(EXIT_FAILURE); } // Check for optional output directory string root("./"); // current is default

150

4038 4039 4040 4041 4042 4043 4044 4045 4046 4047 4048 4049 4050 4051 4052 4053 4054 4055 4056 4057 4058 4059 4060 4061 4062

if(argc == 3) { // See if output directory exists root = argv[2]; if(!exists(root)) { cerr << "no such directory: " << root << endl; exit(EXIT_FAILURE); } size_t rootLen = root.length(); if(root[rootLen-1] != '/' && root[rootLen-1] != '\\') root.append("/"); } // Read input file line by line // checking for code delimiters string line; bool inCode = false; bool printDelims = true; ofstream outf; while(getline(inf, line)) { size_t findDelim = line.find("//" "/:~"); if(findDelim != string::npos) { // Output last line and close file if(!inCode) { cerr << "Lines out of order" << endl; exit(EXIT_FAILURE); }

151

4063 4064 4065 4066 4067 4068 4069 4070 4071 4072 4073 4074 4075 4076 4077 4078 4079 4080 4081 4082 4083 4084 4085 4086 4087
++findDelim;

assert(outf); if(printDelims) outf << line << endl; outf.close(); inCode = false; printDelims = true; } else { findDelim = line.find("//" ":"); if(findDelim == 0) { // Check for '!' directive if(line[3] == '!') { printDelims = false; // To skip '!' for next search } // Extract subdirectory name, if any size_t startOfSubdir = line.find_first_not_of(" \t", findDelim+3); findDelim = line.find(':', startOfSubdir); if(findDelim == string::npos) { cerr << "missing filename information\n" << endl; exit(EXIT_FAILURE); } string subdir; if(findDelim > startOfSubdir) subdir = line.substr(startOfSubdir,

152

4088 4089 4090 4091 4092 4093 4094 4095 4096 4097 4098 4099 4100 4101 4102 4103 4104 4105 4106 4107 4108 4109 4110 4111 4112

findDelim - startOfSubdir); // Extract file name (better be one!) size_t startOfFile = findDelim + 1; size_t endOfFile = line.find_first_of(" \t", startOfFile); if(endOfFile == startOfFile) { cerr << "missing filename" << endl; exit(EXIT_FAILURE); } // We have all the pieces; build fullPath name string fullPath(root); if(subdir.length() > 0) fullPath.append(subdir).append("/"); assert(fullPath[fullPath.length()-1] == '/'); if(!exists(fullPath)) #if defined(__GNUC__) || defined(__MWERKS__) mkdir(fullPath.c_str(), 0); #else mkdir(fullPath.c_str()); #endif fullPath.append(line.substr(startOfFile, endOfFile - startOfFile)); outf.open(fullPath.c_str()); if(!outf) { cerr << "error opening " << fullPath // Create subdir // Create subdir

153

4113 4114 4115 4116 4117 4118 4119 4120 4121 4122 4123 4124 4125 4126 4127 4128

<< " for output" << endl; exit(EXIT_FAILURE); } inCode = true; cout << "Processing " << fullPath << endl; if(printDelims) outf << line << endl; } else if(inCode) { assert(outf); outf << line << endl; // Output middle code line } } } exit(EXIT_SUCCESS); } ///:~

4129 4130 4131 4132 4133 4134 4135 4136 4137 4138

Listado 4.33. C03/ExtractCode.cpp Primero observar algunas directivas de compilacin condicionales. La funcin mkdir(), que crea un directorio en el sistema de ficheros, se define por el estndar POSIX[10] en la cabecera (<direct.h>). La respectiva signatura de mkdir() tambin difiere: POSIX especifica dos argumentos, las viejas versiones slo uno. Por esta razn, existe ms de una directiva de compilacin condicional despus en el programa para elegir la llamada correcta a mkdir(). Normalmente no usamos compilaciones condicionales en los ejemplos de este libro, pero en este programa en particular es demasiado

154
4139 4140 4141 4142 4143 4144 4145 4146 4147 4148 4149 4150 4151 4152 4153 4154 4155 4156 4157 4158 4159 4160 4161 4162 til para no poner un poco de trabajo extra dentro, ya que puede usarse para extraer todo el cdigo con l. La funcin exists() en ExtractCode.cpp prueba que un directorio existe abriendo un fiechero temporal en l. Si la obertura falla, el directorio no existe. Borre el fichero enviando su nombre como unchar* a std::remove(). El programa principal valida los argumentos de la lnea de comandos y despus lee el fichero de entrada lnea por lnea, mirando por los delimitadores especiales de cdigo fuente. La bandera booleana inCode indica que el programa esta en el medio de un fichero fuente, as que las lineas deben ser extradas. La bandera printDelims ser verdadero si el elemento de obertura no est seguido de un signo de exclamancin; si no la primera y la ltima lnea no son escritas. Es importante comprobar el ltimo delimitador primero, por que el elemnto inicial es un subconjuntom y buscando por el elemento inicial debera retornar cierto en ambos casos. Si encontramos el elemento final, verificamos que estamos en el medio del procesamiento de un fichero fuente; sino, algo va mal con la manera en que los delimitadores han sido colocados en el fichero de texto. Si inCode es verdadero, todo est bien, y escribiremos (opcionalmente) la ltima linea y cerraremos el fichero. Cuando el elemento de obertura se encuentra, procesamos el directorio y el nombre del fichero y abrimos el fichero. Las siguientes funciones relacionadas con string fueron usadas en este ejemplo:
length( ), append( ), getline( ), find( )

(dos versiones), y, por

find_first_not_of( ), substr( ),find_first_of( ), c_str( ),

supuesto, operator<<( )

4163 4164 4165 4166 4167 4168 4169 4170

4.5. Resumen
Los objetos string proporcionan a los desarrolladores un gran nmero de ventajas sobre sus contrapartidas en C. La mayoria de veces, la clase string hacen a las cadenas con punteros a caracteres innecesarios. Esto elimina por completo una clase de defectos de software que radican en el uso de punteros no inicializados o con valores incorrectos. FIXME: Los string de C++, de manera transparente y dinmica, hacen crecer el espacio de alamcenamiento para acomodar los cambios de tamao

155
4171 4172 4173 4174 4175 4176 4177 4178 4179 4180 4181 4182 4183 4184 4185 de los datos de la cadena. Cuando los datos en n string crece por encima de los lmites de la memoria asignada inicialmente para ello, el objeto string har las llamadas para la gestin de la memoria para obtener el espacio y retornar el espacio al montn. La gestin consistente de la memoria previente lagunas de memoria y tiene el potencial de ser mucho ms eficiente que un "hgalo usted mismo". Las funciones de la clase string proporcionan un sencillo y comprensivo conjunto de herramientas para crear, modificar y buscar en cadenas. Las comparaciones entre string siempre son sensibles a maysculas/minsculas, pero usted puede solucionar el problema copiando los datos a una cadena estilo C acabada en nulo y usando funciones no sensibles a maysculas/minsculas, convirtiendo temporalmente los datos contenidos a maysculas o minsculas, o creando una clase string sensible que sobreescribe los rasgos de carcter usados para crear un objeto
basic_string

4186 4187 4188 4189 4190 4191 4192 4193 4194 4195 4196 4197 4198 4199 4200

4.6. Ejercicios
Las soluciones a los ejercicios se pueden encontrar en el documento electrnico titulado The Thinking in C++ Annotated Solution Guide, disponible por poco dinero en http://www.bruceeckel.com/. 1. Escriba y pruebe una funcin que invierta el orden de los caracteres en una cadena. 2. 2. Un palindromo es una palabra o grupo de palabras que tanto hacia delante hacia atrs se leen igual. Por ejemplo "madam" o "wow". Escriba un programa que tome un string como argumento desde la lnea de comandos y, usando la funcin del ejercicio anterior, escriba si el string es un palndromo o no. 3. 3. Haga que el programa del Ejercicio 2 retorne verdadero incluso si las letras simetricas difieren en maysculas/minsculas. Por ejemplo, "Civic" debera retornar verdadero aunque la primera letra sea mayscula.

156
4201 4202 4203 4204 4205 4206 4207 4208 4209 4210 4211 4212 4213 4214 4215 4216 4217 4218 4219 4220 4221 4222 4223 4224 4225 4226 4227 4228 4229 4230 4231 4232 4233 4. 4. Cambie el programa del Ejercicio 3 para ignorar la puntuacin y los espacios tambin. Por ejemplo "Able was I, ere I saw Elba." debera retornar verdadero. 5. 5. Usando las siguientes declaraciones de string y solo char (no literales de cadena o nmeros mgicos):
string one("I walked down the canyon with the moving mountain bikers."); string two("The bikers passed by me too close for comfort."); string three("I went hiking instead.");

produzca la siguiente frase: I moved down the canyon with the mountain bikers. The mountain bikers passed by me too close for comfort. So I went hiking instead. 6. 6. Escriba un programa llamado "reemplazo" que tome tres argumentos de la lnea de comandos representando un fichero de texto de entrada, una frase para reemplazar (llmela from), y una cadena de reemplazo (llmela to). El programa debera escribir un nuevo fichero en la salida estandar con todas las ocurrencias de from reemplazadas por to. 7. 7. Repetir el ejercicio anterior pero reemplazando todas las instancias pero ignorando las maysculas/minsculas. 8. 8. Haga su programa a partir del Ejercicio 3 tomando un nombre de fichero de la linea de comandos, y despues mostrando todas las palabras que son palndromos (ignorando las maysculas/minsculas) en el fichero. No intente buscar palabras para palndromos que son mas largos que una palabra (a diferencia del ejercicio 4). 9. 9. Modifique HTMLStripper.cpp para que cuando encuentre una etiqueta, muestre el nombre de la etiqueta, entonces muestre el contenido del fichero entre la etiqueta y la etiqueta de finalizacin de fichero. Asuma que no existen etiquetas anidadas, y que todas las etiquetas tienen etiquetas de finalizacion (denotadas con </TAGNAME>). 10.10. Escriba un programa que tome tres argumentos de la lnea de comandos (un nombre de fichero y dos cadenas) y muestre en la consola todas la lneas en el fichero que tengan las dos cadenas en la

157
4234 4235 4236 4237 4238 4239 4240 4241 4242 4243 4244 4245 4246 4247 4248 4249 4250 4251 4252 4253 4254 4255 4256 4257 4258 4259 4260 4261 4262 4263 4264 4265 4266 lnea, alguna cadena, solo una cadena o ninguna de ellas, basndose en la entreada de un usuario al principio del programa (el usuario elegir que modo de bsqueda usar). Para todo excepto para la opcin "ninguna cadena", destaque la cadena(s) de entrada colocando un asterisco (*) al principio y al final de cada cadena que coincida cuando sea mostrada. 11.11. Escriba un programa que tome dos argumentos de la linea de comandos (un nombre de fichero y una cadena) y cuente el numero de veces que la cadena esta en el fichero, incluso si es una subcadena (pero ignorando los solapamientos). Por ejemplo, una cadena de entrada de "ba" debera coincidir dos veces en la palabra "basquetball", pero la cadena de entrada "ana" solo deberia coincidir una vez en "banana". Muestre por la consola el nmero de veces que la cadena coincide en el fichero, igual que la longitud media de las palabras donde la cadena coincide. (Si la cadena coincide ms de una vez en una palabra, cuente solamente la palabra una vez en el clculo de la media). 12.12. Escriba un programa que tome un nombre de fichero de la lnea de comandos y perfile el uso del carcter, incluyendo la puntuacin y los espacios (todos los valores de caracteres desde el 0x21 [33] hasta el 0x7E [126], adems del carcter de espacio). Esto es, cuente el numero de ocurrencias para cada carcter en el fichero, despus muestre los resultados ordenados secuencialmente (espacio, despues !, ", #, etc.) o por frecuencia descendente o ascendente basado en una entrada de usuario al principio del programa. Para el espacio, muestre la palabra "espacio" en vez del carcter ' '. Una ejecucin de ejemplo debe mostrarse como esto: Formato secuencial, ascendente o descendente (S/A/D): D t: 526 r: 490 etc. 13.13. Usando find() y rfind(), escriba un programa que tome dos argumentos de lnea de comandos (un nombre de fichero y una cadena) y muestre la primera y la ltima palapra (y sus indices) que no coinciden con la cadena, asi como los indice de la primera y la ltima

158
4267 4268 4269 4270 4271 4272 4273 4274 4275 4276 4277 4278 4279 4280 4281 4282 4283 4284 4285 4286 4287 4288 4289 4290 4291 instancia de la cadena. Muestre "No Encontrado" si alguna de las busquedas fallan. 14.14. Usando la familia de fuciones find_first_of (pero no exclusivamente), escriba un programa que borrar todos los caracteres no alfanumricos excepto los espacios y los puntos de un fichero. Despues convierta a maysculas la primera letra que siga a un punto. 15.15. Otra vez, usando la familia de funciones find_first_of, escriba un programa que acepte un nombre de fichero como argumentod elinea de comandos y despus formatee todos los nmeros en un fichero de moneda. Ignore los puntos decimales despus del primero despues de un carcter no mumrico, e redondee al 16.16. Escriba un programa que acepte dos argumentos por lnea de comandos (un nombre de fichero y un numero) y mezcle cada paralabra en el fichero cambiando aleatoriamente dos de sus letras el nmero de veces especificado en el segundo parametro. (Esto es, si le pasamos 0 a su programa desde la lnea de comandos, las palabras no sern mezcladas; si le pasamos un 1, un par de letras aleatoriamente elegidas deben ser cambiadas, para una entrada de 2, dos parejas aleatorias deben ser intercambiadas, etc.). 17.17. Escriba un programa que acepte un nombre de fichero desde la lnea de comandos y muestre el numero de frases (definido como el numero de puntos en el fichero), el nmero medio de caracteres por frase, y el nmero total de caracteres en el fichero.

4292 4293 4294 4295 4296 4297 4298 4299 4300


[2] [1]

Algunos materiales de este captulo fueron creados originalmente por Nancy Nicolaisen

Es dificil hacer implementaciones con multiples referencias para trabajar de manera segura en multihilo. (Ver [More Exceptional C++, pp.104-14]). Ver Capitulo 10 para ms informacin sobre multiples hilos
[3]

Es una abrviacin de "no position", y su valor ms alto puede ser representado por el ubicador de string size_type (std::size_t por defecto).

159
4301 4302 4303 4304 4305 4306 4307 4308 4309 4310 4311 4312 4313 4314 4315 4316 4317 4318 4319 4320 4321 4322
[5] [4]

Descrito en profundidad en el Captulo 6.

Para mantener la exposicin simple, esta version no maneja etiquetas anidadas, como los comentarios.

Es tentador usar aqu las matemticas para evitar algunas llamadas a erase(), pero como en algunos casos uno de los operandos es string::npos (el entero sin signo ms grande posible), ocurre un desbordamiento del entero y se cuelga el algoritmo. Por las razones de seguridad mencionadas, el C++ Standards Committee est considerando una propuesta de redefinicin del string::operator[] para comportarse de manera idntica al string::at() para C++0x. (N.del T.) Se refiere a string amplio puesto que esta formado por caracteres anchos wchar_t que deben soportar la codificacin mas grande que soporte el compilador. Casi siempre esta codificacin es Unicode, por lo que casi siempre el ancho de wchar_t es 2 bytes Est alerta porque algunas versiones de Microsoft Word que substituyen erroneamente los caracteres con comilla simple con un carcter ASCII cuando salva el documento como texto, causan un error de compilacin. No tenemos idea de porqu pasa esto. Simplemente reemplace el carcter manualmente con un apstrofe. POSIX, un estndar IEEE, es un "Portable Operating System Interface" (Interficie de Sistema Operativo Portable) y es una generalizacin de muchas de las llamadas a sistema de bajo nivel encontradas en los sistemas UNIX.
[10] [9] [8] [7]

[6]

4323 4324 4325 4326 4327 4328 4329 4330 4331 4332 4333 4334 4335 4336 4337 4338 4339 4340 4341

5: Iostreams
Tabla de contenidos 5.1. Por que iostream? 5.2. Iostreams al rescate 5.3. Manejo errores de stream 5.4. Iostreams de fichero 5.5. Almacenamiento de iostream 5.6. Buscar en iostreams 5.7. Iostreams de string 5.8. Formateo de stream de salida 5.9. Manipuladores 5.10. 5.11. 5.12. 5.13. Puedes hacer mucho ms con el problema general de E/S que
simplemente coger el E/S estndar y convertirlo en una clase.

No seria genial si pudiera hacer que todos los 'receptculos' -E/S estndar, ficheros, e incluso boques de memoria- parecieran iguales de

160
4342 4343 4344 4345 4346 4347 4348 4349 4350 4351 4352 4353 4354 4355 4356 4357 4358 manera que solo tuviera que recordar una interficie? Esta es la idea que hay detrs de los iostreams. Son mucho ms sencillos, seguros, y a veces incluso ms eficientes que el conjunto de funciones de la libreria estndar de C stdio. Las clases de iostream son generalmente la primera parte de la libreria de C++ que los nuevos programadores de C++ parender a usar. En este captulo se discute sobre las mejoras que representan los iostream sobre las funciones de stdio de C y explora el comprotamiento de los ficheros y streams de strings adems de los streams de consola.

5.1. Por que iostream?


Se debe estar preguntando que hay de malo en la buena y vieja librera de C. Por que no 'incrustar' la libreria de C en una clase y ya est? A veces esta solucin es totalmente vlida. Por ejemplo, suponga que quiere estar seguro que un fichero representado por un puntero de stdio FILE siempre es abierto de forma segura y cerrado correctamente sin tener que confiar en que el usuario se acuerde de llamar a la funcin close(). El siguiente programa es este intento:

4359 4360 4361 4362 4363 4364 4365 4366 4367 4368 4369

//: C04:FileClass.h // stdio files wrapped. #ifndef FILECLASS_H #define FILECLASS_H #include <cstdio> #include <stdexcept>

class FileClass { std::FILE* f; public: struct FileClassError : std::runtime_error {

161

4370 4371 4372 4373 4374 4375 4376 4377

FileClassError(const char* msg) : std::runtime_error(msg) {} }; FileClass(const char* fname, const char* mode = "r"); ~FileClass(); std::FILE* fp(); }; #endif // FILECLASS_H ///:~

4378 4379 4380 4381 4382 4383 4384 4385 4386 4387

Listado 5.1. C04/FileClass.h Cuando trabaja con ficheros E/S en C, usted trabaja con punteros desnudos a una struct de FILE, pero esta clase envuelve los punteros y garantiza que es correctamente inicializada y destruida usando el constructor y el destructor. El segundo parmetro del constructor es el modo del fichero, que por defecto es 'r' para 'leer' Para pedir el valor del puntero para usarlo en las funciones de fichero de E/S, use la funcin de acceso fp(). Aqu estn las definiciones de las funciones miembro:

4388 4389 4390 4391 4392 4393 4394 4395

//: C04:FileClass.cpp {O} // FileClass Implementation. #include "FileClass.h" #include <cstdlib> #include <cstdio> using namespace std;

FileClass::FileClass(const char* fname, const char* mode) {

162

4396 4397 4398 4399 4400 4401 4402

if((f = fopen(fname, mode)) == 0) throw FileClassError("Error opening file"); }

FileClass::~FileClass() { fclose(f); }

FILE* FileClass::fp() { return f; } ///:~

4403 4404 4405 4406 4407 4408 4409

Listado 5.2. C04/FileClass.cpp El constructor llama a fopen(), tal como se hara normalmente, pero adems se asegura que el resultado no es cero, que indica un error al abrir el fichero. Si el fichero no se abre correctamente, se lanza una excepcin. El destructor cierra el fichero, y la funcin de acceso fp() retorna f. Este es un ejemplo de uso de FileClass:

4410 4411 4412 4413 4414 4415 4416 4417 4418 4419 4420

//: C04:FileClassTest.cpp //{L} FileClass #include <cstdlib> #include <iostream> #include "FileClass.h" using namespace std;

int main() { try { FileClass f("FileClassTest.cpp"); const int BSIZE = 100;

163

4421 4422 4423 4424 4425 4426 4427 4428 4429 4430

char buf[BSIZE]; while(fgets(buf, BSIZE, f.fp())) fputs(buf, stdout); } catch(FileClass::FileClassError& e) { cout << e.what() << endl; return EXIT_FAILURE; } return EXIT_SUCCESS; } // File automatically closed by destructor ///:~

4431 4432 4433 4434 4435 4436 4437 4438 4439 4440 4441 4442 4443 4444 4445 4446 4447 4448 4449

Listado 5.3. C04/FileClassTest.cpp Se crea el objeto FileClass y se usa en llamadas a funciones E/S de fichero normal de C, llamando a fp(). Cuando haya acabado con ella, simplemente olvdese; el fichero ser cerrado por el destructor al final del mbito de la variable. Incluso teniendo en cuenta que FILE es un puntero privado, no es particularmente seguro porque fp() lo recupera. Ya que el nico efecto que parece estar garantizado es la inicializacin y la liberacin, por que no hacerlo pblico o usar una struct en su lugar? Ntese que mientras se puede obtener una copia de f usando fp(), no se puede asignar a f -que est completamente bajo el control de la clase. Despus de capturar el puntero retornado por fp(), el programador cliente todava puede asignar a la estructura elementos o incluso cerrarlo, con lo que la seguridad esta en la garanta de un puntero a FILE vlido mas que en el correcto contenido de la estructura. Si quiere completa seguridad, tiene que evitar que el usuario acceda directamente al puntero FILE. Cada una de las versiones de las funciones normales de E/S a ficheros deben ser mostradas como miembros de clase

164
4450 4451 para que todo lo que se pueda hacer desde el acercamiento de C est disponible en la clase de C++.

4452 4453 4454 4455 4456 4457 4458 4459 4460 4461 4462 4463 4464 4465 4466 4467 4468 4469 4470 4471 4472 4473 4474

//: C04:Fullwrap.h // Completely hidden file IO. #ifndef FULLWRAP_H #define FULLWRAP_H #include <cstddef> #include <cstdio> #undef getc #undef putc #undef ungetc using std::size_t; using std::fpos_t;

class File { std::FILE* f; std::FILE* F(); // Produces checked pointer to f public: File(); // Create object but don't open file File(const char* path, const char* mode = "r"); ~File(); int open(const char* path, const char* mode = "r"); int reopen(const char* path, const char* mode); int getc(); int ungetc(int c);

165

4475 4476 4477 4478 4479 4480 4481 4482 4483 4484 4485 4486 4487 4488 4489 4490 4491 4492 4493 4494

int putc(int c); int puts(const char* s); char* gets(char* s, int n); int printf(const char* format, ...); size_t read(void* ptr, size_t size, size_t n); size_t write(const void* ptr, size_t size, size_t n); int eof(); int close(); int flush(); int seek(long offset, int whence); int getpos(fpos_t* pos); int setpos(const fpos_t* pos); long tell(); void rewind(); void setbuf(char* buf); int setvbuf(char* buf, int type, size_t sz); int error(); void clearErr(); }; #endif // FULLWRAP_H ///:~

4495 4496 4497 4498 4499

Listado 5.4. C04/Fullwrap.h Esta clase contiene casi todas las funciones de E/S de fichero de
<cstdio>. (vfprintf()

no esta; se implementa en la funcin miembro


printf()

166
4500 4501 4502 4503 4504 4505 4506 4507 4508 4509 4510 4511 4512 4513 4514 4515 4516 4517 4518 4519 4520 4521 4522 4523 4524 4525 4526 4527 4528 4529 4530 4531 4532 4533 El fichero tiene el mismo constructor que en el ejemplo anterior, y tambin tiene un constructor por defecto. El constructor por defecto es importante si se crea un array de objetos File o se usa un objeto File como miembro de otra clase donde la inicializacin no se realiza en el contructor, sino cierto tiempo despus de que el objeto envolvente se cree. El constructor por defecto pone a cero el puntero a FILE privado f. Pero ahora , antes de cualquier referencia a f, el valor debe ser comprobado para asegurarse que no es cero. Esto se consigue con F(), que es privado porque est pensado para ser usado solamente por otras funciones miembro. (No queremos dar acceso directo a usuarios a la estructura de FILE subyacente en esta clase). Este acercamiento no es terrible en ningn sentido. Es bastante funcional, y se puede imaginar haciendo clases similares para la E/S estndar (consola) y para los formateos en el core (leer/escribir un trozo de la memoria en vez de un fichero o la consola). Este bloque de cdigo es el interprete en tiempo de ejecucin usado para las listas variables de argumentos. Este es el cdigo que analiza el formato de su cadena en tiempo de ejecucin y recoge e interpreta argumentos desde una lista variable de argumentos. Es un problema por cuatro razones: 1. Incluso si solo se usa una fraccin de la funcionalidad del interprete, se carga todo en el ejecutable. Luego si quiere usar un
printf("%c", 'x');

, usted tendr todo el paquete, incluido las partes

que imprimen nmeros en coma flotante y cadenas. No hay una opcin estndar para reducir el la cantidad de espacio usado por el programa. 2. Como la interpretacin pasa en tiempo de ejecucin, no se puede evitar un empeoramiento del rendimiento. Esto es frustrante por que toda la informacin est all, en el formato de la cadena, en tiempo de compilacin, pero no se evalua hasta la ejecucin. Por otro lado, si se pudieran analizar los argumentos en el formateo de la cadena durante la compilacin, se podran hacer llamadas directas a funciones que tuvieran el potencial de ser mucho ms rpidas que un interprete en tiempo de ejecucin (aunque la familia de funciones de printf() acostumbran a estar bastante bien optimizadas).

167
4534 4535 4536 4537 4538 4539 4540 4541 4542 4543 4544 4545 4546 4547 4548 4549 4550 4551 4552 4553 4554 3. Como el formateo de la cadena no se evalua hasta la ejecucin, no se hace una comprobacin de errores al compilar. Probalblemente est familiarizado con este problema si ha intentado buscar errores que provienen del uso de un nmero o tipo de argumentos incorrecto en una sentencia printf(). C++ ayuda mucho a encontrar rpidamente errores durante la compilacin y hacerle la vida ms fcil. Parece una tonteria desechar la seguridad en los tipos de datos para la libreria de E/S, especialmente cuando usamos intensivamente las E/S. 4. Para C++, el ms crucial de los problemas es que la familia de funciones de printf() no es particularmente extensible. Esta realmente diseada para manejar solo los tipos bsicos de datos en C (char, int, float, double, wchar_t, char*, wchar_t*, y void*) y sus variaciones. Debe estar pensando que cada vez que aade una nueva clase, puede aadir funciones sobrecargadas printf() y scanf() (y sus variaciones para ficheros y strings), pero recuerde: las funciones sobrecargadas deben tener diferentes tipos de listas de argumentos, y la familia de funciones de printf() esconde esa informacin en la cadena formateada y su lista variable de argumentos. Para un lenguage como C++, cuya virtud es que se pueden aadir fcilmente nuevos tipos de datos, esta es una restriccin inaceptable.

4555 4556 4557 4558 4559 4560 4561 4562 4563 4564 4565

5.2. Iostreams al rescate


Estos problemas dejan claro que la E/S es una de las principales prioridades para la librera de clases estndar de C++. Como 'hello, worlod' es el primer programa que cualquiera escribe en un nuevo lenguaje, y porque la E/S es parte de virtualmente cualquier programa, la librera de E/S en C++ debe ser particularmente fcil de usar. Tembin tiene el reto mucho mayor de acomodar cualquier nueva clase. Por tanto, estas restricciones requieren que esta librera de clases fundamentales tengan un diseo realmente inspirado. Adems de ganar en abstraccin y claridad en su trabajo con las E/S y el formateo, en este captulo ver lo potente que puede llegar a ser esta librera de C++.

168
4566 4567 4568 4569 4570 4571 4572 4573 4574 4575 4576 4577 4578 4579 4580 4581 4582 4583 4584 4585 4586 4587 4588 4589 4590

5.2.1. Insertadores y extractores


Un stream es un objeto que transporta y formatea carcteres de un ancho fijo. Puede tener un stream de entrada (por medio de los descendientes de la clase istream), o un stream de salida (con objetos derivados de ostream), o un stream que hace las dos cosas simultneamente (con objetos derivados de iostream). La librera iostream provee tipos diferentes de estas clases:
ifstream, ofstream ostringstream,

y fstream para ficheros, y istringstream,

y stringstream para comunicarese con la clase string del

estndar C++. Todas estas clases stream tiene prcticamente la misma interfaz, por lo que usted puede usar streams de manera uniforme, aunque est trabajando con un fichero, la E/S estndar, una regin de la memoria, o un objeto string. La nica interfaz que aprender tambin funciona para extensiones aadidas para soportar nuevas clases. Algunas funciones implementan sus comandos de formateo, y algunas funciones leen y escriben caracteres sin formatear. Las clases stream mencionadas antes son actualmente especializaciones de plantillas, muchas como la clase estndar string son especializaciones de la plantilla basic_string. Las clases bsicas en la jerarquia de herencias son mostradas en la siguiente figura: [11] La clase ios_base declara todo aquello que es comn a todos los stream, independientemente del tipo de carcteres que maneja el stream. Estas declaraciones son principalmente constantes y funciones para manejarlas, algunas de ella las ver a durante este captulo. El resto de clases son plantillas que tienen un tipo de caracter subyacente como parmetro. La clase istream, por ejemplo, est definida a continuacin:

4591

typedef basic_istream<char< istream;

4592 4593 4594 4595 4596

Todas las clases mencionadas antes estan definidas de manera similar. Tambin hay definiciones de tipo para todas las clases de stream usando
wchar_t

(la anchura de este tipo de carcteres se discute en el Captulo 3) en

lugar de char. Miraremos esto al final de este captulo. La plantilla basic_ios define funciones comunes para la entrada y la salida, pero depende del tipo

169
4597 4598 4599 4600 4601 4602 4603 4604 4605 4606 4607 4608 4609 4610 de carcter subyacente (no vamos a usarlo mucho). La plantilla
basic_istream

define funciones genricas para la entrada y basic_ostream

hace lo mismo para la salida. Las clases para ficheros y streams de strings introducidas despus aaden funcionalidad para sus tipos especificos de
stream.

En la librera de iostream, se han sobrecargado dos operadores para simplificar el uso de iostreams. El operador << se denomina frecuentemente instertador para iostreams, y el operador >> se denomina frecuentemente extractor. Los extractores analizan la informacin esperada por su objeto destino de acuerdo con su tipo. Para ver un ejemplo de esto, puede usar el objeto cin, que es el equivalente de iostream de stdin en C, esto es, entrada estndar redireccionable. Este objeto viene predefinido cuando usted incluye la cabecera <iostream>.

4611 4612 4613 4614 4615 4616 4617 4618 4619 4620 4621

int i; cin >> i;

float f; cin >> f;

char c; cin >> c;

char buf[100]; cin >> buf;

4622 4623

Existe un operador sobrecargado >> para cada tipo fundamental de dato. Usted tambin puede sobrecargar los suyos, como ver ms adelante.

170
4624 4625 4626 Para recuperar el contenido de las variables, puede usar el objeto cout (correspondiente con la salida estndar; tambin existe un objeto cerr correspondiente con la salida de error estndar) con el insertador <<:

4627 4628 4629 4630 4631 4632 4633 4634 4635 4636 4637 4638

cout << "i = "; cout << i; cout << "\n"; cout << "f = "; cout << f; cout << "\n"; cout << "c = "; cout << c; cout << "\n"; cout << "buf = "; cout << buf; cout << "\n";

4639 4640 4641 4642

Esto es tedioso y no parece ser un gran avance sobre printf(), aparte de la mejora en la comprobacin de tipos. Afortunadamente, los insertadores y extractores sobrecargados estn diseados para ser encadenados dentro de expresiones ms complejas que son mucho ms fciles de escribir (y leer):

4643 4644 4645 4646

cout << "i = " << i << endl; cout << "f = " << f << endl; cout << "c = " << c << endl; cout << "buf = " << buf << endl;

171
4647 4648 4649 4650 4651 4652 4653 4654 4655 4656 4657 4658 4659 4660 Definir insertadores y extractores para sus propias clases es simplemente una cuestion de sobrecargar los operadores asociados para hacer el trabajo correcto, de la siguente manera: Hacer del primer parmetro una referencia no constante al stream (istream para la entrada, ostream para la salida). Realizar la operacin de insertar/extraer datos hacia/desde el stream (procesando los componentes del objeto). Retornar una referencia al stream El stream no debe ser constante porque el procesado de los datos del
stream

cambian el estado del stream. Retornando el stream, usted permite el antes.

encadenado de operaciones en una sentencia individual, como se mostr

Como ejemplo, considere como representar la salida de un objeto Date en formato MM-DD-AAAA . El siguiente insertador hace este trabajo:

4661 4662 4663 4664 4665 4666 4667

ostream& operator<<(ostream& os, const Date& d) { char fillc = os.fill('0'); os << setw(2) << d.getMonth() << '-' << setw(2) << d.getDay() << '-' << setw(4) << setfill(fillc) << d.getYear(); return os; }

4668 4669 4670 4671 4672 4673 4674

Esta funcin no puede ser miembro de la clase Date por que el operando de la izquierda << debe ser el stream de salida. La funcin miembro fill() de ostream cambia el carcter de relleno usado cuando la anchura del campo de salida, determinada por el manipulador setw(), es mayor que el necesitado por los datos. Usamos un caracter '0' ya que los meses anteriores a Octubre mostrarn un cero en primer lugar, como '09' para Septiembre. La funcion fill() tambin retorna el caracter de relleno anterior (que por

172
4675 4676 4677 4678 4679 4680 defecto es un espacio en blanco) para que podamos recuperarlo despus con el manipulador setfill(). Discutiremos los manipuladores en profundidad ms adelante en este captulo. Los extractores requieren algo ms cuidado porque las cosas pueden ir mal con los datos de entrada. La manera de avisar sobre errores en el
stream

es activar el bit de error del stream, como se muestra a continuacin:

4681 4682 4683 4684 4685 4686 4687 4688 4689 4690 4691 4692 4693

istream& operator>>(istream& is, Date& d) { is >> d.month; char dash; is >> dash; if(dash != '-') is.setstate(ios::failbit); is >> d.day; is >> dash; if(dash != '-') is.setstate(ios::failbit); is >> d.year; return is; }

4694 4695 4696 4697 4698 4699 4700 4701

Cuando se activa el bit de error en un stream, todas las operaciones posteriores sern ignoradas hasta que el stream sea devuelto a un estado correcto (explicado brevemente). Esto es porque el cdigo de arriba continua extrayendo incluso is ios::failbit est activado. Esta implementacin es poco estricta ya que permite espacios en blanco entre los numeros y guiones en la cadena de la fecha (por que el operador >> ignora los espacios en blanco por defecto cuado lee tipos fundamentales). La cadena de fecha a continuacin es vlida para este extractor:

173

4702 4703 4704

"08-10-2003" "8-10-2003" "08 - 10 - 2003"

4705

Pero estas no:

4706 4707

"A-10-2003" // No alpha characters allowed "08%10/2003" // Only dashes allowed as a delimiter

4708 4709

Discutiremos los estados de los stream en mayor profundidad en la seccin 'Manejar errores de stream' despus en este captulo.

4710 4711 4712 4713 4714 4715

5.2.2. Uso comn


Como se ilustraba en el extractor de Date, debe estar alerta por las entradas errneas. Si la entrada produce un valor inesperado, el proceso se tuerce y es difcil de recuperar. Adems, por defecto, la entrada formateada est delimitada por espacios en blanco. Considere que ocurre cuando recogemos los fragmentos de cdigo anteriores en un solo programa:

4716

//: V2C04:Iosexamp.cpp {RunByHand}

4717

y le proporcionamos la siguiente entrada:

4718

12 1.4 c this is a test

4719

esperamos la misma salida que si le hubieramos proporcionado esto:

4720 4721

12 1.4

174

4722 4723

c this is a test

4724

pero la salida es algo inesperado

4725 4726 4727 4728

i = 12 f = 1.4 c = c buf = this 0xc

4729 4730 4731 4732 4733 4734 4735 4736 4737 4738 4739 4740 4741 4742 4743 4744 4745 4746 4747 4748

Ntese que buf solo tiene la primera palabra porque la rutina de entrada busca un espacio que delimite la entrada, que es el que se encuentra despus de 'tihs.' Adems, si la entrada continua de datos es mayor que el espacio reservado por buf, sobrepasamos los limites del buffer. En la prctica, usualmente desear obtener la entrada desde programas interactivos, una linea cada vez como secuencia de carcteres, leerla, y despus hacer las conversiones necesarias hasta que estn seguras en un buffer. De esta manera no deber preocuparse por la rutina de entrada fallando por datos inesperados. Otra consideracin es todo el concepto de interfaz de lnea de comandos. Esto tenia sentido en el pasado cuando la consola era la nica interfaz con la mquina, pero el mundo est cambiando rpidamente hacia otro donde la interfaz grfica de usuario (GUI) domina. Cual es el sentido de la E/S por consola en este mundo? Esto le da mucho ms sentido a ignorar cin en general, salvo para ejemplos simples y tests, y hacer los siguientes acercamientos: 1. Si su programa requiere entrada, leer esta entrada desde un fichero? Pronto ver que es remarcablemente fcil usar ficheros con
iostream. Iostream

para ficheros todavia funciona perfectamente con una GUI.

175
4749 4750 4751 4752 4753 4754 4755 4756 4757 4758 4759 4760 4761 4762 4763 2. Leer la entrada sin intentar convertirla, como hemos sugerido. Cuando la entrada es algun sitio donde no podemos arriesgarnos durante la conversin, podemos escanearla de manera segura. 3. La salida es diferente. Si est usando una interfaz grfica, cout no necesariamente funciona, y usted debe mandarlo a un fichero (que es indntico a mandarlo a un cout) o usar los componentes del GUI para mostrar los datos. En cualquier otra situacion, a menudo tiene sentido mandarlo a cout. En ambos casos, la funciones de formateo de la salida de iostream son muy tiles. Otra prctica comn ahorra tiempo en compilaciones largas. Consideres, por ejemplo, cmo quiere declarar los operadores del stream Date introducidos antes en el captulo en un fichero de cabecera. Usted solo necesita incluir los prototipos para las funciones, luego no es necesario incluir la cabecera entera de <iostream> en Date.h. La prctica estndar es declarar solo las clases, algo como esto:

4764

class ostream;

4765 4766 4767 4768 4769 4770 4771 4772

Esta es una vieja tecnica para separar la interfaz de la implementacin y a menudo la llaman declaracin avanzada( y ostream en este punto debe ser considerada un tipo incompleto, ya que la definicin de la clase no ha sido vista todavia por el compilador). Esto con funcionar asi, igualmente, por dos razones: Las clases stream estan definidas en el espacio de nombres std. Son plantillas. La declaracin correcta debera ser:

4773 4774 4775 4776

namespace std { template<class charT, class traits = char_traits<charT> > class basic_ostream; typedef basic_ostream<char> ostream;

176

4777

4778 4779 4780 4781

(Como puede ver, como las clase string, las clases stream usan las clases de rasgos de caracter mencionadas en el Captulo 3). Como puede ser terriblemente tedioso darle un tipo a todas las clases stream a las que quiere referenciar, el estndar provee una cabecera que lo hace por usted:

4782 4783 4784 4785 4786 4787 4788 4789

// Date.h #include <iosfwd>

class Date { friend std::ostream& operator<<(std::ostream&, const Date&); friend std::istream& operator>>(std::istream&, Date&); // Etc.

4790 4791 4792 4793 4794 4795 4796 4797 4798

5.2.3. Entrada orientada a lneas


Para recoger la entrada de lnea en lnea, tiene tres opciones: La funcin miembre get() La funcin miembro getline() La funcin global getline() definida en la cabecera <string> Las primeras dos funciones toman tres parmentros: Un puntero a un buffer de carcters donde se guarda el resultado. El tamao de este buffer (para no sobrepasarlo). El carcter de finalizacin, para conocer cuando parar de leer la entrada.

177
4799 4800 4801 4802 4803 4804 4805 4806 4807 4808 4809 4810 4811 4812 4813 4814 4815 4816 4817 4818 4819 4820 4821 4822 4823 4824 4825 4826 El carcter de finalizacin tiene un valor por defecto de '\n', que es el que usted usar usualmente. Ambas funciones almacenan un cero en el buffer resultante cuando encuentran el caracter de terminacin en la entrada. Entonces, cual es la diferencia? Sutil pero importante: get() se detiene cuando vee el delimitador en el stream de entrada, pero no lo extrae de
stream

de entrada. Entonces, si usted hace otro get() usando el mismo

delimitador, retornar inmediatamente sin ninguna entrada contenida. (Presumiblemente, en su lugar usar un delimitador diferente en la siguiente sentencia get() o una funcin de entrada diferente.) La funcin getline(), por el contrario, extrae el delimitador del stream de entrada, pero tampoco lo almacena en el buffer resultante. La funcin getline() definida en <string> es conveniente. No es una funcin miembro, sino una funcin aislada declarada en el espacio de nombres std. Slo toma dos parmetros que no son por defecto, el stream de entrada y el objeto string para rellenar. Como su propio nombre dice, lee carcteres hasta que encuentra la primera aparicin del delimitador ('\n' por defecto) y consume y descarta el delimitador. La ventaja de esta funcin es que lo lee dentro del objeto string, as que no se tiene que preocuparse del tamao del buffer. Generalmente, cuando esta procesando un fichero de texto en el que usted quiere leer de lnea en lnea, usted querra usar una de las funciones
getline().

Versiones sobrecargadas de get()

Versiones sobrecargadas de get() La funcin get() tambin viene en tres versiones sobrecargadas: una sin argumentos que retorna el siguiente carcter usando un valor de retorno int; una que recoge un carcter dentro de su argumento char usando una referencia; y una que almacena directamente dentro del buffer subyacente de otro objeto iostream. Este ltimo se explora despus en el captulo.

4827 4828 4829 4830

Leyendo bytes sin formato Si usted sabe exactamente con que esta tratando y quiere mover los bytes directamente dentro de una variable, un array, o una estructura de memoria, puede usar la funcin de E/S sin formatear read(). El primer argumento para

178
4831 4832 4833 4834 4835 4836 esta funcin es un puntero a la destinacin en memoria, y el segundo es el nmero de bytes para leer. Es especialmente til su usted ha almacenado previamente la informacin a un fichero, por ejemplo, en formato binario usando la funcin miembro complementaria write() para el stream de salida (usando el mismo compilador, por supuesto). Ver ejemplos de todas estas funciones ms adelante.

4837 4838 4839 4840 4841 4842 4843 4844 4845 4846 4847 4848 4849 4850 4851 4852 4853 4854 4855 4856 4857 4858 4859 4860

5.3. Manejo errores de stream


El extractor de Date mostrado antes activa el bit de error de un stream bajo ciertas condiciones. Como sabe un usuario que este error ha ocurrido? Puede detectar errores del stream llamando a ciertas funciones miembro del stream para ver si tenemos un estado de error, o si a usted no le preocupa qu tipo de error ha pasado, puede evaluar el stream en un contexto Booleano. Ambas tcnicas derivan del estado del bit de error de un stream.

5.3.1. Estados del stream


La clase ios_base, desde la que ios deriva,[12]define cuatro banderas que puede usar para comprobar el estado de un stream: Bandera Significado
badbit

Algn error fatal (quizs fsico) ha ocurrido. El stream debe considerarse no usable.
eofbit

Ha ocurrido un final de entrada (ya sea por haber encontrado un final fsico de un stream de fichero o por que el usuario ha terminado el stream de consola, (usando un Ctrl-Z o Ctrl-D).
failbit

Una operacin de E/S ha fallado, casi seguro que por datos invlidos (p.e. encontrar letras cuando se intentaba leer un nmero). El stream todava se puede usar. El failbit tambin se activa cuando ocurre un final de entrada.
goodbit

179
4861 4862 4863 4864 4865 4866 4867 4868 4869 4870 4871 4872 4873 4874 4875 4876 4877 Todo va bien; no hay errores. La final de la entrada todava no ha ocurrido. Puede comprobar si alguna de estas condiciones ha ocurrido llamando a la funcin miembro correspondiente que retorna un valor Booleano indicando cual de estas ha sido activada. La funcin miembro de stream good() retorna cierto si ninguno de los otros tres bits se han activado. La funcin eof() retorna cierto si eofbit est activado, que ocurre con un intento de leer de un
stream

que ya no tiene datos (generalmente un fichero). Como el final de una

entrada ocurre en C++ cuando tratamos de leer pasado el final del medio fsico, failbit tambin se activa para indicar que los datos esperados no han sido correctamente ledos. La funcin fail() retorna cierto si failbit o
badbit

estn activados, y bad() retorna cierto solo si badbit est activado.

Una vez alguno de los bit de error de un stream se activa, permanece activo, cosa que no siempre es lo que se quiere. Cuando leemos un fichero, usted puede querer colocarse en una posicin anterior en el fichero antes de su final. Simplemenete moviendo el puntero del fichero no se desactiva el
eofbit

o el failbit; debe hacerlo usted mismo con la funcin clear(), haciendo algo as:

4878

myStream.clear(); // Clears all error bits

4879 4880 4881 4882 4883 4884 4885

Despus de llamar a clear(), good() retornar cierto si es llamada inmediatamente. Como vi en el extractor de Date antes, la funcin
setstate()

activa los bits que usted le pasa.Eso significa que setstate no

afecta a los otros bits? Si ya esta activo, permanece activo. Si usted quiere activar ciertos bits pero en el mismo momento, desactivar el resto, usted puede llamar una versin sobrecargada de clear(), pasandole una expresion binaria representando los bits que quiere que se activen, as:

4886

myStream.clear(ios::failbit | ios::eofbit);

4887 4888 4889

La mayora del tiempo usted no estar interesado en comprobar los bits de estado del stream individualmente. Generalmente usted simplemente quiere conocer si todo va bien. Ese es el caso cuando quiere leer un fichero del

180
4890 4891 4892 4893 4894 principio al final; usted quiere saber simplemente cuando la entrada de datos se ha agotado. Puede usar una conversion de la funcin definida para void* que es automticamente llamada cuando un stream esta en una expresin booleana. Leer un stream hasta el final de la entrada usando este idioma se parece a lo siguiente:

4895 4896 4897

int i; while(myStream >> i) cout << i << endl;

4898 4899 4900 4901 4902 4903 4904

Recuerde que operator>>() retorna su argumento stream, as que la sentencia while anterior comprueba el stream como una expresin booleana. Este ejemplo particular asume que el stream de entrada myStream contiene enteros separados por un espacio en blanco. La funcin ios_base::operator
void*()

simplemente llama a good() en su stream y retorna el resultado.[13] idioma es conveniente.

Como la mayora de operaciones de stream retornan su stream, usar ese

4905 4906 4907 4908 4909 4910 4911 4912 4913 4914 4915 4916 4917 4918

5.3.2. Streams y excepciones


Los iostream han existido como parte de C++ mucho antes que hubieran excepciones, luego comprobar el estado de un stream manualmente era la manera en que se hacia. Para mantener la compatibilidad, este es todava el status quo, pero los modernos iostream pueden lanzar excepciones en su lugar. La funcin miembro de stream exceptions() toma un parmetro representando los bits de estado para los que usted quiere lanzar la excepcion. Siempre que el stream encuentra este estado,este lanza una excepcion de tipo std::ios_base::failure, que hereda de std::exception. Aunque usted puede disparar una excepcin para alguno de los cuatro estados de un stream, no es necesariamente una buena idea activar las excepciones para cada uno de ellos. Tal como explica el Captulo uno, se usan las excepciones para condiciones verdaderamente excepcionales, pero el final de un fichero no solo no es excepcional! Es lo que se espera!

181
4919 4920 Por esta razn, solo debe querer activar las excepciones para errores representados por badbit, que deberia ser como esto:

4921

myStream.exceptions(ios::badbit);

4922 4923 4924 4925 4926 4927 4928 4929 4930 4931 4932 4933 4934 4935 4936 4937

Usted activa las excepciones stream por stream, ya que exceptions() es una funcin miembro para los streams. La funcin exceptions() retorna una mscara de bits [14] (de tipo iostate, que es un tipo dependiente del compilador convertible a int) indicando que estados de stream causarn excepciones. Si estos estados ya han sido activados, la excepcin ser lanzada inmediatamente. Por supuesto, si usa excepciones en conexiones a streams, debera estar preparado paracapturarlas, lo que quiere decir que necesita envolver todos los stream bon bloques try que tengan un manejador ios::failure. Muchos programadores encuentran tedioso y simplemente comprueban manualmente donde esperan encontrar errores (ya que, por ejemplo, no esperan encontrar bad() al retornar true la mayoria de veces). Esto es otra razn que tienen los streams para que el lanzamiento de excepciones sea opcional y no por defecto. en cualquier caso, usted peude elegir como quiere manejar los errores de stream. Por las mismas razones que recomendamos el uso de excepciones para el manejo de rrores en otros contextos, lo hacemos aqui.

4938 4939 4940 4941 4942 4943 4944 4945 4946 4947

5.4. Iostreams de fichero


Manipular ficheros con iostream es mucho ms fcil y seguro que usar
stdio

en C. Todo lo que tiene que hacer es crear un objeto - el constructor

hace el trabajo. No necesita cerrar el fichero explcitamente (aunque puede, usando la funcin miembro close()) porque el destructor lo cerrar cuando el objeto salga del mbito. Para crear un fichero que por defecto sea de entrada, cree un objeto ifstream . Para crear un fichero que por defecto es de salida, cree un objeto ofstream. Un fstream puede hacer ambas cosas. Las clases de stream de fichero encajan dentro de las clases iostream como se muestra en la siguiente figura:

182
4948 4949 4950 Como antes, las clases que usted usa en realidad son especializaciones de plantillas definidas por definiciones de tipo. Por ejemplo, ifstream, que procesa ficheros de char, es definida como:

4951

typedef basic_ifstream<char> ifstream;

4952 4953 4954 4955 4956 4957 4958

5.4.1. Un ejemplo de procesado de fichero.


Aqui tiene un ejemplo que muestra algunas de las caractersticas discutidas antes. Ntese que la inclusin de <fstream> para delarar las clases de fichero de E/S. Aunque en muchas plataformas esto tambin incluye <iostream> automticamente, los compiladores no estn obligados a hacer esto. Si usted quiere compatibilidad, incluya siempre ambas cabeceras.

4959 4960 4961 4962 4963 4964 4965 4966 4967 4968 4969 4970 4971 4972

//: C04:Strfile.cpp // Stream I/O with files; // The difference between get() & getline(). #include <fstream> #include <iostream> #include "../require.h" using namespace std;

int main() { const int SZ = 100; // Buffer size; char buf[SZ]; { ifstream in("Strfile.cpp"); // Read assure(in, "Strfile.cpp"); // Verify open

183

4973 4974 4975 4976 4977 4978 4979 4980 4981 4982 4983 4984 4985 4986 4987 4988 4989 4990 4991 4992 4993 4994 4995 4996

ofstream out("Strfile.out"); // Write assure(out, "Strfile.out"); int i = 1; // Line counter

// A less-convenient approach for line input: while(in.get(buf, SZ)) { // Leaves \n in input in.get(); // Throw away next character (\n) cout << buf << endl; // Must add \n // File output just like standard I/O: out << i++ << ": " << buf << endl; } } // Destructors close in & out

ifstream in("Strfile.out"); assure(in, "Strfile.out"); // More convenient line input: while(in.getline(buf, SZ)) { // Removes \n char* cp = buf; while(*cp != ':') ++cp; cp += 2; // Past ": " cout << cp << endl; // Must still add \n } } ///:~

184
4997 4998 4999 5000 5001 5002 5003 5004 5005 5006 5007 5008 5009 5010 5011 5012 5013 5014 5015 5016 5017 5018 5019 5020 5021 5022 5023 5024 5025 5026 5027 5028 5029 5030 Listado 5.5. C04/Strfile.cpp La creacin tanto del ifstream como del ofstream estn seguidas de un
assure()

para garantizar que el fichero ha sido abierto exitosamente. El

objeto resultante, usado en una situacin donde el compilador espera un resultado booleano, produce un valor que indica xito o fracaso. El primer while demuestra el uso de dos formas de la funcin get(). La primera toma los carcteres dentro de un buffer y pone un delimitador cero en el buffer cuando bien SZ-1 carcteres han sido leidos o bien el tercer argumento (que por defecto es '\n') es encontrado. La funcin get() deja el carcter delimitador en el stream de entrada, as que este delimitador debe ser eliminado via in.get() usando la forma de get() sin argumentos. Puede usar tambien la funcin miembro ignore(), que tiene dos parmetros por defecto. El primer argumento es el nmero de carcteres para descartar y por defecto es uno. El segundo argumento es el carcter en el que ignore() se detiene (despus de extraerlo) y por defecto es EOF. A continuacin, se muestran dos sentencias de salida similares: una hacia
cout

y la otra al fichero de salida. Ntese la conveniencia aqu - no necesita

preocuparse del tipo de objeto porque las sentencias de formateo trabajan igual con todos los objetos ostream. El primero hace eco de la linea en la salida estndar, y el segundo escribe la lnea hacia el fichero de salida e incluye el nmero de lnea. Para demostrar getline(), abra el fichero recin creado y quite los nmeros de linea. Para asegurarse que el fichero se cierra correctamente antes de abrirlo para la lectura, usted tiene dos opciones. Puede envolver la primera parte del programa con llaves para forzar que el objeto out salga del mbito, llamando as al destructor y cerrando el fichero, que es lo que se hace aqu. Tambien puede l lamar a close() para ambos ficheros; si hace esto, puede despues rehusar el objeto de entrada llamando a la funcin miembro open(). El segundo while muestra como getline() borra el caracter terminador (su tercer argumento, que por defecto es '\n') del stream de entrada cuando este es encontrado. Aunque getline(), como get(), pone un cero en el buffer, este todava no inserta el carcter de terminacin.

185
5031 5032 5033 5034 5035 Este ejemplo, as como la mayora de ejemplos en este captulo, asume que cada llamada a alguna sobrecarga de getline() encontrar un carcter de nueva lnea. Si este no es el caso, la estado eofbit del stream ser activado y la llamada a getline() retornar falso, causando que el programa pierda la ltima lnea de la entrada.

5036 5037 5038 5039 5040 5041 5042 5043 5044 5045 5046 5047 5048 5049 5050 5051 5052 5053 5054 5055 5056 5057 5058 5059 5060

5.4.2. Modos de apertura


Puede controlar la manera en que un fichero es abierto sobreescribiendo los argumentos por defecto del constructor. La siguiente tabla muestra las banderas que controlan el modo de un fichero: Bandera Funcin
ios::in

Abre el fichero de entrada. Use esto como un modo de apertura para un


ofstream

para prevenir que un fichero existente sea truncado.


ios::out

Abre un fichero de salida. Cuando es usado por un ofstream sin ios::app,


ios::ate

o ios::in, ios::trunc es implicado.


ios::app

Abre un fichero de salida para solo aadir .


ios::ate

Abre un fichero existente (ya sea de entrada o salida) y busca el final.


ios::trunc

Trunca el fichero antiguo si este ya existe.


ios::binary

Abre un fichero en modo binario. Por defecto es en modo texto. Puede combinar estas banderas usando la operacin or para bits El flag binario, aun siendo portable, solo tiene efecto en algunos sistemas no UNIX, como sistemas operativos derivados de MS-DOS, que tiene convenciones especiales para el almacenamiento de delimitadores de final de lnea. Por ejemplo, en sistemas MS-DOS en modo texto (el cual es por

186
5061 5062 5063 5064 5065 5066 5067 5068 5069 5070 5071 5072 5073 5074 5075 5076 5077 5078 5079 5080 5081 5082 5083 defecto), cada vez que usted inserta un nuevo carcter de nueva lnea ('\n'), el sistema de ficheros en realidad inserta dos carcteres, un par retorno de carro/fin de lnea (CRLF), que es el par de carcteres ASCII 0x0D y 0x0A. En sentido opuesto, cuando usted lee este fichero de vuelta a memoria en modo texto, cada ocurrencia de este par de bytes causa que un '\n' sea enviado al programa en su lugar. Si quiere sobrepasar este procesado especial, puede abrir el fichero en modo binario. El modo binario no tiene nada que ver ya que usted puede escribir bytes sin formato en un fichero - siempre puede (llamando a write()). Usted debera, por tanto, abrir un fichero en modo binario cuando vaya a usar read() o write(), porque estas funciones toman un contador de bytes como parmetro. Tener carcteres extra '\r' estropear su contador de bytes en estas instancias. Usted tambin puede abrir un fichero en formato binario si va a usar comandos de posicionamiento en el
stream

que se discuten ms adelante.

Usted puede abrir un fichero tanto para entrada como salida declarando un objeto fstream. cuando declara un objeto fstream, debe usar suficientes banderas de modos de apertura mencionados antes para dejar que el sistema de ficheros sepa si quiere leer, escribir, o ambos. Para cambiar de salida a entrada, necesita o bien limpiar el stream o bien cambiar la posicin en el fichero. Para cambiar de entrada a salida, cambie la posicion en el fichero. Para crear un fichero usando un objeto fstream, use la bandera de modo de apertura ios::trunc en la llamada al constructor para usar entrada y salida.

5084 5085 5086 5087 5088 5089 5090 5091 5092

5.5. Almacenamiento de iostream


Las buenas prcticas de diseo dictan que, cuando cree una nueva clase, debe esforzarse en ocultar los detalles de la implementacin subyacente tanto como sea posible al usuario de la clase. Usted le muestra solo aquello que necesita conocer y el resto se hace privado para evitar confusiones. Cuando usamos insertadores y extractores, normalmente usted no conoce o tiene cuidado con los bytes que se consumen o se producen, ya que usted est tratando con E/S estndar, ficheros, memoria, o alguna nueva clase o dispositivo creado.

187
5093 5094 5095 5096 5097 5098 5099 5100 5101 5102 5103 5104 5105 5106 5107 5108 5109 5110 5111 5112 5113 5114 5115 5116 5117 5118 5119 Llega un momento, no obstante, en el que es importante comunicar con la parte del iostream que produce o consume bytes. Para proveer esta parte con una interfaz comn y esconder todava su implementacin subyacente, la librera estndar la abstrae dentro de su clase, llamada streambuf. Cada objeto iostream contiene un puntero a alguna clase de streambuf. (El tipo depende de que se est tratando con E/S estndar, ficheros, memoria, etc.). Puede acceder al streambuf directamente; por ejemplo, puede mover bytes sin formatear dentro y fuera del streambuf sin formatearlos a travs de la encapsulacin del iostream. Esto es posible llamando a las funciones miembro del objeto streambuf. Actualmente, la cosa ms importante que debe conocer es que cada objeto iostream contiene un puntero a un objeto streambuf, y el objeto
streambuf

tiene algunas funciones miembro que puede llamar si es

necesario. Para ficheros y streams de string, hay tipos especializados de buffers de stream, como ilustra la figura siguiente: Para permitirle el acceso al streambuf, cada objeto iostream tiene una funcin miembro llamada rdbuf() que retorna el puntero a un objeto
streambuf.

De esta manera usted puede llamar cualquier funcin miembro

del streambuf subyacente. No obstante, una de las cosas ms interesantes que usted puede hacer con el puntero al streambuf es conectarlo con otro objeto iostream usando el operador <<. Esto inserta todos los carcteres del objeto dentro del que est al lado izquierdo del <<. Si quiere mover todos los carcteres de un iostream a otro, no necesita ponerse con el tedioso (y potencialmente inclinado a errores de cdigo) proceso de leer de carcter por carcter o lnea por lnea. Este es un acercamiento mucho ms elegante. Aqui est un programa muy simple que abre un fichero y manda el contenido a la salida estndar (similar al ejemplo previo):

5120 5121 5122 5123

//: C04:Stype.cpp // Type a file to standard output. #include <fstream> #include <iostream>

188

5124 5125 5126 5127 5128 5129 5130 5131

#include "../require.h" using namespace std;

int main() { ifstream in("Stype.cpp"); assure(in, "Stype.cpp"); cout << in.rdbuf(); // Outputs entire file } ///:~

5132 5133 5134 5135 5136

Listado 5.6. C04/Stype.cpp Un ifstream se crea usando el fichero de cdigo fuente para este programa como argumento. La funcin assure() reporta un fallo si el fichero no puede ser abierto. Todo el trabajo pasa realmente en la sentencia

5137

cout << in.rdbuf();

5138 5139 5140 5141 5142 5143 5144

que manda todo el contenido del fichero a cout. No solo es un cdigo ms sucinto, a menudo es ms eficiente que mover los byte de uno en uno. Una forma de get() escribe directamente dentro del streambuf de otro objeto. El primer argumento es una referencia al streambuf de destino, y el segundo es el carcter de terminacin ('\n' por defecto), que detiene la funcin get(). As que existe todava otra manera de imprimir el resultado de un fichero en la salida estndar:

5145 5146 5147 5148

//: C04:Sbufget.cpp // Copies a file to standard output. #include <fstream> #include <iostream>

189

5149 5150 5151 5152 5153 5154 5155 5156 5157 5158 5159 5160 5161

#include "../require.h" using namespace std;

int main() { ifstream in("Sbufget.cpp"); assure(in); streambuf& sb = *cout.rdbuf(); while(!in.get(sb).eof()) { if(in.fail()) // Found blank line in.clear(); cout << char(in.get()); // Process '\n' } } ///:~

5162 5163 5164 5165 5166 5167 5168 5169 5170 5171 5172 5173 5174 5175

Listado 5.7. C04/Sbufget.cpp La funcin rdbuf() retorna un puntero, que tiene que ser desreferenciado para satisfacer las necesidades de la funcin para ver el objeto. Los buffers de stream no estan pensados para ser copiados (no tienen contructor de copia), por lo que definimos sb como una referencia al buffer de stream de
cout.

Necesitamos las llamadas a fail() y clear() en caso de que el fichero

de entrada tenga una lnea en blanco (este la tiene). Cuando esta particular versin sobrecargada de get() vee dos carcteres de nueva lnea en una fila (una evidencia de una lnea en blanco), activa el bit de error del stream de entrada, asi que se debe llamar a clear() para resetearlo y que as el stream pueda continuar siendo ledo. La segunda llamada a get() extrae y hace eco de cualquier delimitador de nueva lnea. (Recuerde, la funcin get() no extrae este delimitador como s lo hace getline()).

190
5176 5177 Probablemente no necesitar usar una tcnica como esta a menudo, pero es bueno saber que existe.[15]

5178 5179 5180 5181 5182 5183 5184 5185 5186 5187 5188 5189 5190 5191 5192 5193 5194 5195 5196 5197 5198 5199 5200 5201 5202 5203 5204 5205 5206

5.6. Buscar en iostreams


Cada tipo de iostream tiene el concepto de donde est el 'siguiente' carcter que proviene de (si es un istream) o que va hacia (si es un
ostream). stream.

En algunas situaciones, puede querer mover la posicin en este

Puede hacer esto usando dos modelos: uno usa una localizacin

absoluta en el stream llamada streampos; el segundo trabaja como las funciones fseek() de la librera estndar de C para un fichero y se mueve un nmero dado de bytes desde el principio, final o la posicin actual en el fichero. El acercamiento de streampos requiere que primero llame una funcin 'tell':( tellp() para un ostream o tellg() para un istream. (La 'p' se refiere a 'put pointer' y la 'g' se refiere a 'get pointer'). Esta funcin retorna un
streampos seekg()

que puede usar despus en llamadas a seekp() para un ostream o para un ostream cuando usted quiere retornar a la posicin en el
stream.

La segunda aproximacin es una bsqueda relativa y usa versiones sobrecargadas de seekp() y seekg(). El primer argumento es el nmero de carcteres a mover: puede ser positivo o negativo. El segundo argumento es la direccin desde donde buscar:
ios::beg

Desde el principio del stream


ios::cur

Posicin actual del stream


ios::end

Desde el principio del stream Aqu un ejemplo que muestra el movimiento por un fichero, pero recuerde, no esta limitado a buscar en ficheros como lo est con stdio de C. Con C++, puede buscar en cualquier tipo de iostream (aunque los objetos stream estndar, como cin y cout, lo impiden explcitamente):

191

5207 5208 5209 5210 5211 5212 5213 5214 5215 5216 5217 5218 5219 5220 5221 5222 5223 5224 5225 5226 5227 5228 5229 5230 5231

//: C04:Seeking.cpp // Seeking in iostreams. #include <cassert> #include <cstddef> #include <cstring> #include <fstream> #include "../require.h" using namespace std;

int main() { const int STR_NUM = 5, STR_LEN = 30; char origData[STR_NUM][STR_LEN] = { "Hickory dickory dus. . .", "Are you tired of C++?", "Well, if you have,", "That's just too bad,", "There's plenty more for us!" }; char readData[STR_NUM][STR_LEN] = {{ 0 }}; ofstream out("Poem.bin", ios::out | ios::binary); assure(out, "Poem.bin"); for(int i = 0; i < STR_NUM; i++) out.write(origData[i], STR_LEN); out.close(); ifstream in("Poem.bin", ios::in | ios::binary);

192

5232 5233 5234 5235 5236 5237 5238 5239 5240 5241 5242 5243 5244 5245 5246 5247 5248 5249 5250 5251 5252 5253 5254

assure(in, "Poem.bin"); in.read(readData[0], STR_LEN); assert(strcmp(readData[0], "Hickory dickory dus. . .") == 0); // Seek -STR_LEN bytes from the end of file in.seekg(-STR_LEN, ios::end); in.read(readData[1], STR_LEN); assert(strcmp(readData[1], "There's plenty more for us!") == 0); // Absolute seek (like using operator[] with a file) in.seekg(3 * STR_LEN); in.read(readData[2], STR_LEN); assert(strcmp(readData[2], "That's just too bad,") == 0); // Seek backwards from current position in.seekg(-STR_LEN * 2, ios::cur); in.read(readData[3], STR_LEN); assert(strcmp(readData[3], "Well, if you have,") == 0); // Seek from the begining of the file in.seekg(1 * STR_LEN, ios::beg); in.read(readData[4], STR_LEN); assert(strcmp(readData[4], "Are you tired of C++?") == 0); } ///:~

5255

Listado 5.8. C04/Seeking.cpp

193
5256 5257 5258 5259 5260 5261 5262 5263 5264 5265 5266 5267 5268

Este programa escribe un poema a un fichero usando un stream de salida binaria. Como reabrimos como un ifstream, usamos seekg() para posicionar el 'get pointer'. Como puede ver, puede buscar desde el principio o el final del archivo o desde la posicin actual del archivo. Obviamente, debe proveer un nmero positivo para mover desde el principio del archivo y un nmero negativo para mover hacia atrs. Ahora que ya conoce el streambuf y como buscar, ya puede entender un mtodo alternativo (aparte de usar un objeto fstream) para crear un objeto
stream

que podr leer y escribir en un archivo. El siguiente cdigo crea un con banderas que dicen que es un fichero de entrada y de salida. con el buffer subyacente del stream:

ifstream

Usted no puede escribir en un ifstream, as que necesita crear un ostream

5269 5270

ifstream in("filename", ios::in | ios::out); ostream out(in.rdbuf());

5271 5272

Debe estar preguntndose que ocurre cuando usted lee en uno de estos objetos. Aqui tiene un ejemplo:

5273 5274 5275 5276 5277 5278 5279 5280 5281 5282

//: C04:Iofile.cpp // Reading & writing one file. #include <fstream> #include <iostream> #include "../require.h" using namespace std;

int main() { ifstream in("Iofile.cpp"); assure(in, "Iofile.cpp");

194

5283 5284 5285 5286 5287 5288 5289 5290 5291 5292 5293 5294 5295 5296 5297 5298

ofstream out("Iofile.out"); assure(out, "Iofile.out"); out << in.rdbuf(); // Copy file in.close(); out.close(); // Open for reading and writing: ifstream in2("Iofile.out", ios::in | ios::out); assure(in2, "Iofile.out"); ostream out2(in2.rdbuf()); cout << in2.rdbuf(); // Print whole file

out2 << "Where does this end up?"; out2.seekp(0, ios::beg); out2 << "And what about this?"; in2.seekg(0, ios::beg); cout << in2.rdbuf(); } ///:~

5299 5300 5301 5302 5303 5304 5305 5306 5307 5308 5309

Listado 5.9. C04/Iofile.cpp Las primeras cinco lneas copian el cdigo fuente de este programa en un fichero llamado iofile.out y despus cierra los ficheros. Esto le da un texto seguro con el que practicar. Entonces, la tcnica antes mencionada se usa para crear dos objetos que leen y escriben en el mismo fichero. En cout <<
in2.rebuf(),

puede ver como puntero 'get' es inicializado al principio del

fichero. El puntero 'put', en cambio, se coloca en el final del fichero para que 'Where does this end up' aparezca aadido al fichero. No obstante, si el puntero 'put' es movido al principio con un seekp(), todo el texto insertado sobreescribe el existente. Ambas escrituras pueden verse cuando el puntero

195
5310 5311 5312 'get' se mueve otra vez al principio con seekg(), y el fichero se muestra. El fichero es automticamente guardado cuando out2 sale del mbito y su destructor es invocado.

5313 5314 5315 5316 5317 5318 5319 5320 5321 5322 5323 5324 5325 5326 5327 5328 5329

5.7. Iostreams de string


Un stream de cadena funciona directamente en memoria en vez de con ficheros o la salida estndar. Usa las mismas funciones de lectura y formateo que us con cin y cout para manipular bits en memoria. En ordenadores antiguos, la memoria se refera al ncleo, con lo que este tipo de funcionalidad se llama a menudo formateo en el ncleo. Los nombres de clases para streams de cadena son una copia de los
streams

de ficheros. Si usted quiere crear un stream de cadena para extraer

carcteres de l, puede crear un istringstream. Si quiere poner carcteres en un stream de cadena, puede crear un ostringstream. Todas las declaraciones para streams de cadena estn en la cabecera estndar <sstream>. Como es habitual, hay plantillas de clases dentro de la jerarquia de los iostreams, como se muestra en la siguiente figura:

5.7.1. Streams de cadena de entrada


Para leer de un string usando operaciones de stream, cree un objeto
istringstream

inicializado con el string. El siguiente programa muestra como usar un objeto istringstream:

5330 5331 5332 5333 5334 5335 5336

//: C04:Istring.cpp // Input string streams. #include <cassert> #include <cmath> // For fabs()

#include <iostream> #include <limits> // For epsilon() #include <sstream>

196

5337 5338 5339 5340 5341 5342 5343 5344 5345 5346 5347 5348 5349 5350 5351 5352

#include <string> using namespace std;

int main() { istringstream s("47 1.414 This is a test"); int i; double f; s >> i >> f; // Whitespace-delimited input assert(i == 47); double relerr = (fabs(f) - 1.414) / 1.414; assert(relerr <= numeric_limits<double>::epsilon()); string buf2; s >> buf2; assert(buf2 == "This"); cout << s.rdbuf(); // " is a test" } ///:~

5353 5354 5355 5356 5357 5358 5359 5360 5361 5362 5363

Listado 5.10. C04/Istring.cpp Puede ver que es un acercamiento ms flexible y general para transformar cadenas de carcteres para valores con tipo que la librera de funciones del estndar de C, como atof() o atoi(), aunque esta ltima puede ser ms eficaz para las conversiones individuales. En la expresin s >> i >> f, el primer nmero se extrae en i, y en el segundo en f. Este no es 'el primer conjunto de carcteres delimitado por espacios en blanco' por que depende del tipo de datos que est siendo extrado. Por ejemplo, si la cadena fuera '1.414 47 This is a test', entonces i tomara el valor 1 porque la rutina de entrada se parara en el punto decimal.

197
5364 5365 5366 5367 5368 5369 5370 5371 5372 5373 5374 5375 5376 5377 5378 5379 5380 Entonces f tomara 0.414. Esto puede ser muy til i si quiere partir un nmero de coma flotante entre la parte entera y la decimal. De otra manera parecera un error. El segundo assert() calcula el error relativo entre lo que leemos y lo que esperamos; siempre es mejor hacer esto que comparar la igualdad de nmeros de coma flotante. La constante devuelta por epsilon(), definida en <limits>, representa la epsilon de la mquina para nmeros de doble precisin, el cual es la mejor tolerancia que se puede esperar para satisfacer las comparaciones de double.[16]. Como debe haber supuesto, buf2 no toma el resto del string, simplemente la siguiente palabra delimitada por espacios en blanco. En general, el mejor usar el extractor en iostreams cuando usted conoce exactamente la secuencia de datos en el stream de entrada y los convierte a algn otro tipo que un string de carcteres. No obstante, si quiere extraer el resto del string de una sola vez y enviarlo a otro iostream, puede usar
rdbuf()

como se muestra.

Para probar el extractor de Date al principio de este captulo, hemos usado un stream de cadena de entrada con el siguiente programa de prueba:

5381 5382 5383 5384 5385 5386 5387 5388 5389 5390 5391 5392

//: C04:DateIOTest.cpp //{L} ../C02/Date #include <iostream> #include <sstream> #include "../C02/Date.h" using namespace std;

void testDate(const string& s) { istringstream os(s); Date d; os >> d; if(os)

198

5393 5394 5395 5396 5397 5398 5399 5400 5401 5402 5403 5404

cout << d << endl; else cout << "input error with \"" << s << "\"" << endl; }

int main() { testDate("08-10-2003"); testDate("8-10-2003"); testDate("08 - 10 - 2003"); testDate("A-10-2003"); testDate("08%10/2003"); } ///:~

5405 5406 5407 5408 5409 5410

Listado 5.11. C04/DateIOTest.cpp Cada literal de cadena en main() se pasa por referencia a testDate(), que a su vez lo envuelve en un istringstream con lo que podemos probar el extractor de stream que escribimos para los objetos Date. La funcin
testDate()

tambin empieza por probar el insertador, operator<<().

5411 5412 5413 5414 5415 5416

5.7.2. Streams de cadena de salida


Para crear un stream de cadena de salida, simplemente cree un objeto ostringstream, que maneja un buffer de carcteres dinamicamente dimensionado para guardar cualquier cosas que usted inserte. Para tomar el resultado formateado como un objeto de string, llame a la funcin miembro setr(). Aqui tiene un ejemplo:

5417

//: C04:Ostring.cpp {RunByHand}

199

5418 5419 5420 5421 5422 5423 5424 5425 5426 5427 5428 5429 5430 5431 5432 5433 5434 5435 5436 5437 5438

// Illustrates ostringstream. #include <iostream> #include <sstream> #include <string> using namespace std;

int main() { cout << "type an int, a float and a string: "; int i; float f; cin >> i >> f; cin >> ws; // Throw away white space string stuff; getline(cin, stuff); // Get rest of the line ostringstream os; os << "integer = " << i << endl; os << "float = " << f << endl; os << "string = " << stuff << endl; string result = os.str(); cout << result << endl; } ///:~

5439 5440 5441 5442 5443

Listado 5.12. C04/Ostring.cpp Esto es similar al ejemplo Istring.cpp anterior que peda un int y un float. A continueacin una simple ejecucin (la entrada por teclado est escrita en negrita).

200
5444 5445 5446 5447 5448 5449 5450 5451 5452 5453 5454 5455 5456
type an int, a float and a string: FIXME:10 20.5 the end integer = 10 float = 20.5 string = the end

Puede ver que, como otros stream de salida, puede usar las herramientas ordinarias de formateo, como el operador << y endl, para enviar bytes hacia el ostringstream. La funcin str() devuelve un nuevo objeto string cada vez que usted la llama con lo que el stringbuf contenido permanece inalterado. En el captulo previo, presentamos un programa, HTMLStripper.cpp, que borraba todas las etiqietas HTML y los cdigos especiales de un fichero de texto. Como prometamos, aqui est una versin ms elegante usando
streams

de cadena.

5457 5458 5459 5460 5461 5462 5463 5464 5465 5466 5467 5468 5469 5470 5471

//: C04:HTMLStripper2.cpp {RunByHand} //{L} ../C03/ReplaceAll // Filter to remove html tags and markers. #include <cstddef> #include <cstdlib> #include <fstream> #include <iostream> #include <sstream> #include <stdexcept> #include <string> #include "../C03/ReplaceAll.h" #include "../require.h" using namespace std;

string& stripHTMLTags(string& s) throw(runtime_error) {

201

5472 5473 5474 5475 5476 5477 5478 5479 5480 5481 5482 5483 5484 5485 5486 5487 5488 5489 5490 5491 5492 5493 5494 5495 5496

size_t leftPos; while((leftPos = s.find('<')) != string::npos) { size_t rightPos = s.find('>', leftPos+1); if(rightPos == string::npos) { ostringstream msg; msg << "Incomplete HTML tag starting in position " << leftPos; throw runtime_error(msg.str()); } s.erase(leftPos, rightPos - leftPos + 1); } // Remove all special HTML characters replaceAll(s, "&lt;", "<"); replaceAll(s, "&gt;", ">"); replaceAll(s, "&amp;", "&"); replaceAll(s, "&nbsp;", " "); // Etc... return s; }

int main(int argc, char* argv[]) { requireArgs(argc, 1, "usage: HTMLStripper2 InputFile"); ifstream in(argv[1]); assure(in, argv[1]);

202

5497 5498 5499 5500 5501 5502 5503 5504 5505 5506 5507 5508

// Read entire file into string; then strip ostringstream ss; ss << in.rdbuf(); try { string s = ss.str(); cout << stripHTMLTags(s) << endl; return EXIT_SUCCESS; } catch(runtime_error& x) { cout << x.what() << endl; return EXIT_FAILURE; } } ///:~

5509 5510 5511 5512 5513 5514 5515 5516 5517

Listado 5.13. C04/HTMLStripper2.cpp En este programa leemos el fichero entero dentro de un string insertando una llamada rdbuf() del stream de fichero al ostringstream. Ahora es fcil buscar parejas de delimitadores HTML y borrarlas sin tener que preocuparnos de lmites de lneas como teniamos con la versin previa en el Captulo 3. El siguiente ejemplo muestra como usar un stream de cadena bidireccional (esto es, lectura/escritura):

5518 5519 5520 5521 5522

//: C04:StringSeeking.cpp {-bor}{-dmc} // Reads and writes a string stream. #include <cassert> #include <sstream> #include <string>

203

5523 5524 5525 5526 5527 5528 5529 5530 5531 5532 5533 5534 5535 5536 5537 5538 5539 5540 5541 5542 5543 5544 5545 5546 5547

using namespace std;

int main() { string text = "We will hook no fish"; stringstream ss(text); ss.seekp(0, ios::end); ss << " before its time."; assert(ss.str() == "We will hook no fish before its time."); // Change "hook" to "ship" ss.seekg(8, ios::beg); string word; ss >> word; assert(word == "hook"); ss.seekp(8, ios::beg); ss << "ship"; // Change "fish" to "code" ss.seekg(16, ios::beg); ss >> word; assert(word == "fish"); ss.seekp(16, ios::beg); ss << "code"; assert(ss.str() == "We will ship no code before its time."); ss.str("A horse of a different color.");

204

5548 5549

assert(ss.str() == "A horse of a different color."); } ///:~

5550 5551 5552 5553 5554 5555 5556 5557 5558 5559

Listado 5.14. C04/StringSeeking.cpp Como siempre para mover el puntero de insercin, usted llama a seekp(), y para reposicionar el fichero de lectura, usted llama a seekg(). Incluso aunque no lo hemos mostrado con este ejemplo, los stream de cadeana son un poco ms permisivos que los stream de fichero ya que podemos cambiar de lectura a escritura y viceversa en cualquier momento. No necesita reposicionar el puntero de lectura o de escritura o vaciar el stream. Este progrma tambin ilustra la sobrecarga de str() que reemplaza el stringbuf contenido en el stream con una nueva cadena.

5560 5561 5562 5563 5564 5565 5566 5567 5568 5569 5570 5571 5572 5573 5574 5575 5576

5.8. Formateo de stream de salida


El objetivo del diseo de los iostream es permitir que usted pueda mover y/o formatear carcteres fcilmente. Ciertamente no podria ser de mucha utilidad si no se pudiera hacer la mayoria de los formateos provistos por la familia de funciones de printf() en C. Es esta seccin, usted aprender todo sobre las funciones de formateo de salida que estan disponibles para
iostream,

con lo que puede formatear los bytes de la manera que usted quiera.

Las funciones de formateo en iostream pueden ser algo cunfusas al principio porque a menudo existe ms de una manera de controlar el formateo: a travs de funciones miembro y manipuladores. Para confundir ms las cosas, una funcin miembro genrica pone banderas de estado para controlar el formateo, como la justificacin a la derecha o izquierda, el uso de letras maysculas para la notacin hexadecimal, para siempre usar un punto decimal para valores de coma flotante, y cosas as. En el otro lado, funciones miembro separadas activan y leen valores para el caracter de relleno, la anchura del campo, y la precisin.

205
5577 5578 5579 5580 5581 5582 5583 5584 5585 5586 5587 5588 5589 5590 5591 5592 5593 En un intento de clarificar todo esto, primero examinaremos el formateo interno de los datos de un iostream, y las funciones miembro que pueden modificar estos datos. (Todo puede ser controlado por funciones miembro si se desea). Cubriremos los manipuladores aparte.

5.8.1. Banderas de formateo


La clase ios contiene los miembros de datos para guardar toda la informacin de formateo perteneciente a un stream. Algunos de estos datos tiene un rango de valores de datos y son guardados en variables: la precisin de la coma flotante, la anchura del campo de salida, y el carcter usado para rellenar la salida (normalmente un espacio). El resto del formateo es determinado por banderas, que generalmente estn combinadas para ahorrar espacio y se llaman colectivamente banderas de formateo. Puede recuperar los valores de las banderas de formateo con la funcin miembro
ios::flag(),

que no toma argumentos y retorna un objeto de tipo fmtflags

(usualmente un sinnimo de long) que contiene las banderas de formateo actuales. El resto de funciones hacen cambios en las banderas de formateo y retornan el valor previo de las banderas de formateo.

5594 5595 5596 5597

fmtflags ios::flags(fmtflags newflags); fmtflags ios::setf(fmtflags ored_flag); fmtflags ios::unsetf(fmtflags clear_flag); fmtflags ios::setf(fmtflags bits, fmtflags field);

5598 5599 5600 5601 5602 5603 5604 5605

La primera funcin fuerza que todas las banderas cambien, que a veces es lo que usted quiere. Ms a menudo, usted cambia una bandera cada vez usando las otras tres funciones. El uso de setf() puede parecer algo confusa. Para conocer qu versin sobrecargada usar, debe conocer el tipo de la bandera que est cambiando. Existen dos tipos de banderas: las que simplemente estan activadas o no, y aquellas que trabajan en grupo con otras banderas. La banderas que estan encendidas/apagadas son las ms simples de entender por que usted las

206
5606 5607 5608 5609 5610 5611 5612 5613 5614 5615 5616 5617 5618 5619 5620 5621 5622 5623 5624 5625 5626 5627 5628 5629 5630 5631 enciende con setf(fmtflags) y las apaga con unsetf(fmtflags). Estas banderas se muestran en la siguiente tabla: bandera activa/inactiva Efecto
ios::skipws

Se salta los espacios en blanco. ( Para la entrada esto es por defecto).


ios::showbase

Indica la base numrica (que puede ser, por ejemplo, decimal, octal o hexadecimal) cuando imprimimos el valor entero. Los stream de entrada tambi reconocen el prefijo de base cuando showbase est activo.
ios::showpoint

Muestra el punto decimal insertando ceros para valores de coma flotante.


ios::uppercase

Muestra A-F maysculas para valores hexadecimales y E para cientficos.


ios::showpos

Muestra el signo de sumar (+) para los valores positivos


ios::unitbuf

'Unit buffering.' El stream es borrado despus de cada insecin. Por ejemplo, para mostrar el signo de sumar para cout, puede usar
cout.setf(ios::showpos).

Para dejar de mostrar el signo de sumar, escriba

cout.unsetf(ios::showpos).

La bandera de unitbuf controla el almacenamiento unitario, que significa que cada insercin es lanzada a su stream de salida inmediatamente. Esto es til para hacer recuento de errores, ya que en caso de fallo del programa, sus datos son todava escritos al fichero de log. El siguiente programa ilustra el almacenamiento unitario.

5632 5633 5634

//: C04:Unitbuf.cpp {RunByHand} #include <cstdlib> // For abort()

#include <fstream>

207

5635 5636 5637 5638 5639 5640 5641 5642 5643

using namespace std;

int main() { ofstream out("log.txt"); out.setf(ios::unitbuf); out << "one" << endl; out << "two" << endl; abort(); } ///:~

5644 5645 5646 5647 5648 5649 5650 5651 5652 5653

Listado 5.15. C04/Unitbuf.cpp Es necesario activar el almacenamiento unitario antes de que cualquier insercin sea hecha en el stream. Cuando hemos descomentado la llamada a setf(), un compilador en particular ha escrito solo la letra 'o' en el fichero log.txt. Con el almacenamiento unitario, ningun dato se perdi. El stream de salida estndar cerr tiene el almacenamiento unitario activado por defecto. Hay un coste para el almacenamiento unitario, asi que si un stream de salida se usa intensivamente, no active el almacenamiento unitario a menos que la eficiencia no sea una consideracin.

5654 5655 5656 5657 5658 5659 5660 5661 5662

5.8.2. Campos de formateo


El segundo tipo de banderas de formateo trabajan en grupo. Solo una de estas banderas pueden ser activadas cada vez, como los botones de una vieja radio de coche - usted apretaba una y el resto saltaban. Desafortunadamente esto no pasa automticamente, y usted tiene que poner atencin a que bandera est activando para no llamar accidentalmente a la funcin setf() incorrecta. Por ejemplo, hay una bandera para cada una de las bases numricas: hexadecimal, decimal y octal. A estas banderas se refiere en conjunto ios::basefield. Si la bandera ios::dec est activa y

208
5663 5664 5665 5666 5667 5668 5669 5670 5671 5672 5673 5674 5675 5676 5677 5678 5679 5680 5681 5682 5683 5684 5685 5686 5687 5688 5689 5690 5691 5692 5693 usted llama setf(ios::hex), usted activar la bandera de ios::hex, pero no desactivar la bandera de ios::dec, resultando en un comportamiento indeterminado. En vez de esto,llame a la segunda forma de la funcin setf() como esta: setf(ios::hex, ios::basefield) . Esta funcin primero limpia todos los bits de ios::basefield y luego activa ios::hex. As, esta forma de
setf()

asegura que las otras banderas en el grupo 'saltan' cuando usted

activa una. El manipulador ios::hex lo hace todo por usted, automticamente, asi que no tiene que preocuparse con los detalles de la implementacin interna de esta clase o tener cuidado de que esto es una seria de banderas binarias. Ms adelante ver que hay manipuladores para proveer de la funcionalidad equivalente en todas las parts donde usted fuera a usar setf(). Aqu estn los grupos de banderas y sus efectos:
ios::basefield

Efecto
ios::dec

Formatea valores enteros en base 10 (decimal)(Formateo por defecto ningn prefijo es visible).
ios::hex

Formatea valores enteros en base 16 (hexadecimal).


ios::oct

Formatea valores enteros en base 8 (octal).


ios::floatfield

Efecto
ios::scientific

Muestra nmeros en coma flotante en formato cientfico. El campo precisin indica el numero de dgitos despus del punto decimal.
ios::fixed

Muestra nmeros en coma flotante en formato fijado. El campo precisin indica en nmero de dgitos despus del punto decimal. 'automatic' (Ninguno de los bits est activado).

209
5694 5695 5696 5697 5698 5699 5700 5701 5702 5703 5704 5705 5706 El campo precisin indica el nmero total de dgitos significativos.
ios::adjustfield

Efecto
ios::left

Valores con alineacin izquierda; se llena hasta la derecha con el carcter de relleno.
ios::right

Valores con alineacin derecha; se llena hasta la izquierda con el carcter de relleno. Esta es la alineacin por defecto.
ios::internal

Aade carcteres de relleno despues de algn signo inicial o indicador de base, pero antes del valor. (En otras palabras, el signo, si est presente, se justifica a la izquierda mientras el nmero se justifica a la derecha).

5707 5708 5709 5710 5711 5712 5713 5714 5715 5716 5717 5718 5719 5720 5721 5722 5723

5.8.3. Anchura, relleno y precisin


La variables internas que controlan la anchura del campo de salida, el carcter usado para rellenar el campo de salida, y la precisin para escribir nmeros de coma flotante son escritos y ledos por funciones miembro del mismo nombre. Funcin Efecto
int ios::width( )

Retorna la anchura actual. Por defecto es 0. Se usa para la insercin y la extraccin.


int ios::width(int n)

Pone la anchura, retorna la anchura previa.


int ios::fill( )

Retorna el carcter de relleno actual. Por defecto es el espacio.


int ios::fill(int n)

Poner el carcter de relleno, retorna el carcter de relleno anterior.


int ios::precision( )

210
5724 5725 5726 5727 5728 5729 5730 5731 5732 5733 5734 5735 5736 5737 5738 5739 5740 5741 Retorna la precisin actual de la coma flotante. Por defecto es 6.
int ios::precision(int n)

Pone la precisin de la coma flotante, retorna la precisin anteriot. Vea la tabla ios::floatfield para el significado de 'precisin'. El relleno y la precisin son bastante claras, pero la anchura requiera alguna explicacin. Cuando la anchura es 0, insertar un valor produce el mnimo nmero de carcteres necesario para representar este valor. Una anchura positiva significa que insertar un valor producir al menos tantos carcteres como la anchura; si el valor tiene menos carcteres que la anchura, el carcter de relleno llena el campo. No obstante, el valor nunca ser truncado, con lo que si usted intenta escribir 123 con una anchura de dos, seguir obteniendo 123. El campo anchura especifica un minimo nmero de carcteres; no hay forma de especificar un nmero mnimo. La anchura tambin es diferente por que vuelve a ser cero por cada insertador o extractor que puede ser influenciado por este valor. Realmente no es una variable de estado, sino ms bien un argumento implcito para los extractores y insertadores. Si quiere una anchura constante, llame a width() despues de cada insercin o extraccin.

5742 5743 5744

5.8.4. Un ejemplo exhaustivo


Para estar seguros de que usted conoce como llamar a todas las funciones discutidas previamente, aqu tiene un ejemplo que las llama a todas:

5745 5746 5747 5748 5749 5750 5751

//: C04:Format.cpp // Formatting Functions. #include <fstream> #include <iostream> #include "../require.h" using namespace std; #define D(A) T << #A << endl; A

211

5752 5753 5754 5755 5756 5757 5758 5759 5760 5761 5762 5763 5764 5765 5766 5767 5768 5769 5770 5771 5772 5773 5774 5775 5776
D(T.setf(ios::unitbuf);) D(T.setf(ios::showbase);) D(T.setf(ios::uppercase | ios::showpos);) D(T << i << endl;) // Default is dec D(T.setf(ios::hex, ios::basefield);) D(T << i << endl;) D(T.setf(ios::oct, ios::basefield);) D(T << i << endl;) D(T.unsetf(ios::showbase);) D(T.setf(ios::dec, ios::basefield);) D(T.setf(ios::left, ios::adjustfield);) D(T.fill('0');) D(T << "fill char: " << T.fill() << endl;) D(T.width(10);) T << i << endl; D(T.setf(ios::right, ios::adjustfield);) D(T.width(10);) int main() { ofstream T("format.out"); assure(T); D(int i = 47;) D(float f = 2300114.414159;) const char* s = "Is there any more?";

212

5777 5778 5779 5780 5781 5782 5783 5784 5785 5786 5787 5788 5789 5790 5791 5792 5793 5794 5795 5796 5797 5798 5799 5800 5801

T << i << endl; D(T.setf(ios::internal, ios::adjustfield);) D(T.width(10);) T << i << endl; D(T << i << endl;) // Without width(10)

D(T.unsetf(ios::showpos);) D(T.setf(ios::showpoint);) D(T << "prec = " << T.precision() << endl;) D(T.setf(ios::scientific, ios::floatfield);) D(T << endl << f << endl;) D(T.unsetf(ios::uppercase);) D(T << endl << f << endl;) D(T.setf(ios::fixed, ios::floatfield);) D(T << f << endl;) D(T.precision(20);) D(T << "prec = " << T.precision() << endl;) D(T << endl << f << endl;) D(T.setf(ios::scientific, ios::floatfield);) D(T << endl << f << endl;) D(T.setf(ios::fixed, ios::floatfield);) D(T << f << endl;)

D(T.width(10);) T << s << endl;

213

5802 5803 5804 5805 5806 5807

D(T.width(40);) T << s << endl; D(T.setf(ios::left, ios::adjustfield);) D(T.width(40);) T << s << endl; } ///:~

5808 5809 5810 5811 5812 5813 5814 5815 5816 5817 5818 5819 5820 5821 5822 5823 5824 5825 5826 5827 5828 5829 5830 5831 5832 5833 5834 5835 5836 5837 5838 5839 5840 5841 5842 5843 5844

Listado 5.16. C04/Format.cpp Este ejemplo usa un truco para crear un fichero de traza para que pueda monitorizar lo que est pasando. La macro D(a) usa el preprocesador 'convirtiendo a string' para convertir a en una cadena para mostrar. Entonces se reitera a con lo que la sentencia se ejecuta. La macro envia toda la informacin a un fichero llamado T, que es el fichero de traza. La salida es:
int i = 47; float f = 2300114.414159; T.setf(ios::unitbuf); T.setf(ios::showbase); T.setf(ios::uppercase | ios::showpos); T << i << endl; +47 T.setf(ios::hex, ios::basefield); T << i << endl; 0X2F T.setf(ios::oct, ios::basefield); T << i << endl; 057 T.unsetf(ios::showbase); T.setf(ios::dec, ios::basefield); T.setf(ios::left, ios::adjustfield); T.fill('0'); T << "fill char: " << T.fill() << endl; fill char: 0 T.width(10); +470000000 T.setf(ios::right, ios::adjustfield); T.width(10); 0000000+47 T.setf(ios::internal, ios::adjustfield); T.width(10); +000000047 T << i << endl; +47 T.unsetf(ios::showpos);

214
5845 5846 5847 5848 5849 5850 5851 5852 5853 5854 5855 5856 5857 5858 5859 5860 5861 5862 5863 5864 5865 5866 5867 5868 5869 5870 5871 5872 5873 5874 5875 5876 5877 5878 5879 5880
T.setf(ios::showpoint); T << "prec = " << T.precision() << endl; prec = 6 T.setf(ios::scientific, ios::floatfield); T << endl << f << endl; 2.300114E+06 T.unsetf(ios::uppercase); T << endl << f << endl; 2.300114e+06 T.setf(ios::fixed, ios::floatfield); T << f << endl; 2300114.500000 T.precision(20); T << "prec = " << T.precision() << endl; prec = 20 T << endl << f << endl; 2300114.50000000000000000000 T.setf(ios::scientific, ios::floatfield); T << endl << f << endl; 2.30011450000000000000e+06 T.setf(ios::fixed, ios::floatfield); T << f << endl; 2300114.50000000000000000000 T.width(10); Is there any more? T.width(40); 0000000000000000000000Is there any more? T.setf(ios::left, ios::adjustfield); T.width(40); Is there any more?0000000000000000000000

Estudiar esta salida debera clarificar su entendimiento del formateo de las funciones miembro de iostream .

5881 5882 5883 5884 5885 5886 5887 5888 5889 5890

5.9. Manipuladores
Como puede ver en el programa previo, llamar a funciones miembro para operaciones de formateo de stream puede ser un poco tedioso. Para hacer las cosas mas fciles de leer y escribir, existe un conjunto de manipuladores para duplicar las acciones previstas por las funciones miembro. Las manipuladores son convenientes por que usted puede insertarlos para que actuen dentro de una expresion contenedora; no necesita crear una sentencia de llamada a funcin separada. Los manipuladores cambian el estado de un stream en vez de (o adems de) procesar los datos. Cuando insertamos un endl en una expresin de

215
5891 5892 5893 5894 salida, por ejemplo, no solo inserta un carcter de nueva linea, sino que ademas termina el stream (esto es, saca todos los carcteres pendientes que han sido almacenadas en el buffer interno del stream pero todavia no en la salida). Puede terminar el stream simplemente asi:

5895

cout << flush;

5896

Lo que causa una llamada a la funcin miembro flush(), como esta:

5897

cout.flush();

5898 5899 5900

como efecto lateral (nada es insertado dentro de stream). Adicionalmente los manipuladores bsicos cambirn la base del nmero a oct (octal), dec (decimal) o hex (hexadecimal).

5901

cout << hex << "0x" << i << endl;

5902 5903 5904 5905

En este caso, la salida numrica continuar en modo hexadecimal hasta que usted lo cambie insertando o dec o oct en el stream de salida. Tambin existe un manipulador para la extraccin que se 'come' los espacios en blanco:

5906

cin >> ws;

5907 5908 5909 5910 5911 5912 5913

Los manipuladores sin argumentos son provistos en <iostream>. Esto incluye dec, oct,y hex, que hacen las mismas acciones que, respectivamente,
setf(ios::dec, ios::basefield), setf(ios::oct, ios::basefield), setf(ios::hex, ios::basefield),

aunque ms sucintamente. La cabecera aqu:

<iostream> tambin incluye ws, endl, y flush y el conjunto adicional mostrado

Manipulador

216
5914 5915 5916 5917 5918 5919 5920 5921 5922 5923 5924 5925 5926 5927 5928 5929 5930 5931 5932 5933 5934 5935 5936 5937 5938 5939 5940 5941 Efecto showbase noshowbase Indica la base numrica (dec, oct, o hex) cuando imprimimos un entero. showpos noshowpos Muestra el signo ms (+) para valores positivos. uppercase nouppercase Muestra maysculas A-F para valores hexadecimales, y muestra E para valores cientficos. showpoint noshowpoint Muestra punto decimal y ceros arrastrados para valores de coma flotante. skipws noskipws Escapa los espacios en blanco en la entrada. left right internal Alineacin izquierda, relleno a la derecha. Alineacin derecha, relleno a la izquierda. Rellenar entre el signo o el indicador de base y el valor. scientific fixed Indica la preferencia al mostrar la salida para coma flotante (notacin cientfica versus coma flotante decimal).

5.9.1. Manipuladores con argumentos


Existen seis manipuladores estndar, como setw(), que toman argumentos. Estn definidos en el fichero de cabecera <iomanip>, y estn enumerados en la siguiente tabla: Manipulador Efecto
setiosflags(fmtflags n)

Equivalente a una llamada a setf(n). La activacin continua hasta el siguiente cambio, como ios::setf().
resetiosflags(fmtflags n)

217
5942 5943 5944 5945 5946 5947 5948 5949 5950 5951 5952 5953 5954 5955 5956 5957 5958 5959 Limpia solo las banderas de formato especificadas por n. La activacin permanece hasta el siguiente cambio, como ios::unsetf().
setbase(base n)

Cambia la base a n, donde n es 10, 8 o 16. (Caulquier otra opcin resulta en 0). Si n es cero, la salida es base 10, pero la entrada usa convenciones de C: 10 es 10, 010 es 8, y 0xf es 15. Puede usar tambin dec, oct y hex para la salida.
setfill(char n)

Cambia el carcter de relleno a n, como ios::fill().


setprecision(int n)

Cambia la precision a n, como ios::precision().


setw(int n)

Cambia la anchura del campo a n, como en ios::width() Si est usando mucho el formateo, usted puede ver como usar los manipuladores en vez de llamar a funciones miembro de stream puede limpiar su cdigo. Como ejemplo, aqu tiene un programa de la seccin previa reescrito para usar los manipuladores. (La macro D() ha sido borrada para hacerlo ms fcil de leer).

5960 5961 5962 5963 5964 5965 5966 5967 5968 5969

//: C04:Manips.cpp // Format.cpp using manipulators. #include <fstream> #include <iomanip> #include <iostream> using namespace std;

int main() { ofstream trc("trace.out"); int i = 47;

218

5970 5971 5972 5973 5974 5975 5976 5977 5978 5979 5980 5981 5982 5983 5984 5985 5986 5987 5988 5989 5990 5991 5992 5993 5994

float f = 2300114.414159; char* s = "Is there any more?";

trc << setiosflags(ios::unitbuf | ios::showbase | ios::uppercase | ios::showpos); trc << i << endl; trc << hex << i << endl << oct << i << endl; trc.setf(ios::left, ios::adjustfield); trc << resetiosflags(ios::showbase) << dec << setfill('0'); trc << "fill char: " << trc.fill() << endl; trc << setw(10) << i << endl; trc.setf(ios::right, ios::adjustfield); trc << setw(10) << i << endl; trc.setf(ios::internal, ios::adjustfield); trc << setw(10) << i << endl; trc << i << endl; // Without setw(10)

trc << resetiosflags(ios::showpos) << setiosflags(ios::showpoint) << "prec = " << trc.precision() << endl; trc.setf(ios::scientific, ios::floatfield); trc << f << resetiosflags(ios::uppercase) << endl;

219

5995 5996 5997 5998 5999 6000 6001 6002 6003 6004 6005 6006 6007 6008 6009 6010 6011

trc.setf(ios::fixed, ios::floatfield); trc << f << endl; trc << f << endl; trc << setprecision(20); trc << "prec = " << trc.precision() << endl; trc << f << endl; trc.setf(ios::scientific, ios::floatfield); trc << f << endl; trc.setf(ios::fixed, ios::floatfield); trc << f << endl; trc << f << endl;

trc << setw(10) << s << endl; trc << setw(40) << s << endl; trc.setf(ios::left, ios::adjustfield); trc << setw(40) << s << endl; } ///:~

6012 6013 6014 6015 6016 6017

Listado 5.17. C04/Manips.cpp Puede ver que un montn de sentencias mltiples han sido condensadas dentro de una sola insercin encadenada. Ntese que la llamada a setiosflags() en que se pasa el OR binario de las banderas. Esto se podra haber hecho tambin con setf() y unsetf() como en el ejemplo previo.

6018 6019

//: C04:InputWidth.cpp // Shows limitations of setw with input.

220

6020 6021 6022 6023 6024 6025 6026 6027 6028 6029 6030 6031 6032 6033 6034 6035 6036 6037 6038 6039

#include <cassert> #include <cmath> #include <iomanip> #include <limits> #include <sstream> #include <string> using namespace std;

int main() { istringstream is("one 2.34 five"); string temp; is >> setw(2) >> temp; assert(temp == "on"); is >> setw(2) >> temp; assert(temp == "e"); double x; is >> setw(2) >> x; double relerr = fabs(x - 2.34) / x; assert(relerr <= numeric_limits<double>::epsilon()); } ///:~

6040 6041 6042

Listado 5.18. C04/InputWidth.cpp

5.9.2.

221

6043 6044 6045 6046 6047

ostream& endl(ostream&); cout << "howdy" << endl; ostream& ostream::operator<<(ostream& (*pf)(ostream&)) { return pf(*this); }

6048 6049 6050 6051 6052 6053 6054 6055 6056 6057 6058 6059 6060

//: C04:nl.cpp // Creating a manipulator. #include <iostream> using namespace std;

ostream& nl(ostream& os) { return os << '\n'; }

int main() { cout << "newlines" << nl << "between" << nl << "each" << nl << "word" << nl; } ///:~

6061 6062

Listado 5.19. C04/nl.cpp

6063 6064

cout.operator<<(nl) nl(cout) os << '\n';

222
6065

5.9.3.
//: C04:Effector.cpp // Jerry Schwarz's "effectors." #include <cassert> #include <limits> // For max()

6066 6067 6068 6069 6070 6071 6072 6073 6074 6075 6076 6077 6078 6079 6080 6081 6082 6083 6084 6085 6086 6087 6088 6089

#include <sstream> #include <string> using namespace std;

// Put out a prefix of a string: class Fixw { string str; public: Fixw(const string& s, int width) : str(s, 0, width) {} friend ostream& operator<<(ostream& os, const Fixw& fw) { return os << fw.str; } };

// Print a number in binary: typedef unsigned long ulong;

class Bin { ulong n; public:

223

6090 6091 6092 6093 6094 6095 6096 6097 6098 6099 6100 6101 6102 6103 6104 6105 6106 6107 6108 6109 6110 6111 6112 6113 6114

Bin(ulong nn) { n = nn; } friend ostream& operator<<(ostream& os, const Bin& b) { const ulong ULMAX = numeric_limits<ulong>::max(); ulong bit = ~(ULMAX >> 1); // Top bit set while(bit) { os << (b.n & bit ? '1' : '0'); bit >>= 1; } return os; } };

int main() { string words = "Things that make us happy, make us wise"; for(int i = words.size(); --i >= 0;) { ostringstream s; s << Fixw(words, i); assert(s.str() == words.substr(0, i)); } ostringstream xs, ys; xs << Bin(0xCAFEBABEUL); assert(xs.str() == "1100""1010""1111""1110""1011""1010""1011""1110"); ys << Bin(0x76543210UL); assert(ys.str() ==

224

6115 6116

"0111""0110""0101""0100""0011""0010""0001""0000"); } ///:~

6117 6118

Listado 5.20. C04/Effector.cpp

6119 6120

5.10.
5.10.1.
//: C04:Cppcheck.cpp // Configures .h & .cpp files to conform to style // standard. Tests existing files for conformance. #include <fstream> #include <sstream> #include <string> #include <cstddef> #include "../require.h" using namespace std;

6121 6122 6123 6124 6125 6126 6127 6128 6129 6130 6131 6132 6133 6134 6135 6136 6137

bool startsWith(const string& base, const string& key) { return base.compare(0, key.size(), key) == 0; }

void cppCheck(string fileName) { enum bufs { BASE, HEADER, IMPLEMENT, HLINE1, GUARD1, GUARD2, GUARD3, CPPLINE1, INCLUDE, BUFNUM };

225

6138 6139 6140 6141 6142 6143 6144 6145 6146 6147 6148 6149 6150 6151 6152 6153 6154 6155 6156 6157 6158 6159 6160 6161 6162

string part[BUFNUM]; part[BASE] = fileName; // Find any '.' in the string: size_t loc = part[BASE].find('.'); if(loc != string::npos) part[BASE].erase(loc); // Strip extension // Force to upper case: for(size_t i = 0; i < part[BASE].size(); i++) part[BASE][i] = toupper(part[BASE][i]); // Create file names and internal lines: part[HEADER] = part[BASE] + ".h"; part[IMPLEMENT] = part[BASE] + ".cpp"; part[HLINE1] = "//" ": " + part[HEADER]; part[GUARD1] = "#ifndef " + part[BASE] + "_H"; part[GUARD2] = "#define " + part[BASE] + "_H"; part[GUARD3] = "#endif // " + part[BASE] +"_H"; part[CPPLINE1] = string("//") + ": " + part[IMPLEMENT]; part[INCLUDE] = "#include \"" + part[HEADER] + "\""; // First, try to open existing files: ifstream existh(part[HEADER].c_str()), existcpp(part[IMPLEMENT].c_str()); if(!existh) { // Doesn't exist; create it ofstream newheader(part[HEADER].c_str()); assure(newheader, part[HEADER].c_str()); newheader << part[HLINE1] << endl

226

6163 6164 6165 6166 6167 6168 6169 6170 6171 6172 6173 6174 6175 6176 6177 6178 6179 6180 6181 6182 6183 6184 6185 6186 6187

<< part[GUARD1] << endl << part[GUARD2] << endl << endl << part[GUARD3] << endl; } else { // Already exists; verify it stringstream hfile; // Write & read ostringstream newheader; // Write hfile << existh.rdbuf(); // Check that first three lines conform: bool changed = false; string s; hfile.seekg(0); getline(hfile, s); bool lineUsed = false; // The call to good() is for Microsoft (later too): for(int line = HLINE1; hfile.good() && line <= GUARD2; ++line) { if(startsWith(s, part[line])) { newheader << s << endl; lineUsed = true; if(getline(hfile, s)) lineUsed = false; } else { newheader << part[line] << endl; changed = true; lineUsed = false;

227

6188 6189 6190 6191 6192 6193 6194 6195 6196 6197 6198 6199 6200 6201 6202 6203 6204 6205 6206 6207 6208 6209 6210 6211 6212

} } // Copy rest of file if(!lineUsed) newheader << s << endl; newheader << hfile.rdbuf(); // Check for GUARD3 string head = hfile.str(); if(head.find(part[GUARD3]) == string::npos) { newheader << part[GUARD3] << endl; changed = true; } // If there were changes, overwrite file: if(changed) { existh.close(); ofstream newH(part[HEADER].c_str()); assure(newH, part[HEADER].c_str()); newH << "//@//\n" // Change marker

<< newheader.str(); } } if(!existcpp) { // Create cpp file ofstream newcpp(part[IMPLEMENT].c_str()); assure(newcpp, part[IMPLEMENT].c_str()); newcpp << part[CPPLINE1] << endl

228

6213 6214 6215 6216 6217 6218 6219 6220 6221 6222 6223 6224 6225 6226 6227 6228 6229 6230 6231 6232 6233 6234 6235 6236 6237

<< part[INCLUDE] << endl; } else { // Already exists; verify it stringstream cppfile; ostringstream newcpp; cppfile << existcpp.rdbuf(); // Check that first two lines conform: bool changed = false; string s; cppfile.seekg(0); getline(cppfile, s); bool lineUsed = false; for(int line = CPPLINE1; cppfile.good() && line <= INCLUDE; ++line) { if(startsWith(s, part[line])) { newcpp << s << endl; lineUsed = true; if(getline(cppfile, s)) lineUsed = false; } else { newcpp << part[line] << endl; changed = true; lineUsed = false; } } // Copy rest of file

229

6238 6239 6240 6241 6242 6243 6244 6245 6246 6247 6248 6249 6250 6251 6252 6253 6254 6255 6256 6257

if(!lineUsed) newcpp << s << endl; newcpp << cppfile.rdbuf(); // If there were changes, overwrite file: if(changed) { existcpp.close(); ofstream newCPP(part[IMPLEMENT].c_str()); assure(newCPP, part[IMPLEMENT].c_str()); newCPP << "//@//\n" // Change marker

<< newcpp.str(); } } }

int main(int argc, char* argv[]) { if(argc > 1) cppCheck(argv[1]); else cppCheck("cppCheckTest.h"); } ///:~

6258 6259

Listado 5.21. C04/Cppcheck.cpp

6260 6261

// CPPCHECKTEST.h #ifndef CPPCHECKTEST_H

230

6262 6263 6264 6265

#define CPPCHECKTEST_H #endif // CPPCHECKTEST_H // PPCHECKTEST.cpp #include "CPPCHECKTEST.h"

6266

5.10.2.
//: C04:Showerr.cpp {RunByHand} // Un-comment error generators. #include <cstddef> #include <cstdlib> #include <cstdio> #include <fstream> #include <iostream> #include <sstream> #include <string> #include "../require.h" using namespace std;

6267 6268 6269 6270 6271 6272 6273 6274 6275 6276 6277 6278 6279 6280 6281 6282 6283 6284

const string USAGE = "usage: showerr filename chapnum\n" "where filename is a C++ source file\n" "and chapnum is the chapter name it's in.\n" "Finds lines commented with //! and removes\n" "the comment, appending //(#) where # is unique\n"

231

6285 6286 6287 6288 6289 6290 6291 6292 6293 6294 6295 6296 6297 6298 6299 6300 6301 6302 6303 6304 6305 6306 6307 6308 6309

"across all files, so you can determine\n" "if your compiler finds the error.\n" "showerr /r\n" "resets the unique counter.";

class Showerr { const int CHAP; const string MARKER, FNAME; // File containing error number counter: const string ERRNUM; // File containing error lines: const string ERRFILE; stringstream edited; // Edited file int counter; public: Showerr(const string& f, const string& en, const string& ef, int c) : CHAP(c), MARKER("//!"), FNAME(f), ERRNUM(en), ERRFILE(ef), counter(0) {} void replaceErrors() { ifstream infile(FNAME.c_str()); assure(infile, FNAME.c_str()); ifstream count(ERRNUM.c_str()); if(count) count >> counter; int linecount = 1;

232

6310 6311 6312 6313 6314 6315 6316 6317 6318 6319 6320 6321 6322 6323 6324 6325 6326 6327 6328 6329 6330 6331 6332 6333 6334

string buf; ofstream errlines(ERRFILE.c_str(), ios::app); assure(errlines, ERRFILE.c_str()); while(getline(infile, buf)) { // Find marker at start of line: size_t pos = buf.find(MARKER); if(pos != string::npos) { // Erase marker: buf.erase(pos, MARKER.size() + 1); // Append counter & error info: ostringstream out; out << buf << " // (" << ++counter << ") " << "Chapter " << CHAP << " File: " << FNAME << " Line " << linecount << endl; edited << out.str(); errlines << out.str(); // Append error file } else edited << buf << "\n"; // Just copy ++linecount; } } void saveFiles() { ofstream outfile(FNAME.c_str()); // Overwrites

233

6335 6336 6337 6338 6339 6340 6341 6342 6343 6344 6345 6346 6347 6348 6349 6350 6351 6352 6353 6354 6355 6356 6357 6358 6359

assure(outfile, FNAME.c_str()); outfile << edited.rdbuf(); ofstream count(ERRNUM.c_str()); // Overwrites assure(count, ERRNUM.c_str()); count << counter; // Save new counter } };

int main(int argc, char* argv[]) { const string ERRCOUNT("../errnum.txt"), ERRFILE("../errlines.txt"); requireMinArgs(argc, 1, USAGE.c_str()); if(argv[1][0] == '/' || argv[1][0] == '-') { // Allow for other switches: switch(argv[1][1]) { case 'r': case 'R': cout << "reset counter" << endl; remove(ERRCOUNT.c_str()); // Delete files remove(ERRFILE.c_str()); return EXIT_SUCCESS; default: cerr << USAGE << endl; return EXIT_FAILURE; } }

234

6360 6361 6362 6363 6364 6365

if(argc == 3) { Showerr s(argv[1], ERRCOUNT, ERRFILE, atoi(argv[2])); s.replaceErrors(); s.saveFiles(); } } ///:~

6366 6367 6368

Listado 5.22. C04/Showerr.cpp

5.10.3.
//: C04:DataLogger.h // Datalogger record layout. #ifndef DATALOG_H #define DATALOG_H #include <ctime> #include <iosfwd> #include <string> using std::ostream;

6369 6370 6371 6372 6373 6374 6375 6376 6377 6378 6379 6380 6381 6382

struct Coord { int deg, min, sec; Coord(int d = 0, int m = 0, int s = 0) : deg(d), min(m), sec(s) {} std::string toString() const;

235

6383 6384 6385 6386 6387 6388 6389 6390 6391 6392 6393 6394 6395 6396 6397 6398 6399

};

ostream& operator<<(ostream&, const Coord&);

class DataPoint { std::time_t timestamp; // Time & day Coord latitude, longitude; double depth, temperature; public: DataPoint(std::time_t ts, const Coord& lat, const Coord& lon, double dep, double temp) : timestamp(ts), latitude(lat), longitude(lon), depth(dep), temperature(temp) {} DataPoint() : timestamp(0), depth(0), temperature(0) {} friend ostream& operator<<(ostream&, const DataPoint&); }; #endif // DATALOG_H ///:~

6400 6401

Listado 5.23. C04/DataLogger.h

6402 6403 6404 6405 6406

//: C04:DataLogger.cpp {O} // Datapoint implementations. #include "DataLogger.h" #include <iomanip> #include <iostream>

236

6407 6408 6409 6410 6411 6412 6413 6414 6415 6416 6417 6418 6419 6420 6421 6422 6423 6424 6425 6426 6427 6428 6429 6430 6431

#include <sstream> #include <string> using namespace std;

ostream& operator<<(ostream& os, const Coord& c) { return os << c.deg << '*' << c.min << '\'' << c.sec << '"'; }

string Coord::toString() const { ostringstream os; os << *this; return os.str(); }

ostream& operator<<(ostream& os, const DataPoint& d) { os.setf(ios::fixed, ios::floatfield); char fillc = os.fill('0'); // Pad on left with '0' tm* tdata = localtime(&d.timestamp); os << setw(2) << tdata->tm_mon + 1 << '\\' << setw(2) << tdata->tm_mday << '\\' << setw(2) << tdata->tm_year+1900 << ' ' << setw(2) << tdata->tm_hour << ':' << setw(2) << tdata->tm_min << ':' << setw(2) << tdata->tm_sec;

237

6432 6433 6434 6435 6436 6437 6438 6439 6440 6441

os.fill(' '); // Pad on left with ' ' streamsize prec = os.precision(4); os << " Lat:" << ", Long:" << setw(9) << d.latitude.toString() << setw(9) << d.longitude.toString()

<< ", depth:" << setw(9) << d.depth << ", temp:" << setw(9) << d.temperature; os.fill(fillc); os.precision(prec); return os; } ///:~

6442 6443

Listado 5.24. C04/DataLogger.cpp

6444 6445 6446 6447 6448 6449 6450 6451 6452 6453 6454 6455

//: C04:Datagen.cpp // Test data generator. //{L} DataLogger #include <cstdlib> #include <ctime> #include <cstring> #include <fstream> #include "DataLogger.h" #include "../require.h" using namespace std;

int main() {

238

6456 6457 6458 6459 6460 6461 6462 6463 6464 6465 6466 6467 6468 6469 6470 6471 6472 6473 6474 6475 6476 6477

time_t timer; srand(time(&timer)); // Seed the random number generator ofstream data("data.txt"); assure(data, "data.txt"); ofstream bindata("data.bin", ios::binary); assure(bindata, "data.bin"); for(int i = 0; i < 100; i++, timer += 55) { // Zero to 199 meters: double newdepth = rand() % 200;

double fraction = rand() % 100 + 1; newdepth += 1.0 / fraction; double newtemp = 150 + rand() % 200; // Kelvin fraction = rand() % 100 + 1; newtemp += 1.0 / fraction; const DataPoint d(timer, Coord(45,20,31), Coord(22,34,18), newdepth, newtemp); data << d << endl; bindata.write(reinterpret_cast<const char*>(&d), sizeof(d)); } } ///:~

6478 6479

Listado 5.25. C04/Datagen.cpp

239

6480 6481 6482 6483 6484 6485 6486 6487 6488 6489 6490 6491 6492 6493 6494 6495

//: C04:Datascan.cpp //{L} DataLogger #include <fstream> #include <iostream> #include "DataLogger.h" #include "../require.h" using namespace std;

int main() { ifstream bindata("data.bin", ios::binary); assure(bindata, "data.bin"); DataPoint d; while(bindata.read(reinterpret_cast<char*>(&d), sizeof d)) cout << d << endl; } ///:~

6496 6497

Listado 5.26. C04/Datascan.cpp

6498 6499 6500

5.11.
5.11.1. 5.11.2.
//: C04:Locale.cpp {-g++}{-bor}{-edg} {RunByHand} // Illustrates effects of locales.

6501 6502

240

6503 6504 6505 6506 6507 6508 6509 6510 6511 6512 6513 6514 6515 6516 6517 6518 6519 6520 6521 6522 6523 6524 6525 6526

#include <iostream> #include <locale> using namespace std;

int main() { locale def; cout << def.name() << endl; locale current = cout.getloc(); cout << current.name() << endl; float val = 1234.56; cout << val << endl; // Change to French/France cout.imbue(locale("french")); current = cout.getloc(); cout << current.name() << endl; cout << val << endl;

cout << "Enter the literal 7890,12: "; cin.imbue(cout.getloc()); cin >> val; cout << val << endl; cout.imbue(def); cout << val << endl; } ///:~

241
6527 6528 Listado 5.27. C04/Locale.cpp

6529 6530 6531 6532 6533 6534 6535 6536 6537 6538 6539 6540 6541 6542 6543 6544 6545

//: C04:Facets.cpp {-bor}{-g++}{-mwcc}{-edg} #include <iostream> #include <locale> #include <string> using namespace std;

int main() { // Change to French/France locale loc("french"); cout.imbue(loc); string currency = use_facet<moneypunct<char> >(loc).curr_symbol(); char point = use_facet<moneypunct<char> >(loc).decimal_point(); cout << "I made " << currency << 12.34 << " today!" << endl; } ///:~

6546 6547

Listado 5.28. C04/Facets.cpp

6548

5.12. 5.13.

6549

242

6550 6551 6552 6553 6554 6555 6556 6557 6558 6559 6560 6561 6562 6563 6564 6565 6566 6567 6568 6569 6570 6571 6572 6573 6574

//: C04:Exercise14.cpp #include <fstream> #include <iostream> #include <sstream> #include "../require.h" using namespace std;

#define d(a) cout << #a " ==\t" << a << endl;

void tellPointers(fstream& s) { d(s.tellp()); d(s.tellg()); cout << endl; } void tellPointers(stringstream& s) { d(s.tellp()); d(s.tellg()); cout << endl; } int main() { fstream in("Exercise14.cpp"); assure(in, "Exercise14.cpp"); in.seekg(10); tellPointers(in); in.seekp(20);

243

6575 6576 6577 6578 6579 6580 6581

tellPointers(in); stringstream memStream("Here is a sentence."); memStream.seekg(10); tellPointers(memStream); memStream.seekp(5); tellPointers(memStream); } ///:~

6582 6583

Listado 5.29. C04/Exercise14.cpp

6584 6585 6586 6587 6588 6589 6590 6591 6592 6593 6594 6595 6596 6597 6598

//: C04:Exercise15.txt&#13; Australia&#13; 5E56,7667230284,Langler,Tyson,31.2147,0.00042117361&#13; 2B97,7586701,Oneill,Zeke,553.429,0.0074673053156065&#13; 4D75,7907252710,Nickerson,Kelly,761.612,0.010276276&#13; 9F2,6882945012,Hartenbach,Neil,47.9637,0.0006471644&#13; Austria&#13; 480F,7187262472,Oneill,Dee,264.012,0.00356226040013&#13; 1B65,4754732628,Haney,Kim,7.33843,0.000099015948475&#13; DA1,1954960784,Pascente,Lester,56.5452,0.0007629529&#13; 3F18,1839715659,Elsea,Chelsy,801.901,0.010819887645&#13; Belgium&#13; BDF,5993489554,Oneill,Meredith,283.404,0.0038239127&#13; 5AC6,6612945602,Parisienne,Biff,557.74,0.0075254727&#13; 6AD,6477082,Pennington,Lizanne,31.0807,0.0004193544&#13;

244

6599 6600 6601 6602 6603 6604 6605 6606 6607

4D0E,7861652688,Sisca,Francis,704.751,0.00950906238&#13; Bahamas&#13; 37D8,6837424208,Parisienne,Samson,396.104,0.0053445&#13; 5E98,6384069,Willis,Pam,90.4257,0.00122009564059246&#13; 1462,1288616408,Stover,Hazal,583.939,0.007878970561&#13; 5FF3,8028775718,Stromstedt,Bunk,39.8712,0.000537974&#13; 1095,3737212,Stover,Denny,3.05387,0.000041205248883&#13; 7428,2019381883,Parisienne,Shane,363.272,0.00490155&#13; ///:~&#13;

6608 6609 6610 6611 6612 6613 6614 6615 6616 6617 6618 6619 6620 6621 6622 6623 6624 6625 6626 6627
[15] [13] [12] [11]

Listado 5.30. C04/Exercise15.txt

Explicadas en profundidad en el captulo 5.

Por esa razn usted puede escribir ios::failbit en lugar de ios_base::failbit para ahorrar pulsaciones.

Es comn el uso de operator void*() en vez de operator bool() porque las conversiones implcitas de booleano a entero pueden causar sorpresas; pueden emplazarle incorrectamente un stream en un contexto donde una conversion a integer puede ser aplicada. La funcin operator void*() solo ser llamada implcitamente en el cuerpo de una expresin booleana.
[14]

un tipo integral usado para alojar bits aislados.

Un tratado mucho ms en profundidad de buffers de stream y streams en general puede ser encontrado en[ Langer & Kreft's, Standar C++ iostreams and Locales, Addison-Wesley, 1999.] Para ms informacin sobre la epsilon de la mquina y el cmputo de punto flotante en general, vea el artculo de Chuck, "The Standard C Library, Part 3", C/C++ Users Journal, Marzo 1995, disponible en www.freshsources.com/1995006a.htm
[16]

245

6628 6629 6630 6631 6632 6633 6634 6635 6636 6637 6638

6: Las plantillas en profundidad


Tabla de contenidos 6.1. 6.2. 6.3. 6.4. 6.5. 6.6. 6.7. 6.8.

6.1.
template<class T> class Stack { T* data; size_t count; public: void push(const T& t); // Etc. }; Stack<int> myStack; // A Stack of ints

6639 6640 6641 6642 6643 6644 6645 6646

6647

6.1.1.
template<class T, size_t N> class Stack { T data[N]; // Fixed capacity is N size_t count; public: void push(const T& t); // Etc.

6648 6649 6650 6651 6652 6653

246

6654 6655

}; Stack<int, 100> myFixedStack;

6656 6657 6658 6659 6660 6661 6662 6663 6664 6665 6666 6667 6668 6669 6670 6671 6672 6673 6674 6675 6676 6677

//: C05:Urand.h {-bor} // Unique randomizer. #ifndef URAND_H #define URAND_H #include <bitset> #include <cstddef> #include <cstdlib> #include <ctime> using std::size_t; using std::bitset;

template<size_t UpperBound> class Urand { bitset<UpperBound> used; public: Urand() { srand(time(0)); } // Randomize size_t operator()(); // The "generator" function };

template<size_t UpperBound> inline size_t Urand<UpperBound>::operator()() { if(used.count() == UpperBound) used.reset(); // Start over (clear bitset)

247

6678 6679 6680 6681 6682 6683 6684

size_t newval; while(used[newval = rand() % UpperBound]) ; // Until unique value is found used[newval] = true; return newval; } #endif // URAND_H ///:~

6685 6686

Listado 6.1. C05/Urand.h

6687 6688 6689 6690 6691 6692 6693 6694 6695 6696

//: C05:UrandTest.cpp {-bor} #include <iostream> #include "Urand.h" using namespace std;

int main() { Urand<10> u; for(int i = 0; i < 20; ++i) cout << u() << ' '; } ///:~

6697 6698 6699

Listado 6.2. C05/UrandTest.cpp

6.1.2.

248

6700 6701 6702 6703 6704 6705 6706 6707 6708 6709 6710 6711 6712 6713 6714 6715 6716 6717 6718

template<class T, size_t N = 100> class Stack { T data[N]; // Fixed capacity is N size_t count; public: void push(const T& t); // Etc. }; template<class T = int, size_t N = 100> class Stack { T data[N]; // Fixed capacity is N size_t count; public: void push(const T& t); // Etc. }; // Both defaulted

Stack<> myStack;

// Same as Stack<int, 100>

template<class T, class Allocator = allocator<T> > class vector;

6719 6720 6721 6722 6723

//: C05:FuncDef.cpp #include <iostream> using namespace std;

template<class T> T sum(T* b, T* e, T init = T()) {

249

6724 6725 6726 6727 6728 6729 6730 6731 6732

while(b != e) init += *b++; return init; }

int main() { int a[] = { 1, 2, 3 }; cout << sum(a, a + sizeof a / sizeof a[0]) << endl; // 6 } ///:~

6733 6734 6735

Listado 6.3. C05/FuncDef.cpp

6.1.3.
//: C05:TempTemp.cpp // Illustrates a template template parameter. #include <cstddef> #include <iostream> using namespace std;

6736 6737 6738 6739 6740 6741 6742 6743 6744 6745 6746

template<class T> class Array { // A simple, expandable sequence enum { INIT = 10 }; T* data; size_t capacity;

250

6747 6748 6749 6750 6751 6752 6753 6754 6755 6756 6757 6758 6759 6760 6761 6762 6763 6764 6765 6766 6767 6768 6769 6770 6771

size_t count; public: Array() { count = 0; data = new T[capacity = INIT]; } ~Array() { delete [] data; } void push_back(const T& t) { if(count == capacity) { // Grow underlying array size_t newCap = 2 * capacity; T* newData = new T[newCap]; for(size_t i = 0; i < count; ++i) newData[i] = data[i]; delete [] data; data = newData; capacity = newCap; } data[count++] = t; } void pop_back() { if(count > 0) --count; } T* begin() { return data; }

251

6772 6773 6774 6775 6776 6777 6778 6779 6780 6781 6782 6783 6784 6785 6786 6787 6788 6789 6790 6791

T* end() { return data + count; } };

template<class T, template<class> class Seq> class Container { Seq<T> seq; public: void append(const T& t) { seq.push_back(t); } T* begin() { return seq.begin(); } T* end() { return seq.end(); } };

int main() { Container<int, Array> container; container.append(1); container.append(2); int* p = container.begin(); while(p != container.end()) cout << *p++ << endl; } ///:~

6792 6793

Listado 6.4. C05/TempTemp.cpp

6794 6795

Seq<T> seq; template<class T, template<class> class Seq>

252

6796 6797

template<class T, template<class U> class Seq> T operator++(int);

6798 6799 6800 6801 6802 6803 6804 6805 6806 6807 6808 6809 6810 6811 6812 6813 6814 6815 6816 6817 6818 6819

//: C05:TempTemp2.cpp // A multi-variate template template parameter. #include <cstddef> #include <iostream> using namespace std;

template<class T, size_t N> class Array { T data[N]; size_t count; public: Array() { count = 0; } void push_back(const T& t) { if(count < N) data[count++] = t; } void pop_back() { if(count > 0) --count; } T* begin() { return data; } T* end() { return data + count; } };

253

6820 6821 6822 6823 6824 6825 6826 6827 6828 6829 6830 6831 6832 6833 6834 6835 6836 6837 6838
int main() { const size_t N = 10; Container<int, N, Array> container; container.append(1); container.append(2); int* p = container.begin(); while(p != container.end()) cout << *p++ << endl; } ///:~ template<class T,size_t N,template<class,size_t> class Seq> class Container { Seq<T,N> seq; public: void append(const T& t) { seq.push_back(t); } T* begin() { return seq.begin(); } T* end() { return seq.end(); } };

6839 6840

Listado 6.5. C05/TempTemp2.cpp

6841 6842 6843

//: C05:TempTemp3.cpp {-bor}{-msc} // Template template parameters and default arguments. #include <cstddef>

254

6844 6845 6846 6847 6848 6849 6850 6851 6852 6853 6854 6855 6856 6857 6858 6859 6860 6861 6862 6863 6864 6865 6866 6867 6868

#include <iostream> using namespace std;

template<class T, size_t N = 10> class Array { T data[N]; size_t count; public:

// A default argument

Array() { count = 0; } void push_back(const T& t) { if(count < N) data[count++] = t; } void pop_back() { if(count > 0) --count; } T* begin() { return data; } T* end() { return data + count; } };

template<class T, template<class, size_t = 10> class Seq> class Container { Seq<T> seq; // Default used

public:

255

6869 6870 6871 6872 6873 6874 6875 6876 6877 6878 6879 6880 6881

void append(const T& t) { seq.push_back(t); } T* begin() { return seq.begin(); } T* end() { return seq.end(); } };

int main() { Container<int, Array> container; container.append(1); container.append(2); int* p = container.begin(); while(p != container.end()) cout << *p++ << endl; } ///:~

6882 6883

Listado 6.6. C05/TempTemp3.cpp

6884

template<class T, template<class, size_t = 10> class Seq>

6885 6886 6887 6888 6889 6890 6891

//: C05:TempTemp4.cpp {-bor}{-msc} // Passes standard sequences as template arguments. #include <iostream> #include <list> #include <memory> // Declares allocator<T>

#include <vector> using namespace std;

256

6892 6893 6894 6895 6896 6897 6898 6899 6900 6901 6902 6903 6904 6905 6906 6907 6908 6909 6910 6911 6912 6913 6914 6915 6916
int main() { // Use a vector Container<int, vector> vContainer; vContainer.push_back(1); vContainer.push_back(2); for(vector<int>::iterator p = vContainer.begin(); p != vContainer.end(); ++p) { cout << *p << endl; } // Use a list Container<int, list> lContainer; lContainer.push_back(3); lContainer.push_back(4); for(list<int>::iterator p2 = lContainer.begin(); template<class T, template<class U, class = allocator<U> > class Seq> class Container { Seq<T> seq; // Default of allocator<T> applied implicitly public: void push_back(const T& t) { seq.push_back(t); } typename Seq<T>::iterator begin() { return seq.begin(); } typename Seq<T>::iterator end() { return seq.end(); } };

257

6917 6918 6919 6920

p2 != lContainer.end(); ++p2) { cout << *p2 << endl; } } ///:~

6921 6922 6923

Listado 6.7. C05/TempTemp4.cpp

6.1.4.
//: C05:TypenamedID.cpp {-bor} // Uses 'typename' as a prefix for nested types.

6924 6925 6926 6927 6928 6929 6930 6931 6932 6933 6934 6935 6936 6937 6938 6939

template<class T> class X { // Without typename, you should get an error: typename T::id i; public: void f() { i.g(); } };

class Y { public: class id { public: void g() {} };

258

6940 6941 6942 6943 6944 6945

};

int main() { X<Y> xy; xy.f(); } ///:~

6946 6947

Listado 6.8. C05/TypenamedID.cpp

6948 6949 6950 6951 6952 6953 6954 6955 6956 6957 6958 6959 6960 6961 6962 6963

//: C05:PrintSeq.cpp {-msc}{-mwcc} // A print function for Standard C++ sequences. #include <iostream> #include <list> #include <memory> #include <vector> using namespace std;

template<class T, template<class U, class = allocator<U> > class Seq> void printSeq(Seq<T>& seq) { for(typename Seq<T>::iterator b = seq.begin(); b != seq.end();) cout << *b++ << endl; }

259

6964 6965 6966 6967 6968 6969 6970 6971 6972 6973 6974 6975

int main() { // Process a vector vector<int> v; v.push_back(1); v.push_back(2); printSeq(v); // Process a list list<int> lst; lst.push_back(3); lst.push_back(4); printSeq(lst); } ///:~

6976 6977

Listado 6.9. C05/PrintSeq.cpp

6978 6979

typename Seq<T>::iterator It; typedef typename Seq<It>::iterator It;

6980 6981 6982 6983 6984 6985

//: C05:UsingTypename.cpp // Using 'typename' in the template argument list.

template<typename T> class X {};

int main() {

260

6986 6987

X<int> x; } ///:~

6988 6989 6990

Listado 6.10. C05/UsingTypename.cpp

6.1.5.
//: C05:DotTemplate.cpp // Illustrate the .template construct. #include <bitset> #include <cstddef> #include <iostream> #include <string> using namespace std;

6991 6992 6993 6994 6995 6996 6997 6998 6999 7000 7001 7002 7003 7004 7005 7006 7007 7008

template<class charT, size_t N> basic_string<charT> bitsetToString(const bitset<N>& bs) { return bs. template to_string<charT, char_traits<charT>, allocator<charT> >(); }

int main() { bitset<10> bs; bs.set(1); bs.set(5);

261

7009 7010 7011 7012

cout << bs << endl; // 0000100010 string s = bitsetToString<char>(bs); cout << s << endl; } ///:~ // 0000100010

7013 7014

Listado 6.11. C05/DotTemplate.cpp

7015 7016 7017

template<class charT, class traits, class Allocator> basic_string<charT, traits, Allocator> to_string() const; wstring s = bitsetToString<wchar_t>(bs);

7018

6.1.6.
template<typename T> class complex { public: template<class X> complex(const complex<X>&); complex<float> z(1, 2); complex<double> w(z); template<typename T> template<typename X> complex<T>::complex(const complex<X>& c) {/* Body here' */} int data[5] = { 1, 2, 3, 4, 5 }; vector<int> v1(data, data+5); vector<double> v2(v1.begin(), v1.end()); template<class InputIterator>

7019 7020 7021 7022 7023 7024 7025 7026 7027 7028 7029 7030

262

7031 7032

vector(InputIterator first, InputIterator last, const Allocator& = Allocator());

7033 7034 7035 7036 7037 7038 7039 7040 7041 7042 7043 7044 7045 7046 7047 7048 7049 7050 7051 7052 7053 7054

//: C05:MemberClass.cpp // A member class template. #include <iostream> #include <typeinfo> using namespace std;

template<class T> class Outer { public: template<class R> class Inner { public: void f(); }; };

template<class T> template<class R> void Outer<T>::Inner<R>::f() { cout << "Outer == " << typeid(T).name() << endl; cout << "Inner == " << typeid(R).name() << endl; cout << "Full Inner == " << typeid(*this).name() << endl; }

int main() {

263

7055 7056 7057

Outer<int>::Inner<bool> inner; inner.f(); } ///:~

7058 7059 7060 7061 7062

Listado 6.12. C05/MemberClass.cpp

Outer == int Inner == bool Full Inner == Outer<int>::Inner<bool>

7063

6.2.
template<typename T> const T& min(const T& a, const T& b) { return (a < b) ? a : b; } int z = min<int>(i, j);

7064 7065 7066 7067

7068

6.2.1.
int z = min(i, j); int z = min(x, j); // x is a double int z = min<double>(x, j); template<typename T, typename U> const T& min(const T& a, const U& b) { return (a < b) ? a : b; }

7069 7070 7071 7072 7073 7074 7075

7076

//: C05:StringConv.h

264

7077 7078 7079 7080 7081 7082 7083 7084 7085 7086 7087 7088 7089 7090 7091 7092 7093 7094 7095

// Function templates to convert to and from strings. #ifndef STRINGCONV_H #define STRINGCONV_H #include <string> #include <sstream>

template<typename T> T fromString(const std::string& s) { std::istringstream is(s); T t; is >> t; return t; }

template<typename T> std::string toString(const T& t) { std::ostringstream s; s << t; return s.str(); } #endif // STRINGCONV_H ///:~

7096 7097

Listado 6.13. C05/StringConv.h

7098 7099 7100

//: C05:StringConvTest.cpp #include <complex> #include <iostream>

265

7101 7102 7103 7104 7105 7106 7107 7108 7109 7110 7111 7112 7113 7114 7115 7116 7117 7118 7119

#include "StringConv.h" using namespace std;

int main() { int i = 1234; cout << "i == \"" << toString(i) << "\"" << endl; float x = 567.89; cout << "x == \"" << toString(x) << "\"" << endl; complex<float> c(1.0, 2.0); cout << "c == \"" << toString(c) << "\"" << endl; cout << endl;

i = fromString<int>(string("1234")); cout << "i == " << i << endl; x = fromString<float>(string("567.89")); cout << "x == " << x << endl; c = fromString<complex<float> >(string("(1.0,2.0)")); cout << "c == " << c << endl; } ///:~

7120 7121 7122 7123 7124 7125 7126 7127 7128

Listado 6.14. C05/StringConvTest.cpp

i == "1234" x == "567.89" c == "(1,2)" i == 1234 x == 567.89 c == (1,2)

266

7129 7130 7131 7132 7133 7134 7135 7136 7137 7138 7139 7140 7141

//: C05:ImplicitCast.cpp

template<typename R, typename P> R implicit_cast(const P& p) { return p; }

int main() { int i = 1; float x = implicit_cast<float>(i); int j = implicit_cast<int>(x); //! char* p = implicit_cast<char*>(i); } ///:~

7142 7143

Listado 6.15. C05/ImplicitCast.cpp

7144 7145 7146 7147 7148 7149 7150 7151 7152

//: C05:ArraySize.cpp #include <cstddef> using std::size_t;

template<size_t R, size_t C, typename T> void init1(T a[R][C]) { for(size_t i = 0; i < R; ++i) for(size_t j = 0; j < C; ++j) a[i][j] = T();

267

7153 7154 7155 7156 7157 7158 7159 7160 7161 7162 7163 7164 7165 7166

template<size_t R, size_t C, class T> void init2(T (&a)[R][C]) { // Reference parameter

for(size_t i = 0; i < R; ++i) for(size_t j = 0; j < C; ++j) a[i][j] = T(); }

int main() { int a[10][20]; init1<10,20>(a); init2(a); // Must specify // Sizes deduced } ///:~

7167 7168 7169

Listado 6.16. C05/ArraySize.cpp

6.2.2.
//: C05:MinTest.cpp #include <cstring> #include <iostream> using std::strcmp; using std::cout; using std::endl;

7170 7171 7172 7173 7174 7175

268

7176 7177 7178 7179 7180 7181 7182 7183 7184 7185 7186 7187 7188 7189 7190 7191 7192 7193 7194 7195 7196 7197 7198
int main() { const char *s2 = "say \"Ni-!\"", *s1 = "knights who"; cout << min(1, 2) << endl; cout << min(1.0, 2.0) << endl; cout << min(1, 2.0) << endl; cout << min(s1, s2) << endl; // cout << min<>(s1, s2) << endl; // (template) } ///:~ // 1: 1 (template) // 2: 1 (double) // 3: 1 (double) // 4: knights who (const char*) // 5: say "Ni-!" double min(double x, double y) { return (x < y) ? x : y; } const char* min(const char* a, const char* b) { return (strcmp(a, b) < 0) ? a : b; } template<typename T> const T& min(const T& a, const T& b) { return (a < b) ? a : b; }

7199

Listado 6.17. C05/MinTest.cpp

269
7200

7201 7202

template<typename T> const T& min(const T& a, const T& b, const T& c);

7203

6.2.3.
//: C05:TemplateFunctionAddress.cpp {-mwcc} // Taking the address of a function generated // from a template.

7204 7205 7206 7207 7208 7209 7210 7211 7212 7213 7214 7215 7216 7217 7218 7219 7220

template<typename T> void f(T*) {}

void h(void (*pf)(int*)) {}

template<typename T> void g(void (*pf)(T*)) {}

int main() { h(&f<int>); // Full type specification h(&f); // Type deduction g<int>(&f<int>); // Full type specification g(&f<int>); // Type deduction g<int>(&f); // Partial (but sufficient) specification } ///:~

7221 7222

Listado 6.18. C05/TemplateFunctionAddress.cpp

270

7223 7224

// The variable s is a std::string transform(s.begin(), s.end(), s.begin(), tolower);

7225 7226 7227 7228 7229 7230 7231 7232 7233 7234 7235 7236

//: C05:FailedTransform.cpp {-xo} #include <algorithm> #include <cctype> #include <iostream> #include <string> using namespace std;

int main() { string s("LOWER"); transform(s.begin(), s.end(), s.begin(), tolower); cout << s << endl; } ///:~

7237 7238

Listado 6.19. C05/FailedTransform.cpp

7239 7240 7241 7242 7243 7244

template<class charT> charT toupper(charT c, const locale& loc); template<class charT> charT tolower(charT c, const locale& loc); transform(s.begin(),s.end(),s.begin() static_cast<int(*)(int)>(tolower));

271

7245 7246 7247 7248 7249 7250 7251 7252 7253 7254

//: C05:StrTolower.cpp {O} {-mwcc} #include <algorithm> #include <cctype> #include <string> using namespace std;

string strTolower(string s) { transform(s.begin(), s.end(), s.begin(), tolower); return s; } ///:~

7255 7256

Listado 6.20. C05/StrTolower.cpp

7257 7258 7259 7260 7261 7262 7263 7264 7265 7266 7267 7268

//: C05:Tolower.cpp {-mwcc} //{L} StrTolower #include <algorithm> #include <cctype> #include <iostream> #include <string> using namespace std; string strTolower(string);

int main() { string s("LOWER"); cout << strTolower(s) << endl;

272

7269

} ///:~

7270 7271

Listado 6.21. C05/Tolower.cpp

7272 7273 7274 7275 7276 7277 7278 7279 7280 7281 7282 7283 7284 7285 7286 7287

//: C05:ToLower2.cpp {-mwcc} #include <algorithm> #include <cctype> #include <iostream> #include <string> using namespace std;

template<class charT> charT strTolower(charT c) { return tolower(c); // One-arg version called }

int main() { string s("LOWER"); transform(s.begin(),s.end(),s.begin(),&strTolower<char>); cout << s << endl; } ///:~

7288 7289 7290

Listado 6.22. C05/ToLower2.cpp

6.2.4.

273

7291 7292 7293 7294 7295 7296 7297 7298 7299 7300 7301 7302 7303 7304 7305 7306 7307 7308 7309 7310 7311 7312 7313 7314 7315

//: C05:ApplySequence.h // Apply a function to an STL sequence container.

// const, 0 arguments, any type of return value: template<class Seq, class T, class R> void apply(Seq& sq, R (T::*f)() const) { typename Seq::iterator it = sq.begin(); while(it != sq.end()) ((*it++)->*f)(); }

// const, 1 argument, any type of return value: template<class Seq, class T, class R, class A> void apply(Seq& sq, R(T::*f)(A) const, A a) { typename Seq::iterator it = sq.begin(); while(it != sq.end()) ((*it++)->*f)(a); }

// const, 2 arguments, any type of return value: template<class Seq, class T, class R, class A1, class A2> void apply(Seq& sq, R(T::*f)(A1, A2) const, A1 a1, A2 a2) { typename Seq::iterator it = sq.begin();

274

7316 7317 7318 7319 7320 7321 7322 7323 7324 7325 7326 7327 7328 7329 7330 7331 7332 7333 7334 7335 7336 7337 7338 7339 7340

while(it != sq.end()) ((*it++)->*f)(a1, a2); }

// Non-const, 0 arguments, any type of return value: template<class Seq, class T, class R> void apply(Seq& sq, R (T::*f)()) { typename Seq::iterator it = sq.begin(); while(it != sq.end()) ((*it++)->*f)(); }

// Non-const, 1 argument, any type of return value: template<class Seq, class T, class R, class A> void apply(Seq& sq, R(T::*f)(A), A a) { typename Seq::iterator it = sq.begin(); while(it != sq.end()) ((*it++)->*f)(a); }

// Non-const, 2 arguments, any type of return value: template<class Seq, class T, class R, class A1, class A2> void apply(Seq& sq, R(T::*f)(A1, A2), A1 a1, A2 a2) {

275

7341 7342 7343 7344 7345

typename Seq::iterator it = sq.begin(); while(it != sq.end()) ((*it++)->*f)(a1, a2); } // Etc., to handle maximum likely arguments ///:~

7346 7347

Listado 6.23. C05/ApplySequence.h

7348 7349 7350 7351 7352 7353 7354 7355 7356 7357 7358 7359 7360 7361 7362 7363 7364

//: C05:Gromit.h // The techno-dog. Has member functions // with various numbers of arguments. #include <iostream>

class Gromit { int arf; int totalBarks; public: Gromit(int arf = 1) : arf(arf + 1), totalBarks(0) {} void speak(int) { for(int i = 0; i < arf; i++) { std::cout << "arf! "; ++totalBarks; } std::cout << std::endl; }

276

7365 7366 7367 7368 7369 7370 7371 7372 7373 7374 7375 7376

char eat(float) const { std::cout << "chomp!" << std::endl; return 'z'; } int sleep(char, double) const { std::cout << "zzz..." << std::endl; return 0; } void sit() const { std::cout << "Sitting..." << std::endl; } }; ///:~

7377 7378

Listado 6.24. C05/Gromit.h

7379 7380 7381 7382 7383 7384 7385 7386 7387 7388

//: C05:ApplyGromit.cpp // Test ApplySequence.h. #include <cstddef> #include <iostream> #include <vector> #include "ApplySequence.h" #include "Gromit.h" #include "../purge.h" using namespace std;

277

7389 7390 7391 7392 7393 7394 7395 7396 7397 7398

int main() { vector<Gromit*> dogs; for(size_t i = 0; i < 5; i++) dogs.push_back(new Gromit(i)); apply(dogs, &Gromit::speak, 1); apply(dogs, &Gromit::eat, 2.0f); apply(dogs, &Gromit::sleep, 'z', 3.0); apply(dogs, &Gromit::sit); purge(dogs); } ///:~

7399 7400 7401

Listado 6.25. C05/ApplyGromit.cpp

6.2.5.
template<class T> void f(T); template<class T> void f(T*); template<class T> void f(const T*);

7402 7403 7404

7405 7406 7407 7408 7409 7410

//: C05:PartialOrder.cpp // Reveals ordering of function templates. #include <iostream> using namespace std;

template<class T> void f(T) {

278

7411 7412 7413 7414 7415 7416 7417 7418 7419 7420 7421 7422 7423 7424 7425 7426 7427 7428

cout << "T" << endl; }

template<class T> void f(T*) { cout << "T*" << endl; }

template<class T> void f(const T*) { cout << "const T*" << endl; }

int main() { f(0); int i = 0; f(&i); // T* // T

const int j = 0; f(&j); // const T* } ///:~

7429 7430

Listado 6.26. C05/PartialOrder.cpp

7431 7432

6.3.
6.3.1.
const char* min(const char* a, const char* b) {

7433

279

7434 7435

return (strcmp(a, b) < 0) ? a : b; }

7436 7437 7438 7439 7440 7441 7442 7443 7444 7445 7446 7447 7448 7449 7450 7451 7452 7453 7454 7455 7456 7457

//: C05:MinTest2.cpp #include <cstring> #include <iostream> using std::strcmp; using std::cout; using std::endl;

template<class T> const T& min(const T& a, const T& b) { return (a < b) ? a : b; }

// An explicit specialization of the min template template<> const char* const& min<const char*>(const char* const& a, const char* const& b) { return (strcmp(a, b) < 0) ? a : b; }

int main() { const char *s2 = "say \"Ni-!\"", *s1 = "knights who"; cout << min(s1, s2) << endl; cout << min<>(s1, s2) << endl;

280

7458

} ///:~

7459 7460

Listado 6.27. C05/MinTest2.cpp

7461 7462 7463

template<class T, class Allocator = allocator<T> > class vector {...}; template<> class vector<bool, allocator<bool> > {...};

7464

6.3.2.
template<class Allocator> class vector<bool, Allocator>;

7465

7466 7467 7468 7469 7470 7471 7472 7473 7474 7475 7476 7477 7478

//: C05:PartialOrder2.cpp // Reveals partial ordering of class templates. #include <iostream> using namespace std;

template<class T, class U> class C { public: void f() { cout << "Primary Template\n"; } };

template<class U> class C<int, U> { public: void f() { cout << "T == int\n"; }

281

7479 7480 7481 7482 7483 7484 7485 7486 7487 7488 7489 7490 7491 7492 7493 7494 7495 7496 7497 7498 7499 7500 7501 7502 7503

};

template<class T> class C<T, double> { public: void f() { cout << "U == double\n"; } };

template<class T, class U> class C<T*, U> { public: void f() { cout << "T* used\n"; } };

template<class T, class U> class C<T, U*> { public: void f() { cout << "U* used\n"; } };

template<class T, class U> class C<T*, U*> { public: void f() { cout << "T* and U* used\n"; } };

template<class T> class C<T, T> { public: void f() { cout << "T == U\n"; }

282

7504 7505 7506 7507 7508 7509 7510 7511 7512 7513 7514 7515 7516 7517 7518 7519 7520
// //

};

int main() { C<float, int>().f(); C<int, float>().f(); // 1: Primary template // 2: T == int

C<float, double>().f(); // 3: U == double C<float, float>().f(); // 4: T == U

C<float*, float>().f(); // 5: T* used [T is float] C<float, float*>().f(); // 6: U* used [U is float] C<float*, int*>().f(); // 7: T* and U* used [float,int]

// The following are ambiguous: // 8: C<int, int>().f(); 9: C<double, double>().f(); 10: C<float*, float*>().f(); // // 11: C<int, int*>().f(); 12: C<int*, int*>().f(); } ///:~

7521 7522 7523

Listado 6.28. C05/PartialOrder2.cpp

6.3.3.
//: C05:Sortable.h // Template specialization. #ifndef SORTABLE_H

7524 7525 7526

283

7527 7528 7529 7530 7531 7532 7533 7534 7535 7536 7537 7538 7539 7540 7541 7542 7543 7544 7545 7546 7547 7548 7549 7550 7551

#define SORTABLE_H #include <cstring> #include <cstddef> #include <string> #include <vector> using std::size_t;

template<class T> class Sortable : public std::vector<T> { public: void sort(); };

template<class T> void Sortable<T>::sort() { // A simple sort for(size_t i = this->size(); i > 0; --i) for(size_t j = 1; j < i; ++j) if(this->at(j-1) > this->at(j)) { T t = this->at(j-1); this->at(j-1) = this->at(j); this->at(j) = t; } }

// Partial specialization for pointers:

284

7552 7553 7554 7555 7556 7557 7558 7559 7560 7561 7562 7563 7564 7565 7566 7567 7568 7569 7570 7571 7572 7573 7574 7575 7576

template<class T> class Sortable<T*> : public std::vector<T*> { public: void sort(); };

template<class T> void Sortable<T*>::sort() { for(size_t i = this->size(); i > 0; --i) for(size_t j = 1; j < i; ++j) if(*this->at(j-1) > *this->at(j)) { T* t = this->at(j-1); this->at(j-1) = this->at(j); this->at(j) = t; } }

// Full specialization for char* // (Made inline here for convenience -- normally you would // place the function body in a separate file and only // leave the declaration here). template<> inline void Sortable<char*>::sort() { for(size_t i = this->size(); i > 0; --i) for(size_t j = 1; j < i; ++j) if(std::strcmp(this->at(j-1), this->at(j)) > 0) {

285

7577 7578 7579 7580 7581 7582

char* t = this->at(j-1); this->at(j-1) = this->at(j); this->at(j) = t; } } #endif // SORTABLE_H ///:~

7583 7584

Listado 6.29. C05/Sortable.h

7585 7586 7587 7588 7589 7590 7591 7592 7593 7594 7595 7596 7597 7598 7599 7600

//: C05:Sortable.cpp //{-bor} (Because of bitset in Urand.h) // Testing template specialization. #include <cstddef> #include <iostream> #include "Sortable.h" #include "Urand.h" using namespace std;

#define asz(a) (sizeof a / sizeof a[0])

char* words[] = { "is", "running", "big", "dog", "a", }; char* words2[] = { "this", "that", "theother", };

int main() { Sortable<int> is;

286

7601 7602 7603 7604 7605 7606 7607 7608 7609 7610 7611 7612 7613 7614 7615 7616 7617 7618 7619 7620 7621 7622 7623 7624 7625

Urand<47> rnd; for(size_t i = 0; i < 15; ++i) is.push_back(rnd()); for(size_t i = 0; i < is.size(); ++i) cout << is[i] << ' '; cout << endl; is.sort(); for(size_t i = 0; i < is.size(); ++i) cout << is[i] << ' '; cout << endl;

// Uses the template partial specialization: Sortable<string*> ss; for(size_t i = 0; i < asz(words); ++i) ss.push_back(new string(words[i])); for(size_t i = 0; i < ss.size(); ++i) cout << *ss[i] << ' '; cout << endl; ss.sort(); for(size_t i = 0; i < ss.size(); ++i) { cout << *ss[i] << ' '; delete ss[i]; } cout << endl;

287

7626 7627 7628 7629 7630 7631 7632 7633 7634 7635 7636 7637

// Uses the full char* specialization: Sortable<char*> scp; for(size_t i = 0; i < asz(words2); ++i) scp.push_back(words2[i]); for(size_t i = 0; i < scp.size(); ++i) cout << scp[i] << ' '; cout << endl; scp.sort(); for(size_t i = 0; i < scp.size(); ++i) cout << scp[i] << ' '; cout << endl; } ///:~

7638 7639 7640

Listado 6.30. C05/Sortable.cpp

6.3.4.
//: C05:DelayedInstantiation.cpp // Member functions of class templates are not // instantiated until they're needed.

7641 7642 7643 7644 7645 7646 7647 7648

class X { public: void f() {} };

288

7649 7650 7651 7652 7653 7654 7655 7656 7657 7658 7659 7660 7661 7662 7663 7664 7665 7666 7667
int main() { Z<X> zx; zx.a(); // Doesn't create Z<X>::b() Z<Y> zy; zy.b(); // Doesn't create Z<Y>::a() } ///:~ template<typename T> class Z { T t; public: void a() { t.f(); } void b() { t.g(); } }; class Y { public: void g() {} };

7668 7669

Listado 6.31. C05/DelayedInstantiation.cpp

7670 7671 7672

//: C05:Nobloat.h // Shares code for storing pointers in a Stack. #ifndef NOBLOAT_H

289

7673 7674 7675 7676 7677 7678 7679 7680 7681 7682 7683 7684 7685 7686 7687 7688 7689 7690 7691 7692 7693 7694 7695 7696 7697

#define NOBLOAT_H #include <cassert> #include <cstddef> #include <cstring>

// The primary template template<class T> class Stack { T* data; std::size_t count; std::size_t capacity; enum { INIT = 5 }; public: Stack() { count = 0; capacity = INIT; data = new T[INIT]; } void push(const T& t) { if(count == capacity) { // Grow array store std::size_t newCapacity = 2 * capacity; T* newData = new T[newCapacity]; for(size_t i = 0; i < count; ++i) newData[i] = data[i]; delete [] data;

290

7698 7699 7700 7701 7702 7703 7704 7705 7706 7707 7708 7709 7710 7711 7712 7713 7714 7715 7716 7717 7718 7719 7720 7721 7722

data = newData; capacity = newCapacity; } assert(count < capacity); data[count++] = t; } void pop() { assert(count > 0); --count; } T top() const { assert(count > 0); return data[count-1]; } std::size_t size() const { return count; } };

// Full specialization for void* template<> class Stack<void *> { void** data; std::size_t count; std::size_t capacity; enum { INIT = 5 }; public: Stack() {

291

7723 7724 7725 7726 7727 7728 7729 7730 7731 7732 7733 7734 7735 7736 7737 7738 7739 7740 7741 7742 7743 7744 7745 7746 7747

count = 0; capacity = INIT; data = new void*[INIT]; } void push(void* const & t) { if(count == capacity) { std::size_t newCapacity = 2*capacity; void** newData = new void*[newCapacity]; std::memcpy(newData, data, count*sizeof(void*)); delete [] data; data = newData; capacity = newCapacity; } assert(count < capacity); data[count++] = t; } void pop() { assert(count > 0); --count; } void* top() const { assert(count > 0); return data[count-1]; } std::size_t size() const { return count; }

292

7748 7749 7750 7751 7752 7753 7754 7755 7756 7757 7758 7759

};

// Partial specialization for other pointer types template<class T> class Stack<T*> : private Stack<void *> { typedef Stack<void *> Base; public: void push(T* const & t) { Base::push(t); } void pop() {Base::pop();} T* top() const { return static_cast<T*>(Base::top()); } std::size_t size() { return Base::size(); } }; #endif // NOBLOAT_H ///:~

7760 7761

Listado 6.32. C05/Nobloat.h

7762 7763 7764 7765 7766 7767 7768 7769 7770 7771

//: C05:NobloatTest.cpp #include <iostream> #include <string> #include "Nobloat.h" using namespace std;

template<class StackType> void emptyTheStack(StackType& stk) { while(stk.size() > 0) { cout << stk.top() << endl;

293

7772 7773 7774 7775 7776 7777 7778 7779 7780 7781 7782 7783 7784 7785 7786 7787 7788 7789 7790 7791 7792 7793 7794 7795 7796

stk.pop(); } }

// An overload for emptyTheStack (not a specialization!) template<class T> void emptyTheStack(Stack<T*>& stk) { while(stk.size() > 0) { cout << *stk.top() << endl; stk.pop(); } }

int main() { Stack<int> s1; s1.push(1); s1.push(2); emptyTheStack(s1); Stack<int *> s2; int i = 3; int j = 4; s2.push(&i); s2.push(&j); emptyTheStack(s2); } ///:~

294
7797 7798 Listado 6.33. C05/NobloatTest.cpp

7799 7800

6.4.
6.4.1.
MyClass::f(); x.f(); p->f(); #include <iostream> #include <string> // ... std::string s("hello"); std::cout << s << std::endl; std::operator<<(std::operator<<(std::cout,s),std::endl); operator<<(std::cout, s); (f)(x, y); // ADL suppressed

7801 7802 7803 7804 7805 7806 7807 7808 7809 7810 7811

7812 7813 7814 7815 7816 7817 7818 7819

//: C05:Lookup.cpp // Only produces correct behavior with EDG, // and Metrowerks using a special option. #include <iostream> using std::cout; using std::endl;

void f(double) { cout << "f(double)" << endl; }

295

7820 7821 7822 7823 7824 7825 7826 7827 7828 7829 7830
int main() { X<int>().g(); } ///:~ void f(int) { cout << "f(int)" << endl; } template<class T> class X { public: void g() { f(1); } };

7831 7832

Listado 6.34. C05/Lookup.cpp

7833

f(double)

7834 7835 7836 7837 7838 7839 7840 7841 7842

//: C05:Lookup2.cpp {-bor}{-g++}{-dmc} // Microsoft: use option -Za (ANSI mode) #include <algorithm> #include <iostream> #include <typeinfo> using std::cout; using std::endl;

void g() { cout << "global g()" << endl; }

296

7843 7844 7845 7846 7847 7848 7849 7850 7851 7852 7853 7854 7855 7856 7857 7858 7859 7860 7861 7862 7863 7864 7865 7866 7867
template<class T> class X : public Y<T> { public: E f() { g(); template<class T> void swap(T& t1, T& t2) { cout << "global swap" << endl; T temp = t1; t1 = t2; t2 = temp; } typedef double E; template<class T> class Y { public: void g() { cout << "Y<" << typeid(T).name() << ">::g()" << endl; } void h() { cout << "Y<" << typeid(T).name() << ">::h()" << endl; } typedef int E; };

297

7868 7869 7870 7871 7872 7873 7874 7875 7876 7877 7878 7879 7880 7881

this->h(); T t1 = T(), t2 = T(1); cout << t1 << endl; swap(t1, t2); std::swap(t1, t2); cout << typeid(E).name() << endl; return E(t2); } };

int main() { X<int> x; cout << x.f() << endl; } ///:~

7882 7883 7884 7885 7886 7887 7888 7889

Listado 6.35. C05/Lookup2.cpp

global g() Y<int>::h() 0 global swap double 1

7890

6.4.2.
//: C05:FriendScope.cpp #include <iostream> using namespace std;

7891 7892 7893

298

7894 7895 7896 7897 7898 7899 7900 7901 7902 7903 7904 7905 7906 7907 7908 7909 7910 7911 7912 7913 7914
int main() { h(); // Prints 1 Friendly(2).g(); // Prints 2 } ///:~ void f(const Friendly& fo) { // Definition of friend void h() { f(Friendly(1)); } // Uses ADL class Friendly { int i; public: Friendly(int theInt) { i = theInt; } friend void f(const Friendly&); // Needs global def. void g() { f(*this); } };

cout << fo.i << endl; }

7915 7916

Listado 6.36. C05/FriendScope.cpp

7917

//: C05:FriendScope2.cpp

299

7918 7919 7920 7921 7922 7923 7924 7925 7926 7927 7928 7929 7930 7931 7932 7933 7934 7935 7936 7937 7938 7939 7940 7941 7942

#include <iostream> using namespace std;

// Necessary forward declarations: template<class T> class Friendly; template<class T> void f(const Friendly<T>&);

template<class T> class Friendly { T t; public: Friendly(const T& theT) : t(theT) {} friend void f<>(const Friendly<T>&); void g() { f(*this); } };

void h() { f(Friendly<int>(1)); }

template<class T> void f(const Friendly<T>& fo) { cout << fo.t << endl; }

int main() { h();

300

7943 7944

Friendly<int>(2).g(); } ///:~

7945 7946

Listado 6.37. C05/FriendScope2.cpp

7947 7948 7949 7950 7951 7952 7953 7954 7955 7956 7957 7958 7959 7960 7961 7962 7963 7964 7965 7966

//: C05:FriendScope3.cpp {-bor} // Microsoft: use the -Za (ANSI-compliant) option #include <iostream> using namespace std;

template<class T> class Friendly { T t; public: Friendly(const T& theT) : t(theT) {} friend void f(const Friendly<T>& fo) { cout << fo.t << endl; } void g() { f(*this); } };

void h() { f(Friendly<int>(1)); }

int main() {

301

7967 7968 7969

h(); Friendly<int>(2).g(); } ///:~

7970 7971

Listado 6.38. C05/FriendScope3.cpp

7972 7973 7974 7975 7976

template<class T> class Box { T t; public: Box(const T& theT) : t(theT) {} };

7977 7978 7979 7980 7981 7982 7983 7984 7985 7986 7987 7988 7989

//: C05:Box1.cpp // Defines template operators. #include <iostream> using namespace std;

// Forward declarations template<class T> class Box;

template<class T> Box<T> operator+(const Box<T>&, const Box<T>&);

template<class T> ostream& operator<<(ostream&, const Box<T>&);

302

7990 7991 7992 7993 7994 7995 7996 7997 7998 7999 8000 8001 8002 8003 8004 8005 8006 8007 8008 8009 8010 8011 8012 8013
// int main() { Box<int> b1(1), b2(2); cout << b1 + b2 << endl; // [3] template<class T> ostream& operator<<(ostream& os, const Box<T>& b) { return os << '[' << b.t << ']'; } template<class T> Box<T> operator+(const Box<T>& b1, const Box<T>& b2) { return Box<T>(b1.t + b2.t); } template<class T> class Box { T t; public: Box(const T& theT) : t(theT) {} friend Box operator+<>(const Box<T>&, const Box<T>&); friend ostream& operator<< <>(ostream&, const Box<T>&); };

cout << b1 + 2 << endl; // No implicit conversions! } ///:~

303
8014 8015 Listado 6.39. C05/Box1.cpp

8016 8017 8018 8019 8020 8021 8022 8023 8024 8025 8026 8027 8028 8029 8030 8031 8032 8033 8034 8035 8036 8037 8038

//: C05:Box2.cpp // Defines non-template operators. #include <iostream> using namespace std;

template<class T> class Box { T t; public: Box(const T& theT) : t(theT) {} friend Box<T> operator+(const Box<T>& b1, const Box<T>& b2) { return Box<T>(b1.t + b2.t); } friend ostream& operator<<(ostream& os, const Box<T>& b) { return os << '[' << b.t << ']'; } };

int main() { Box<int> b1(1), b2(2); cout << b1 + b2 << endl; // [3] cout << b1 + 2 << endl; // [3]

304

8039

} ///:~

8040 8041

Listado 6.40. C05/Box2.cpp

8042 8043 8044 8045 8046 8047

// Inside Friendly: friend void f<>(const Friendly<double>&); // Inside Friendly: friend void g(int); // g(int) befriends all Friendlys template<class T> class Friendly { template<class U> friend void f<>(const Friendly<U>&);

8048 8049

6.5.
6.5.1.
template<class T> class numeric_limits { public: static const bool is_specialized = false; static T min() throw(); static T max() throw(); static const int digits = 0; static const int digits10 = 0; static const bool is_signed = false; static const bool is_integer = false; static const bool is_exact = false; static const int radix = 0;

8050 8051 8052 8053 8054 8055 8056 8057 8058 8059 8060

305

8061 8062 8063 8064 8065 8066 8067 8068 8069 8070 8071 8072 8073 8074 8075 8076 8077 8078 8079 8080 8081 8082 8083 8084 8085

static T epsilon() throw(); static T round_error() throw(); static const int min_exponent = 0; static const int min_exponent10 = 0; static const int max_exponent = 0; static const int max_exponent10 = 0; static const bool has_infinity = false; static const bool has_quiet_NaN = false; static const bool has_signaling_NaN = false; static const float_denorm_style has_denorm = denorm_absent; static const bool has_denorm_loss = false; static T infinity() throw(); static T quiet_NaN() throw(); static T signaling_NaN() throw(); static T denorm_min() throw(); static const bool is_iec559 = false; static const bool is_bounded = false; static const bool is_modulo = false; static const bool traps = false; static const bool tinyness_before = false; static const float_round_style round_style = round_toward_zero; }; template<class charT,

306

8086 8087 8088 8089 8090 8091 8092 8093 8094 8095 8096 8097 8098 8099 8100 8101 8102 8103 8104 8105 8106 8107 8108 8109 8110

class traits = char_traits<charT>, class allocator = allocator<charT> > class basic_string; template<> struct char_traits<char> { typedef char char_type; typedef int int_type; typedef streamoff off_type; typedef streampos pos_type; typedef mbstate_t state_type; static void assign(char_type& c1, const char_type& c2); static bool eq(const char_type& c1, const char_type& c2); static bool lt(const char_type& c1, const char_type& c2); static int compare(const char_type* s1, const char_type* s2, size_t n); static size_t length(const char_type* s); static const char_type* find(const char_type* s, size_t n, const char_type& a); static char_type* move(char_type* s1, const char_type* s2, size_t n); static char_type* copy(char_type* s1, const char_type* s2, size_t n); static char_type* assign(char_type* s, size_t n, char_type a); static int_type not_eof(const int_type& c);

307

8111 8112 8113 8114 8115 8116 8117 8118 8119

static char_type to_char_type(const int_type& c); static int_type to_int_type(const char_type& c); static bool eq_int_type(const int_type& c1, const int_type& c2); static int_type eof(); }; std::string s; std::basic_string<char, std::char_traits<char>, std::allocator<char> > s;

8120 8121 8122 8123 8124 8125 8126 8127 8128 8129 8130 8131 8132 8133 8134

//: C05:BearCorner.h #ifndef BEARCORNER_H #define BEARCORNER_H #include <iostream> using std::ostream;

// Item classes (traits of guests): class Milk { public: friend ostream& operator<<(ostream& os, const Milk&) { return os << "Milk"; } };

class CondensedMilk {

308

8135 8136 8137 8138 8139 8140 8141 8142 8143 8144 8145 8146 8147 8148 8149 8150 8151 8152 8153 8154 8155 8156 8157 8158 8159

public: friend ostream& operator<<(ostream& os, const CondensedMilk &) { return os << "Condensed Milk"; } };

class Honey { public: friend ostream& operator<<(ostream& os, const Honey&) { return os << "Honey"; } };

class Cookies { public: friend ostream& operator<<(ostream& os, const Cookies&) { return os << "Cookies"; } };

// Guest classes: class Bear { public: friend ostream& operator<<(ostream& os, const Bear&) {

309

8160 8161 8162 8163 8164 8165 8166 8167 8168 8169 8170 8171 8172 8173 8174 8175 8176 8177 8178 8179 8180 8181 8182 8183 8184

return os << "Theodore"; } };

class Boy { public: friend ostream& operator<<(ostream& os, const Boy&) { return os << "Patrick"; } };

// Primary traits template (empty-could hold common types) template<class Guest> class GuestTraits;

// Traits specializations for Guest types template<> class GuestTraits<Bear> { public: typedef CondensedMilk beverage_type; typedef Honey snack_type; };

template<> class GuestTraits<Boy> { public: typedef Milk beverage_type; typedef Cookies snack_type;

310

8185 8186

}; #endif // BEARCORNER_H ///:~

8187 8188 8189

Listado 6.41. C05/BearCorner.h

6.5.2.
template<> struct char_traits<wchar_t> { typedef wchar_t char_type; typedef wint_t int_type; typedef streamoff off_type; typedef wstreampos pos_type; typedef mbstate_t state_type; static void assign(char_type& c1, const char_type& c2); static bool eq(const char_type& c1, const char_type& c2); static bool lt(const char_type& c1, const char_type& c2); static int compare(const char_type* s1, const char_type* s2, size_t n);

8190 8191 8192 8193 8194 8195 8196 8197 8198 8199 8200 8201 8202 8203 8204 8205 8206 8207

static size_t length(const char_type* s); static const char_type* find(const char_type* s, size_t n, const char_type& a); static char_type* move(char_type* s1, const char_type* s2, size_t n); static char_type* copy(char_type* s1,

311

8208 8209 8210 8211 8212 8213 8214 8215 8216 8217

const char_type* s2, size_t n); static char_type* assign(char_type* s, size_t n, char_type a); static int_type not_eof(const int_type& c); static char_type to_char_type(const int_type& c); static int_type to_int_type(const char_type& c); static bool eq_int_type(const int_type& c1, const int_type& c2); static int_type eof(); };

8218 8219 8220 8221 8222 8223 8224 8225 8226 8227 8228 8229 8230 8231

//: C05:BearCorner2.cpp // Illustrates policy classes. #include <iostream> #include "BearCorner.h" using namespace std;

// Policy classes (require a static doAction() function): class Feed { public: static const char* doAction() { return "Feeding"; } };

class Stuff { public:

312

8232 8233 8234 8235 8236 8237 8238 8239 8240 8241 8242 8243 8244 8245 8246 8247 8248 8249 8250 8251 8252 8253 8254 8255 8256

static const char* doAction() { return "Stuffing"; } };

// The Guest template (uses a policy and a traits class) template<class Guest, class Action, class traits = GuestTraits<Guest> > class BearCorner { Guest theGuest; typedef typename traits::beverage_type beverage_type; typedef typename traits::snack_type snack_type; beverage_type bev; snack_type snack; public: BearCorner(const Guest& g) : theGuest(g) {} void entertain() { cout << Action::doAction() << " " << theGuest << " with " << bev << " and " << snack << endl; } };

int main() { Boy cr; BearCorner<Boy, Feed> pc1(cr); pc1.entertain();

313

8257 8258 8259 8260

Bear pb; BearCorner<Bear, Stuff> pc2(pb); pc2.entertain(); } ///:~

8261 8262 8263

Listado 6.42. C05/BearCorner2.cpp

6.5.3.
//: C05:CountedClass.cpp // Object counting via static members. #include <iostream> using namespace std;

8264 8265 8266 8267 8268 8269 8270 8271 8272 8273 8274 8275 8276 8277 8278 8279

class CountedClass { static int count; public: CountedClass() { ++count; } CountedClass(const CountedClass&) { ++count; } ~CountedClass() { --count; } static int getCount() { return count; } };

int CountedClass::count = 0;

314

8280 8281 8282 8283 8284 8285 8286 8287 8288 8289 8290 8291 8292

int main() { CountedClass a; cout << CountedClass::getCount() << endl; CountedClass b; cout << CountedClass::getCount() << endl; { // An arbitrary scope: CountedClass c(b); cout << CountedClass::getCount() << endl; // 3 a = c; cout << CountedClass::getCount() << endl; // 3 } cout << CountedClass::getCount() << endl; } ///:~ // 2 // 2 // 1

8293 8294

Listado 6.43. C05/CountedClass.cpp

8295 8296 8297 8298 8299 8300 8301 8302 8303

//: C05:CountedClass2.cpp // Erroneous attempt to count objects. #include <iostream> using namespace std;

class Counted { static int count; public: Counted() { ++count; }

315

8304 8305 8306 8307 8308 8309 8310 8311 8312 8313 8314 8315 8316 8317 8318 8319 8320 8321

Counted(const Counted&) { ++count; } ~Counted() { --count; } static int getCount() { return count; } };

int Counted::count = 0;

class CountedClass : public Counted {}; class CountedClass2 : public Counted {};

int main() { CountedClass a; cout << CountedClass::getCount() << endl; CountedClass b; cout << CountedClass::getCount() << endl; CountedClass2 c; cout << CountedClass2::getCount() << endl; } ///:~ // 3 (Error) // 2 // 1

8322 8323

Listado 6.44. C05/CountedClass2.cpp

8324 8325 8326 8327

//: C05:CountedClass3.cpp #include <iostream> using namespace std;

316

8328 8329 8330 8331 8332 8333 8334 8335 8336 8337 8338 8339 8340 8341 8342 8343 8344 8345 8346 8347 8348 8349 8350

template<class T> class Counted { static int count; public: Counted() { ++count; } Counted(const Counted<T>&) { ++count; } ~Counted() { --count; } static int getCount() { return count; } };

template<class T> int Counted<T>::count = 0;

// Curious class definitions class CountedClass : public Counted<CountedClass> {}; class CountedClass2 : public Counted<CountedClass2> {};

int main() { CountedClass a; cout << CountedClass::getCount() << endl; CountedClass b; cout << CountedClass::getCount() << endl; CountedClass2 c; cout << CountedClass2::getCount() << endl; } ///:~ // 1 (!) // 2 // 1

8351

Listado 6.45. C05/CountedClass3.cpp

317
8352

8353

6.6.
//: C05:Factorial.cpp // Compile-time computation using templates. #include <iostream> using namespace std;

8354 8355 8356 8357 8358 8359 8360 8361 8362 8363 8364 8365 8366 8367 8368 8369

template<int n> struct Factorial { enum { val = Factorial<n-1>::val * n }; };

template<> struct Factorial<0> { enum { val = 1 }; };

int main() { cout << Factorial<12>::val << endl; // 479001600 } ///:~

8370 8371

Listado 6.46. C05/Factorial.cpp

8372 8373

double nums[Factorial<5>::val]; assert(sizeof nums == sizeof(double)*120);

318
8374

6.6.1.
//: C05:Fibonacci.cpp #include <iostream> using namespace std;

8375 8376 8377 8378 8379 8380 8381 8382 8383 8384 8385 8386 8387 8388 8389 8390

template<int n> struct Fib { enum { val = Fib<n-1>::val + Fib<n-2>::val }; };

template<> struct Fib<1> { enum { val = 1 }; };

template<> struct Fib<0> { enum { val = 0 }; };

int main() { cout << Fib<5>::val << endl; cout << Fib<20>::val << endl; } ///:~ // 6 // 6765

8391 8392

Listado 6.47. C05/Fibonacci.cpp

8393 8394 8395 8396 8397

int val = 1; while(p--) val *= n; --> int power(int n, int p) { return (p == 0) ? 1 : n*power(n, p - 1);

319

8398

8399 8400 8401 8402 8403 8404 8405 8406 8407 8408 8409 8410 8411 8412 8413

//: C05:Power.cpp #include <iostream> using namespace std;

template<int N, int P> struct Power { enum { val = N * Power<N, P-1>::val }; };

template<int N> struct Power<N, 0> { enum { val = 1 }; };

int main() { cout << Power<2, 5>::val << endl; } ///:~ // 32

8414 8415

Listado 6.48. C05/Power.cpp

8416 8417 8418 8419 8420

//: C05:Accumulate.cpp // Passes a "function" as a parameter at compile time. #include <iostream> using namespace std;

320

8421 8422 8423 8424 8425 8426 8427 8428 8429 8430 8431 8432 8433 8434 8435 8436 8437 8438 8439 8440 8441 8442 8443 8444 8445

// Accumulates the results of F(0)..F(n) template<int n, template<int> class F> struct Accumulate { enum { val = Accumulate<n-1, F>::val + F<n>::val }; };

// The stopping criterion (returns the value F(0)) template<template<int> class F> struct Accumulate<0, F> { enum { val = F<0>::val }; };

// Various "functions": template<int n> struct Identity { enum { val = n }; };

template<int n> struct Square { enum { val = n*n }; };

template<int n> struct Cube { enum { val = n*n*n }; };

int main() { cout << Accumulate<4, Identity>::val << endl; // 10

321

8446 8447 8448

cout << Accumulate<4, Square>::val << endl; cout << Accumulate<4, Cube>::val << endl; } ///:~

// 30 // 100

8449 8450

Listado 6.49. C05/Accumulate.cpp

8451 8452 8453 8454 8455 8456 8457 8458 8459 8460 8461 8462 8463 8464

void mult(int a[ROWS][COLS], int x[COLS], int y[COLS]) { for(int i = 0; i < ROWS; ++i) { y[i] = 0; for(int j = 0; j < COLS; ++j) y[i] += a[i][j]*x[j]; } } void mult(int a[ROWS][COLS], int x[COLS], int y[COLS]) { for(int i = 0; i < ROWS; ++i) { y[i] = 0; for(int j = 0; j < COLS; j += 2) y[i] += a[i][j]*x[j] + a[i][j+1]*x[j+1]; } }

8465 8466 8467 8468

//: C05:Unroll.cpp // Unrolls an implicit loop via inlining. #include <iostream> using namespace std;

322

8469 8470 8471 8472 8473 8474 8475 8476 8477 8478 8479 8480 8481 8482 8483 8484 8485
int main() { int m = 4; cout << power<3>(m) << endl; } ///:~ template<> inline int power<0>(int m) { return 1; } template<> inline int power<1>(int m) { return m; } template<int n> inline int power(int m) { return power<n-1>(m) * m; }

8486 8487

Listado 6.50. C05/Unroll.cpp

8488 8489 8490 8491 8492

//: C05:Max.cpp #include <iostream> using namespace std;

template<int n1, int n2> struct Max {

323

8493 8494 8495 8496 8497 8498

enum { val = n1 > n2 ? n1 : n2 }; };

int main() { cout << Max<10, 20>::val << endl; } ///:~ // 20

8499 8500

Listado 6.51. C05/Max.cpp

8501 8502 8503 8504 8505 8506 8507 8508 8509 8510 8511 8512 8513 8514 8515 8516

//: C05:Conditionals.cpp // Uses compile-time conditions to choose code. #include <iostream> using namespace std;

template<bool cond> struct Select {};

template<> class Select<true> { static void statement1() { cout << "This is statement1 executing\n"; } public: static void f() { statement1(); } };

template<> class Select<false> {

324

8517 8518 8519 8520 8521 8522 8523 8524 8525 8526 8527 8528 8529 8530

static void statement2() { cout << "This is statement2 executing\n"; } public: static void f() { statement2(); } };

template<bool cond> void execute() { Select<cond>::f(); }

int main() { execute<sizeof(int) == 4>(); } ///:~

8531 8532

Listado 6.52. C05/Conditionals.cpp

8533 8534 8535 8536

if(cond) statement1(); else statement2();

8537 8538

//: C05:StaticAssert1.cpp {-xo} // A simple, compile-time assertion facility

325

8539 8540 8541 8542 8543 8544 8545 8546


int main() { STATIC_ASSERT(sizeof(int) <= sizeof(long)); // Passes STATIC_ASSERT(sizeof(double) <= sizeof(int)); // Fails } ///:~ #define STATIC_ASSERT(x) \ do { typedef int a[(x) ? 1 : -1]; } while(0)

8547 8548

Listado 6.53. C05/StaticAssert1.cpp

8549 8550 8551 8552 8553 8554 8555 8556 8557 8558 8559 8560 8561 8562

//: C05:StaticAssert2.cpp {-g++} #include <iostream> using namespace std;

// A template and a specialization template<bool> struct StaticCheck { StaticCheck(...); };

template<> struct StaticCheck<false> {};

// The macro (generates a local class) #define STATIC_CHECK(expr, msg) { class Error_##msg {}; \ \

326

8563 8564 8565 8566 8567 8568 8569 8570 8571 8572 8573 8574 8575 8576 8577 8578

sizeof((StaticCheck<expr>(Error_##msg()))); \ }

// Detects narrowing conversions template<class To, class From> To safe_cast(From from) { STATIC_CHECK(sizeof(From) <= sizeof(To), NarrowingConversion); return reinterpret_cast<To>(from); }

int main() { void* p = 0; int i = safe_cast<int>(p); cout << "int cast okay" << endl; //! char c = safe_cast<char>(p); } ///:~

8579 8580

Listado 6.54. C05/StaticAssert2.cpp

8581 8582 8583 8584 8585 8586


{

int i = safe_cast<int>(p); \ class Error_NarrowingConversion {}; \

sizeof(StaticCheck<sizeof(void*) <= sizeof(int)> \ (Error_NarrowingConversion())); } \

327

8587 8588 8589 8590 8591 8592 8593


{

char c = safe_cast<char>(p); \ class Error_NarrowingConversion {}; \

sizeof(StaticCheck<sizeof(void*) <= sizeof(char)> \ (Error_NarrowingConversion())); } sizeof(StaticCheck<false>(Error_NarrowingConversion())); \

8594 8595 8596

Cannot cast from 'Error_NarrowingConversion' to 'StaticCheck<0>' in function char safe_cast<char,void *>(void *)

8597

6.6.2.
D = A + B + C;

8598

8599 8600 8601 8602 8603 8604 8605 8606 8607 8608 8609

//: C05:MyVector.cpp // Optimizes away temporaries via templates. #include <cstddef> #include <cstdlib> #include <ctime> #include <iostream> using namespace std;

// A proxy class for sums of vectors template<class, size_t> class MyVectorSum;

328

8610 8611 8612 8613 8614 8615 8616 8617 8618 8619 8620 8621 8622 8623 8624 8625 8626 8627 8628 8629 8630 8631 8632 8633 8634

template<class T, size_t N> class MyVector { T data[N]; public: MyVector<T,N>& operator=(const MyVector<T,N>& right) { for(size_t i = 0; i < N; ++i) data[i] = right.data[i]; return *this; } MyVector<T,N>& operator=(const MyVectorSum<T,N>& right); const T& operator[](size_t i) const { return data[i]; } T& operator[](size_t i) { return data[i]; } };

// Proxy class hold references; uses lazy addition template<class T, size_t N> class MyVectorSum { const MyVector<T,N>& left; const MyVector<T,N>& right; public: MyVectorSum(const MyVector<T,N>& lhs, const MyVector<T,N>& rhs) : left(lhs), right(rhs) {} T operator[](size_t i) const { return left[i] + right[i]; } };

329

8635 8636 8637 8638 8639 8640 8641 8642 8643 8644 8645 8646 8647 8648 8649 8650 8651 8652 8653 8654 8655 8656 8657 8658 8659
template<class T, size_t N> void print(MyVector<T,N>& v) { for(size_t i = 0; i < N; ++i) cout << v[i] << ' '; // Convenience functions for the test program below template<class T, size_t N> void init(MyVector<T,N>& v) { for(size_t i = 0; i < N; ++i) v[i] = rand() % 100; } // operator+ just stores references template<class T, size_t N> inline MyVectorSum<T,N> operator+(const MyVector<T,N>& left, const MyVector<T,N>& right) { return MyVectorSum<T,N>(left, right); } // Operator to support v3 = v1 + v2 template<class T, size_t N> MyVector<T,N>& MyVector<T,N>::operator=(const MyVectorSum<T,N>& right) { for(size_t i = 0; i < N; ++i) data[i] = right[i]; return *this; }

330

8660 8661 8662 8663 8664 8665 8666 8667 8668 8669 8670 8671 8672 8673 8674 8675 8676 8677

cout << endl; }

int main() { srand(time(0)); MyVector<int, 5> v1; init(v1); print(v1); MyVector<int, 5> v2; init(v2); print(v2); MyVector<int, 5> v3; v3 = v1 + v2; print(v3); MyVector<int, 5> v4; // Not yet supported: //! v4 = v1 + v2 + v3; } ///:~

8678 8679

Listado 6.55. C05/MyVector.cpp

8680 8681 8682

v1 = v2 + v3; // Add two vectors v3.operator=<int,5>(MyVectorSum<int,5>(v2, v3)); v4 = v1 + v2 + v3;

331

8683

(v1 + v2) + v3;

8684 8685 8686 8687 8688 8689 8690 8691 8692 8693 8694 8695 8696 8697 8698 8699 8700 8701 8702 8703 8704 8705 8706

//: C05:MyVector2.cpp // Handles sums of any length with expression templates. #include <cstddef> #include <cstdlib> #include <ctime> #include <iostream> using namespace std;

// A proxy class for sums of vectors template<class, size_t, class, class> class MyVectorSum;

template<class T, size_t N> class MyVector { T data[N]; public: MyVector<T,N>& operator=(const MyVector<T,N>& right) { for(size_t i = 0; i < N; ++i) data[i] = right.data[i]; return *this; } template<class Left, class Right> MyVector<T,N>& operator=(const MyVectorSum<T,N,Left,Right>& right); const T& operator[](size_t i) const { return data[i];

332

8707 8708 8709 8710 8711 8712 8713 8714 8715 8716 8717 8718 8719 8720 8721 8722 8723 8724 8725 8726 8727 8728 8729 8730 8731

} T& operator[](size_t i) { return data[i]; } };

// Allows mixing MyVector and MyVectorSum template<class T, size_t N, class Left, class Right> class MyVectorSum { const Left& left; const Right& right; public: MyVectorSum(const Left& lhs, const Right& rhs) : left(lhs), right(rhs) {} T operator[](size_t i) const { return left[i] + right[i]; } };

template<class T, size_t N> template<class Left, class Right> MyVector<T,N>& MyVector<T,N>:: operator=(const MyVectorSum<T,N,Left,Right>& right) { for(size_t i = 0; i < N; ++i)

333

8732 8733 8734 8735 8736 8737 8738 8739 8740 8741 8742 8743 8744 8745 8746 8747 8748 8749 8750 8751 8752 8753 8754 8755 8756

data[i] = right[i]; return *this; } // operator+ just stores references template<class T, size_t N> inline MyVectorSum<T,N,MyVector<T,N>,MyVector<T,N> > operator+(const MyVector<T,N>& left, const MyVector<T,N>& right) { return MyVectorSum<T,N,MyVector<T,N>,MyVector<T,N> > (left,right); }

template<class T, size_t N, class Left, class Right> inline MyVectorSum<T, N, MyVectorSum<T,N,Left,Right>, MyVector<T,N> > operator+(const MyVectorSum<T,N,Left,Right>& left, const MyVector<T,N>& right) { return MyVectorSum<T,N,MyVectorSum<T,N,Left,Right>, MyVector<T,N> > (left, right); } // Convenience functions for the test program below template<class T, size_t N> void init(MyVector<T,N>& v) { for(size_t i = 0; i < N; ++i) v[i] = rand() % 100;

334

8757 8758 8759 8760 8761 8762 8763 8764 8765 8766 8767 8768 8769 8770 8771 8772 8773 8774 8775 8776 8777 8778 8779 8780 8781

template<class T, size_t N> void print(MyVector<T,N>& v) { for(size_t i = 0; i < N; ++i) cout << v[i] << ' '; cout << endl; }

int main() { srand(time(0)); MyVector<int, 5> v1; init(v1); print(v1); MyVector<int, 5> v2; init(v2); print(v2); MyVector<int, 5> v3; v3 = v1 + v2; print(v3); // Now supported: MyVector<int, 5> v4; v4 = v1 + v2 + v3; print(v4); MyVector<int, 5> v5; v5 = v1 + v2 + v3 + v4;

335

8782 8783

print(v5); } ///:~

8784 8785

Listado 6.56. C05/MyVector2.cpp

8786 8787

v4 = v1 + v2 + v3; v4.operator+(MVS(MVS(v1, v2), v3));

8788 8789 8790

6.7.
6.7.1. 6.7.2.
//: C05:OurMin.h #ifndef OURMIN_H #define OURMIN_H // The declaration of min() template<typename T> const T& min(const T&, const T&); #endif // OURMIN_H ///:~

8791 8792 8793 8794 8795 8796

8797 8798

Listado 6.57. C05/OurMin.h

8799 8800 8801

//: C05:MinInstances.cpp {O} #include "OurMin.cpp"

336

8802 8803 8804 8805 8806

// Explicit Instantiations for int and double template const int& min<int>(const int&, const int&); template const double& min<double>(const double&, const double&); ///:~

8807 8808

Listado 6.58. C05/MinInstances.cpp

8809 8810 8811 8812 8813 8814 8815 8816 8817

//: C05:OurMin.cpp {O} #ifndef OURMIN_CPP #define OURMIN_CPP #include "OurMin.h"

template<typename T> const T& min(const T& a, const T& b) { return (a < b) ? a : b; } #endif // OURMIN_CPP ///:~

8818 8819 8820 8821

Listado 6.59. C05/OurMin.cpp

1 3.1

8822

6.7.3.
//: C05:OurMin2.h // Declares min as an exported template

8823 8824

337

8825 8826 8827 8828 8829 8830

// (Only works with EDG-based compilers) #ifndef OURMIN2_H #define OURMIN2_H export template<typename T> const T& min(const T&, const T&); #endif // OURMIN2_H ///:~

8831 8832

Listado 6.60. C05/OurMin2.h

8833 8834 8835 8836 8837 8838 8839 8840

// C05:OurMin2.cpp // The definition of the exported min template // (Only works with EDG-based compilers) #include "OurMin2.h" export template<typename T> const T& min(const T& a, const T& b) { return (a < b) ? a : b; } ///:~

8841 8842 8843

Listado 6.61. C05/OurMin2.cpp

6.7.4.

8844

6.8.
//: C05:Exercise4.cpp {-xo}

8845

338

8846 8847 8848 8849 8850 8851 8852 8853 8854 8855 8856 8857 8858 8859 8860 8861 8862 8863 8864 8865 8866

class Noncomparable {};

struct HardLogic { Noncomparable nc1, nc2; void compare() { return nc1 == nc2; // Compiler error } };

template<class T> struct SoftLogic { Noncomparable nc1, nc2; void noOp() {} void compare() { nc1 == nc2; } };

int main() { SoftLogic<Noncomparable> l; l.noOp(); } ///:~

8867 8868

Listado 6.62. C05/Exercise4.cpp

8869

//: C05:Exercise7.cpp {-xo}

339

8870 8871 8872 8873 8874 8875 8876 8877 8878 8879 8880 8881 8882 8883 8884 8885

class Buddy {};

template<class T> class My { int i; public: void play(My<Buddy>& s) { s.i = 3; } };

int main() { My<int> h; My<Buddy> me, bud; h.play(bud); me.play(bud); } ///:~

8886 8887

Listado 6.63. C05/Exercise7.cpp

8888 8889 8890 8891 8892 8893

//: C05:Exercise8.cpp {-xo} template<class T> double pythag(T a, T b, T c) { return (-b + sqrt(double(b*b - 4*a*c))) / 2*a; }

int main() {

340

8894 8895 8896 8897 8898

pythag(1, 2, 3); pythag(1.0, 2.0, 3.0); pythag(1, 2.0, 3.0); pythag<double>(1, 2.0, 3.0); } ///:~

8899 8900

Listado 6.64. C05/Exercise8.cpp

8901 8902 8903 8904 8905 8906 8907 8908 8909 8910 8911 8912 8913 8914 8915 8916 8917 8918 8919 8920 8921 8922 8923

7: Algoritmos genricos
Tabla de contenidos 7.1. Un primer vistazo 7.2. Objetos-funcin 7.3. Un catlogo de algoritmos STL 7.4. Creando sus propios algoritmos tipo STL 7.5. Resumen 7.6. Ejercicios Los algoritmos son la base de la computacin. Ser capaz de escribir un algoritmo que funcione con cualquier tipo de se secuencia hace que sus programas sean simples y seguros. La habilidad para adaptar algoritmos en tiempo de ejecucin a revolucionado el desarrollo de software. El subconjunto de la Librera Estndar de C++ conocido como Standard Template Library (STL)[17] fue diseado entorno a algoritmos genricos cdigo que procesa secuencias de cualquier tipo de valores de un modo seguro. El objetivo era usar algoritmos predefinidos para casi cualquier tarea, en lugar de codificar a mano cada vez que se necesitara procesar una coleccin de datos. Sin embargo, ese potencial requiere cierto aprendizaje. Para cuando llegue al final de este captulo, debera ser capaz de decidir por s mismo si los algoritmos le resultan tiles o demasiado confusos de recordar. Si es como la mayora de la gente, se resistir al principio pero entonces tender a usarlos ms y ms con el tiempo.

7.1. Un primer vistazo

341
8924 8925 8926 8927 8928 8929 8930 8931 8932 8933 8934 8935 Entre otras cosas, los algoritmos genricos de la librera estndar proporcionan un vocabulario con el que desribir soluciones. Una vez que los algoritmos le sean familiares, tendr un nuevo conjunto de palabras con el que discutir que est haciendo, y esas palabras son de un nivel mayor que las que tena antes. No necesitar decir Este bucle recorre y asigna de aqu a ah... oh, ya veo, est copiando! En su lugar dir simplemente copy(). Esto es lo que hemos estado haciendo desde el principio de la programacin de computadores creando abstracciones de alto nivel para expresar lo que est haciendo y perder menos tiempo diciendo cmo hacerlo. El cmo se ha resuelto una vez y para todo y est oculto en el cdigo del algoritmo, listo para ser reutilizado cuando se necesite. Vea aqu un ejemplo de cmo utilizar el algoritmo copy:

8936 8937 8938 8939 8940 8941 8942 8943 8944 8945 8946 8947 8948 8949 8950

//: C06:CopyInts.cpp // Copies ints without an explicit loop. #include <algorithm> #include <cassert> #include <cstddef> // For size_t

using namespace std;

int main() { int a[] = { 10, 20, 30 }; const size_t SIZE = sizeof a / sizeof a[0]; int b[SIZE]; copy(a, a + SIZE, b); for(size_t i = 0; i < SIZE; ++i) assert(a[i] == b[i]); } ///:~

342
8951 8952 8953 8954 8955 8956 8957 8958 8959 8960 8961 8962 8963 8964 8965 8966 8967 Listado 7.1. C06/CopyInts.cpp Los dos primeros parmetros de copy representan el rango de la secuencia de entrada en este caso del array a. Los rangos se especifican con un par de punteros. El primero apunta al primer elemento de la secuencia, y el segungo apunta una posicin despus del final del array (justo despus del ltimo elemento). Esto puede parecer extrao al principio, pero es una antigua expresin idiomtica de C que resulta bastante prctica. Por ejemplo, la diferencia entre esos dos punteros devuelve el nmero de elementos de la secuencia. Ms importante, en la implementacin de copy(), el segundo puntero puede actual como un centinela para para la iteracin a travs de la secuencia. El tercer argumento hace referencia al comienzo de la secuencia de salida, que es el array b en el ejemplo. Se asume que el array b tiene suficiente espacio para recibir los elementos copiados. El algotirmo copy() no parece muy excitante if solo puediera procesar enteros. Puede copiar cualquier tipo de secuencia. El siguiente ejemplo copia objetos string.

8968 8969 8970 8971 8972 8973 8974 8975 8976 8977 8978 8979

//: C06:CopyStrings.cpp // Copies strings. #include <algorithm> #include <cassert> #include <cstddef> #include <string> using namespace std;

int main() { string a[] = {"read", "my", "lips"}; const size_t SIZE = sizeof a / sizeof a[0]; string b[SIZE];

343

8980 8981 8982

copy(a, a + SIZE, b); assert(equal(a, a + SIZE, b)); } ///:~

8983 8984 8985 8986 8987 8988 8989 8990 8991 8992

Listado 7.2. C06/CopyStrings.cpp Este ejmeplo presenta otro algoritmo, equal(), que devuelve cierto solo si cada elemento de la primera secuencia es igual (usando su operator==()) a su elemento correspondiente en la segunda secuencia. Este ejemplo recorre cada secuencia 2 veces, una para copiar, y otra para comparar, sin ningn bucle explcito. Los algoritmos genricos consiguen esta flexibilidad porque son funciones parametrizadas (plantillas). Si piensa en la implementacin de copy() ver que es algo como lo siguiente, que es casi correcto:

8993 8994 8995 8996 8997

template<typename T> void copy(T* begin, T* end, T* dest) { while (begin != end) *dest++ = *begin++; }

8998 8999 9000 9001

Decimos casi porque copy() puede procesar secuencias delimitadas por cualquier cosa que acte como un puntero, tal como un iterador. De ese modo, copy() se puede utilizar para duplicar un vector, como en el siguiente ejemplo.

9002 9003 9004

//: C06:CopyVector.cpp // Copies the contents of a vector. #include <algorithm>

344

9005 9006 9007 9008 9009 9010 9011 9012 9013 9014 9015 9016 9017

#include <cassert> #include <cstddef> #include <vector> using namespace std;

int main() { int a[] = { 10, 20, 30 }; const size_t SIZE = sizeof a / sizeof a[0]; vector<int> v1(a, a + SIZE); vector<int> v2(SIZE); copy(v1.begin(), v1.end(), v2.begin()); assert(equal(v1.begin(), v1.end(), v2.begin())); } ///:~

9018 9019 9020 9021 9022 9023 9024 9025 9026 9027 9028 9029 9030

Listado 7.3. C06/CopyVector.cpp El primer vector, v1, es inicializado a partir de una secuencia de enteros en el array a. La definicin del vector v2 usa un contructor diferente de vector que reserva sitio para SIZE elementos, inicializados a cero (el valor por defecto para enteros). Igual que con el ejemplo anterior con el array, es importante que v2 tenga suficiente espacio para recibir una copia de los contenidos de v1. Por conveniencia, una funcin de librera especial, back_inserter(), retorna un tipo especial de iterador que inserta elementos en lugar de sobre-escribirlos, de modo que la memoria del contenedor se expande conforme se necesita. El siguiente ejemplo usa back_inserter(), y por eso no hay que establecer el tamao del vector de salida, v2, antes de tiempo.

9031

//: C06:InsertVector.cpp

345

9032 9033 9034 9035 9036 9037 9038 9039 9040 9041 9042 9043 9044 9045 9046 9047

// Appends the contents of a vector to another. #include <algorithm> #include <cassert> #include <cstddef> #include <iterator> #include <vector> using namespace std;

int main() { int a[] = { 10, 20, 30 }; const size_t SIZE = sizeof a / sizeof a[0]; vector<int> v1(a, a + SIZE); vector<int> v2; // v2 is empty here

copy(v1.begin(), v1.end(), back_inserter(v2)); assert(equal(v1.begin(), v1.end(), v2.begin())); } ///:~

9048 9049 9050 9051 9052 9053 9054 9055 9056

Listado 7.4. C06/InsertVector.cpp La funcin back_inserter() est definida en el fichero de cabecera


<iterator>.

Explicaremos los iteradores de insercin en profundidad en el prximo captulo.

Dado que los iteradores son idnticos a punteros en todos los sentidos importantes, puede escribir los algoritmos de la librera estndar de modo que los argumentos puedan ser tanto punteros como iteradores. Por esta razn, la implementacin de copy() se parece ms al siguiente cdigo:

9057

template<typename Iterator>

346

9058 9059 9060 9061

void copy(Iterator begin, Iterator end, Iterator dest) { while (begin != end) *begin++ = *dest++; }

9062 9063 9064 9065 9066 9067 9068 9069 9070 9071 9072 9073

Para cualquier tipo de argumento que use en la llamada, copy() asume que implementa adecuadamente la indireccin y los operadores de incremento. Si no lo hace, obtendrs un error de compilacin.

7.1.1. Predicados
A veces, podra querer copiar solo un subconjunto bien definido de una secuencia a otra; solo aquellos elementos que satisfagan una condicin particular. Para conseguir esta flexibilidad, muchos algoritmos tienen una forma alternativa de llamada que permite proporcionar un predicado, que es simplemente una funcin que retorna un valor booleano basado en algn criterio. Suponga por ejemplo, que solo quiere extraer de una secuencia de enteros, aquellos que son menores o iguales de 15. Una versin de copy() llamada remove_copy_if() puede hacer el trabajo, tal que as:

9074 9075 9076 9077 9078 9079 9080 9081 9082 9083

//: C06:CopyInts2.cpp // Ignores ints that satisfy a predicate. #include <algorithm> #include <cstddef> #include <iostream> using namespace std;

// You supply this predicate bool gt15(int x) { return 15 < x; }

347

9084 9085 9086 9087 9088 9089 9090 9091 9092

int main() { int a[] = { 10, 20, 30 }; const size_t SIZE = sizeof a / sizeof a[0]; int b[SIZE]; int* endb = remove_copy_if(a, a+SIZE, b, gt15); int* beginb = b; while(beginb != endb) cout << *beginb++ << endl; // Prints 10 only } ///:~

9093 9094 9095 9096 9097 9098 9099 9100 9101 9102 9103

Listado 7.5. C06/CopyInts2.cpp La funcin remove_copy_if() acepta los rangos definidos por punteros habituales, seguidos de un predicado de su eleccin. El predicado debe ser un puntero a funcin[FIXME] que toma un argumento simple del mismo tipo que los elementos de la secuencia, y que debe retornar un booleano. Aqu, la funcin gt15 returna verdadero si su argumento es mayor que 15. El algoritmo remove_copy_if() aplica gt15() a cada elemento en la secuencia de entrada e ignora aquellos elementos para los cuales el predicado devuelve verdad cuando escribe la secuencia de salida. El siguiente programa ilustra otra variacin ms del algoritmo de copia.

9104 9105 9106 9107 9108 9109

//: C06:CopyStrings2.cpp // Replaces strings that satisfy a predicate. #include <algorithm> #include <cstddef> #include <iostream> #include <string>

348

9110 9111 9112 9113 9114 9115 9116 9117 9118 9119 9120 9121 9122 9123 9124 9125 9126

using namespace std;

// The predicate bool contains_e(const string& s) { return s.find('e') != string::npos; }

int main() { string a[] = {"read", "my", "lips"}; const size_t SIZE = sizeof a / sizeof a[0]; string b[SIZE]; string* endb = replace_copy_if(a, a + SIZE, b, contains_e, string("kiss")); string* beginb = b; while(beginb != endb) cout << *beginb++ << endl; } ///:~

9127 9128 9129 9130 9131 9132 9133 9134

Listado 7.6. C06/CopyStrings2.cpp En lugar de simplemente ignorar elementos que no satisfagan el predicado, replace_copy_if() substituye un valor fijo para esos elementos cuando escribe la secuencia de salida. La salida es:
kiss my lips

349
9135 9136 9137 9138 9139 9140 como la ocurrencia original de read, la nica cadena de entrada que contiene la letra e, es reemplazada por la palabra kiss, como se especific en el ltimo argumento en la llamada a replace_copy_if(). El algoritmo replace_if() cambia la secuencia original in situ, en lugar de escribir en una secuencia de salida separada, tal como muestra el siguiente programa:

9141 9142 9143 9144 9145 9146 9147 9148 9149 9150 9151 9152 9153 9154 9155 9156 9157 9158 9159

//: C06:ReplaceStrings.cpp // Replaces strings in-place. #include <algorithm> #include <cstddef> #include <iostream> #include <string> using namespace std;

bool contains_e(const string& s) { return s.find('e') != string::npos; }

int main() { string a[] = {"read", "my", "lips"}; const size_t SIZE = sizeof a / sizeof a[0]; replace_if(a, a + SIZE, contains_e, string("kiss")); string* p = a; while(p != a + SIZE) cout << *p++ << endl;

350

9160

} ///:~

9161 9162 9163 9164 9165 9166 9167 9168 9169 9170 9171 9172 9173

Listado 7.7. C06/ReplaceStrings.cpp

7.1.2. Iteradores de flujo


Como cualquier otra buena librera, la Librera Estndar de C++ intenta proporcionar modos convenientes de automatizar tareas comunes. Mencionamos al principio de este captulo puede usar algoritmos genricos en lugar de bucles. Hasta el momento, sin embargo, nuestros ejemplos siguen usando un bucle explcito para imprimir su salida. Dado que imprimir la salida es una de las tareas ms comunes, es de esperar que haya una forma de automatizar eso tambin. Ah es donde los iteradores de flujo entran en juego. Un iterador de flujo usa un flujo como secuencia de entrada o salida. Para eliminar el bucle de salida en el programa CopyInts2.cpp, puede hacer algo como lo siguiente:

9174 9175 9176 9177 9178 9179 9180 9181 9182 9183 9184

//: C06:CopyInts3.cpp // Uses an output stream iterator. #include <algorithm> #include <cstddef> #include <iostream> #include <iterator> using namespace std;

bool gt15(int x) { return 15 < x; }

int main() {

351

9185 9186 9187 9188 9189

int a[] = { 10, 20, 30 }; const size_t SIZE = sizeof a / sizeof a[0]; remove_copy_if(a, a + SIZE, ostream_iterator<int>(cout, "\n"), gt15); } ///:~

9190 9191 9192 9193 9194 9195 9196 9197 9198 9199 9200 9201 9202 9203

Listado 7.8. C06/CopyInts3.cpp En este ejemplo, reemplazaremos la secuencia de salida b en el tercer argumento de remove_copy_if() con un iterador de flujo de salida, que es una instancia de la clase ostream_iterator declarada en el fichero
<iterator>.

Los iteradores de flujo de salida sobrecargan sus operadores de est vinculada al flujo de salida cout. Cada vez que

copia-asignacin para escribir a sus flujos. Esta instancia en particular de


ostream_iterator remove_copy_if()

asigna un entero de la secuencia a a cout a travs de este

iterador, el iterador escribe el entero a cout y automticamente escribe tambin una instancia de la cada de separador indicada en su segundo argumento, que en este caso contiene el carcter de nueva linea. Es igual de fcil escribir en un fichero proporcionando un flujo de salida asociado a un fichero en lugar de cout.

9204 9205 9206 9207 9208 9209 9210 9211

//: C06:CopyIntsToFile.cpp // Uses an output file stream iterator. #include <algorithm> #include <cstddef> #include <fstream> #include <iterator> using namespace std;

352

9212 9213 9214 9215 9216 9217 9218 9219 9220

bool gt15(int x) { return 15 < x; }

int main() { int a[] = { 10, 20, 30 }; const size_t SIZE = sizeof a / sizeof a[0]; ofstream outf("ints.out"); remove_copy_if(a, a + SIZE, ostream_iterator<int>(outf, "\n"), gt15); } ///:~

9221 9222 9223 9224 9225 9226 9227 9228 9229

Listado 7.9. C06/CopyIntsToFile.cpp Un iterador de flujo de entrada permite a un algoritmo leer su secuencia de entrada desde un flujo de entrada. Esto se consigue haciendo que tanto el constructor como operator++() lean el siguiente elemento del flujo subyacente y sobrecargando operator*() para conseguir el valor ledo previamente. Dado que los algoritmos requieren dos punteros para delimitar la secuencia de entrada, puede construir un istream_iterator de dos formas, como puede ver en el siguiente programa.

9230 9231 9232 9233 9234 9235 9236 9237

//: C06:CopyIntsFromFile.cpp // Uses an input stream iterator. #include <algorithm> #include <fstream> #include <iostream> #include <iterator> #include "../require.h" using namespace std;

353

9238 9239 9240 9241 9242 9243 9244 9245 9246 9247 9248 9249 9250
int main() { ofstream ints("someInts.dat"); ints << "1 3 47 5 84 9"; ints.close(); ifstream inf("someInts.dat"); assure(inf, "someInts.dat"); remove_copy_if(istream_iterator<int>(inf), istream_iterator<int>(), ostream_iterator<int>(cout, "\n"), gt15); } ///:~ bool gt15(int x) { return 15 < x; }

9251 9252 9253 9254 9255 9256 9257 9258 9259 9260

Listado 7.10. C06/CopyIntsFromFile.cpp El primer argumento de replace_copy_if() en este programa asocia un objeto istream_iterator al fichero de entrada que contiene enteros. El segundo argumento usa el constructor por defecto de la clase
istream_iterator. istream_iterator

Esta llamada construye un valor especial de

que indica el fin de fichero, de modo que cuando el primer permitiendo al algoritmo terminar correctamente.

iterador encuentra el final del fichero fsico, se compara con el valor de


istream_iterator<int>(),

Fjese que este ejemplo evita usar un array explcito.

9261 9262 9263 9264

7.1.3. Complejidad algortmica


Usar una librera es una cuestin de confianza. Debe confiar en que los desarrolladores no solo proporcionan la funcionalidad correcta, sino tambin esperar que las funciones se ejecutan tan eficientemente como sea posible.

354
9265 9266 9267 9268 9269 9270 9271 9272 9273 9274 9275 9276 9277 9278 Es mejor escribir sus propios bucles que usar algoritmos que degradan el rendimiento. Para garantizar la calidad de las implementaciones de la librera, la estndar de C++ no solo especifica lo que debera hacer un algoritmo, tambin cmo de rpido debera hacerlo y a veces cunto espacio debera usar. Cualquier algoritmo que no cumpla con los requisitos de rendimiento no es conforma al estndar. La medida de la eficiencia operacional de un algoritmo se llama complejidad. Cuando es posible, el estndar especifica el nmero exacto de operaciones que un algoritmo debera usar. El algoritmo count_if(), por ejemplo, retorna el nmero de elementos de una secuencia que cumplan el predicado especificado. La siguiente llamada a count_if(), si se aplica a una secuencia de enteros similar a los ejemplos anteriores de este captulo, devuelve el nmero de elementos mayores que 15:

9279

size_t n = count_if(a, a + SIZE, gt15);

9280 9281 9282 9283 9284 9285 9286

Dado que count_if() debe comprobar cada elemento exactamente una vez, se especific hacer un nmero de comprobaciones que sea exactamente igual que el nmero de elementos en la secuencia. El algoritmo
copy()

tiene la misma especificacin.

Otros algoritmos pueden estar especificados para realizar cierto nmero mximo de operaciones. El algoritmo find() busca a travs de una secuencia hasta encontrar un elemento igual a su tercer argumento.

9287

int* p = find(a, a + SIZE, 20);

9288 9289 9290 9291 9292

Para tan pronto como encuentre el elemento y devuelve un puntero a la primera ocurrencia. Si no encuentra ninguno, retorna un puntero a una posicin pasado el final de la secuencia (a+SIZE en este ejemplo). De modo que find() realiza como mximo tantas comparaciones como elementos tenga la secuencia.

355
9293 9294 9295 9296 9297 9298 9299 9300 9301 9302 9303 9304 9305 9306 9307 A veces el nmero de operaciones que realiza un algoritmo no se puede medir con tanta precisin. En esos casos, el estndar especifica la complejidad asinttica del algoritmo, que es una medida de cmo se comportar el algoritmo con secuencias largas comparadas con formulas bien conocidas. Un buen ejemplo es el algoritmo sort(), del que el estndar dice que requiere aproximadamente n log n comparaciones de media (n es el nmero de elementos de la secuencia). [FIXME]. Esta medida de complejidad da una idea del coste de un algoritmo y al menos le da una base fiable para comparar algoritmos. Como ver en el siguiente captulo, el mtodo find() para el contendor set tiene complejidad logartmica, que implica que el coste de una bsqueda de un elemento en un set ser, para conjuntos grandes, proporcional al logaritmo del nmero de elementos. Eso es mucho menor que el nmero de elementos para un n grande, de modo que siempre es mejor buscar en un set utilizando el mtodo en lugar del algoritmo genrico.

9308

7.2. Objetos-funcin
//: C06:GreaterThanN.cpp #include <iostream> using namespace std;

9309 9310 9311 9312 9313 9314 9315 9316 9317 9318 9319 9320

class gt_n { int value; public: gt_n(int val) : value(val) {} bool operator()(int n) { return n > value; } };

int main() {

356

9321 9322 9323 9324

gt_n f(4); cout << f(3) << endl; cout << f(5) << endl; // Prints 0 (for false) // Prints 1 (for true)

} ///:~

9325 9326 9327 9328

Listado 7.11. C06/GreaterThanN.cpp

7.2.1. Clasificacin de objetos-funcin 7.2.2. Creacin automtica de objetos-funcin


//: C06:CopyInts4.cpp // Uses a standard function object and adaptor. #include <algorithm> #include <cstddef> #include <functional> #include <iostream> #include <iterator> using namespace std;

9329 9330 9331 9332 9333 9334 9335 9336 9337 9338 9339 9340 9341 9342 9343

int main() { int a[] = { 10, 20, 30 }; const size_t SIZE = sizeof a / sizeof a[0]; remove_copy_if(a, a + SIZE, ostream_iterator<int>(cout, "\n"), bind2nd(greater<int>(), 15));

357

9344

} ///:~

9345 9346

Listado 7.12. C06/CopyInts4.cpp

9347 9348 9349 9350 9351 9352 9353 9354 9355 9356 9357 9358 9359 9360

//: C06:CountNotEqual.cpp // Count elements not equal to 20. #include <algorithm> #include <cstddef> #include <functional> #include <iostream> using namespace std;

int main() { int a[] = { 10, 20, 30 }; const size_t SIZE = sizeof a / sizeof a[0]; cout << count_if(a, a + SIZE, not1(bind1st(equal_to<int>(), 20)));// 2 } ///:~

9361 9362 9363 9364

Listado 7.13. C06/CountNotEqual.cpp

7.2.3. Objetos-funcin adaptables 7.2.4. Ms ejemplos de objetos-funcin


//: C06:Generators.h

9365

358

9366 9367 9368 9369 9370 9371 9372 9373 9374 9375 9376 9377 9378 9379 9380 9381 9382 9383 9384 9385 9386 9387 9388 9389 9390

// Different ways to fill sequences. #ifndef GENERATORS_H #define GENERATORS_H #include <cstring> #include <set> #include <cstdlib>

// A generator that can skip over numbers: class SkipGen { int i; int skp; public: SkipGen(int start = 0, int skip = 1) : i(start), skp(skip) {} int operator()() { int r = i; i += skp; return r; } };

// Generate unique random numbers from 0 to mod: class URandGen { std::set<int> used; int limit;

359

9391 9392 9393 9394 9395 9396 9397 9398 9399 9400 9401 9402 9403 9404 9405 9406 9407 9408 9409 9410 9411 9412 9413

public: URandGen(int lim) : limit(lim) {} int operator()() { while(true) { int i = int(std::rand()) % limit; if(used.find(i) == used.end()) { used.insert(i); return i; } } } };

// Produces random characters: class CharGen { static const char* source; static const int len; public: char operator()() { return source[std::rand() % len]; } }; #endif // GENERATORS_H ///:~

9414

Listado 7.14. C06/Generators.h

360
9415

9416 9417 9418 9419 9420 9421

//: C06:Generators.cpp {O} #include "Generators.h" const char* CharGen::source = "ABCDEFGHIJK" "LMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; const int CharGen::len = std::strlen(source); ///:~

9422 9423

Listado 7.15. C06/Generators.cpp

9424 9425 9426 9427 9428 9429 9430 9431 9432 9433 9434 9435 9436 9437 9438

//: C06:FunctionObjects.cpp {-bor} // Illustrates selected predefined function object // templates from the Standard C++ library. //{L} Generators #include <algorithm> #include <cstdlib> #include <ctime> #include <functional> #include <iostream> #include <iterator> #include <vector> #include "Generators.h" #include "PrintSequence.h" using namespace std;

361

9439 9440 9441 9442 9443 9444 9445 9446 9447 9448 9449 9450 9451 9452 9453 9454 9455 9456 9457 9458 9459 9460 9461 9462 9463

template<typename Contain, typename UnaryFunc> void testUnary(Contain& source, Contain& dest, UnaryFunc f) { transform(source.begin(), source.end(), dest.begin(), f); }

template<typename Contain1, typename Contain2, typename BinaryFunc> void testBinary(Contain1& src1, Contain1& src2, Contain2& dest, BinaryFunc f) { transform(src1.begin(), src1.end(), src2.begin(), dest.begin(), f); }

// Executes the expression, then stringizes the // expression into the print statement: #define T(EXPR) EXPR; print(r.begin(), r.end(), \ "After " #EXPR); // For Boolean tests: #define B(EXPR) EXPR; print(br.begin(), br.end(), \ "After " #EXPR);

// Boolean random generator: struct BRand { bool operator()() { return rand() % 2 == 0; }

362

9464 9465 9466 9467 9468 9469 9470 9471 9472 9473 9474 9475 9476 9477 9478 9479 9480 9481 9482 9483 9484 9485 9486 9487 9488

};

int main() { const int SZ = 10; const int MAX = 50; vector<int> x(SZ), y(SZ), r(SZ); // An integer random number generator: URandGen urg(MAX); srand(time(0)); // Randomize

generate_n(x.begin(), SZ, urg); generate_n(y.begin(), SZ, urg); // Add one to each to guarantee nonzero divide: transform(y.begin(), y.end(), y.begin(), bind2nd(plus<int>(), 1)); // Guarantee one pair of elements is ==: x[0] = y[0]; print(x.begin(), x.end(), "x"); print(y.begin(), y.end(), "y"); // Operate on each element pair of x & y, // putting the result into r: T(testBinary(x, y, r, plus<int>())); T(testBinary(x, y, r, minus<int>())); T(testBinary(x, y, r, multiplies<int>())); T(testBinary(x, y, r, divides<int>())); T(testBinary(x, y, r, modulus<int>()));

363

9489 9490 9491 9492 9493 9494 9495 9496 9497 9498 9499 9500 9501 9502 9503 9504 9505 9506 9507 9508

T(testUnary(x, r, negate<int>())); vector<bool> br(SZ); // For Boolean results B(testBinary(x, y, br, equal_to<int>())); B(testBinary(x, y, br, not_equal_to<int>())); B(testBinary(x, y, br, greater<int>())); B(testBinary(x, y, br, less<int>())); B(testBinary(x, y, br, greater_equal<int>())); B(testBinary(x, y, br, less_equal<int>())); B(testBinary(x, y, br, not2(greater_equal<int>()))); B(testBinary(x,y,br,not2(less_equal<int>()))); vector<bool> b1(SZ), b2(SZ); generate_n(b1.begin(), SZ, BRand()); generate_n(b2.begin(), SZ, BRand()); print(b1.begin(), b1.end(), "b1"); print(b2.begin(), b2.end(), "b2"); B(testBinary(b1, b2, br, logical_and<int>())); B(testBinary(b1, b2, br, logical_or<int>())); B(testUnary(b1, br, logical_not<int>())); B(testUnary(b1, br, not1(logical_not<int>()))); } ///:~

9509 9510

Listado 7.16. C06/FunctionObjects.cpp

9511 9512

//: C06:FBinder.cpp // Binders aren't limited to producing predicates.

364

9513 9514 9515 9516 9517 9518 9519 9520 9521 9522 9523 9524 9525 9526 9527 9528 9529 9530 9531 9532 9533

//{L} Generators #include <algorithm> #include <cstdlib> #include <ctime> #include <functional> #include <iostream> #include <iterator> #include <vector> #include "Generators.h" using namespace std;

int main() { ostream_iterator<int> out(cout," "); vector<int> v(15); srand(time(0)); // Randomize

generate(v.begin(), v.end(), URandGen(20)); copy(v.begin(), v.end(), out); transform(v.begin(), v.end(), v.begin(), bind2nd(multiplies<int>(), 10)); copy(v.begin(), v.end(), out); } ///:~

9534 9535

Listado 7.17. C06/FBinder.cpp

9536

//: C06:BinderValue.cpp

365

9537 9538 9539 9540 9541 9542 9543 9544 9545 9546 9547 9548 9549 9550 9551 9552 9553 9554 9555 9556 9557 9558 9559 9560 9561

// The bound argument can vary. #include <algorithm> #include <functional> #include <iostream> #include <iterator> #include <cstdlib> using namespace std;

int boundedRand() { return rand() % 100; }

int main() { const int SZ = 20; int a[SZ], b[SZ] = {0}; generate(a, a + SZ, boundedRand); int val = boundedRand(); int* end = remove_copy_if(a, a + SZ, b, bind2nd(greater<int>(), val)); // Sort for easier viewing: sort(a, a + SZ); sort(b, end); ostream_iterator<int> out(cout, " "); cout << "Original Sequence:" << endl; copy(a, a + SZ, out); cout << endl; cout << "Values <= " << val << endl; copy(b, end, out); cout << endl;

366

9562

} ///:~

9563 9564 9565

Listado 7.18. C06/BinderValue.cpp

7.2.5. Adaptadores de puntero a funcin


//: C06:PtrFun1.cpp // Using ptr_fun() with a unary function. #include <algorithm> #include <cmath> #include <functional> #include <iostream> #include <iterator> #include <vector> using namespace std;

9566 9567 9568 9569 9570 9571 9572 9573 9574 9575 9576 9577 9578 9579 9580 9581 9582 9583 9584

int d[] = { 123, 94, 10, 314, 315 }; const int DSZ = sizeof d / sizeof *d;

bool isEven(int x) { return x % 2 == 0; }

int main() { vector<bool> vb; transform(d, d + DSZ, back_inserter(vb), not1(ptr_fun(isEven)));

367

9585 9586 9587 9588 9589

copy(vb.begin(), vb.end(), ostream_iterator<bool>(cout, " ")); cout << endl; // Output: 1 0 0 0 1 } ///:~

9590 9591

Listado 7.19. C06/PtrFun1.cpp

9592 9593 9594 9595 9596 9597 9598 9599 9600 9601 9602 9603 9604 9605 9606 9607 9608

//: C06:PtrFun2.cpp {-edg} // Using ptr_fun() for a binary function. #include <algorithm> #include <cmath> #include <functional> #include <iostream> #include <iterator> #include <vector> using namespace std;

double d[] = { 01.23, 91.370, 56.661, 023.230, 19.959, 1.0, 3.14159 }; const int DSZ = sizeof d / sizeof *d;

int main() { vector<double> vd; transform(d, d + DSZ, back_inserter(vd),

368

9609 9610 9611 9612 9613

bind2nd(ptr_fun<double, double, double>(pow), 2.0)); copy(vd.begin(), vd.end(), ostream_iterator<double>(cout, " ")); cout << endl; } ///:~

9614 9615

Listado 7.20. C06/PtrFun2.cpp

9616 9617 9618 9619 9620 9621 9622 9623 9624 9625 9626 9627 9628 9629 9630 9631 9632

//: C06:MemFun1.cpp // Applying pointers to member functions. #include <algorithm> #include <functional> #include <iostream> #include <vector> #include "../purge.h" using namespace std;

class Shape { public: virtual void draw() = 0; virtual ~Shape() {} };

class Circle : public Shape { public:

369

9633 9634 9635 9636 9637 9638 9639 9640 9641 9642 9643 9644 9645 9646 9647 9648 9649

virtual void draw() { cout << "Circle::Draw()" << endl; } ~Circle() { cout << "Circle::~Circle()" << endl; } };

class Square : public Shape { public: virtual void draw() { cout << "Square::Draw()" << endl; } ~Square() { cout << "Square::~Square()" << endl; } };

int main() { vector<Shape*> vs; vs.push_back(new Circle); vs.push_back(new Square); for_each(vs.begin(), vs.end(), mem_fun(&Shape::draw)); purge(vs); } ///:~

9650 9651

Listado 7.21. C06/MemFun1.cpp

9652 9653 9654 9655 9656

//: C06:MemFun2.cpp // Calling member functions through an object reference. #include <algorithm> #include <functional> #include <iostream>

370

9657 9658 9659 9660 9661 9662 9663 9664 9665 9666 9667 9668 9669 9670 9671 9672 9673 9674 9675 9676 9677 9678

#include <iterator> #include <vector> using namespace std;

class Angle { int degrees; public: Angle(int deg) : degrees(deg) {} int mul(int times) { return degrees *= times; } };

int main() { vector<Angle> va; for(int i = 0; i < 50; i += 10) va.push_back(Angle(i)); int x[] = { 1, 2, 3, 4, 5 }; transform(va.begin(), va.end(), x, ostream_iterator<int>(cout, " "), mem_fun_ref(&Angle::mul)); cout << endl; // Output: 0 20 60 120 200 } ///:~

9679 9680

Listado 7.22. C06/MemFun2.cpp

371

9681 9682 9683 9684 9685 9686 9687 9688 9689 9690 9691 9692 9693 9694 9695 9696 9697 9698 9699 9700 9701 9702 9703 9704 9705

//: C06:FindBlanks.cpp // Demonstrates mem_fun_ref() with string::empty(). #include <algorithm> #include <cassert> #include <cstddef> #include <fstream> #include <functional> #include <string> #include <vector> #include "../require.h" using namespace std;

typedef vector<string>::iterator LSI;

int main(int argc, char* argv[]) { char* fname = "FindBlanks.cpp"; if(argc > 1) fname = argv[1]; ifstream in(fname); assure(in, fname); vector<string> vs; string s; while(getline(in, s)) vs.push_back(s); vector<string> cpy = vs; // For testing LSI lsi = find_if(vs.begin(), vs.end(),

372

9706 9707 9708 9709 9710 9711 9712 9713 9714 9715 9716 9717

mem_fun_ref(&string::empty)); while(lsi != vs.end()) { *lsi = "A BLANK LINE"; lsi = find_if(vs.begin(), vs.end(), mem_fun_ref(&string::empty)); } for(size_t i = 0; i < cpy.size(); i++) if(cpy[i].size() == 0) assert(vs[i] == "A BLANK LINE"); else assert(vs[i] != "A BLANK LINE"); } ///:~

9718 9719 9720

Listado 7.23. C06/FindBlanks.cpp

7.2.6. Escribir sus propios adaptadores de objeto-funcin


//: C06:NumStringGen.h // A random number generator that produces // strings representing floating-point numbers. #ifndef NUMSTRINGGEN_H #define NUMSTRINGGEN_H #include <cstdlib> #include <string>

9721 9722 9723 9724 9725 9726 9727 9728

373

9729 9730 9731 9732 9733 9734 9735 9736 9737 9738 9739 9740 9741 9742 9743 9744 9745 9746 9747 9748

class NumStringGen { const int sz; // Number of digits to make public: NumStringGen(int ssz = 5) : sz(ssz) {} std::string operator()() { std::string digits("0123456789"); const int ndigits = digits.size(); std::string r(sz, ' '); // Don't want a zero as the first digit r[0] = digits[std::rand() % (ndigits - 1)] + 1; // Now assign the rest for(int i = 1; i < sz; ++i) if(sz >= 3 && i == sz/2) r[i] = '.'; // Insert a decimal point else r[i] = digits[std::rand() % ndigits]; return r; } }; #endif // NUMSTRINGGEN_H ///:~

9749 9750

Listado 7.24. C06/NumStringGen.h

9751 9752

//: C06:MemFun3.cpp // Using mem_fun().

374

9753 9754 9755 9756 9757 9758 9759 9760 9761 9762 9763 9764 9765 9766 9767 9768 9769 9770 9771 9772 9773 9774 9775 9776 9777

#include <algorithm> #include <cstdlib> #include <ctime> #include <functional> #include <iostream> #include <iterator> #include <string> #include <vector> #include "NumStringGen.h" using namespace std;

int main() { const int SZ = 9; vector<string> vs(SZ); // Fill it with random number strings: srand(time(0)); // Randomize generate(vs.begin(), vs.end(), NumStringGen()); copy(vs.begin(), vs.end(), ostream_iterator<string>(cout, "\t")); cout << endl; const char* vcp[SZ]; transform(vs.begin(), vs.end(), vcp, mem_fun_ref(&string::c_str)); vector<double> vd; transform(vcp, vcp + SZ, back_inserter(vd),

375

9778 9779 9780 9781 9782 9783 9784

std::atof); cout.precision(4); cout.setf(ios::showpoint); copy(vd.begin(), vd.end(), ostream_iterator<double>(cout, "\t")); cout << endl; } ///:~

9785 9786

Listado 7.25. C06/MemFun3.cpp

9787 9788 9789 9790 9791 9792 9793 9794 9795 9796 9797 9798 9799 9800 9801

//: C06:ComposeTry.cpp // A first attempt at implementing function composition. #include <cassert> #include <cstdlib> #include <functional> #include <iostream> #include <string> using namespace std;

template<typename R, typename E, typename F1, typename F2> class unary_composer { F1 f1; F2 f2; public: unary_composer(F1 fone, F2 ftwo) : f1(fone), f2(ftwo) {}

376

9802 9803 9804 9805 9806 9807 9808 9809 9810 9811 9812 9813 9814

R operator()(E x) { return f1(f2(x)); } };

template<typename R, typename E, typename F1, typename F2> unary_composer<R, E, F1, F2> compose(F1 f1, F2 f2) { return unary_composer<R, E, F1, F2>(f1, f2); }

int main() { double x = compose<double, const string&>( atof, mem_fun_ref(&string::c_str))("12.34"); assert(x == 12.34); } ///:~

9815 9816

Listado 7.26. C06/ComposeTry.cpp

9817 9818 9819 9820 9821 9822 9823 9824 9825

//: C06:ComposeFinal.cpp {-edg} // An adaptable composer. #include <algorithm> #include <cassert> #include <cstdlib> #include <functional> #include <iostream> #include <iterator> #include <string>

377

9826 9827 9828 9829 9830 9831 9832 9833 9834 9835 9836 9837 9838 9839 9840 9841 9842 9843 9844 9845 9846 9847 9848 9849 9850

#include <vector> #include "NumStringGen.h" using namespace std;

template<typename F1, typename F2> class unary_composer : public unary_function<typename F2::argument_type, typename F1::result_type> { F1 f1; F2 f2; public: unary_composer(F1 f1, F2 f2) : f1(f1), f2(f2) {} typename F1::result_type operator()(typename F2::argument_type x) { return f1(f2(x)); } };

template<typename F1, typename F2> unary_composer<F1, F2> compose(F1 f1, F2 f2) { return unary_composer<F1, F2>(f1, f2); }

int main() { const int SZ = 9; vector<string> vs(SZ);

378

9851 9852 9853 9854 9855 9856 9857 9858 9859 9860 9861 9862

// Fill it with random number strings: generate(vs.begin(), vs.end(), NumStringGen()); copy(vs.begin(), vs.end(), ostream_iterator<string>(cout, "\t")); cout << endl; vector<double> vd; transform(vs.begin(), vs.end(), back_inserter(vd), compose(ptr_fun(atof), mem_fun_ref(&string::c_str))); copy(vd.begin(), vd.end(), ostream_iterator<double>(cout, "\t")); cout << endl; } ///:~

9863 9864

Listado 7.27. C06/ComposeFinal.cpp

9865 9866

7.3. Un catlogo de algoritmos STL


7.3.1. Herramientas de soporte para la creacin de ejemplos
//: C06:PrintSequence.h // Prints the contents of any sequence. #ifndef PRINTSEQUENCE_H #define PRINTSEQUENCE_H #include <algorithm> #include <iostream> #include <iterator>

9867 9868 9869 9870 9871 9872 9873

379

9874 9875 9876 9877 9878 9879 9880 9881 9882 9883 9884 9885 9886 9887
template<typename Iter> void print(Iter first, Iter last, const char* nm = "", const char* sep = "\n", std::ostream& os = std::cout) { if(nm != 0 && *nm != '\0') os << nm << ": " << sep; typedef typename std::iterator_traits<Iter>::value_type T; std::copy(first, last, std::ostream_iterator<T>(std::cout, sep)); os << std::endl; } #endif // PRINTSEQUENCE_H ///:~

9888 9889 9890

Listado 7.28. C06/PrintSequence.h

Reordenacin estable vs. inestable

9891 9892 9893 9894 9895 9896 9897

//: C06:NString.h // A "numbered string" that keeps track of the // number of occurrences of the word it contains. #ifndef NSTRING_H #define NSTRING_H #include <algorithm> #include <iostream>

380

9898 9899 9900 9901 9902 9903 9904 9905 9906 9907 9908 9909 9910 9911 9912 9913 9914 9915 9916 9917 9918 9919 9920 9921 9922

#include <string> #include <utility> #include <vector>

typedef std::pair<std::string, int> psi;

// Only compare on the first element bool operator==(const psi& l, const psi& r) { return l.first == r.first; }

class NString { std::string s; int thisOccurrence; // Keep track of the number of occurrences: typedef std::vector<psi> vp; typedef vp::iterator vpit; static vp words; void addString(const std::string& x) { psi p(x, 0); vpit it = std::find(words.begin(), words.end(), p); if(it != words.end()) thisOccurrence = ++it->second; else { thisOccurrence = 0;

381

9923 9924 9925 9926 9927 9928 9929 9930 9931 9932 9933 9934 9935 9936 9937 9938 9939 9940 9941 9942 9943 9944 9945 9946 9947

words.push_back(p); } } public: NString() : thisOccurrence(0) {} NString(const std::string& x) : s(x) { addString(x); } NString(const char* x) : s(x) { addString(x); } // Implicit operator= and copy-constructor are OK here. friend std::ostream& operator<<( std::ostream& os, const NString& ns) { return os << ns.s << " [" << ns.thisOccurrence << "]"; } // Need this for sorting. Notice it only // compares strings, not occurrences: friend bool operator<(const NString& l, const NString& r) { return l.s < r.s; } friend bool operator==(const NString& l, const NString& r) { return l.s == r.s; } // For sorting with greater<NString>: friend bool operator>(const NString& l, const NString& r) {

382

9948 9949 9950 9951 9952 9953 9954 9955 9956 9957

return l.s > r.s; } // To get at the string directly: operator const std::string&() const { return s; } };

// Because NString::vp is a template and we are using the // inclusion model, it must be defined in this header file: NString::vp NString::words; #endif // NSTRING_H ///:~

9958 9959 9960 9961

Listado 7.29. C06/NString.h

7.3.2. Relleno y generacin


Ejemplo

9962 9963 9964 9965 9966 9967 9968 9969 9970

//: C06:FillGenerateTest.cpp // Demonstrates "fill" and "generate." //{L} Generators #include <vector> #include <algorithm> #include <string> #include "Generators.h" #include "PrintSequence.h" using namespace std;

383

9971 9972 9973 9974 9975 9976 9977 9978 9979 9980 9981 9982 9983 9984 9985
int main() { vector<string> v1(5); fill(v1.begin(), v1.end(), "howdy"); print(v1.begin(), v1.end(), "v1", " "); vector<string> v2; fill_n(back_inserter(v2), 7, "bye"); print(v2.begin(), v2.end(), "v2"); vector<int> v3(10); generate(v3.begin(), v3.end(), SkipGen(4,5)); print(v3.begin(), v3.end(), "v3", " "); vector<int> v4; generate_n(back_inserter(v4),15, URandGen(30)); print(v4.begin(), v4.end(), "v4", " "); } ///:~

9986 9987 9988 9989

Listado 7.30. C06/FillGenerateTest.cpp

7.3.3. Conteo
Ejemplo

9990 9991 9992 9993

//: C06:Counting.cpp // The counting algorithms. //{L} Generators #include <algorithm>

384

9994 9995 9996 9997 9998 9999 10000 10001 10002 10003 10004 10005 10006 10007 10008 10009 10010 10011 10012 10013 10014 10015 10016 10017 10018

#include <functional> #include <iterator> #include <set> #include <vector> #include "Generators.h" #include "PrintSequence.h" using namespace std;

int main() { vector<char> v; generate_n(back_inserter(v), 50, CharGen()); print(v.begin(), v.end(), "v", ""); // Create a set of the characters in v: set<char> cs(v.begin(), v.end()); typedef set<char>::iterator sci; for(sci it = cs.begin(); it != cs.end(); it++) { int n = count(v.begin(), v.end(), *it); cout << *it << ": " << n << ", "; } int lc = count_if(v.begin(), v.end(), bind2nd(greater<char>(), 'a')); cout << "\nLowercase letters: " << lc << endl; sort(v.begin(), v.end()); print(v.begin(), v.end(), "sorted", ""); } ///:~

385
10019 10020 10021 10022 Listado 7.31. C06/Counting.cpp

7.3.4. Manipulacin de secuencias


Ejemplo

10023 10024 10025 10026 10027 10028 10029 10030 10031 10032 10033 10034 10035 10036 10037 10038 10039 10040 10041 10042 10043

//: C06:Manipulations.cpp // Shows basic manipulations. //{L} Generators // NString #include <vector> #include <string> #include <algorithm> #include "PrintSequence.h" #include "NString.h" #include "Generators.h" using namespace std;

int main() { vector<int> v1(10); // Simple counting: generate(v1.begin(), v1.end(), SkipGen()); print(v1.begin(), v1.end(), "v1", " "); vector<int> v2(v1.size()); copy_backward(v1.begin(), v1.end(), v2.end()); print(v2.begin(), v2.end(), "copy_backward", " "); reverse_copy(v1.begin(), v1.end(), v2.begin());

386

10044 10045 10046 10047 10048 10049 10050 10051 10052 10053 10054 10055 10056 10057 10058 10059 10060 10061 10062 10063 10064 10065 10066 10067 10068

print(v2.begin(), v2.end(), "reverse_copy", " "); reverse(v1.begin(), v1.end()); print(v1.begin(), v1.end(), "reverse", " "); int half = v1.size() / 2; // Ranges must be exactly the same size: swap_ranges(v1.begin(), v1.begin() + half, v1.begin() + half); print(v1.begin(), v1.end(), "swap_ranges", " "); // Start with a fresh sequence: generate(v1.begin(), v1.end(), SkipGen()); print(v1.begin(), v1.end(), "v1", " "); int third = v1.size() / 3; for(int i = 0; i < 10; i++) { rotate(v1.begin(), v1.begin() + third, v1.end()); print(v1.begin(), v1.end(), "rotate", " "); } cout << "Second rotate example:" << endl; char c[] = "aabbccddeeffgghhiijj"; const char CSZ = strlen(c); for(int i = 0; i < 10; i++) { rotate(c, c + 2, c + CSZ); print(c, c + CSZ, "", ""); } cout << "All n! permutations of abcd:" << endl; int nf = 4 * 3 * 2 * 1;

387

10069 10070 10071 10072 10073 10074 10075 10076 10077 10078 10079 10080 10081 10082 10083 10084 10085 10086 10087 10088 10089 10090 10091 10092 10093

char p[] = "abcd"; for(int i = 0; i < nf; i++) { next_permutation(p, p + 4); print(p, p + 4, "", ""); } cout << "Using prev_permutation:" << endl; for(int i = 0; i < nf; i++) { prev_permutation(p, p + 4); print(p, p + 4, "", ""); } cout << "random_shuffling a word:" << endl; string s("hello"); cout << s << endl; for(int i = 0; i < 5; i++) { random_shuffle(s.begin(), s.end()); cout << s << endl; } NString sa[] = { "a", "b", "c", "d", "a", "b", "c", "d", "a", "b", "c", "d", "a", "b", "c"}; const int SASZ = sizeof sa / sizeof *sa; vector<NString> ns(sa, sa + SASZ); print(ns.begin(), ns.end(), "ns", " "); vector<NString>::iterator it = partition(ns.begin(), ns.end(), bind2nd(greater<NString>(), "b"));

388

10094 10095 10096 10097 10098 10099 10100 10101 10102 10103

cout << "Partition point: " << *it << endl; print(ns.begin(), ns.end(), "", " "); // Reload vector: copy(sa, sa + SASZ, ns.begin()); it = stable_partition(ns.begin(), ns.end(), bind2nd(greater<NString>(), "b")); cout << "Stable partition" << endl; cout << "Partition point: " << *it << endl; print(ns.begin(), ns.end(), "", " "); } ///:~

10104 10105 10106 10107

Listado 7.32. C06/Manipulations.cpp

7.3.5. Bsqueda y reemplazo


Ejemplo

10108 10109 10110 10111 10112 10113 10114 10115 10116

//: C06:SearchReplace.cpp // The STL search and replace algorithms. #include <algorithm> #include <functional> #include <vector> #include "PrintSequence.h" using namespace std;

struct PlusOne {

389

10117 10118 10119 10120 10121 10122 10123 10124 10125 10126 10127 10128 10129 10130 10131 10132 10133 10134 10135 10136 10137 10138 10139 10140 10141

bool operator()(int i, int j) { return j == i + 1; } };

class MulMoreThan { int value; public: MulMoreThan(int val) : value(val) {} bool operator()(int v, int m) { return v * m > value; } };

int main() { int a[] = { 1, 2, 3, 4, 5, 6, 6, 7, 7, 7, 8, 8, 8, 8, 11, 11, 11, 11, 11 }; const int ASZ = sizeof a / sizeof *a; vector<int> v(a, a + ASZ); print(v.begin(), v.end(), "v", " "); vector<int>::iterator it = find(v.begin(), v.end(), 4); cout << "find: " << *it << endl; it = find_if(v.begin(), v.end(), bind2nd(greater<int>(), 8)); cout << "find_if: " << *it << endl; it = adjacent_find(v.begin(), v.end()); while(it != v.end()) { cout << "adjacent_find: " << *it << ", " << *(it + 1) << endl;

390

10142 10143 10144 10145 10146 10147 10148 10149 10150 10151 10152 10153 10154 10155 10156 10157 10158 10159 10160 10161 10162 10163 10164 10165 10166

it = adjacent_find(it + 1, v.end()); } it = adjacent_find(v.begin(), v.end(), PlusOne()); while(it != v.end()) { cout << "adjacent_find PlusOne: " << *it << ", " << *(it + 1) << endl; it = adjacent_find(it + 1, v.end(), PlusOne()); } int b[] = { 8, 11 }; const int BSZ = sizeof b / sizeof *b; print(b, b + BSZ, "b", " "); it = find_first_of(v.begin(), v.end(), b, b + BSZ); print(it, it + BSZ, "find_first_of", " "); it = find_first_of(v.begin(), v.end(), b, b + BSZ, PlusOne()); print(it,it + BSZ,"find_first_of PlusOne"," "); it = search(v.begin(), v.end(), b, b + BSZ); print(it, it + BSZ, "search", " "); int c[] = { 5, 6, 7 }; const int CSZ = sizeof c / sizeof *c; print(c, c + CSZ, "c", " "); it = search(v.begin(), v.end(), c, c + CSZ, PlusOne()); print(it, it + CSZ,"search PlusOne", " "); int d[] = { 11, 11, 11 }; const int DSZ = sizeof d / sizeof *d;

391

10167 10168 10169 10170 10171 10172 10173 10174 10175 10176 10177 10178 10179 10180 10181 10182 10183 10184 10185 10186 10187 10188 10189 10190 10191

print(d, d + DSZ, "d", " "); it = find_end(v.begin(), v.end(), d, d + DSZ); print(it, v.end(),"find_end", " "); int e[] = { 9, 9 }; print(e, e + 2, "e", " "); it = find_end(v.begin(), v.end(), e, e + 2, PlusOne()); print(it, v.end(),"find_end PlusOne"," "); it = search_n(v.begin(), v.end(), 3, 7); print(it, it + 3, "search_n 3, 7", " "); it = search_n(v.begin(), v.end(), 6, 15, MulMoreThan(100)); print(it, it + 6, "search_n 6, 15, MulMoreThan(100)", " "); cout << "min_element: " << *min_element(v.begin(), v.end()) << endl; cout << "max_element: " << *max_element(v.begin(), v.end()) << endl; vector<int> v2; replace_copy(v.begin(), v.end(), back_inserter(v2), 8, 47); print(v2.begin(), v2.end(), "replace_copy 8 -> 47", " "); replace_if(v.begin(), v.end(), bind2nd(greater_equal<int>(), 7), -1); print(v.begin(), v.end(), "replace_if >= 7 -> -1", " "); } ///:~

392
10192 10193 10194 10195 Listado 7.33. C06/SearchReplace.cpp

7.3.6. Comparacin de rangos


Ejemplo

10196 10197 10198 10199 10200 10201 10202 10203 10204 10205 10206 10207 10208 10209 10210 10211 10212 10213 10214 10215 10216

//: C06:Comparison.cpp // The STL range comparison algorithms. #include <algorithm> #include <functional> #include <string> #include <vector> #include "PrintSequence.h" using namespace std;

int main() { // Strings provide a convenient way to create // ranges of characters, but you should // normally look for native string operations: string s1("This is a test"); string s2("This is a Test"); cout << "s1: " << s1 << endl << "s2: " << s2 << endl; cout << "compare s1 & s1: " << equal(s1.begin(), s1.end(), s1.begin()) << endl; cout << "compare s1 & s2: " << equal(s1.begin(), s1.end(), s2.begin()) << endl; cout << "lexicographical_compare s1 & s1: "

393

10217 10218 10219 10220 10221 10222 10223 10224 10225 10226 10227 10228 10229 10230 10231 10232 10233 10234 10235 10236 10237 10238 10239 10240

<< lexicographical_compare(s1.begin(), s1.end(), s1.begin(), s1.end()) << endl;

cout << "lexicographical_compare s1 & s2: " << lexicographical_compare(s1.begin(), s1.end(), s2.begin(), s2.end()) << endl; cout << "lexicographical_compare s2 & s1: " << lexicographical_compare(s2.begin(), s2.end(), s1.begin(), s1.end()) << endl; cout << "lexicographical_compare shortened " "s1 & full-length s2: " << endl; string s3(s1); while(s3.length() != 0) { bool result = lexicographical_compare( s3.begin(), s3.end(), s2.begin(),s2.end()); cout << s3 << endl << s2 << ", result = " << result << endl; if(result == true) break; s3 = s3.substr(0, s3.length() - 1); } pair<string::iterator, string::iterator> p = mismatch(s1.begin(), s1.end(), s2.begin()); print(p.first, s1.end(), "p.first", ""); print(p.second, s2.end(), "p.second",""); } ///:~

394
10241 10242 10243 10244 Listado 7.34. C06/Comparison.cpp

7.3.7. Eliminacin de elementos


Ejemplo

10245 10246 10247 10248 10249 10250 10251 10252 10253 10254 10255 10256 10257 10258 10259 10260 10261 10262 10263 10264 10265

//: C06:Removing.cpp // The removing algorithms. //{L} Generators #include <algorithm> #include <cctype> #include <string> #include "Generators.h" #include "PrintSequence.h" using namespace std;

struct IsUpper { bool operator()(char c) { return isupper(c); } };

int main() { string v; v.resize(25); generate(v.begin(), v.end(), CharGen()); print(v.begin(), v.end(), "v original", ""); // Create a set of the characters in v: string us(v.begin(), v.end());

395

10266 10267 10268 10269 10270 10271 10272 10273 10274 10275 10276 10277 10278 10279 10280 10281 10282 10283 10284 10285 10286 10287 10288 10289 10290

sort(us.begin(), us.end()); string::iterator it = us.begin(), cit = v.end(), uend = unique(us.begin(), us.end()); // Step through and remove everything: while(it != uend) { cit = remove(v.begin(), cit, *it); print(v.begin(), v.end(), "Complete v", ""); print(v.begin(), cit, "Pseudo v ", " "); cout << "Removed element:\t" << *it << "\nPsuedo Last Element:\t" << *cit << endl << endl; ++it; } generate(v.begin(), v.end(), CharGen()); print(v.begin(), v.end(), "v", ""); cit = remove_if(v.begin(), v.end(), IsUpper()); print(v.begin(), cit, "v after remove_if IsUpper", " "); // Copying versions are not shown for remove() // and remove_if(). sort(v.begin(), cit); print(v.begin(), cit, "sorted", " "); string v2; v2.resize(cit - v.begin()); unique_copy(v.begin(), cit, v2.begin()); print(v2.begin(), v2.end(), "unique_copy", " ");

396

10291 10292 10293 10294

// Same behavior: cit = unique(v.begin(), cit, equal_to<char>()); print(v.begin(), cit, "unique equal_to<char>", " "); } ///:~

10295 10296 10297 10298 10299

Listado 7.35. C06/Removing.cpp

7.3.8. Ordenacin y operacin sobre rangos ordenados


Ordenacin Ejemplo

10300 10301 10302 10303 10304 10305 10306 10307 10308 10309 10310 10311 10312 10313

//: C06:SortedSearchTest.cpp // Test searching in sorted ranges. // NString #include <algorithm> #include <cassert> #include <ctime> #include <cstdlib> #include <cstddef> #include <fstream> #include <iostream> #include <iterator> #include <vector> #include "NString.h" #include "PrintSequence.h"

397

10314 10315 10316 10317 10318 10319 10320 10321 10322 10323 10324 10325 10326 10327 10328 10329 10330 10331 10332 10333 10334 10335 10336 10337 10338

#include "../require.h" using namespace std;

int main(int argc, char* argv[]) { typedef vector<NString>::iterator sit; char* fname = "Test.txt"; if(argc > 1) fname = argv[1]; ifstream in(fname); assure(in, fname); srand(time(0)); cout.setf(ios::boolalpha); vector<NString> original; copy(istream_iterator<string>(in), istream_iterator<string>(), back_inserter(original)); require(original.size() >= 4, "Must have four elements"); vector<NString> v(original.begin(), original.end()), w(original.size() / 2); sort(v.begin(), v.end()); print(v.begin(), v.end(), "sort"); v = original; stable_sort(v.begin(), v.end()); print(v.begin(), v.end(), "stable_sort"); v = original; sit it = v.begin(), it2; // Move iterator to middle

398

10339 10340 10341 10342 10343 10344 10345 10346 10347 10348 10349 10350 10351 10352 10353 10354 10355 10356 10357 10358 10359 10360 10361 10362 10363

for(size_t i = 0; i < v.size() / 2; i++) ++it; partial_sort(v.begin(), it, v.end()); cout << "middle = " << *it << endl; print(v.begin(), v.end(), "partial_sort"); v = original; // Move iterator to a quarter position it = v.begin(); for(size_t i = 0; i < v.size() / 4; i++) ++it; // Less elements to copy from than to the destination partial_sort_copy(v.begin(), it, w.begin(), w.end()); print(w.begin(), w.end(), "partial_sort_copy"); // Not enough room in destination partial_sort_copy(v.begin(), v.end(), w.begin(),w.end()); print(w.begin(), w.end(), "w partial_sort_copy"); // v remains the same through all this process assert(v == original); nth_element(v.begin(), it, v.end()); cout << "The nth_element = " << *it << endl; print(v.begin(), v.end(), "nth_element"); string f = original[rand() % original.size()]; cout << "binary search: " << binary_search(v.begin(), v.end(), f) << endl; sort(v.begin(), v.end());

399

10364 10365 10366 10367 10368 10369

it = lower_bound(v.begin(), v.end(), f); it2 = upper_bound(v.begin(), v.end(), f); print(it, it2, "found range"); pair<sit, sit> ip = equal_range(v.begin(), v.end(), f); print(ip.first, ip.second, "equal_range"); } ///:~

10370 10371 10372 10373

Listado 7.36. C06/SortedSearchTest.cpp

Mezcla de rangos ordenados Ejemplo

10374 10375 10376 10377 10378 10379 10380 10381 10382 10383 10384 10385 10386

//: C06:MergeTest.cpp // Test merging in sorted ranges. //{L} Generators #include <algorithm> #include "PrintSequence.h" #include "Generators.h" using namespace std;

int main() { const int SZ = 15; int a[SZ*2] = {0}; // Both ranges go in the same array: generate(a, a + SZ, SkipGen(0, 2));

400

10387 10388 10389 10390 10391 10392 10393 10394 10395 10396 10397 10398 10399 10400 10401 10402

a[3] = 4; a[4] = 4; generate(a + SZ, a + SZ*2, SkipGen(1, 3)); print(a, a + SZ, "range1", " "); print(a + SZ, a + SZ*2, "range2", " "); int b[SZ*2] = {0}; // Initialize all to zero merge(a, a + SZ, a + SZ, a + SZ*2, b); print(b, b + SZ*2, "merge", " "); // Reset b for(int i = 0; i < SZ*2; i++) b[i] = 0; inplace_merge(a, a + SZ, a + SZ*2); print(a, a + SZ*2, "inplace_merge", " "); int* end = set_union(a, a + SZ, a + SZ, a + SZ*2, b); print(b, end, "set_union", " "); } ///:~

10403 10404 10405

Listado 7.37. C06/MergeTest.cpp

Ejemplo

10406 10407 10408 10409

//: C06:SetOperations.cpp // Set operations on sorted ranges. //{L} Generators #include <algorithm>

401

10410 10411 10412 10413 10414 10415 10416 10417 10418 10419 10420 10421 10422 10423 10424 10425 10426 10427 10428 10429 10430 10431 10432 10433 10434

#include <vector> #include "Generators.h" #include "PrintSequence.h" using namespace std;

int main() { const int SZ = 30; char v[SZ + 1], v2[SZ + 1]; CharGen g; generate(v, v + SZ, g); generate(v2, v2 + SZ, g); sort(v, v + SZ); sort(v2, v2 + SZ); print(v, v + SZ, "v", ""); print(v2, v2 + SZ, "v2", ""); bool b = includes(v, v + SZ, v + SZ/2, v + SZ); cout.setf(ios::boolalpha); cout << "includes: " << b << endl; char v3[SZ*2 + 1], *end; end = set_union(v, v + SZ, v2, v2 + SZ, v3); print(v3, end, "set_union", ""); end = set_intersection(v, v + SZ, v2, v2 + SZ, v3); print(v3, end, "set_intersection", ""); end = set_difference(v, v + SZ, v2, v2 + SZ, v3); print(v3, end, "set_difference", "");

402

10435 10436 10437 10438

end = set_symmetric_difference(v, v + SZ, v2, v2 + SZ, v3); print(v3, end, "set_symmetric_difference",""); } ///:~

10439 10440 10441 10442 10443

Listado 7.38. C06/SetOperations.cpp

7.3.9. Operaciones sobre el montculo 7.3.10. Aplicando una operacin a cada elemento de un rango
Ejemplos

10444 10445 10446 10447 10448 10449 10450 10451 10452 10453 10454 10455 10456 10457

//: C06:Counted.h // An object that keeps track of itself. #ifndef COUNTED_H #define COUNTED_H #include <vector> #include <iostream>

class Counted { static int count; char* ident; public: Counted(char* id) : ident(id) { ++count; } ~Counted() { std::cout << ident << " count = "

403

10458 10459 10460 10461 10462 10463 10464 10465 10466 10467 10468 10469

<< --count << std::endl; } };

class CountedVector : public std::vector<Counted*> { public: CountedVector(char* id) { for(int i = 0; i < 5; i++) push_back(new Counted(id)); } }; #endif // COUNTED_H ///:~

10470 10471

Listado 7.39. C06/Counted.h

10472 10473 10474 10475 10476 10477 10478 10479 10480 10481

//: C06:ForEach.cpp {-mwcc} // Use of STL for_each() algorithm. //{L} Counted #include <algorithm> #include <iostream> #include "Counted.h" using namespace std;

// Function object: template<class T> class DeleteT {

404

10482 10483 10484 10485 10486 10487 10488 10489 10490 10491 10492 10493 10494

public: void operator()(T* x) { delete x; } };

// Template function: template<class T> void wipe(T* x) { delete x; }

int main() { CountedVector B("two"); for_each(B.begin(), B.end(), DeleteT<Counted>()); CountedVector C("three"); for_each(C.begin(), C.end(), wipe<Counted>); } ///:~

10495 10496

Listado 7.40. C06/ForEach.cpp

10497 10498 10499 10500 10501 10502 10503 10504 10505

//: C06:Transform.cpp {-mwcc} // Use of STL transform() algorithm. //{L} Counted #include <iostream> #include <vector> #include <algorithm> #include "Counted.h" using namespace std;

405

10506 10507 10508 10509 10510 10511 10512 10513 10514 10515 10516 10517 10518 10519

template<class T> T* deleteP(T* x) { delete x; return 0; }

template<class T> struct Deleter { T* operator()(T* x) { delete x; return 0; } };

int main() { CountedVector cv("one"); transform(cv.begin(), cv.end(), cv.begin(), deleteP<Counted>); CountedVector cv2("two"); transform(cv2.begin(), cv2.end(), cv2.begin(), Deleter<Counted>()); } ///:~

10520 10521

Listado 7.41. C06/Transform.cpp

10522 10523 10524 10525 10526 10527 10528 10529

//: C06:Inventory.h #ifndef INVENTORY_H #define INVENTORY_H #include <iostream> #include <cstdlib> using std::rand;

class Inventory {

406

10530 10531 10532 10533 10534 10535 10536 10537 10538 10539 10540 10541 10542 10543 10544 10545 10546 10547 10548 10549 10550 10551 10552 10553 10554

char item; int quantity; int value; public: Inventory(char it, int quant, int val) : item(it), quantity(quant), value(val) {} // Synthesized operator= & copy-constructor OK char getItem() const { return item; } int getQuantity() const { return quantity; } void setQuantity(int q) { quantity = q; } int getValue() const { return value; } void setValue(int val) { value = val; } friend std::ostream& operator<<( std::ostream& os, const Inventory& inv) { return os << inv.item << ": " << "quantity " << inv.quantity << ", value " << inv.value; } };

// A generator: struct InvenGen { Inventory operator()() { static char c = 'a'; int q = rand() % 100;

407

10555 10556 10557 10558 10559

int v = rand() % 500; return Inventory(c++, q, v); } }; #endif // INVENTORY_H ///:~

10560 10561

Listado 7.42. C06/Inventory.h

10562 10563 10564 10565 10566 10567 10568 10569 10570 10571 10572 10573 10574 10575 10576 10577 10578

//: C06:CalcInventory.cpp // More use of for_each(). #include <algorithm> #include <ctime> #include <vector> #include "Inventory.h" #include "PrintSequence.h" using namespace std;

// To calculate inventory totals: class InvAccum { int quantity; int value; public: InvAccum() : quantity(0), value(0) {} void operator()(const Inventory& inv) { quantity += inv.getQuantity();

408

10579 10580 10581 10582 10583 10584 10585 10586 10587 10588 10589 10590 10591 10592 10593 10594 10595

value += inv.getQuantity() * inv.getValue(); } friend ostream& operator<<(ostream& os, const InvAccum& ia) { return os << "total quantity: " << ia.quantity << ", total value: " << ia.value; } };

int main() { vector<Inventory> vi; srand(time(0)); // Randomize

generate_n(back_inserter(vi), 15, InvenGen()); print(vi.begin(), vi.end(), "vi"); InvAccum ia = for_each(vi.begin(),vi.end(), InvAccum()); cout << ia << endl; } ///:~

10596 10597

Listado 7.43. C06/CalcInventory.cpp

10598 10599 10600 10601 10602

//: C06:TransformNames.cpp // More use of transform(). #include <algorithm> #include <cctype> #include <ctime>

409

10603 10604 10605 10606 10607 10608 10609 10610 10611 10612 10613 10614 10615 10616 10617 10618 10619 10620 10621 10622

#include <vector> #include "Inventory.h" #include "PrintSequence.h" using namespace std;

struct NewImproved { Inventory operator()(const Inventory& inv) { return Inventory(toupper(inv.getItem()), inv.getQuantity(), inv.getValue()); } };

int main() { vector<Inventory> vi; srand(time(0)); // Randomize

generate_n(back_inserter(vi), 15, InvenGen()); print(vi.begin(), vi.end(), "vi"); transform(vi.begin(),vi.end(),vi.begin(),NewImproved()); print(vi.begin(), vi.end(), "vi"); } ///:~

10623 10624

Listado 7.44. C06/TransformNames.cpp

10625 10626

//: C06:SpecialList.cpp // Using the second version of transform().

410

10627 10628 10629 10630 10631 10632 10633 10634 10635 10636 10637 10638 10639 10640 10641 10642 10643 10644 10645 10646 10647 10648 10649 10650 10651

#include <algorithm> #include <ctime> #include <vector> #include "Inventory.h" #include "PrintSequence.h" using namespace std;

struct Discounter { Inventory operator()(const Inventory& inv, float discount) { return Inventory(inv.getItem(), inv.getQuantity(), int(inv.getValue() * (1 - discount))); } };

struct DiscGen { float operator()() { float r = float(rand() % 10); return r / 100.0; } };

int main() { vector<Inventory> vi; srand(time(0)); // Randomize

411

10652 10653 10654 10655 10656 10657 10658 10659 10660 10661

generate_n(back_inserter(vi), 15, InvenGen()); print(vi.begin(), vi.end(), "vi"); vector<float> disc; generate_n(back_inserter(disc), 15, DiscGen()); print(disc.begin(), disc.end(), "Discounts:"); vector<Inventory> discounted; transform(vi.begin(),vi.end(), disc.begin(), back_inserter(discounted), Discounter()); print(discounted.begin(), discounted.end(),"discounted"); } ///:~

10662 10663 10664 10665

Listado 7.45. C06/SpecialList.cpp

7.3.11. Algoritmos numricos


Ejemplo

10666 10667 10668 10669 10670 10671 10672 10673 10674

//: C06:NumericTest.cpp #include <algorithm> #include <iostream> #include <iterator> #include <functional> #include <numeric> #include "PrintSequence.h" using namespace std;

412

10675 10676 10677 10678 10679 10680 10681 10682 10683 10684 10685 10686 10687 10688 10689 10690 10691 10692 10693 10694 10695 10696 10697 10698 10699

int main() { int a[] = { 1, 1, 2, 2, 3, 5, 7, 9, 11, 13 }; const int ASZ = sizeof a / sizeof a[0]; print(a, a + ASZ, "a", " "); int r = accumulate(a, a + ASZ, 0); cout << "accumulate 1: " << r << endl; // Should produce the same result: r = accumulate(a, a + ASZ, 0, plus<int>()); cout << "accumulate 2: " << r << endl; int b[] = { 1, 2, 3, 4, 1, 2, 3, 4, 1, 2 }; print(b, b + sizeof b / sizeof b[0], "b", " "); r = inner_product(a, a + ASZ, b, 0); cout << "inner_product 1: " << r << endl; // Should produce the same result: r = inner_product(a, a + ASZ, b, 0, plus<int>(), multiplies<int>()); cout << "inner_product 2: " << r << endl; int* it = partial_sum(a, a + ASZ, b); print(b, it, "partial_sum 1", " "); // Should produce the same result: it = partial_sum(a, a + ASZ, b, plus<int>()); print(b, it, "partial_sum 2", " "); it = adjacent_difference(a, a + ASZ, b); print(b, it, "adjacent_difference 1"," "); // Should produce the same result:

413

10700 10701 10702

it = adjacent_difference(a, a + ASZ, b, minus<int>()); print(b, it, "adjacent_difference 2"," "); } ///:~

10703 10704 10705

Listado 7.46. C06/NumericTest.cpp

7.3.12. Utilidades generales

10706

7.4. Creando sus propios algoritmos tipo STL


// Assumes pred is the incoming condition replace_copy_if(begin, end, not1(pred));

10707 10708

10709 10710 10711 10712 10713 10714 10715 10716 10717 10718 10719 10720 10721

//: C06:copy_if.h // Create your own STL-style algorithm. #ifndef COPY_IF_H #define COPY_IF_H

template<typename ForwardIter, typename OutputIter, typename UnaryPred> OutputIter copy_if(ForwardIter begin, ForwardIter end, OutputIter dest, UnaryPred f) { while(begin != end) { if(f(*begin)) *dest++ = *begin; ++begin;

414

10722 10723 10724 10725

} return dest; } #endif // COPY_IF_H ///:~

10726 10727

Listado 7.47. C06/copy_if.h

10728

7.5. Resumen 7.6. Ejercicios

10729 10730 10731 10732


[17]

N. de T.: Librera Estndar de Plantillas.

10733 10734 10735 10736 10737 10738 10739 10740 10741 10742 10743 10744 10745 10746

Parte III. Temas especiales


La marca de un profesional aparece en su atencin a los detalles ms finos del oficio. En esta seccin del libro veremos caractersticas avanzadas de C++ junto con tcnicas de desarrollo usadas por profesionales brillantes de C++.

A veces necesita salir de los convencionalismos que suenan a diseo orientado a objetos, inspeccionando el tipo de un objeto en tiempo de ejecucin. La mayora de las veces debera dejar que las funciones virtuales hagan ese trabajo por usted, pero cuando escriba herramientas software para propsitos especiales, tales como depuradores, visualizadores de bases de datos, o navegadores de clases, necesitar determinar la informacin de tipado en tiempo de ejecucin. Ah es cuando el mecanismo de identificacin de tipo en tiempo de ejecucin (RTTI) resulta til. RTTI es el tema del Captulo 8.

415
10747 10748 10749 10750 10751 10752 10753 10754 10755 10756 10757 10758 10759 10760 10761 10762 10763 10764 10765 10766 10767 10768 10769 10770 10771 10772 10773 10774 10775 10776 10777 10778 10779 10780 10781 10782 10783 10784 10785 10786 La herencia mltiple ha sido maltratado a lo largo de los aos, y algunos lenguajes incluso no la permiten. No obstante, cuando se usa adecuadamente, puede ser una herramienta potente para conseguir cdigo eficiente y elegante. Un buen nmero de prcticas estndar que involucran herencia mltiple han evolucionado con el tiempo; las veremos en el Captulo 9. Quizs la innovacin ms notable en el desarrollo de software desde las tcnicas de orientacin a objetos es el uso de los patrones de diseo. Un patrn de diseo describe soluciones para muchos problemas comunes del diseo de software, y se puede aplicar a muchas situaciones e implementacin en cualquier lenguaje. En el Captulo 10 describiremos una seleccin de patrones de diseo y los implementaremos en C++. El Captulo 11 explica los beneficios y desafos de la programacin multihilo. La versin actual de C++ Estndar no especifica soporte para hilos, aunque la mayora de los sistema operativos los ofrecen. Usaremos un librera portable y disponible libremente para ilustrar cmo los programadores pueden sacar provecho de los hilos para construir aplicaciones ms usables y receptivas. Tabla de contenidos 8. Herencia mltiple 8.1. Perspectiva 8.2. Herencia de interfaces 8.3. Herencia de implementacin 8.4. Subobjetos duplicados 8.5. Clases base virtuales 8.6. Cuestin sobre bsqueda de nombres 8.7. Evitar la MI 8.8. Extender una interface 8.9. Resumen 8.10. Ejercicios 9. Patrones de Diseo 9.1. El Concepto de Patrn 9.2. Clasificacin de los patrones 9.3. Simplificacin de modismos 9.4. Singleton 9.5. Comando: elegir la operacin 9.6. Desacoplamiento de objetos 9.7. Adaptador 9.8. Template Method 9.9. Estrategia: elegir el algoritno en tiempo de ejecucin

416
10787 10788 10789 10790 10791 10792 10793 10794 10795 10796 10797 10798 10799 10800 10801 10802 10803 10804 10805 10806 10807 10808 10809 10810 10811 10812 10813 10814 10815 10816 10817 10818 10819 10820 10821 10822 10823 10824 10825 10826 9.10. Cadena de Responsabilidad: intentar una secuencia de estrategias 9.11. Factoras: encapsular la creacin de objetos 9.12. Builder: creacin de objetos complejos 9.13. Observador 9.14. Despachado mltiple 9.15. Resumen 9.16. Ejercicios 10. Concurrencia 10.1. Motivacin 10.2. Concurrencia en C++ 10.3. Utilizacin de los hilos 10.4. Comparicin de recursos limitados 10.5. Finalizacin de tareas 10.6. Cooperacin entre hilos 10.7. Bloqueo letal 10.8. Resumen 10.9. Ejercicios

8: Herencia mltiple
Tabla de contenidos 8.1. Perspectiva 8.2. Herencia de interfaces 8.3. Herencia de implementacin 8.4. Subobjetos duplicados 8.5. Clases base virtuales 8.6. Cuestin sobre bsqueda de nombres 8.7. Evitar la MI 8.8. Extender una interface 8.9. Resumen 8.10. Ejercicios El concepto bsico de la herencia mltiple (HM) suena bastante simple: puede crear un nuevo tipo heredando de ms una una clase base. La sintaxis es exactamente la que espera, y en la medida en que los diagramas de herencia sean simples, la HM puede ser simple tambin. Sin embargo, la HM puede presentar un buen nmero de situaciones ambiguas y extraas, que se cubren en este captulo. Pero primero, es til tener algo de perspectiva sobre el asunto.

8.1. Perspectiva
Antes de C++, el lenguaje orientado a objetos ms popular era Smaltalk. Smaltalk fue creado desde cero como un lenguaje orientado a objetos. A

417
10827 10828 10829 10830 10831 10832 10833 10834 10835 10836 10837 10838 10839 10840 10841 10842 10843 10844 10845 10846 10847 10848 10849 10850 10851 10852 10853 10854 10855 10856 10857 10858 10859 menudo se dice que es puro, mientras que a C++ se le llama lenguaje hbrido porque soporta mltiples paradigmas de programacin, no slo el paradigma orientado a objeto. Uno de las decisiones de diseo de Smalltalk fue que todas las clases tendran solo herencia simple, empezando en una clase base (llamada Object - ese es el modelo para la jerarqua basada en objetos) [18] En Smalltalk no puede crear una nueva clase sin derivar de un clase existente, que es la razn por la que lleva cierto tiempo ser productivo con Smalltalk: debe aprender la librera de clases antes de empezar a hacer clases nuevas. La jerarqua de clases de Smalltalk es por tanto un nico rbol monoltico. Las clases de Smalltalk normalmente tienen ciertas cosas en comn, y siempre tienen algunas cosas en comn (las caractersticas y el comportamiento de Object), de modo que no suelen aparecer situaciones en las que se necesite heredad de ms de una clase base. Sin embargo, con C++ puede crear tantos rboles de herencia distintos como quiera. Por completitud lgica el lenguaje debe ser capaz de combinar ms de una clase a la vez - por eso la necesidad de herencia mltiple. No fue obvio, sin embargo, que los programadores requiriesen herencia mltiple, y haba (y sigue habiendo) mucha discrepancia sobre si es algo esencial en C++. La HM fue aadida en cfront release 2.0 de AT&T en 1989 y fue el primer cambio significativo en el lenguaje desde la versin 1.0. [19] Desde entonces, se han aadido muchas caractersticas al Estndar C++ (las plantillas son dignas de mencin) que cambian la manera de pensar al programar y le dan a la HM un papel mucho menos importante. Puede pensar en la HM como una prestacin menor del lenguaje que raramente est involucrada en las decisiones de diseo diarias. Uno de los argumentos ms convincentes para la HM involucra a los contenedores. Suponga que quiere crear un contenedor que todo el mundo pueda usar fcilmente. Una propuesta es usar void* como tipo para el contenido. La propuesta de Smalltalk, sin embargo, es hacer un contenedor que aloja Object, dado que Object es el tipo base de la jerarqua de Smalltalk. Como todo en Smalltalk est derivado de Object, un contenedor que aloja Objects puede contener cualquier cosa.

418
10860 10861 10862 10863 10864 10865 10866 10867 Ahora considere la situacin en C++. Suponga que el fabricante A crea una jerarqua basada-en-objetos que incluye un conjunto de contenedores incluye uno que desea usar llamado Holder. Despus, se da cuenta de que la jerarqua de clases del fabricante B contiene alguna clase que tambin es importante para usted, una clase BitImage, por ejemplo, que contiene imgenes. La nica forma de hacer que un Holder de BitImage es derivar de una nueva clase que derive tambin de Object, y as poder almacenarlos en el Holder, y BitImage:

10868 10869 10870 10871 10872 10873 10874 10875 10876 10877 10878 Figura 8.1.

ste fue un motivo importante para la HM, y muchas libreras de clases estn hechas con este model. Sin embargo, tal como se vio en el Captulo 5, la aportacin de las plantillas ha cambiado la forma de crear contenedores, y por eso esta situacin ya no es un asunto crucial en favor de la HM. El otro motivo por el que se necesita la HM est relacionado con el diseo. Puede usar la HM intencionadamente para hacer un diseo ms flexible y til (o al menos aparentarlo). Un ejemplo de esto es el diseo de la librera original iostream (que persiste hoy da, como vio en el Captulo 4.

10879 10880 10881 10882 10883 10884 10885 10886 10887 10888 Figura 8.2.

Tanto iostream como ostream son clases tiles por si mismas, pero se pueden derivar simultneamente por una clase que combina sus caractersticas y comportamientos. La clase ios proporciona una combinacin de las dos clases, y por eso en este caso la HM es un mecanismo de FIXME:code-factoring. Sin importar lo que le motive a usar HM, debe saber que es ms difcil de usar de lo que podra parecer.

10889

8.2. Herencia de interfaces

419
10890 10891 10892 10893 10894 10895 10896 10897 10898 10899 10900 10901 10902 10903 10904 10905 10906 Un uso no controvertido de la herencia mltiple es la herencia de interfaz. En C++, toda herencia lo es de implementacin, dado que todo en una clase base, interface e implentacin, pasa a formar parte de la clase derivada. No es posible heredar solo una parte de una clase (es decir, la interface nicamente). Tal como se explica en el [FIXME:enlace en la versin web] Captulo 14 del volumen 1, es posible hacer herencia privada y protegida para restringir el acceso a los miembros heredados desde las clases base cuando se usa por clientes de instancias de una clase derivada, pero esto no afecta a la propia clase derivada; esa clase sigue conteniendo todos los datos de la clase base y puede acceder a todos los miembros no-privados de la clase base. La herencia de interfaces. por otra parte, slo aade declaraciones de miembros a la interfaz de la clase derivada, algo que no est soportado directamente en C++. La tcnica habitual para simular la herencia de interfaz en C++ es derivar de una clase interfaz, que es una clase que slo contiene declaraciones (ni datos ni cuerpos de funciones). Estas declaraciones sern funciones virtuales puras, excepto el destructor. Aqu hay un ejemplo:

10907 10908 10909 10910 10911 10912 10913 10914 10915 10916 10917 10918

//: C09:Interfaces.cpp // Multiple interface inheritance. #include <iostream> #include <sstream> #include <string> using namespace std;

class Printable { public: virtual ~Printable() {} virtual void print(ostream&) const = 0; };

420

10919 10920 10921 10922 10923 10924 10925 10926 10927 10928 10929 10930 10931 10932 10933 10934 10935 10936 10937 10938 10939 10940 10941 10942 10943
class Able : public Printable, public Intable, public Stringable { int myData; public: Able(int x) { myData = x; } void print(ostream& os) const { os << myData; } int toInt() const { return myData; } string toString() const { ostringstream os; os << myData; return os.str(); } class Stringable { public: virtual ~Stringable() {} virtual string toString() const = 0; }; class Intable { public: virtual ~Intable() {} virtual int toInt() const = 0; };

421

10944 10945 10946 10947 10948 10949 10950 10951 10952 10953 10954 10955 10956 10957 10958 10959 10960 10961 10962 10963 10964

};

void testPrintable(const Printable& p) { p.print(cout); cout << endl; }

void testIntable(const Intable& n) { cout << n.toInt() + 1 << endl; }

void testStringable(const Stringable& s) { cout << s.toString() + "th" << endl; }

int main() { Able a(7); testPrintable(a); testIntable(a); testStringable(a); } ///:~

10965 10966 10967 10968 10969

Listado 8.1. C09/Interfaces.cpp La clase Able implementa las interfaces Printable, Intable y
Stringable

dado que proporciona implementaciones para las funciones que

stas declaran. Dado que Able deriva de las tres clases, los objetos Able

422
10970 10971 10972 10973 10974 10975 tienen mltiples relaciones es-un. Por ejemplo, el objeto a puede actuar como un objeto Printable dado que su clase, Able, deriva pblicamente de
Printable

y proporciona una implementacin para print(). Las funciones de

prueba no necesitan saber el tipo ms derivado de su parmetro; slo necesitan un objeto que sea substituible por el tipo de su parmetro. Como es habitual, una plantilla es una solucin ms compacta:

10976 10977 10978 10979 10980 10981 10982 10983 10984 10985 10986 10987 10988 10989 10990 10991 10992 10993 10994 10995

//: C09:Interfaces2.cpp // Implicit interface inheritance via templates. #include <iostream> #include <sstream> #include <string> using namespace std;

class Able { int myData; public: Able(int x) { myData = x; } void print(ostream& os) const { os << myData; } int toInt() const { return myData; } string toString() const { ostringstream os; os << myData; return os.str(); } };

423

10996 10997 10998 10999 11000 11001 11002 11003 11004 11005 11006 11007 11008 11009 11010 11011 11012 11013 11014 11015 11016 11017

template<class Printable> void testPrintable(const Printable& p) { p.print(cout); cout << endl; }

template<class Intable> void testIntable(const Intable& n) { cout << n.toInt() + 1 << endl; }

template<class Stringable> void testStringable(const Stringable& s) { cout << s.toString() + "th" << endl; }

int main() { Able a(7); testPrintable(a); testIntable(a); testStringable(a); } ///:~

11018 11019

Listado 8.2. C09/Interfaces2.cpp

424
11020 11021 11022 11023 11024 11025 11026 11027 11028 11029 11030 11031 11032 11033 Los nombres Printable, Intable y Stringable ahora no son mas que parmetros de la plantilla que asume la existencia de las operaciones indicadas en sus respectivos argumentos. En otras palabras, las funciones de prueba pueden aceptar argumentos de cualquier tipo que proporciona una definicin de mtodo con la signatura y tipo de retorno correctos. Hay gente que encuentra ms cmoda la primera versin porque los nombres de tipo garantizan que las interfaces esperadas estn implementadas. Otros estn contentos con el hecho de que si las operaciones requeridas por las funciones de prueba no se satisfacen por los argumentos de la plantilla, el error puede ser capturado en la compilacin. Esta segunda es una forma de comprobacin de tipos tcnicamente ms dbil que el primer enfoque (herencia), pero el efecto para el programador (y el programa) es el mismo. Se trata de una forma de comprobacin dbil de tipo que es aceptable para muchos de los programadores C++ de hoy en da.

11034

8.3. Herencia de implementacin


//: C09:Database.h // A prototypical resource class. #ifndef DATABASE_H #define DATABASE_H #include <iostream> #include <stdexcept> #include <string>

11035 11036 11037 11038 11039 11040 11041 11042 11043 11044 11045 11046 11047

struct DatabaseError : std::runtime_error { DatabaseError(const std::string& msg) : std::runtime_error(msg) {} };

425

11048 11049 11050 11051 11052 11053 11054 11055 11056 11057 11058 11059 11060 11061

class Database { std::string dbid; public: Database(const std::string& dbStr) : dbid(dbStr) {} virtual ~Database() {} void open() throw(DatabaseError) { std::cout << "Connected to " << dbid << std::endl; } void close() { std::cout << dbid << " closed" << std::endl; } // Other database functions... }; #endif // DATABASE_H ///:~

11062 11063

Listado 8.3. C09/Database.h

11064 11065 11066 11067 11068 11069 11070 11071

//: C09:UseDatabase.cpp #include "Database.h"

int main() { Database db("MyDatabase"); db.open(); // Use other db functions... db.close();

426

11072 11073 11074 11075 11076

} /* Output: connected to MyDatabase MyDatabase closed */ ///:~

11077 11078

Listado 8.4. C09/UseDatabase.cpp

11079 11080 11081 11082 11083 11084 11085 11086 11087 11088 11089 11090 11091 11092 11093 11094 11095

//: C09:Countable.h // A "mixin" class. #ifndef COUNTABLE_H #define COUNTABLE_H #include <cassert>

class Countable { long count; protected: Countable() { count = 0; } virtual ~Countable() { assert(count == 0); } public: long attach() { return ++count; } long detach() { return (--count > 0) ? count : (delete this, 0); } long refCount() const { return count; }

427

11096 11097

}; #endif // COUNTABLE_H ///:~

11098 11099

Listado 8.5. C09/Countable.h

11100 11101 11102 11103 11104 11105 11106 11107 11108 11109 11110 11111 11112 11113 11114 11115 11116 11117 11118 11119

//: C09:DBConnection.h // Uses a "mixin" class. #ifndef DBCONNECTION_H #define DBCONNECTION_H #include <cassert> #include <string> #include "Countable.h" #include "Database.h" using std::string;

class DBConnection : public Database, public Countable { DBConnection(const DBConnection&); // Disallow copy DBConnection& operator=(const DBConnection&); protected: DBConnection(const string& dbStr) throw(DatabaseError) : Database(dbStr) { open(); } ~DBConnection() { close(); } public: static DBConnection* create(const string& dbStr) throw(DatabaseError) {

428

11120 11121 11122 11123 11124 11125 11126 11127

DBConnection* con = new DBConnection(dbStr); con->attach(); assert(con->refCount() == 1); return con; } // Other added functionality as desired... }; #endif // DBCONNECTION_H ///:~

11128 11129

Listado 8.6. C09/DBConnection.h

11130 11131 11132 11133 11134 11135 11136 11137 11138 11139 11140 11141 11142 11143

//: C09:UseDatabase2.cpp // Tests the Countable "mixin" class. #include <cassert> #include "DBConnection.h"

class DBClient { DBConnection* db; public: DBClient(DBConnection* dbCon) { db = dbCon; db->attach(); } ~DBClient() { db->detach(); } // Other database requests using db...

429

11144 11145 11146 11147 11148 11149 11150 11151 11152 11153 11154 11155 11156

};

int main() { DBConnection* db = DBConnection::create("MyDatabase"); assert(db->refCount() == 1); DBClient c1(db); assert(db->refCount() == 2); DBClient c2(db); assert(db->refCount() == 3); // Use database, then release attach from original create db->detach(); assert(db->refCount() == 2); } ///:~

11157 11158

Listado 8.7. C09/UseDatabase2.cpp

11159 11160 11161 11162 11163 11164 11165 11166 11167

//: C09:DBConnection2.h // A parameterized mixin. #ifndef DBCONNECTION2_H #define DBCONNECTION2_H #include <cassert> #include <string> #include "Database.h" using std::string;

430

11168 11169 11170 11171 11172 11173 11174 11175 11176 11177 11178 11179 11180 11181 11182 11183 11184 11185 11186

template<class Counter> class DBConnection : public Database, public Counter { DBConnection(const DBConnection&); // Disallow copy DBConnection& operator=(const DBConnection&); protected: DBConnection(const string& dbStr) throw(DatabaseError) : Database(dbStr) { open(); } ~DBConnection() { close(); } public: static DBConnection* create(const string& dbStr) throw(DatabaseError) { DBConnection* con = new DBConnection(dbStr); con->attach(); assert(con->refCount() == 1); return con; } // Other added functionality as desired... }; #endif // DBCONNECTION2_H ///:~

11187 11188

Listado 8.8. C09/DBConnection2.h

11189 11190 11191

//: C09:UseDatabase3.cpp // Tests a parameterized "mixin" class. #include <cassert>

431

11192 11193 11194 11195 11196 11197 11198 11199 11200 11201 11202 11203 11204 11205 11206 11207 11208 11209 11210 11211 11212 11213 11214 11215

#include "Countable.h" #include "DBConnection2.h"

class DBClient { DBConnection<Countable>* db; public: DBClient(DBConnection<Countable>* dbCon) { db = dbCon; db->attach(); } ~DBClient() { db->detach(); } };

int main() { DBConnection<Countable>* db = DBConnection<Countable>::create("MyDatabase"); assert(db->refCount() == 1); DBClient c1(db); assert(db->refCount() == 2); DBClient c2(db); assert(db->refCount() == 3); db->detach(); assert(db->refCount() == 2); } ///:~

432
11216 11217 Listado 8.9. C09/UseDatabase3.cpp

11218 11219 11220 11221 11222

template<class Mixin1, class Mixin2, ?? , class MixinK> class Subject : public Mixin1, public Mixin2, ?? public MixinK {??};

11223

8.4. Subobjetos duplicados


//: C09:Offset.cpp // Illustrates layout of subobjects with MI. #include <iostream> using namespace std;

11224 11225 11226 11227 11228 11229 11230 11231 11232 11233 11234 11235 11236 11237 11238

class A { int x; }; class B { int y; }; class C : public A, public B { int z; };

int main() { cout << "sizeof(A) == " << sizeof(A) << endl; cout << "sizeof(B) == " << sizeof(B) << endl; cout << "sizeof(C) == " << sizeof(C) << endl; C c; cout << "&c == " << &c << endl;

433

11239 11240 11241 11242 11243 11244 11245 11246 11247 11248 11249 11250 11251 11252 11253 11254 11255 11256 11257 11258 11259 11260

A* ap = &c; B* bp = &c; cout << "ap == " << static_cast<void*>(ap) << endl; cout << "bp == " << static_cast<void*>(bp) << endl; C* cp = static_cast<C*>(bp); cout << "cp == " << static_cast<void*>(cp) << endl; cout << "bp == cp? " << boolalpha << (bp == cp) << endl; cp = 0; bp = cp; cout << bp << endl; } /* Output: sizeof(A) == 4 sizeof(B) == 4 sizeof(C) == 12 &c == 1245052 ap == 1245052 bp == 1245056 cp == 1245052 bp == cp? true 0 */ ///:~

11261 11262

Listado 8.10. C09/Offset.cpp

434

11263 11264 11265 11266 11267 11268 11269 11270 11271 11272 11273 11274 11275 11276 11277 11278 11279 11280 11281 11282 11283 11284 11285 11286 11287

//: C09:Duplicate.cpp // Shows duplicate subobjects. #include <iostream> using namespace std;

class Top { int x; public: Top(int n) { x = n; } };

class Left : public Top { int y; public: Left(int m, int n) : Top(m) { y = n; } };

class Right : public Top { int z; public: Right(int m, int n) : Top(m) { z = n; } };

class Bottom : public Left, public Right { int w;

435

11288 11289 11290 11291 11292 11293 11294 11295 11296

public: Bottom(int i, int j, int k, int m) : Left(i, k), Right(j, k) { w = m; } };

int main() { Bottom b(1, 2, 3, 4); cout << sizeof b << endl; // 20 } ///:~

11297 11298

Listado 8.11. C09/Duplicate.cpp

11299

8.5. Clases base virtuales


//: C09:VirtualBase.cpp // Shows a shared subobject via a virtual base. #include <iostream> using namespace std;

11300 11301 11302 11303 11304 11305 11306 11307 11308 11309 11310

class Top { protected: int x; public: Top(int n) { x = n; } virtual ~Top() {}

436

11311 11312 11313 11314 11315 11316 11317 11318 11319 11320 11321 11322 11323 11324 11325 11326 11327 11328 11329 11330 11331 11332 11333 11334 11335

friend ostream& operator<<(ostream& os, const Top& t) { return os << t.x; } };

class Left : virtual public Top { protected: int y; public: Left(int m, int n) : Top(m) { y = n; } };

class Right : virtual public Top { protected: int z; public: Right(int m, int n) : Top(m) { z = n; } };

class Bottom : public Left, public Right { int w; public: Bottom(int i, int j, int k, int m) : Top(i), Left(0, j), Right(0, k) { w = m; }

437

11336 11337 11338 11339 11340 11341 11342 11343 11344 11345 11346 11347 11348 11349 11350 11351 11352

friend ostream& operator<<(ostream& os, const Bottom& b) { return os << b.x << ',' << b.y << ',' << b.z << ',' << b.w; } };

int main() { Bottom b(1, 2, 3, 4); cout << sizeof b << endl; cout << b << endl; cout << static_cast<void*>(&b) << endl; Top* p = static_cast<Top*>(&b); cout << *p << endl; cout << static_cast<void*>(p) << endl; cout << dynamic_cast<void*>(p) << endl; } ///:~

11353 11354 11355 11356 11357 11358 11359 11360

Listado 8.12. C09/VirtualBase.cpp

36 1,2,3,4 1245032 1 1245060 1245032

11361 11362

//: C09:VirtualBase2.cpp // How NOT to implement operator<<.

438

11363 11364 11365 11366 11367 11368 11369 11370 11371 11372 11373 11374 11375 11376 11377 11378 11379 11380 11381 11382 11383 11384 11385 11386 11387

#include <iostream> using namespace std;

class Top { int x; public: Top(int n) { x = n; } virtual ~Top() {} friend ostream& operator<<(ostream& os, const Top& t) { return os << t.x; } };

class Left : virtual public Top { int y; public: Left(int m, int n) : Top(m) { y = n; } friend ostream& operator<<(ostream& os, const Left& l) { return os << static_cast<const Top&>(l) << ',' << l.y; } };

class Right : virtual public Top { int z; public:

439

11388 11389 11390 11391 11392 11393 11394 11395 11396 11397 11398 11399 11400 11401 11402 11403 11404 11405 11406 11407 11408 11409

Right(int m, int n) : Top(m) { z = n; } friend ostream& operator<<(ostream& os, const Right& r) { return os << static_cast<const Top&>(r) << ',' << r.z; } };

class Bottom : public Left, public Right { int w; public: Bottom(int i, int j, int k, int m) : Top(i), Left(0, j), Right(0, k) { w = m; } friend ostream& operator<<(ostream& os, const Bottom& b){ return os << static_cast<const Left&>(b) << ',' << static_cast<const Right&>(b) << ',' << b.w; } };

int main() { Bottom b(1, 2, 3, 4); cout << b << endl; } ///:~ // 1,2,1,3,4

11410 11411

Listado 8.13. C09/VirtualBase2.cpp

440

11412 11413 11414 11415 11416 11417 11418 11419 11420 11421 11422 11423 11424 11425 11426 11427 11428 11429 11430 11431 11432 11433 11434 11435 11436

//: C09:VirtualBase3.cpp // A correct stream inserter. #include <iostream> using namespace std;

class Top { int x; public: Top(int n) { x = n; } virtual ~Top() {} friend ostream& operator<<(ostream& os, const Top& t) { return os << t.x; } };

class Left : virtual public Top { int y; protected: void specialPrint(ostream& os) const { // Only print Left's part os << ','<< y; } public: Left(int m, int n) : Top(m) { y = n; } friend ostream& operator<<(ostream& os, const Left& l) {

441

11437 11438 11439 11440 11441 11442 11443 11444 11445 11446 11447 11448 11449 11450 11451 11452 11453 11454 11455 11456 11457 11458 11459 11460 11461

return os << static_cast<const Top&>(l) << ',' << l.y; } };

class Right : virtual public Top { int z; protected: void specialPrint(ostream& os) const { // Only print Right's part os << ','<< z; } public: Right(int m, int n) : Top(m) { z = n; } friend ostream& operator<<(ostream& os, const Right& r) { return os << static_cast<const Top&>(r) << ',' << r.z; } };

class Bottom : public Left, public Right { int w; public: Bottom(int i, int j, int k, int m) : Top(i), Left(0, j), Right(0, k) { w = m; } friend ostream& operator<<(ostream& os, const Bottom& b){ os << static_cast<const Top&>(b);

442

11462 11463 11464 11465 11466 11467 11468 11469 11470 11471

b.Left::specialPrint(os); b.Right::specialPrint(os); return os << ',' << b.w; } };

int main() { Bottom b(1, 2, 3, 4); cout << b << endl; } ///:~ // 1,2,3,4

11472 11473

Listado 8.14. C09/VirtualBase3.cpp

11474 11475 11476 11477 11478 11479 11480 11481 11482 11483 11484 11485

//: C09:VirtInit.cpp // Illustrates initialization order with virtual bases. #include <iostream> #include <string> using namespace std;

class M { public: M(const string& s) { cout << "M " << s << endl; } };

class A {

443

11486 11487 11488 11489 11490 11491 11492 11493 11494 11495 11496 11497 11498 11499 11500 11501 11502 11503 11504 11505 11506 11507 11508 11509 11510

M m; public: A(const string& s) : m("in A") { cout << "A " << s << endl; } virtual ~A() {} };

class B { M m; public: B(const string& s) : m("in B") cout << "B " << s << endl; } virtual ~B() {} }; {

class C { M m; public: C(const string& s) : m("in C") cout << "C " << s << endl; } virtual ~C() {} }; {

444

11511 11512 11513 11514 11515 11516 11517 11518 11519 11520 11521 11522 11523 11524 11525 11526 11527 11528 11529 11530 11531 11532 11533 11534 11535
class F : virtual public B, virtual public C, public D { M m; public: F(const string& s) : B("from F"), C("from F"), D("from F"), m("in F") { cout << "F " << s << endl; class E : public A, virtual public B, virtual public C { M m; public: E(const string& s) : A("from E"), B("from E"), C("from E"), m("in E") { cout << "E " << s << endl; } }; class D { M m; public: D(const string& s) : m("in D") { cout << "D " << s << endl; } virtual ~D() {} };

445

11536 11537 11538 11539 11540 11541 11542 11543 11544 11545 11546 11547 11548 11549 11550

} };

class G : public E, public F { M m; public: G(const string& s) : B("from G"), C("from G"), E("from G"), F("from G"), m("in G") {

cout << "G " << s << endl; } };

int main() { G g("from main"); } ///:~

11551 11552 11553 11554 11555 11556 11557 11558 11559 11560 11561 11562 11563 11564 11565 11566

Listado 8.15. C09/VirtInit.cpp

M in B B from G M in C C from G M in A A from E M in E E from G M in D D from F M in F F from G M in G G from main

446
11567

8.6. Cuestin sobre bsqueda de nombres


//: C09:AmbiguousName.cpp {-xo}

11568 11569 11570 11571 11572 11573 11574 11575 11576 11577 11578 11579 11580 11581 11582 11583 11584 11585 11586 11587 11588 11589 11590

class Top { public: virtual ~Top() {} };

class Left : virtual public Top { public: void f() {} };

class Right : virtual public Top { public: void f() {} };

class Bottom : public Left, public Right {};

int main() { Bottom b; b.f(); // Error here } ///:~

447
11591 11592 Listado 8.16. C09/AmbiguousName.cpp

11593 11594 11595 11596 11597 11598 11599 11600 11601 11602 11603 11604 11605 11606 11607 11608 11609 11610 11611 11612 11613 11614 11615

//: C09:BreakTie.cpp

class Top { public: virtual ~Top() {} };

class Left : virtual public Top { public: void f() {} };

class Right : virtual public Top { public: void f() {} };

class Bottom : public Left, public Right { public: using Left::f; };

int main() {

448

11616 11617 11618

Bottom b; b.f(); // Calls Left::f() } ///:~

11619 11620

Listado 8.17. C09/BreakTie.cpp

11621 11622 11623 11624 11625 11626 11627 11628 11629 11630 11631 11632 11633 11634 11635 11636 11637 11638 11639

//: C09:Dominance.cpp

class Top { public: virtual ~Top() {} virtual void f() {} };

class Left : virtual public Top { public: void f() {} };

class Right : virtual public Top {};

class Bottom : public Left, public Right {};

int main() { Bottom b;

449

11640 11641

b.f(); // Calls Left::f() } ///:~

11642 11643

Listado 8.18. C09/Dominance.cpp

11644 11645 11646 11647 11648 11649 11650 11651 11652 11653 11654 11655 11656 11657 11658 11659 11660 11661 11662 11663

//: C09:Dominance2.cpp #include <iostream> using namespace std;

class A { public: virtual ~A() {} virtual void f() { cout << "A::f\n"; } };

class B : virtual public A { public: void f() { cout << "B::f\n"; } };

class C : public B {}; class D : public C, virtual public A {};

int main() { B* p = new D;

450

11664 11665 11666

p->f(); // Calls B::f() delete p; } ///:~

11667 11668

Listado 8.19. C09/Dominance2.cpp

11669

8.7. Evitar la MI 8.8. Extender una interface


//: C09:Vendor.h // Vendor-supplied class header // You only get this & the compiled Vendor.obj. #ifndef VENDOR_H #define VENDOR_H

11670

11671 11672 11673 11674 11675 11676 11677 11678 11679 11680 11681 11682 11683 11684 11685 11686

class Vendor { public: virtual void v() const; void f() const; // Might want this to be virtual... ~Vendor(); // Oops! Not virtual! };

class Vendor1 : public Vendor { public: void v() const;

451

11687 11688 11689 11690 11691 11692 11693 11694

void f() const; ~Vendor1(); };

void A(const Vendor&); void B(const Vendor&); // Etc. #endif // VENDOR_H ///:~

11695 11696

Listado 8.20. C09/Vendor.h

11697 11698 11699 11700 11701 11702 11703 11704 11705 11706 11707 11708 11709 11710

//: C09:Vendor.cpp {O} // Assume this is compiled and unavailable to you. #include "Vendor.h" #include <iostream> using namespace std;

void Vendor::v() const { cout << "Vendor::v()" << endl; }

void Vendor::f() const { cout << "Vendor::f()" << endl; }

Vendor::~Vendor() { cout << "~Vendor()" << endl; }

void Vendor1::v() const { cout << "Vendor1::v()" << endl; }

452

11711 11712 11713 11714 11715 11716 11717 11718 11719 11720 11721 11722 11723 11724 11725 11726 11727

void Vendor1::f() const { cout << "Vendor1::f()" << endl; }

Vendor1::~Vendor1() { cout << "~Vendor1()" << endl; }

void A(const Vendor& v) { // ... v.v(); v.f(); // ... }

void B(const Vendor& v) { // ... v.v(); v.f(); // ... } ///:~

11728 11729

Listado 8.21. C09/Vendor.cpp

11730 11731 11732 11733 11734

//: C09:Paste.cpp //{L} Vendor // Fixing a mess with MI. #include <iostream> #include "Vendor.h"

453

11735 11736 11737 11738 11739 11740 11741 11742 11743 11744 11745 11746 11747 11748 11749 11750 11751 11752 11753 11754 11755 11756 11757 11758 11759

using namespace std;

class MyBase { // Repair Vendor interface public: virtual void v() const = 0; virtual void f() const = 0; // New interface function: virtual void g() const = 0; virtual ~MyBase() { cout << "~MyBase()" << endl; } };

class Paste1 : public MyBase, public Vendor1 { public: void v() const { cout << "Paste1::v()" << endl; Vendor1::v(); } void f() const { cout << "Paste1::f()" << endl; Vendor1::f(); } void g() const { cout << "Paste1::g()" << endl; } ~Paste1() { cout << "~Paste1()" << endl; } };

454

11760 11761 11762 11763 11764 11765 11766 11767 11768 11769 11770 11771 11772 11773 11774

int main() { Paste1& p1p = *new Paste1; MyBase& mp = p1p; // Upcast cout << "calling f()" << endl; mp.f(); // Right behavior

cout << "calling g()" << endl; mp.g(); // New behavior cout << "calling A(p1p)" << endl; A(p1p); // Same old behavior cout << "calling B(p1p)" << endl; B(p1p); // Same old behavior

cout << "delete mp" << endl; // Deleting a reference to a heap object: delete &mp; // Right behavior } ///:~

11775 11776

Listado 8.22. C09/Paste.cpp

11777

MyBase* mp = p1p; // Upcast

11778 11779 11780 11781 11782 11783 11784 11785 11786 11787 11788

calling f() Paste1::f() Vendor1::f() calling g() Paste1::g() calling A(p1p) Paste1::v() Vendor1::v() Vendor::f() calling B(p1p) Paste1::v()

455
11789 11790 11791 11792 11793 11794 11795
Vendor1::v() Vendor::f() delete mp ~Paste1() ~Vendor1() ~Vendor() ~MyBase()

11796

8.9. Resumen 8.10. Ejercicios

11797 11798 11799 11800 11801


[18]

Esto tambin ocurre en Java, y en otros lenguajes orientados a objetos.


[19]

Son nmeros de versin internos de AT&T.

11802 11803 11804 11805 11806 11807 11808 11809 11810 11811 11812 11813 11814 11815 11816 11817 11818 11819 11820 11821 11822 11823

9: Patrones de Diseo
Tabla de contenidos 9.1. El Concepto de Patrn 9.2. Clasificacin de los patrones 9.3. Simplificacin de modismos 9.4. Singleton 9.5. Comando: elegir la operacin 9.6. Desacoplamiento de objetos 9.7. Adaptador 9.8. Template Method 9.9. Estrategia: elegir el algoritno en tiempo de ejecucin 9.10. Cadena de Responsabilidad: intentar una secuencia de estrategias 9.11. Factoras: encapsular la creacin de objetos 9.12. Builder: creacin de objetos complejos 9.13. Observador 9.14. Despachado mltiple 9.15. Resumen 9.16. Ejercicios "...describa un problema que sucede una y otra vez en nuestro entorno, y luego describa el ncleo de la solucin a ese problema, de tal forma que pueda utilizar esa solucin un milln de veces ms, sin siquiera hacerlo dos veces de la misma manera." - Christopher Alexander

456
11824 11825 11826 11827 11828 11829 11830 11831 11832 11833 11834 11835 11836 11837 11838 11839 11840 11841 11842 11843 11844 11845 11846 11847 11848 11849 11850 11851 11852 11853 11854 11855 11856 Este captulo presenta el importante y an no tradicional enfoque de los patrones para el diseo de programas. El avance reciente ms importante en el diseo orientado a objetos es probablemente el movimiento de los patrones de diseo, inicialmente narrado en "Design Patterns", por Gamma, Helm, Johnson y Vlissides (Addison Wesley, 1995), que suele llamarse el libro de la "Banda de los Cuatro" (en ingls, GoF: Gang of Four). El GoF muestra 23 soluciones para clases de problemas muy particulares. En este captulo se discuten los conceptos bsicos de los patrones de diseo y se ofrecen ejemplos de cdigo que ilustran los patrones escogidos. Esto debera abrirle el apetito para leer ms acerca de los patrones de diseo, una fuente de lo que se ha convertido en vocabulario esencial, casi obligatorio, para la programacin orientada a objetos.

9.1. El Concepto de Patrn


En principio, puede pensar en un patrn como una manera especialmente inteligente e intuitiva de resolver una clase de problema en particular. Parece que un equipo de personas han estudiado todos los ngulos de un problema y han dado con la solucin ms general y flexible para ese tipo de problema. Este problema podra ser uno que usted ha visto y resuelto antes, pero su solucin probablemente no tena la clase de completitud que ver plasmada en un patrn. Es ms, el patrn existe independientemente de cualquier implementacin particular y puede implementarse de numerosas maneras. Aunque se llaman "patrones de diseo", en realidad no estn ligados al mbito del diseo. Un patrn parece apartarse de la manera tradicional de pensar sobre el anlisis, diseo e implementacin. En cambio, un patrn abarca una idea completa dentro de un programa, y por lo tanto puede tambin abarcar las fases de anlisis y diseo de alto nivel. Sin embargo, dado que un patrn a menudo tiene una implementacin directa en cdigo, podra no mostrarse hasta el diseo de bajo nivel o la implementacin (y usted no se dara cuenta de que necesita ese patrn hasta que llegase a esas fases). El concepto bsico de un patrn puede verse tambin como el concepto bsico del diseo de programas en general: aadir capas de abstraccin.

457
11857 11858 11859 11860 11861 11862 11863 11864 11865 11866 11867 11868 11869 11870 11871 11872 11873 11874 11875 11876 11877 11878 11879 11880 11881 11882 11883 11884 11885 11886 11887 11888 11889 11890 Cuando se abstrae algo, se estn aislando detalles concretos, y una de las razones de mayor peso para hacerlo es separar las cosas que cambian de las cosas que no. Otra forma de verlo es que una vez que encuentra una parte de su programa que es susceptible de cambiar, querr prevenir que esos cambios propagen efectos colaterales por su cdigo. Si lo consigue, su cdigo no slo ser ms fcil de leer y comprender, tambin ser ms fcil de mantener, lo que a la larga, siempre redunda en menores costes. La parte ms difcil de desarrollar un diseo elegante y mantenible a menudo es descubrir lo que llamamos el "vector de cambio". (Aqu "vector" se refiere al mayor gradiente tal y como se entiende en ciencias, no como la clase contenedora.) Esto implica encontrar la cosa ms importante que cambia en su sistema o, dicho de otra forma, descubrir dnde estn sus mayores costes. Una vez que descubra el vector de cambios, tendr el punto focal alrededor del cual estructurar su diseo. Por lo tanto, el objetivo de los patrones de diseo es encapsular el cambio. Si lo enfoca de esta forma, ya habr visto algunos patrones de diseo en este libro. Por ejemplo, la herencia podra verse como un patrn de diseo (aunque uno implementado por el compilador). Expresa diferencias de comportamiento (eso es lo que cambia) en objetos que tienen todos la misma interfaz (esto es lo que no cambia). La composicin tambin podra considerarse un patrn, ya que puede cambiar dinmica o estticamente los objetos que implementan su clase, y por lo tanto, la forma en la que funciona la clase. Normalmente, sin embargo, las caractersticas que los lenguajes de programacin soportan directamente no se han clasificado como patrones de diseo. Tambin ha visto ya otro patrn que aparece en el GoF: el iterador. Esta es la herramienta fundamental usada en el diseo del STL, descrito en captulos anteriores. El iterador esconde la implementacin concreta del contenedor a medida que se avanza y se seleccionan los elementos uno a uno. Los iteradores le ayudan a escribir cdigo genrico que realiza una operacin en todos los elementos de un rango sin tener en cuenta el contenedor que contiene el rango. Por lo tanto, cualquier contenedor que pueda producir iteradores puede utilizar su cdigo genrico.

9.1.1. La composicin es preferible a la herencia

458
11891 11892 11893 11894 11895 11896 11897 11898 11899 11900 11901 11902 11903 11904 11905 11906 La contribucin ms importante del GoF puede que no sea un patrn, si no una mxima que introducen en el Captulo 1: Prefiera siempre la composicin de objetos antes que la herencia de clases. Entender la herecia y el polimorfismo es un reto tal, que podra empezar a otorgarle una importancia excesiva a estas tcnicas. Se ven muchos diseos excesivamente complicados (el nuestro includo) como resultado de ser demasiado indulgentes con la herencia - por ejemplo, muchos diseos de herencia mltiple se desarrollan por insistir en usar la herencia en todas partes. Una de las directrices en la Programacin Extrema es "haga la cosa ms simple que pueda funcionar". Un diseo que parece requerir de herencia puede a menudo simplificarse drsticamente usando una composicin en su lugar, y descubrir tambin que el resultado es ms flexible, como comprender al estudiar algunos de los patrones de diseo de este captulo. Por lo tanto, al considerar un diseo, pregntese: Podra ser ms simple si usara Composicin? De verdad necesito Herencia aqu, y qu me aporta?

11907 11908 11909 11910 11911 11912 11913 11914 11915 11916 11917 11918 11919 11920 11921 11922

9.2. Clasificacin de los patrones


El GoF describe 23 patrones, clasificados segn tres propsitos FIXME: (all of which revolve around the particular aspect that can vary): 1. Creacional: Cmo se puede crear un objeto. Habitualmente esto incluye aislar los detalles de la creacin del objeto, de forma que su cdigo no dependa de los tipos de objeto que hay y por lo tantok, no tenga que cambiarlo cuando aada un nuevo tipo de objeto. Este captulo presenta los patrones Singleton, Fbricas (Factories), y Constructor (Builder). 2. Estructural: Esto afecta a la manera en que los objetos se conectan con otros objetos para asegurar que los cambios del sistema no requieren cambiar esas conexiones. Los patrones estructurales suelen imponerlos las restricciones del proyecto. En este captulo ver el Proxy y el Adaptador (Adapter). 3. Comportacional: Objetos que manejan tipos particulares de acciones dentro de un programa. stos encapsulan procesos que quiere que se ejecuten, como interpretar un lenguaje, completar una peticin, moverse a

459
11923 11924 11925 11926 11927 11928 11929 11930 11931 11932 11933 11934 11935 11936 11937 11938 11939 11940 11941 11942 11943 11944 11945 11946 11947 11948 11949 11950 11951 11952 11953 11954 11955 travs de una secuencia (como en un iterador) o implementar un algoritmo. Este captulo contiene ejemplos de Comando (Command), Mtodo Plantilla (Template Method), Estado (State), Estrategia (Strategy), Cadena de Responsabilidad (Chain of Responsibility), Observador (Observer), FIXME: Despachador Mltiple (Multiple Dispatching) y Visitador (Visitor). El GoF incluye una seccin sobre cada uno de los 23 patrones, junto con uno o ms ejemplos de cada uno, tpicamente en C++ aunque a veces en SmallTalk. Este libro no repite los detalles de los patrones mostrados en GoF, ya que aqul FIXME: "stands on its own" y debera estudiarse aparte. La descripcin y los ejemplos que se dan aqu intentan darle una visin de los patrones, de forma que pueda hacerse una idea de lo que tratan y de porqu son importantes.

9.2.1. Caractersticas, modismos patrones


El trabajo va ms all de lo que se muestra en el libro del GoF. Desde su publicacin, hay ms patrones y un proceso ms refinado para definir patrones de diseo.[135] Esto es importante porque no es fcil identificar nuevos patrones ni describirlos adecuadamente. Hay mucha confusin en la literatura popular acerca de qu es un patrn de diseo, por ejemplo. Los patrones no son triviales, ni estn representados por caractersticas implementadas en un lenguaje de programacin. Los constructores y destructores, por ejemplo, podran llamarse el patrn de inicializacin garantizada y el de limpieza. Hay constructores importantes y esenciales, pero son caractersticas del lenguaje rutinarias, y no son lo suficientemente ricas como para ser consideradas patrones. Otro FIXME: (no-ejemplo? anti-ejemplo?) viene de varias formas de agregacin. La agregacin es un principio completamente fundamental en la programacin orientada a objetos: se hacen objetos a partir de otros objetos. Aunque a veces, esta idea se clasifica errneamente como un patrn. Esto no es bueno, porque contamina la idea del patrn de diseo, y sugiere que cualquier cosa que le sorprenda la primera vez que la ve debera convertirse en un patrn de diseo. El lenguaje Java da otro ejemplo equivocado: Los diseadores de la especificacin de JavaBeans decidieron referirse a la notacin get/set como

460
11956 11957 11958 un patrn de diseo (por ejemplo, getInfo() devuelve una propiedad Info y setInfo() la modifica). Esto es nicamente una convencin de nombrado, y de ninguna manera constituye un patrn de diseo.

11959 11960 11961 11962 11963 11964 11965 11966

9.3. Simplificacin de modismos


Antes de adentrarnos en tcnicas ms complejas, es til echar un vistazo a algunos mtodos bsicos de mantener el cdigo simple y sencillo.

9.3.1. Mensajero
El ms trivial es el Mensajero (Messenger), [136] que empaqueta informacin en un objeto que se envia, en lugar de ir enviando todas las piezas independientemente. Ntese que sin el Mensajero, el cdigo para la funcin translate() sera mucho ms confuso:

11967 11968 11969 11970 11971 11972 11973 11974 11975 11976 11977 11978 11979 11980

//: C10:MessengerDemo.cpp #include <iostream> #include <string> using namespace std;

class Point { // A messenger public: int x, y, z; // Since it's just a carrier Point(int xi, int yi, int zi) : x(xi), y(yi), z(zi) {} Point(const Point& p) : x(p.x), y(p.y), z(p.z) {}

Point& operator=(const Point& rhs) { x = rhs.x; y = rhs.y; z = rhs.z;

461

11981 11982 11983 11984 11985 11986 11987 11988 11989 11990 11991 11992 11993 11994 11995 11996 11997 11998 11999 12000 12001 12002 12003 12004 12005

return *this; } friend ostream& operator<<(ostream& os, const Point& p) { return os << "x=" << p.x << " y=" << p.y << " z=" << p.z; } };

class Vector { // Mathematical vector public: int magnitude, direction; Vector(int m, int d) : magnitude(m), direction(d) {} };

class Space { public: static Point translate(Point p, Vector v) { // Copy-constructor prevents modifying the original. // A dummy calculation: p.x += v.magnitude + v.direction; p.y += v.magnitude + v.direction; p.z += v.magnitude + v.direction; return p; }

462

12006 12007 12008 12009 12010 12011 12012

};

int main() { Point p1(1, 2, 3); Point p2 = Space::translate(p1, Vector(11, 47)); cout << "p1: " << p1 << " p2: " << p2 << endl; } ///:~

12013 12014 12015 12016 12017 12018

Listado 9.1. C10/MessengerDemo.cpp El cdigo ha sido simplificado para evitar distracciones. Como el objetivo del Mensajero es simplemente llevar datos, dichos datos se hacen pblicos para facilitar el acceso. Sin embargo, podra tener razones para hacer estos campos privados.

12019 12020 12021 12022 12023 12024 12025

9.3.2. Parmetro de Recoleccin


El hermano mayor del Mensajero es el parmetro de recoleccin, cuyo trabajo es capturar informacin sobre la funcin a la que es pasado. Generalmente se usa cuando el parmetro de recoleccin se pasa a mltiples funciones; es como una abeja recogiendo polen. Un contenedor (container) es un parmetro de recoleccin especialmente til, ya que est configurado para aadir objetos dinmicamente:

12026 12027 12028 12029 12030

//: C10:CollectingParameterDemo.cpp #include <iostream> #include <string> #include <vector> using namespace std;

463

12031 12032 12033 12034 12035 12036 12037 12038 12039 12040 12041 12042 12043 12044 12045 12046 12047 12048 12049 12050 12051 12052 12053 12054 12055
int main() { Filler filler; CollectingParameter cp; filler.f(cp); filler.g(cp); filler.h(cp); vector<string>::iterator it = cp.begin(); while(it != cp.end()) cout << *it++ << " "; class Filler { public: void f(CollectingParameter& cp) { cp.push_back("accumulating"); } void g(CollectingParameter& cp) { cp.push_back("items"); } void h(CollectingParameter& cp) { cp.push_back("as we go"); } }; class CollectingParameter : public vector<string> {};

464

12056 12057

cout << endl; } ///:~

12058 12059 12060 12061 12062 12063

Listado 9.2. C10/CollectingParameterDemo.cpp El parmetro de recoleccin debe tener alguna forma de establecer o insertar valores. Ntese que por esta definicin, un Mensajero podra usarse como parmetro de recoleccin. La clave reside en que el parmetro de recoleccin se pasa y es modificado por la funcin que lo recibe.

12064 12065 12066 12067

9.4. Singleton
Posiblemente, el patrn de diseo ms simple del GoF es el Singleton, que es una forma de asegurar una nica instancia de una clase. El siguiente programa muestra cmo implementar un Singleton en C++:

12068 12069 12070 12071 12072 12073 12074 12075 12076 12077 12078 12079 12080

//: C10:SingletonPattern.cpp #include <iostream> using namespace std;

class Singleton { static Singleton s; int i; Singleton(int x) : i(x) { } Singleton& operator=(Singleton&); Singleton(const Singleton&); public: static Singleton& instance() { return s; } int getValue() { return i; } // Disallowed // Disallowed

465

12081 12082 12083 12084 12085 12086 12087 12088 12089 12090 12091 12092

void setValue(int x) { i = x; } };

Singleton Singleton::s(47);

int main() { Singleton& s = Singleton::instance(); cout << s.getValue() << endl; Singleton& s2 = Singleton::instance(); s2.setValue(9); cout << s.getValue() << endl; } ///:~

12093 12094 12095 12096 12097 12098 12099 12100 12101 12102 12103 12104 12105 12106 12107 12108

Listado 9.3. C10/SingletonPattern.cpp La clave para crear un Singleton es evitar que el programador cliente tenga control sobre el ciclo de vida del objeto. Para lograrlo, declare todos los constructores privados, y evite que el compilador genere implcitamente cualquier constructor. Fjese que el FIXME: constructor de copia? y el operador de asignacin (que intencionadamente carecen de implementacin alguna, ya que nunca van a ser llamados) estn declarados como privados, para evitar que se haga cualquier tipo de copia. Tambin debe decidir cmo va a crear el objeto. Aqu, se crea de forma esttica, pero tambin puede esperar a que el programador cliente pida uno y crearlo bajo demanda. Esto se llama "inicializacin vaga", y slo tiene sentido si resulta caro crear el objeto y no siempre se necesita. Si devuelve un puntero en lugar de una referencia, el usuario podra borrar el puntero sin darse cuenta, por lo que la implementacin citada anteriormente es ms segura (el destructor tambin podra declararse

466
12109 12110 12111 12112 12113 12114 12115 12116 12117 12118 12119 12120 12121 12122 12123 12124 12125 12126 12127 12128 12129 12130 12131 12132 12133
log

privado o protegido para solventar el problema). En cualquier caso, el objeto debera almacenarse de forma privada. Usted da acceso a travs de FIXME (funciones de miembros) pblicas. Aqu, instance() genera una referencia al objeto Singleton. El resto de la interfaz (getValue() y setValue()) es la interfaz regular de la clase. Fjese en que no est restringido a crear un nico objeto. Esta tcnica tambin soporta la creacion de un pool limitado de objetos. En este caso, sin embargo, puede enfrentarse al problema de compartir objetos del pool. Si esto supone un problema, puede crear una solucin que incluya un checkout

y un check-in de los objetos compartidos.

9.4.1. Variantes del Singleton


Cualquier miembro static dentro de una clase es una forma de Singleton se har uno y slo uno. En cierto modo, el lenguaje da soporte directo a esta idea; se usa de forma regular. Sin embargo, los objetos estticos tienen un problema (ya miembros o no): el orden de inicializacin, tal y como se describe en el volumen 1 de este libro. Si un objeto static depende de otro, es importante que los objetos se inicializen en el orden correcto. En el volumen 1, se mostr cmo controlar el orden de inicializacin definiendo un objeto esttico dentro de una funcin. Esto retrasa la inicializacin del objeto hasta la primera vez que se llama a la funcin. Si la funcin devuelve una referencia al objeto esttico, hace las veces de
Singleton

a la vez que elimina gran parte de la preocupacin de la

inicializacin esttica. Por ejemplo, suponga que quiere crear un fichero de en la primera llamada a una funcin que devuelve una referencia a dicho fichero. Basta con este fichero de cabecera:

12134 12135 12136 12137 12138

//: C10:LogFile.h #ifndef LOGFILE_H #define LOGFILE_H #include <fstream> std::ofstream& logfile();

467

12139

#endif // LOGFILE_H ///:~

12140 12141 12142 12143 12144 12145 12146 12147 12148

Listado 9.4. C10/LogFile.h La implementacin no debe FIXME: hacerse en la misma lnea, porque eso significara que la funcin entera, includa la definicin del objeto esttico que contiene, podra ser duplicada en cualquier unidad de traduccin donde se incluya, lo que viola la regla de nica definicin de C++. [137] Con toda seguridad, esto frustrara cualquier intento de controlar el orden de inicializacin (pero potencialmente de una forma sutil y difcil de detectar). De forma que la implementacin debe separarse:

12149 12150 12151 12152 12153 12154

//: C10:LogFile.cpp {O} #include "LogFile.h" std::ofstream& logfile() { static std::ofstream log("Logfile.log"); return log; } ///:~

12155 12156 12157 12158

Listado 9.5. C10/LogFile.cpp Ahora el objeto log no se inicializar hasta la primera vez que se llame a
logfile().

As que, si crea una funcin:

12159 12160 12161 12162 12163

//: C10:UseLog1.h #ifndef USELOG1_H #define USELOG1_H void f(); #endif // USELOG1_H ///:~

468
12164 12165 12166 Listado 9.6. C10/UseLog1.h que use logfile() en su implementacin:

12167 12168 12169 12170 12171 12172

//: C10:UseLog1.cpp {O} #include "UseLog1.h" #include "LogFile.h" void f() { logfile() << __FILE__ << std::endl; } ///:~

12173 12174 12175

Listado 9.7. C10/UseLog1.cpp y utiliza logfile() otra vez en otro fichero:

12176 12177 12178 12179 12180 12181 12182 12183 12184 12185 12186 12187

//: C10:UseLog2.cpp //{L} LogFile UseLog1 #include "UseLog1.h" #include "LogFile.h" using namespace std; void g() { logfile() << __FILE__ << endl; }

int main() { f(); g();

469

12188

} ///:~

12189 12190 12191 12192 12193 12194

Listado 9.8. C10/UseLog2.cpp el objecto log no se crea hasta la primera llamada a f(). Puede combinar fcilmente la creacin de objetos estticos dentro de una funcin miembro con la clase Singleton. SingletonPattern.cpp puede modificarse para usar esta aproximacin:[138]

12195 12196 12197 12198 12199 12200 12201 12202 12203 12204 12205 12206 12207 12208 12209 12210 12211 12212

//: C10:SingletonPattern2.cpp // Meyers' Singleton. #include <iostream> using namespace std;

class Singleton { int i; Singleton(int x) : i(x) { } void operator=(Singleton&); Singleton(const Singleton&); public: static Singleton& instance() { static Singleton s(47); return s; } int getValue() { return i; } void setValue(int x) { i = x; } };

470

12213 12214 12215 12216 12217 12218 12219 12220


int main() { Singleton& s = Singleton::instance(); cout << s.getValue() << endl; Singleton& s2 = Singleton::instance(); s2.setValue(9); cout << s.getValue() << endl; } ///:~

12221 12222 12223 12224

Listado 9.9. C10/SingletonPattern2.cpp Se da un caso especialmente interesante cuando dos Singletons dependen mutuamente el uno del otro, de esta forma:

12225 12226 12227 12228 12229 12230 12231 12232 12233 12234 12235 12236

//: C10:FunctionStaticSingleton.cpp

class Singleton1 { Singleton1() {} public: static Singleton1& ref() { static Singleton1 single; return single; } };

class Singleton2 {

471

12237 12238 12239 12240 12241 12242 12243 12244 12245 12246 12247 12248 12249

Singleton1& s1; Singleton2(Singleton1& s) : s1(s) {} public: static Singleton2& ref() { static Singleton2 single(Singleton1::ref()); return single; } Singleton1& f() { return s1; } };

int main() { Singleton1& s1 = Singleton2::ref().f(); } ///:~

12250 12251 12252 12253 12254 12255 12256 12257 12258 12259

Listado 9.10. C10/FunctionStaticSingleton.cpp Cuando se llama a Singleton2::ref(), hace que se cree su nico objeto
Singleton2.

En el proceso de esta creacin, se llama a Singleton1::ref(), y

esto hace que se cree su objeto nico Singleton1. Como esta tcnica no se basa en el orden de linkado ni el de carga, el programador tiene mucho mayor control sobre la inicializacin, lo que redunda en menos problemas. Otra variacin del Singleton separa la unicidad de un objeto de su implementacin. Esto se logra usando el "Patrn Plantilla Curiosamente Recursivo" mencionado en el Captulo 5:

12260 12261 12262

//: C10:CuriousSingleton.cpp // Separates a class from its Singleton-ness (almost). #include <iostream>

472

12263 12264 12265 12266 12267 12268 12269 12270 12271 12272 12273 12274 12275 12276 12277 12278 12279 12280 12281 12282 12283 12284 12285 12286 12287

using namespace std;

template<class T> class Singleton { Singleton(const Singleton&); Singleton& operator=(const Singleton&); protected: Singleton() {} virtual ~Singleton() {} public: static T& instance() { static T theInstance; return theInstance; } };

// A sample class to be made into a Singleton class MyClass : public Singleton<MyClass> { int x; protected: friend class Singleton<MyClass>; MyClass() { x = 0; } public: void setValue(int n) { x = n; } int getValue() const { return x; } };

473

12288 12289 12290 12291 12292 12293 12294


int main() { MyClass& m = MyClass::instance(); cout << m.getValue() << endl; m.setValue(1); cout << m.getValue() << endl; } ///:~

12295 12296 12297 12298 12299 12300 12301 12302 12303 12304 12305 12306 12307 12308 12309 12310 12311 12312 12313

Listado 9.11. C10/CuriousSingleton.cpp


MyClass

se convierte en Singleton:

1. Haciendo que su constructor sea private o protected. 2. Hacindose amigo de Singleton<MyClass>. 3. Derivando MyClass desde Singleton<MyClass>. La auto-referencia del paso 3 podra sonar inversmil, pero tal como se explic en el Captulo 5, funciona porque slo hay una dependencia esttica sobre el argumento plantilla de la plantilla Singleton. En otras palabras, el cdigo de la clase Singleton<MyClass> puede ser instanciado por el compilador porque no depende del tamao de MyClass. Es despus, cuando se a Singleton<MyClass>::instance(), cuando se necesita el tamao de
MyClass,

y para entonces MyClass ya se ha compilado y su tamao se conoce.[139]

Es interesante lo intrincado que un patrn tan simple como el Singleton puede llegar a ser, y ni siquiera se han tratado todava asuntos de seguridad de hilos. Por ltimo, el patrn Singleton debera usarse lo justo y necesario. Los verdaderos objetos Singleton rara vez aparecen, y la ltima cosa para la que debe usarse un Singleton es para remplazar a una variable global. [140]

12314

9.5. Comando: elegir la operacin

474
12315 12316 12317 12318 12319 12320 12321 12322 12323 12324 12325 12326 12327 12328 12329 12330 El patrn Comando es estructuralmente muy sencillo, pero puede tener un impacto importante en el desacoplamiento (y, por ende, en la limpieza) de su cdigo. En "Advanced C++: Programming Styles And Idioms (Addison Wesley, 1992)", Jim Coplien acua el trmino functor, que es un objeto cuyo nico propsito es encapsular una funcin (dado que functor tiene su significado en matemticas, usaremos el trmino "objeto funcin", que es ms explcito). El quid est en desacoplar la eleccin de la funcin que hay que llamar del sitio donde se llama a dicha funcin. Este trmino se menciona en el GoF, pero no se usa. Sin embargo, el concepto de "objeto funcin" se repite en numerosos patrones del libro. Un Comando es un objeto funcin en su estado ms puro: una funcin que tiene un objeto. Al envolver una funcin en un objeto, puede pasarla a otras funciones u objetos como parmetro, para decirles que realicen esta operacin concreta mientras llevan a cabo su peticin. Se podra decir que un Comando es un Mensajero que lleva un comportamiento.

12331 12332 12333 12334 12335 12336 12337 12338 12339 12340 12341 12342

//: C10:CommandPattern.cpp #include <iostream> #include <vector> using namespace std;

class Command { public: virtual void execute() = 0; };

class Hello : public Command { public:

475

12343 12344 12345 12346 12347 12348 12349 12350 12351 12352 12353 12354 12355 12356 12357 12358 12359 12360 12361 12362 12363 12364 12365 12366 12367

void execute() { cout << "Hello "; } };

class World : public Command { public: void execute() { cout << "World! "; } };

class IAm : public Command { public: void execute() { cout << "I'm the command pattern!"; } };

// An object that holds commands: class Macro { vector<Command*> commands; public: void add(Command* c) { commands.push_back(c); } void run() { vector<Command*>::iterator it = commands.begin(); while(it != commands.end()) (*it++)->execute(); } };

476

12368 12369 12370 12371 12372 12373 12374

int main() { Macro macro; macro.add(new Hello); macro.add(new World); macro.add(new IAm); macro.run(); } ///:~

12375 12376 12377 12378 12379 12380 12381 12382 12383 12384 12385 12386 12387 12388 12389 12390 12391 12392 12393 12394 12395

Listado 9.12. C10/CommandPattern.cpp El punto principal del Comando es permitirle dar una accin deseada a una funcin u objeto. En el ejemplo anterior, esto provee una manera de encolar un conjunto de acciones que se deben ejecutar colectivamente. Aqu, puede crear dinmicamente nuevos comportamientos, algo que puede hacer normalmente escribiendo nuevo cdigo, pero en el ejemplo anterior podra hacerse interpretando un script (vea el patrn Intrprete si lo que necesita hacer se vuelve demasiado complicado). Segn el GoF, los Comandos son un sustituto orientado a objetos de las
retrollamadas (callbacks).

[141] Sin embargo, pensamos que la palabra

"retro" es una parte esencial del concepto de retrollamada -una retrollamada retorna al creador de la misma. Por otro lado, un objeto Comando, simplemente se crea y se entrega a alguna funcin u objeto, y no se permanece conectado de por vida al objecto Comando. Un ejemplo habitual del patrn Comando es la implementacin de la funcionalidad de "deshacer" en una aplicacin. Cada vez que el usuario realiza una operacin, se coloca el correspondiente objeto Comando de deshacer en una cola. Cada objeto Comando que se ejecuta guarda el estado del programa en el paso anterior.

9.5.1. Desacoplar la gestin de eventos con Comando

477
12396 12397 12398 12399 12400 12401 12402 12403 12404 12405 12406 12407 12408 12409 12410 12411 12412 Como se ver en el siguiente captulo, una de las razones para emplear tcnicas de concurrencia es facilitar la gestin de la programacin dirigida por eventos, donde los eventos pueden aparecer en el programa de forma impredecible. Por ejemplo, un usuario que pulsa un botn de "Salir" mientras se est realizando una operacin espera que el programa responda rpidamente. Un motivo para usar concurrencia es que previene el aclopamiento entre los bloques del cdigo. Es decir, si est ejecutando un hilo aparte para vigilar el botnde salida, las operaciones normales de su programa no necesitan saber nada sobre el botn ni sobe ninguna de las dems operaciones que se estn vigilando. Sin embargo, una vez que comprenda que el quiz est en el acoplamiento, puede evitarlo usando el patrn Comando. Cada operacin normal debe llamar peridicamente a una funcin para que compruebe el estado de los eventos, pero con el patrn Comando, estas operaciones normales no tienen porqu saber nada sobre lo que estn comprobando, y por lo tanto, estn desacopladas del cdigo de manejo de eventos:

12413 12414 12415 12416 12417 12418 12419 12420 12421 12422 12423 12424

//: C10:MulticastCommand.cpp {RunByHand} // Decoupling event management with the Command pattern. #include <iostream> #include <vector> #include <string> #include <ctime> #include <cstdlib> using namespace std;

// Framework for running tasks: class Task { public:

478

12425 12426 12427 12428 12429 12430 12431 12432 12433 12434 12435 12436 12437 12438 12439 12440 12441 12442 12443 12444 12445 12446 12447 12448 12449

virtual void operation() = 0; };

class TaskRunner { static vector<Task*> tasks; TaskRunner() {} // Make it a Singleton TaskRunner& operator=(TaskRunner&); // Disallowed TaskRunner(const TaskRunner&); // Disallowed static TaskRunner tr; public: static void add(Task& t) { tasks.push_back(&t); } static void run() { vector<Task*>::iterator it = tasks.begin(); while(it != tasks.end()) (*it++)->operation(); } };

TaskRunner TaskRunner::tr; vector<Task*> TaskRunner::tasks;

class EventSimulator { clock_t creation; clock_t delay; public:

479

12450 12451 12452 12453 12454 12455 12456 12457 12458 12459 12460 12461 12462 12463 12464 12465 12466 12467 12468 12469 12470 12471 12472 12473 12474

EventSimulator() : creation(clock()) { delay = CLOCKS_PER_SEC/4 * (rand() % 20 + 1); cout << "delay = " << delay << endl; } bool fired() { return clock() > creation + delay; } };

// Something that can produce asynchronous events: class Button { bool pressed; string id; EventSimulator e; // For demonstration public: Button(string name) : pressed(false), id(name) {} void press() { pressed = true; } bool isPressed() { if(e.fired()) press(); // Simulate the event return pressed; } friend ostream& operator<<(ostream& os, const Button& b) { return os << b.id; }

480

12475 12476 12477 12478 12479 12480 12481 12482 12483 12484 12485 12486 12487 12488 12489 12490 12491 12492 12493 12494 12495 12496 12497 12498 12499

};

// The Command object class CheckButton : public Task { Button& button; bool handled; public: CheckButton(Button & b) : button(b), handled(false) {} void operation() { if(button.isPressed() && !handled) { cout << button << " pressed" << endl; handled = true; } } };

// The procedures that perform the main processing. These // need to be occasionally "interrupted" in order to // check the state of the buttons or other events: void procedure1() { // Perform procedure1 operations here. // ... TaskRunner::run(); // Check all events }

481

12500 12501 12502 12503 12504 12505 12506 12507 12508 12509 12510 12511 12512 12513 12514 12515 12516 12517 12518 12519 12520 12521 12522 12523 12524

void procedure2() { // Perform procedure2 operations here. // ... TaskRunner::run(); // Check all events }

void procedure3() { // Perform procedure3 operations here. // ... TaskRunner::run(); // Check all events }

int main() { srand(time(0)); // Randomize Button b1("Button 1"), b2("Button 2"), b3("Button 3"); CheckButton cb1(b1), cb2(b2), cb3(b3); TaskRunner::add(cb1); TaskRunner::add(cb2); TaskRunner::add(cb3); cout << "Control-C to exit" << endl; while(true) { procedure1(); procedure2(); procedure3(); }

482

12525

} ///:~

12526 12527 12528 12529 12530 12531 12532 12533 12534 12535 12536 12537 12538 12539 12540 12541 12542 12543

Listado 9.13. C10/MulticastCommand.cpp Aqu, el objeto Comando est representado por Tareas ejecutadas por el Singleton TaskRunner. EventSimulator crea un retraso aleatorio, de modo que si se llama peridicamente a la funcin fired() el resultado cambiar de
false

a true en algn momento aleatorio. Los objetos EventSimulator se

utilizan dentro de los Botones para simular que ocurre un evento de usuario en un momento impredecible. CheckButton es la implementacin de la Tarea que es comprobada peridicamente por todo el cdigo "normal" del progama. Puede ver cmo ocurre al final de procedure1(), procedure2() y procedure3(). Aunque esto requiere un poco ms de razonamiento para establecerlo, ver en el Captulo 11 que utilizar hilos requiere mucho pensamiento y cuidado para prevenir las muchas dificultades inhenerentes a la programacin concurrente, por lo que la solucin ms simple puede ser preferible. Tambin puede crear un esquema de hilos muy simple moviendo las llamadas a TaskRunner::run() a un objeto temporizador multi-hilo. Al hacer esto, se elimina todo el acoplamiento entre las operaciones "normales" (los "procedures" en el ejemplo anterior) y el cdigo de eventos.

12544 12545 12546 12547 12548 12549 12550 12551 12552 12553 12554

9.6. Desacoplamiento de objetos


Tanto el Proxy como el Estado proporcionen una clase sucednea. El cdigo habla a esta clase sucednea, y la verdadera clase que hace el trabajo est escondida detrs de la sucednea. Cuando usted llama a una funcin en la clase sucednea, simplemente da un rodeo y llama a la funcin en la clase implementadora. Estos dos patrones son tan familiares que, estructuralmente, Proxy es un caso especial de Estado. Uno est tentado de juntar ambos en un patrn llamado Sucednea, pero la intencin de los dos patrones es distinta. Puede ser fcil caer en la trampa de pensar que si la estructura es la misma, los patrones son el mismo. Debe mirar siempre la intencin del patrn para tener claro lo que hace.

483
12555 12556 12557 12558 12559 12560 12561 12562 12563 12564 12565 12566 La idea bsica es simple: cree una clase base, la sucednea se deriva junto con la clase o clases que aportan la siguiente implementacin: Cuando se crea una clase sucednea, se le da una implementacin a la que enva las llamadas a funcin. Estructuralmente, la diferencia entre Proxy y Estado es simple: un Proxy slo tiene una implementacin, mientras que Estado tiene ms de una. La aplicacin de los patrones se considera (en el GoF) distinta: Proxy controla el acceso a su implementacin, mientras que Estado cambia la implementacin dinmicamente. Sin embargo, si se ampla la nocin de "controlar el acceso a la implementacin", entonces los dos parecen ser parte de un todo.

9.6.1. Proxy: FIXME: hablando en nombre de otro objeto


Si se implementa un Proxy usando el diagrama anterior, tiene esta pinta:

12567 12568 12569 12570 12571 12572 12573 12574 12575 12576 12577 12578 12579 12580 12581

//: C10:ProxyDemo.cpp // Simple demonstration of the Proxy pattern. #include <iostream> using namespace std;

class ProxyBase { public: virtual void f() = 0; virtual void g() = 0; virtual void h() = 0; virtual ~ProxyBase() {} };

class Implementation : public ProxyBase { public:

484

12582 12583 12584 12585 12586 12587 12588 12589 12590 12591 12592 12593 12594 12595 12596 12597 12598 12599 12600 12601 12602 12603

void f() { cout << "Implementation.f()" << endl; } void g() { cout << "Implementation.g()" << endl; } void h() { cout << "Implementation.h()" << endl; } };

class Proxy : public ProxyBase { ProxyBase* implementation; public: Proxy() { implementation = new Implementation(); } ~Proxy() { delete implementation; } // Forward calls to the implementation: void f() { implementation->f(); } void g() { implementation->g(); } void h() { implementation->h(); } };

int main() Proxy p; p.f(); p.g(); p.h(); } ///:~

12604 12605

Listado 9.14. C10/ProxyDemo.cpp

485
12606 12607 12608 12609 12610 12611 12612 12613 12614 12615 12616 12617 12618 12619 12620 12621 12622 12623 12624 12625 12626 12627 12628 En algunos casos, Implementation no necesita la misma interfaz que Proxy, siempre y cuando Proxy est de alguna forma hablando en nombre de la clase Implementacin y referenciando llamadas a funcin hacia ella, entonces la idea bsica se satisface (note que esta afirmacin est reida con la definicin de Proxy del GoF). Sin embargo, con una interfaz comn es posible realizar un reemplazo FIXME: drop-in del proxy en el cdigo del cliente -el cdigo del cliente est escrito para hablar al objeto original, y no necesita ser cambiado para aceptar el proxy (ste es probablemente el quiz principal de Proxy). Adems, se fuerza a que Implementation complete, a travs de la interfaz comn, todas las funciones que Proxy necesita llamar. La diferencia entre Proxy y Estado est en los problemas que pueden resolver. Los usos ms comunes para Proxy que describe el GoF son: 1. Proxy remoto. Representan a objetos en un espacio de direcciones distinto. Lo implementan algunas tecnologas de objetos remotos. 2. Proxy virtual. Proporciona inicializacin FIXME: vaga para crear objetos costosos bajo demanda. 3. Proxy de proteccin. Se usa cuando no se desea que el programador cliente tenga acceso completo al objecto representado. 4. Referencia inteligente. Para aadir acciones adicionales cuando se acceda al objeto representado. El conteo de referencias es un buen ejemplo: mantiene un registro del nmero de referencias que se mantienen para un objeto en particular, para implementar el FIXME: copy-on-write idiom y para prevenir el FIXME: object aliasing.

12629 12630 12631 12632 12633 12634 12635 12636 12637

9.6.2. Estado: cambiar el comportamiento del objeto


El patrn Estado produce un objeto que parece que cambia su clase, y le ser til cuando descubra que tiene cdigo condicional en todas o casi todas sus funciones. Al igual que Proxy, un Estado se crea teniendo un objeto front-end que usa un objeto back-end de implementacin para completar sus tareas. Sin embargo, el patrn Estado alterna entre una implementacin y otra durante la vida del objeto front-end, para mostrar un comportamiento distinto ante las mismas llamadas a funcin. Es una forma de mejorar la implementacin de su cdigo cuando realiza un montn de pruebas en cada

486
12638 12639 12640 12641 una de sus funciones antes de decidir qu hacer con esa funcin. Por ejemplo, el cuento del prncipe convertido en rana contiene un objeto (la criatura) que se comporta de modo distinto dependiendo del estado en el que se encuentre. Podra implementar esto comprobando un bool:

12642 12643 12644 12645 12646 12647 12648 12649 12650 12651 12652 12653 12654 12655 12656 12657 12658 12659 12660 12661 12662 12663

//: C10:KissingPrincess.cpp #include <iostream> using namespace std;

class Creature { bool isFrog; public: Creature() : isFrog(true) {} void greet() { if(isFrog) cout << "Ribbet!" << endl; else cout << "Darling!" << endl; } void kiss() { isFrog = false; } };

int main() { Creature creature; creature.greet(); creature.kiss(); creature.greet();

487

12664

} ///:~

12665 12666 12667 12668 12669 12670 12671

Listado 9.15. C10/KissingPrincess.cpp Sin embargo, la funcin greet(), y cualquier otra funcin que tenga que comprobar isFrog antes de realizar sus operaciones, acaban con cdigo poco elegante, especialmente cuando haya que aadir estados adicionales al sistema. Delegando las operaciones a un objeto Estado que puede cambiarse, el cdigo se simplifica.

12672 12673 12674 12675 12676 12677 12678 12679 12680 12681 12682 12683 12684 12685 12686 12687 12688

//: C10:KissingPrincess2.cpp // The State pattern. #include <iostream> #include <string> using namespace std;

class Creature { class State { public: virtual string response() = 0; }; class Frog : public State { public: string response() { return "Ribbet!"; } }; class Prince : public State { public:

488

12689 12690 12691 12692 12693 12694 12695 12696 12697 12698 12699 12700 12701 12702 12703 12704 12705 12706 12707 12708

string response() { return "Darling!"; } }; State* state; public: Creature() : state(new Frog()) {} void greet() { cout << state->response() << endl; } void kiss() { delete state; state = new Prince(); } };

int main() { Creature creature; creature.greet(); creature.kiss(); creature.greet(); } ///:~

12709 12710 12711 12712

Listado 9.16. C10/KissingPrincess2.cpp No es necesario hacer las clases FIXME: implementadoras anidadas ni privadas, pero si lo hace, el cdigo ser ms limpio.

489
12713 12714 12715 Note que los cambios en las clases Estado se propagan automticamente por todo su cdigo, en lugar de requerir una edicin de las clases para efectuar los cambios.

12716 12717 12718 12719 12720 12721 12722 12723

9.7. Adaptador
Un Adaptador coge un tipo y genera una interfaz para algn otro tipo. Es til cuando se tiene una librera o trozo de cdigo que tiene una interfaz particular, y otra librera o trozo de cdigo que usa las mismas ideas bsicas que la primera librera, pero se expresa de forma diferente. Si se adaptan las formas de expresin entre s, se puede crear una solucin rpidamente. Suponga que tiene una clase productora que genera los nmeros de Fibonacci:

12724 12725 12726 12727 12728 12729 12730 12731 12732 12733 12734 12735 12736 12737 12738

//: C10:FibonacciGenerator.h #ifndef FIBONACCIGENERATOR_H #define FIBONACCIGENERATOR_H

class FibonacciGenerator { int n; int val[2]; public: FibonacciGenerator() : n(0) { val[0] = val[1] = 0; } int operator()() { int result = n > 2 ? val[0] + val[1] : n > 0 ? 1 : 0; ++n; val[0] = val[1]; val[1] = result; return result;

490

12739 12740 12741 12742

} int count() { return n; } }; #endif // FIBONACCIGENERATOR_H ///:~

12743 12744 12745

Listado 9.17. C10/FibonacciGenerator.h Como es un productor, se usa llamando al operador(), de esta forma:

12746 12747 12748 12749 12750 12751 12752 12753 12754 12755

//: C10:FibonacciGeneratorTest.cpp #include <iostream> #include "FibonacciGenerator.h" using namespace std;

int main() { FibonacciGenerator f; for(int i =0; i < 20; i++) cout << f.count() << ": " << f() << endl; } ///:~

12756 12757 12758 12759 12760 12761 12762 12763 12764

Listado 9.18. C10/FibonacciGeneratorTest.cpp A lo mejor le gustara coger este generador y realizar operaciones de algoritmos numricos STL con l. Desafortunadamente, los algoritmos STL slo trabajan con iteradores, as que tiene dos interfaces que no casan. La solucin es crear un adaptador que coja el FibonacciGenerator y produzca un iterador para los algoritmos STL a usar. Dado que los algoritmos numricos slo necesitan un iterador de entrada, el Adaptador es bastante directo (para algo que produce un iterador STL, es decir):

491

12765 12766 12767 12768 12769 12770 12771 12772 12773 12774 12775 12776 12777 12778 12779 12780 12781 12782 12783 12784 12785 12786 12787 12788 12789

//: C10:FibonacciAdapter.cpp // Adapting an interface to something you already have. #include <iostream> #include <numeric> #include "FibonacciGenerator.h" #include "../C06/PrintSequence.h" using namespace std;

class FibonacciAdapter { // Produce an iterator FibonacciGenerator f; int length; public: FibonacciAdapter(int size) : length(size) {} class iterator; friend class iterator; class iterator : public std::iterator< std::input_iterator_tag, FibonacciAdapter, ptrdiff_t> { FibonacciAdapter& ap; public: typedef int value_type; iterator(FibonacciAdapter& a) : ap(a) {} bool operator==(const iterator&) const { return ap.f.count() == ap.length; } bool operator!=(const iterator& x) const {

492

12790 12791 12792 12793 12794 12795 12796 12797 12798 12799 12800 12801 12802 12803 12804 12805 12806 12807 12808 12809 12810 12811 12812 12813 12814

return !(*this == x); } int operator*() const { return ap.f(); } iterator& operator++() { return *this; } iterator operator++(int) { return *this; } }; iterator begin() { return iterator(*this); } iterator end() { return iterator(*this); } };

int main() { const int SZ = 20; FibonacciAdapter a1(SZ); cout << "accumulate: " << accumulate(a1.begin(), a1.end(), 0) << endl; FibonacciAdapter a2(SZ), a3(SZ); cout << "inner product: " << inner_product(a2.begin(), a2.end(), a3.begin(), 0) << endl; FibonacciAdapter a4(SZ); int r1[SZ] = {0}; int* end = partial_sum(a4.begin(), a4.end(), r1); print(r1, end, "partial_sum", " "); FibonacciAdapter a5(SZ); int r2[SZ] = {0};

493

12815 12816 12817

end = adjacent_difference(a5.begin(), a5.end(), r2); print(r2, end, "adjacent_difference", " "); } ///:~

12818 12819 12820 12821 12822 12823 12824 12825 12826 12827 12828 12829 12830 12831 12832

Listado 9.19. C10/FibonacciAdapter.cpp Se inicializa un FibonacciAdapter dicindole cun largo puede ser la secuencia de Fibonacci. Cuando se crea un iterador, simplemente captura una referencia al FibonacciAdapter que lo contiene para que pueda acceder al FibonacciGenerator y la longitud. Observe que la comparacin de equivalencia ignora el valor de la derecha, porque el nico asunto importante es si el generador ha alcanzado su longitud. Adems, el operator++() no modifica el iterador; la nica operacin que cambia el estado del FibonacciAdapter es llamar a la funcin operator() del generador en el FibonacciGenerator. Puede aceptarse esta versin extremadamente simple del iterador porque las restricciones de un Input Iterator son muy estrictas; concretamente, slo se puede leer cada valor de la secuencia una vez. En main(), puede verse que los cuatro tipos distintos de algoritmos numricos se testan satisfactoriamente con el FibonacciAdapter.

12833 12834 12835 12836 12837 12838 12839 12840 12841 12842 12843

9.8. Template Method


El marco de trabajo de una aplicacin nos permite heredar de una clase o conjunto de ellas y crear una nueva aplicacin, reutilizando la mayora del cdigo de las clases existentes y sobreescribiendo una o ms funciones para adaptar la aplicacin a nuestras necesidades. Una caracterstica importante de Template Method es que est definido en la clase base (a veces como una funcin privada) y no puede cambiarse -el Template Method es lo que permanece invariable. Llama a otras funciones de clase base (las que se sobreescriben) para hacer su trabajo, pero el programador cliente no es necesariamente capaz de llamarlo directamente, como puede verse aqu:

494

12844 12845 12846 12847 12848 12849 12850 12851 12852 12853 12854 12855 12856 12857 12858 12859 12860 12861 12862 12863 12864 12865 12866 12867 12868

//: C10:TemplateMethod.cpp // Simple demonstration of Template Method. #include <iostream> using namespace std;

class ApplicationFramework { protected: virtual void customize1() = 0; virtual void customize2() = 0; public: void templateMethod() { for(int i = 0; i < 5; i++) { customize1(); customize2(); } } };

// Create a new "application": class MyApp : public ApplicationFramework { protected: void customize1() { cout << "Hello "; } void customize2() { cout << "World!" << endl; } };

495

12869 12870 12871 12872

int main() { MyApp app; app.templateMethod(); } ///:~

12873 12874 12875 12876 12877 12878

Listado 9.20. C10/TemplateMethod.cpp El motor que ejecuta la aplicacin es el Template Method. En una aplicacin grfica, este motor sera el bucle principal de eventos. El programador cliente simplemente proporciona las definiciones para customize1() y customize2(), y la aplicacin est lista para ejecutarse.

12879 12880 12881 12882 12883 12884 12885 12886 12887 12888 12889 12890 12891 12892 12893

9.9. Estrategia: elegir el algoritno en tiempo de ejecucin


Observe que el Template Method es el cdigo que no cambia, y las funciones que sobreescribe son el cdigo cambiante. Sin embargo, este cambio est fijado en tiempo de compilacin, a travs de la herencia. Siguiendo la mxima de preferir composicin a herencia, se puede usar una composicin para aproximar el problema de separar cdigo que cambia de cdigo que permanece, y generar el patrn Estrategia. Esta aproximacin tiene un beneficio nico: en tiempo de ejecucin se puede insertar el cdigo que cambia. Estrategia tambin aade un Contexto que puede ser una clase sucednea que controla la seleccin y uso del objeto estrategia -igual que Estado!. Estrategia significa exactamente eso: se puede resolver un problema de muchas maneras. Imagine que ha olvidado el nombre de alguien. Estas son las diferentes maneras para lidiar con esa situacin:

12894 12895 12896

//: C10:Strategy.cpp // The Strategy design pattern. #include <iostream>

496

12897 12898 12899 12900 12901 12902 12903 12904 12905 12906 12907 12908 12909 12910 12911 12912 12913 12914 12915 12916 12917 12918 12919 12920 12921

using namespace std;

class NameStrategy { public: virtual void greet() = 0; };

class SayHi : public NameStrategy { public: void greet() { cout << "Hi! How's it going?" << endl; } };

class Ignore : public NameStrategy { public: void greet() { cout << "(Pretend I don't see you)" << endl; } };

class Admission : public NameStrategy { public: void greet() { cout << "I'm sorry. I forgot your name." << endl;

497

12922 12923 12924 12925 12926 12927 12928 12929 12930 12931 12932 12933 12934 12935 12936 12937 12938 12939 12940 12941

} };

// The "Context" controls the strategy: class Context { NameStrategy& strategy; public: Context(NameStrategy& strat) : strategy(strat) {} void greet() { strategy.greet(); } };

int main() { SayHi sayhi; Ignore ignore; Admission admission; Context c1(sayhi), c2(ignore), c3(admission); c1.greet(); c2.greet(); c3.greet(); } ///:~

12942 12943 12944 12945 12946

Listado 9.21. C10/Strategy.cpp Normalmente, Context::greet() sera ms complejo; es el anlogo de Template Method porque contiene el cdigo que no cambia. Pero puede ver en main() que la eleccin de la estrategia puede realizarse en tiempo de

498
12947 12948 ejecucin. Llendo un paso ms all, se puede combinar esto con el patrn Estado y cambiar la Estrategia durante el tiempo de vida del objeto Contexto.

12949 12950 12951 12952 12953 12954 12955 12956 12957 12958 12959 12960 12961 12962 12963 12964 12965 12966 12967 12968 12969 12970 12971 12972 12973 12974 12975 12976 12977 12978 12979

9.10. Cadena de Responsabilidad: intentar una secuencia de estrategias


Debe pensarse en Cadena de Responsabilidad como en una generalizacin dinmica de la recursin, usando objetos Estrategia. Se hace una llamada y cada Estrategia de la secuencia intenta satisfacer la llamada. El proceso termina cuando una de las Estrategias tiene xito o la cadena termina. En la recursin, una funcin se llama a s misma una y otra vez hasta que se alcanza una condicin de finalizacin; con Cadena de Responsabilidad, una funcin se llama a s misma, la cual (moviendo la cadena de Estrategias) llama a una implementacin diferente de la funcin, etc, hasta que se alcanza la condicin de finalizacin. Dicha condicin puede ser que se ha llegado al final de la cadena (lo que devuelve un objeto por defecto; puede que no sea capaz de proporcionar un resultado por defecto, as que debe ser capaz de determinar el xito o fracaso de la cadena) o que una de las Estrategias ha tenido xito. En lugar de llamar a una nica funcin para satisfacer una peticin, hay mltiples funciones en la cadetna que tienen la oportunidad de hacerlo, de manera que tiene el aspecto de un sistema experto. Dado que la cadena es en la prctica una lista, puede crearse dinmicamente, as que podra verse como una sentencia switch ms general y construida dinmicamente. En el GoF, hay bastante discusin sobre cmo crear la cadena de responsabilidad como una lista enlazada. Sin embargo, cuando se estudia el patrn, no debera importar cmo se crea la cadena; eso es un detalle de implementacin. Como el GoF se escribi antes de que los contenedores STL estuvieran disponibles en la mayora de los compiladores de C++, las razones ms probables son (1) que no haba listas includas y por lo tanto tenan que crear una y (2) que las estructuras de datos suelen verse como una habilidad fundamental en las Escuelas (o Facultades), y a los autores del GoF no se les ocurri la idea de que las estructuras de datos fueran herramientas estndar disponibles junto con el lenguaje de programacin.. Los detalles del contenedor usado para implementar la Cadena de

499
12980 12981 12982 12983 12984 12985 Responsabilidad como una cadena (una lista enlazada en el GoF) no aaden nada a la solucin, y puede implementarse usando un contenedor STL, como se muestra abajo. Aqu puede ver una Cadena de Responsabilidad que encuentra automticamente una solucin usando un mecanismo para recorrer automtica y recursivamente cada Estrategia de la cadena:

12986 12987 12988 12989 12990 12991 12992 12993 12994 12995 12996 12997 12998 12999 13000 13001 13002 13003 13004 13005

//: C10:ChainOfReponsibility.cpp // The approach of the five-year-old. #include <iostream> #include <vector> #include "../purge.h" using namespace std;

enum Answer { NO, YES };

class GimmeStrategy { public: virtual Answer canIHave() = 0; virtual ~GimmeStrategy() {} };

class AskMom : public GimmeStrategy { public: Answer canIHave() { cout << "Mooom? Can I have this?" << endl; return NO;

500

13006 13007 13008 13009 13010 13011 13012 13013 13014 13015 13016 13017 13018 13019 13020 13021 13022 13023 13024 13025 13026 13027 13028 13029 13030

} };

class AskDad : public GimmeStrategy { public: Answer canIHave() { cout << "Dad, I really need this!" << endl; return NO; } };

class AskGrandpa : public GimmeStrategy { public: Answer canIHave() { cout << "Grandpa, is it my birthday yet?" << endl; return NO; } };

class AskGrandma : public GimmeStrategy { public: Answer canIHave() { cout << "Grandma, I really love you!" << endl; return YES; }

501

13031 13032 13033 13034 13035 13036 13037 13038 13039 13040 13041 13042 13043 13044 13045 13046 13047 13048 13049 13050 13051 13052 13053 13054 13055

};

class Gimme : public GimmeStrategy { vector<GimmeStrategy*> chain; public: Gimme() { chain.push_back(new AskMom()); chain.push_back(new AskDad()); chain.push_back(new AskGrandpa()); chain.push_back(new AskGrandma()); } Answer canIHave() { vector<GimmeStrategy*>::iterator it = chain.begin(); while(it != chain.end()) if((*it++)->canIHave() == YES) return YES; // Reached end without success... cout << "Whiiiiinnne!" << endl; return NO; } ~Gimme() { purge(chain); } };

int main() { Gimme chain;

502

13056 13057

chain.canIHave(); } ///:~

13058 13059 13060 13061 13062 13063 13064 13065 13066 13067 13068

Listado 9.22. C10/ChainOfReponsibility.cpp Observe que la clase de Contexto Gimme y todas las clases Estrategia derivan de la misma clase base, GimmeStrategy. Si estudia la seccin sobre Cadena de Responsabilidad del GoF, ver que la estructura difiere significativamente de la que se muestra ms arriba, porque ellos se centran en crear su propia lista enlazada. Sin embargo, si mantiene en mente que la esencia de Cadena de Responsabilidad es probar muchas soluciones hasta que encuentre la que funciona, se dar cuenta de que la implementacin del mecanismo de secuenciacin no es parte esencial del patrn.

13069 13070 13071 13072 13073 13074 13075 13076 13077 13078 13079 13080 13081 13082 13083 13084 13085

9.11. Factoras: encapsular la creacin de objetos


Cuando se descubre que se necesitan aadir nuevos tipos a un sistema, el primer paso ms sensato es usar polimorfismo para crear una interfaz comn para esos nuevos tipos. As, se separa el resto del cdigo en el sistema del conocimiento de los tipos especficos que se estn aadiendo. Los tipos nuevos pueden aadirse sin "molestar" al cdigo existente, o eso parece. A primera vista, podra parecer que hace falta cambiar el cdigo nicamente en los lugares donde se hereda un tipo nuevo, pero esto no es del todo cierto. Todava hay que crear un objeto de este nuevo tipo, y en el momento de la creacin hay que especificar qu constructor usar. Por lo tanto, si el codigo que crea objetos est distribuido por toda la aplicacin, se obtiene el mismo problema que cuando se aaden tipos -hay que localizar todos los puntos del cdigo donde el tipo tiene importancia. Lo que imoporta es la creacin del tipo, ms que el uso del mismo (de eso se encarga el polimorfismo), pero el efecto es el mismo: aadir un nuevo tipo puede causar problemas. La solucin es forzar a que la creacin de objetos se lleve a cabo a travs de una factora comn, en lugar de permitir que el cdigo creacional se

503
13086 13087 13088 13089 13090 13091 13092 13093 13094 13095 disperse por el sistema. Si todo el cdigo del programa debe ir a esta factora cada vez que necesita crear uno de esos objetos, todo lo que hay que hacer para aadir un objeto es modificar la factora. Este diseo es una variacin del patrn conocido comnmente como Factory Method. Dado que todo programa orientado a objetos crea objetos, y como es probable que haya que extender el programa aadiendo nuevos tipos, las factoras pueden ser el ms til de todos los patrones de diseo. Como ejemplo, considere el ampliamente usado ejemplo de figura (Shape). Una aproximacin para implementar una factora es definir una funcin miembro esttica en la clase base:

13096 13097 13098 13099 13100 13101 13102 13103 13104 13105 13106 13107 13108 13109 13110 13111 13112

//: C10:ShapeFactory1.cpp #include <iostream> #include <stdexcept> #include <cstddef> #include <string> #include <vector> #include "../purge.h" using namespace std;

class Shape { public: virtual void draw() = 0; virtual void erase() = 0; virtual ~Shape() {} class BadShapeCreation : public logic_error { public: BadShapeCreation(string type)

504

13113 13114 13115 13116 13117 13118 13119 13120 13121 13122 13123 13124 13125 13126 13127 13128 13129 13130 13131 13132 13133 13134 13135 13136 13137

: logic_error("Cannot create type " + type) {} }; static Shape* factory(const string& type) throw(BadShapeCreation); };

class Circle : public Shape { Circle() {} // Private constructor friend class Shape; public: void draw() { cout << "Circle::draw" << endl; } void erase() { cout << "Circle::erase" << endl; } ~Circle() { cout << "Circle::~Circle" << endl; } };

class Square : public Shape { Square() {} friend class Shape; public: void draw() { cout << "Square::draw" << endl; } void erase() { cout << "Square::erase" << endl; } ~Square() { cout << "Square::~Square" << endl; } };

Shape* Shape::factory(const string& type)

505

13138 13139 13140 13141 13142 13143 13144 13145 13146 13147 13148 13149 13150 13151 13152 13153 13154 13155 13156 13157 13158 13159 13160 13161 13162

throw(Shape::BadShapeCreation) { if(type == "Circle") return new Circle; if(type == "Square") return new Square; throw BadShapeCreation(type); }

char* sl[] = { "Circle", "Square", "Square", "Circle", "Circle", "Circle", "Square" };

int main() { vector<Shape*> shapes; try { for(size_t i = 0; i < sizeof sl / sizeof sl[0]; i++) shapes.push_back(Shape::factory(sl[i])); } catch(Shape::BadShapeCreation e) { cout << e.what() << endl; purge(shapes); return EXIT_FAILURE; } for(size_t i = 0; i < shapes.size(); i++) { shapes[i]->draw(); shapes[i]->erase(); } purge(shapes); } ///:~

506
13163 13164 13165 13166 13167 13168 13169 13170 13171 13172 13173 13174 13175 13176 13177 13178 13179 13180 13181 13182 13183 13184 13185 13186 13187 13188 13189 13190 13191 13192 13193 Listado 9.23. C10/ShapeFactory1.cpp La funcin factory() toma un argumento que le permite determinar qu tipo de figura crear. Aqu, el argumento es una cadena, pero podra ser cualquier conjunto de datos. El mtodo factory() es el nico cdigo del sistema que hay que cambiar cuando se aade un nuevo tipo de figura. (Los datos de inicializacin para los objetos vendrn supuestamente de algn sitio fuera del sistema y no sern un FIXME: hard-coded array como en el ejemplo.) Para asegurar que la creacin slo puede realizarse en factory(), los constructores de cada tipo especfico de figura se hacen privados, y Shape se declara como friend de forma que factory() tiene acceso a los mismos. (Tambin se podra declarar slamente Shape::factory() como friend, pero parece razonablemente inocuo declarar la clase base entera.) Hay otra implicacin importante de este diseo -la clase base, Shape, debe conocer ahora los detalles de todas las clases derivadas -una propiedad que el diseo orientado a objetos intenta evitar. Para frameworks o cualquier librera de clases que deban poder extenderse, esto hace que se convierta rpidamente en algo difcil de manejar, ya que la clase base debe actualizarse en cuanto se aada un tipo nuevo a la jerarqua. Las factoras polimrficas, descritas en la siguiente subseccin, se pueden usar para evitar esta dependencia circular tan poco deseada.

9.11.1. Factoras polimrficas


La funcin esttica factory() en el ejemplo anterior fuerza que las operaciones de creacin se centren en un punto, de forma que sea el nico sitio en el que haya que cambiar cdigo. Esto es, sin duda, una solucin razonable, ya que encapsula amablemente el proceso de crear objetos. Sin embargo, el GoF enfatiza que la razn de ser del patrn Factory Method es que diferentes tipos de factoras se puedan derivar de la factora bsica. Factory Method es, de hecho, un tipo especial de factora polimrfica. Esto es ShapeFactory1.cpp modificado para que los Factory Methods estn en una clase aparte como funciones virtuales.

13194

//: C10:ShapeFactory2.cpp

507

13195 13196 13197 13198 13199 13200 13201 13202 13203 13204 13205 13206 13207 13208 13209 13210 13211 13212 13213 13214 13215 13216 13217 13218 13219

// Polymorphic Factory Methods. #include <iostream> #include <map> #include <string> #include <vector> #include <stdexcept> #include <cstddef> #include "../purge.h" using namespace std;

class Shape { public: virtual void draw() = 0; virtual void erase() = 0; virtual ~Shape() {} };

class ShapeFactory { virtual Shape* create() = 0; static map<string, ShapeFactory*> factories; public: virtual ~ShapeFactory() {} friend class ShapeFactoryInitializer; class BadShapeCreation : public logic_error { public:

508

13220 13221 13222 13223 13224 13225 13226 13227 13228 13229 13230 13231 13232 13233 13234 13235 13236 13237 13238 13239 13240 13241 13242 13243 13244

BadShapeCreation(string type) : logic_error("Cannot create type " + type) {} }; static Shape* createShape(const string& id) throw(BadShapeCreation) { if(factories.find(id) != factories.end()) return factories[id]->create(); else throw BadShapeCreation(id); } };

// Define the static object: map<string, ShapeFactory*> ShapeFactory::factories;

class Circle : public Shape { Circle() {} // Private constructor friend class ShapeFactoryInitializer; class Factory; friend class Factory; class Factory : public ShapeFactory { public: Shape* create() { return new Circle; } friend class ShapeFactoryInitializer; };

509

13245 13246 13247 13248 13249 13250 13251 13252 13253 13254 13255 13256 13257 13258 13259 13260 13261 13262 13263 13264 13265 13266 13267 13268 13269

public: void draw() { cout << "Circle::draw" << endl; } void erase() { cout << "Circle::erase" << endl; } ~Circle() { cout << "Circle::~Circle" << endl; } };

class Square : public Shape { Square() {} friend class ShapeFactoryInitializer; class Factory; friend class Factory; class Factory : public ShapeFactory { public: Shape* create() { return new Square; } friend class ShapeFactoryInitializer; }; public: void draw() { cout << "Square::draw" << endl; } void erase() { cout << "Square::erase" << endl; } ~Square() { cout << "Square::~Square" << endl; } };

// Singleton to initialize the ShapeFactory: class ShapeFactoryInitializer { static ShapeFactoryInitializer si;

510

13270 13271 13272 13273 13274 13275 13276 13277 13278 13279 13280 13281 13282 13283 13284 13285 13286 13287 13288 13289 13290 13291 13292 13293 13294

ShapeFactoryInitializer() { ShapeFactory::factories["Circle"]= new Circle::Factory; ShapeFactory::factories["Square"]= new Square::Factory; } ~ShapeFactoryInitializer() { map<string, ShapeFactory*>::iterator it = ShapeFactory::factories.begin(); while(it != ShapeFactory::factories.end()) delete it++->second; } };

// Static member definition: ShapeFactoryInitializer ShapeFactoryInitializer::si;

char* sl[] = { "Circle", "Square", "Square", "Circle", "Circle", "Circle", "Square" };

int main() { vector<Shape*> shapes; try { for(size_t i = 0; i < sizeof sl / sizeof sl[0]; i++) shapes.push_back(ShapeFactory::createShape(sl[i])); } catch(ShapeFactory::BadShapeCreation e) { cout << e.what() << endl;

511

13295 13296 13297 13298 13299 13300 13301 13302

return EXIT_FAILURE; } for(size_t i = 0; i < shapes.size(); i++) { shapes[i]->draw(); shapes[i]->erase(); } purge(shapes); } ///:~

13303 13304 13305 13306 13307 13308 13309 13310 13311 13312 13313 13314 13315 13316 13317 13318 13319 13320 13321 13322 13323 13324

Listado 9.24. C10/ShapeFactory2.cpp Ahora, Factory Method aparece en su propia clase, ShapeFactory, como
virtual create().

Es una funcin miembro privada, lo que significa que no

puede ser llamada directametne, pero puede ser sobreescrita. Las subclases de Shape deben crear cada una su propias subclases de ShapeFactory y sobreescribir el mtodo create para crear un objeto de su propio tipe. Estas factoras son privadas, de forma que slo pueden ser accedidas desde el Factory Method principal. De esta forma, todo el cdigo cliente debe pasar a travs del Factory Method para crear objetos. La verdadera creacin de figuras se realiza llamando a ShapeFactory::createShape( ), que es una funcin esttica que usa el mapa en ShapeFactory para encontrar la objeto factora apropiado basndose en el identificador que se le pasa. La factora crea el objeto figura directamente, pero podra imaginarse un problema ms complejo en el que el objeto factora apropiado se devuelve y luego lo usa quien lo ha llamado para crear un objeto de una manera ms sofisticada. Sin embargo, parece que la mayora del tiempo no hacen falta las complejidades del Factory Method polimrfico, y bastar con una nica funcin esttica en la clase base (como se muestra en ShapeFactory1.cpp). Observe que el ShapeFactory debe ser inicializado cargando su mapa con objetos factory, lo que tiene lugar en el Singleton ShapeFactoryInitializer. As

512
13325 13326 13327 13328 13329 que para aadir un nuevo tipo a este diseo debe definir el tipo, crear una factora, y modificar ShapeFactoryInitializer para que se inserte una instancia de su factora en el mapa. Esta complejidad extra, sugiere de nuevo el uso de un Factory Method esttico si no necesita crear objetos factora individuales.

13330 13331 13332 13333 13334 13335 13336 13337 13338 13339 13340 13341 13342 13343

9.11.2. Factoras abstractas


El patrn Factora Abstracta se parece a las factoras que hemos visto anteriormente, pero con varios Factory Methods. Cada uno de los Factory Method crea una clase distinta de objeto. Cuando se crea el objecto factora, se decide cmo se usarn todos los objetos creados con esa factora. El ejemplo del GoF implementa la portabilidad a travs de varias interfaces grficas de usuario (GUI): se crea el objeto factora apropiado para la GUI con la que se est trabajando y desde ah en adelante, cuando le pida un men, botn, barra deslizante y dems, crear automticamente la versin apropiada para la GUI de ese elemento. Por lo tanto, es posible aislar, en un solo lugar, el efecto de cambiar de una GUI a otra. Por ejemplo, suponga que est creando un entorno para juegos de propsito general y quiere ser capaz de soportar diferentes tipos de juegos. As es como sera usando una Factora Abstracta:

13344 13345 13346 13347 13348 13349 13350 13351 13352

//: C10:AbstractFactory.cpp // A gaming environment. #include <iostream> using namespace std;

class Obstacle { public: virtual void action() = 0; };

513

13353 13354 13355 13356 13357 13358 13359 13360 13361 13362 13363 13364 13365 13366 13367 13368 13369 13370 13371 13372 13373 13374 13375 13376 13377
class Puzzle: public Obstacle { public: void action() { cout << "Puzzle" << endl; } }; class KungFuGuy: public Player { virtual void interactWith(Obstacle* ob) { cout << "KungFuGuy now battles against a "; ob->action(); } }; class Kitty: public Player { virtual void interactWith(Obstacle* ob) { cout << "Kitty has encountered a "; ob->action(); } }; class Player { public: virtual void interactWith(Obstacle*) = 0; };

514

13378 13379 13380 13381 13382 13383 13384 13385 13386 13387 13388 13389 13390 13391 13392 13393 13394 13395 13396 13397 13398 13399 13400 13401 13402

class NastyWeapon: public Obstacle { public: void action() { cout << "NastyWeapon" << endl; } };

// The abstract factory: class GameElementFactory { public: virtual Player* makePlayer() = 0; virtual Obstacle* makeObstacle() = 0; };

// Concrete factories: class KittiesAndPuzzles : public GameElementFactory { public: virtual Player* makePlayer() { return new Kitty; } virtual Obstacle* makeObstacle() { return new Puzzle; } };

class KillAndDismember : public GameElementFactory { public: virtual Player* makePlayer() { return new KungFuGuy; } virtual Obstacle* makeObstacle() { return new NastyWeapon; }

515

13403 13404 13405 13406 13407 13408 13409 13410 13411 13412 13413 13414 13415 13416 13417 13418 13419 13420 13421 13422 13423 13424 13425 13426 13427

};

class GameEnvironment { GameElementFactory* gef; Player* p; Obstacle* ob; public: GameEnvironment(GameElementFactory* factory) : gef(factory), p(factory->makePlayer()), ob(factory->makeObstacle()) {} void play() { p->interactWith(ob); } ~GameEnvironment() { delete p; delete ob; delete gef; } };

int main() { GameEnvironment g1(new KittiesAndPuzzles), g2(new KillAndDismember); g1.play(); g2.play(); }

516

13428 13429 13430

/* Output: Kitty has encountered a Puzzle KungFuGuy now battles against a NastyWeapon */ ///:~

13431 13432 13433 13434 13435 13436 13437 13438 13439 13440 13441 13442

Listado 9.25. C10/AbstractFactory.cpp En este entorno, los objetos Player interactan con objetos Obstacle, pero los tipos de los jugadores y los obstculos dependen del juego. El tipo de juego se determina eligiendo un GameElementFactory concreto, y luego el GameEnvironment controla la configuracin y ejecucin del juego. En este ejemplo, la configuracin y ejecucin son simples, pero dichas actividades (las condiciones iniciales y los cambios de estado) pueden determinar gran parte del resultado del juego. Aqu, GameEnvironment no est diseado para ser heredado, aunque puede tener sentido hacerlo. Este ejemplo tambin ilustra el despachado doble, que se explicar ms adelante.

13443

9.11.3. Constructores virtuales


//: C10:VirtualConstructor.cpp #include <iostream> #include <string> #include <stdexcept> #include <stdexcept> #include <cstddef> #include <vector> #include "../purge.h" using namespace std;

13444 13445 13446 13447 13448 13449 13450 13451 13452 13453

517

13454 13455 13456 13457 13458 13459 13460 13461 13462 13463 13464 13465 13466 13467 13468 13469 13470 13471 13472 13473 13474 13475 13476 13477 13478

class Shape { Shape* s; // Prevent copy-construction & operator= Shape(Shape&); Shape operator=(Shape&); protected: Shape() { s = 0; } public: virtual void draw() { s->draw(); } virtual void erase() { s->erase(); } virtual void test() { s->test(); } virtual ~Shape() { cout << "~Shape" << endl; if(s) { cout << "Making virtual call: "; s->erase(); // Virtual call } cout << "delete s: "; delete s; // The polymorphic deletion // (delete 0 is legal; it produces a no-op) } class BadShapeCreation : public logic_error { public: BadShapeCreation(string type) : logic_error("Cannot create type " + type) {}

518

13479 13480 13481 13482 13483 13484 13485 13486 13487 13488 13489 13490 13491 13492 13493 13494 13495 13496 13497 13498 13499 13500 13501 13502 13503

}; Shape(string type) throw(BadShapeCreation); };

class Circle : public Shape { Circle(Circle&); Circle operator=(Circle&); Circle() {} // Private constructor friend class Shape; public: void draw() { cout << "Circle::draw" << endl; } void erase() { cout << "Circle::erase" << endl; } void test() { draw(); } ~Circle() { cout << "Circle::~Circle" << endl; } };

class Square : public Shape { Square(Square&); Square operator=(Square&); Square() {} friend class Shape; public: void draw() { cout << "Square::draw" << endl; } void erase() { cout << "Square::erase" << endl; } void test() { draw(); }

519

13504 13505 13506 13507 13508 13509 13510 13511 13512 13513 13514 13515 13516 13517 13518 13519 13520 13521 13522 13523 13524 13525 13526 13527 13528

~Square() { cout << "Square::~Square" << endl; } };

Shape::Shape(string type) throw(Shape::BadShapeCreation) { if(type == "Circle") s = new Circle; else if(type == "Square") s = new Square; else throw BadShapeCreation(type); draw(); // Virtual call in the constructor }

char* sl[] = { "Circle", "Square", "Square", "Circle", "Circle", "Circle", "Square" };

int main() { vector<Shape*> shapes; cout << "virtual constructor calls:" << endl; try { for(size_t i = 0; i < sizeof sl / sizeof sl[0]; i++) shapes.push_back(new Shape(sl[i])); } catch(Shape::BadShapeCreation e) { cout << e.what() << endl; purge(shapes); return EXIT_FAILURE;

520

13529 13530 13531 13532 13533 13534 13535 13536 13537 13538 13539 13540

} for(size_t i = 0; i < shapes.size(); i++) { shapes[i]->draw(); cout << "test" << endl; shapes[i]->test(); cout << "end test" << endl; shapes[i]->erase(); } Shape c("Circle"); // Create on the stack cout << "destructor calls:" << endl; purge(shapes); } ///:~

13541 13542

Listado 9.26. C10/VirtualConstructor.cpp

13543

9.12. Builder: creacin de objetos complejos


//: C10:Bicycle.h // Defines classes to build bicycles; // Illustrates the Builder design pattern. #ifndef BICYCLE_H #define BICYCLE_H #include <iostream> #include <string> #include <vector>

13544 13545 13546 13547 13548 13549 13550 13551

521

13552 13553 13554 13555 13556 13557 13558 13559 13560 13561 13562 13563 13564 13565 13566 13567 13568 13569 13570 13571 13572 13573 13574 13575 13576

#include <cstddef> #include "../purge.h" using std::size_t;

class BicyclePart { public: enum BPart { FRAME, WHEEL, SEAT, DERAILLEUR, HANDLEBAR, SPROCKET, RACK, SHOCK, NPARTS }; private: BPart id; static std::string names[NPARTS]; public: BicyclePart(BPart bp) { id = bp; } friend std::ostream& operator<<(std::ostream& os, const BicyclePart& bp) { return os << bp.names[bp.id]; } };

class Bicycle { std::vector<BicyclePart*> parts; public: ~Bicycle() { purge(parts); } void addPart(BicyclePart* bp) { parts.push_back(bp); } friend std::ostream&

522

13577 13578 13579 13580 13581 13582 13583 13584 13585 13586 13587 13588 13589 13590 13591 13592 13593 13594 13595 13596 13597 13598 13599 13600 13601

operator<<(std::ostream& os, const Bicycle& b) { os << "{ "; for(size_t i = 0; i < b.parts.size(); ++i) os << *b.parts[i] << ' '; return os << '}'; } };

class BicycleBuilder { protected: Bicycle* product; public: BicycleBuilder() { product = 0; } void createProduct() { product = new Bicycle; } virtual void buildFrame() = 0; virtual void buildWheel() = 0; virtual void buildSeat() = 0; virtual void buildDerailleur() = 0; virtual void buildHandlebar() = 0; virtual void buildSprocket() = 0; virtual void buildRack() = 0; virtual void buildShock() = 0; virtual std::string getBikeName() const = 0; Bicycle* getProduct() { Bicycle* temp = product;

523

13602 13603 13604 13605 13606 13607 13608 13609 13610 13611 13612 13613 13614 13615 13616 13617 13618 13619 13620 13621 13622 13623 13624 13625 13626

product = 0;

// Relinquish product

return temp; } };

class MountainBikeBuilder : public BicycleBuilder { public: void buildFrame(); void buildWheel(); void buildSeat(); void buildDerailleur(); void buildHandlebar(); void buildSprocket(); void buildRack(); void buildShock(); std::string getBikeName() const { return "MountainBike";} };

class TouringBikeBuilder : public BicycleBuilder { public: void buildFrame(); void buildWheel(); void buildSeat(); void buildDerailleur(); void buildHandlebar();

524

13627 13628 13629 13630 13631 13632 13633 13634 13635 13636 13637 13638 13639 13640 13641 13642 13643 13644 13645 13646 13647 13648 13649 13650 13651

void buildSprocket(); void buildRack(); void buildShock(); std::string getBikeName() const { return "TouringBike"; } };

class RacingBikeBuilder : public BicycleBuilder { public: void buildFrame(); void buildWheel(); void buildSeat(); void buildDerailleur(); void buildHandlebar(); void buildSprocket(); void buildRack(); void buildShock(); std::string getBikeName() const { return "RacingBike"; } };

class BicycleTechnician { BicycleBuilder* builder; public: BicycleTechnician() { builder = 0; } void setBuilder(BicycleBuilder* b) { builder = b; } void construct();

525

13652 13653

}; #endif // BICYCLE_H ///:~

13654 13655

Listado 9.27. C10/Bicycle.h

13656 13657 13658 13659 13660 13661 13662 13663 13664 13665 13666 13667 13668 13669 13670 13671 13672 13673 13674 13675

//: C10:Bicycle.cpp {O} {-mwcc} #include "Bicycle.h" #include <cassert> #include <cstddef> using namespace std;

std::string BicyclePart::names[NPARTS] = { "Frame", "Wheel", "Seat", "Derailleur", "Handlebar", "Sprocket", "Rack", "Shock" };

// MountainBikeBuilder implementation void MountainBikeBuilder::buildFrame() { product->addPart(new BicyclePart(BicyclePart::FRAME)); } void MountainBikeBuilder::buildWheel() { product->addPart(new BicyclePart(BicyclePart::WHEEL)); } void MountainBikeBuilder::buildSeat() { product->addPart(new BicyclePart(BicyclePart::SEAT)); }

526

13676 13677 13678 13679 13680 13681 13682 13683 13684 13685 13686 13687 13688 13689 13690 13691 13692 13693 13694 13695 13696 13697 13698 13699 13700

void MountainBikeBuilder::buildDerailleur() { product->addPart( new BicyclePart(BicyclePart::DERAILLEUR)); } void MountainBikeBuilder::buildHandlebar() { product->addPart( new BicyclePart(BicyclePart::HANDLEBAR)); } void MountainBikeBuilder::buildSprocket() { product->addPart(new BicyclePart(BicyclePart::SPROCKET)); } void MountainBikeBuilder::buildRack() {} void MountainBikeBuilder::buildShock() { product->addPart(new BicyclePart(BicyclePart::SHOCK)); }

// TouringBikeBuilder implementation void TouringBikeBuilder::buildFrame() { product->addPart(new BicyclePart(BicyclePart::FRAME)); } void TouringBikeBuilder::buildWheel() { product->addPart(new BicyclePart(BicyclePart::WHEEL)); } void TouringBikeBuilder::buildSeat() { product->addPart(new BicyclePart(BicyclePart::SEAT));

527

13701 13702 13703 13704 13705 13706 13707 13708 13709 13710 13711 13712 13713 13714 13715 13716 13717 13718 13719 13720 13721 13722 13723 13724 13725

} void TouringBikeBuilder::buildDerailleur() { product->addPart( new BicyclePart(BicyclePart::DERAILLEUR)); } void TouringBikeBuilder::buildHandlebar() { product->addPart( new BicyclePart(BicyclePart::HANDLEBAR)); } void TouringBikeBuilder::buildSprocket() { product->addPart(new BicyclePart(BicyclePart::SPROCKET)); } void TouringBikeBuilder::buildRack() { product->addPart(new BicyclePart(BicyclePart::RACK)); } void TouringBikeBuilder::buildShock() {}

// RacingBikeBuilder implementation void RacingBikeBuilder::buildFrame() { product->addPart(new BicyclePart(BicyclePart::FRAME)); } void RacingBikeBuilder::buildWheel() { product->addPart(new BicyclePart(BicyclePart::WHEEL)); } void RacingBikeBuilder::buildSeat() {

528

13726 13727 13728 13729 13730 13731 13732 13733 13734 13735 13736 13737 13738 13739 13740 13741 13742 13743 13744 13745 13746 13747 13748 13749 13750

product->addPart(new BicyclePart(BicyclePart::SEAT)); } void RacingBikeBuilder::buildDerailleur() {} void RacingBikeBuilder::buildHandlebar() { product->addPart( new BicyclePart(BicyclePart::HANDLEBAR)); } void RacingBikeBuilder::buildSprocket() { product->addPart(new BicyclePart(BicyclePart::SPROCKET)); } void RacingBikeBuilder::buildRack() {} void RacingBikeBuilder::buildShock() {}

// BicycleTechnician implementation void BicycleTechnician::construct() { assert(builder); builder->createProduct(); builder->buildFrame(); builder->buildWheel(); builder->buildSeat(); builder->buildDerailleur(); builder->buildHandlebar(); builder->buildSprocket(); builder->buildRack(); builder->buildShock();

529

13751

} ///:~

13752 13753

Listado 9.28. C10/Bicycle.cpp

13754 13755 13756 13757 13758 13759 13760 13761 13762 13763 13764 13765 13766 13767 13768 13769 13770 13771 13772 13773 13774

//: C10:BuildBicycles.cpp //{L} Bicycle // The Builder design pattern. #include <cstddef> #include <iostream> #include <map> #include <vector> #include "Bicycle.h" #include "../purge.h" using namespace std;

// Constructs a bike via a concrete builder Bicycle* buildMeABike( BicycleTechnician& t, BicycleBuilder* builder) { t.setBuilder(builder); t.construct(); Bicycle* b = builder->getProduct(); cout << "Built a " << builder->getBikeName() << endl; return b; }

530

13775 13776 13777 13778 13779 13780 13781 13782 13783 13784 13785 13786 13787 13788 13789 13790 13791 13792 13793 13794 13795 13796 13797 13798 13799

int main() { // Create an order for some bicycles map <string, size_t> order; order["mountain"] = 2; order["touring"] = 1; order["racing"] = 3;

// Build bikes vector<Bicycle*> bikes; BicycleBuilder* m = new MountainBikeBuilder; BicycleBuilder* t = new TouringBikeBuilder; BicycleBuilder* r = new RacingBikeBuilder; BicycleTechnician tech; map<string, size_t>::iterator it = order.begin(); while(it != order.end()) { BicycleBuilder* builder; if(it->first == "mountain") builder = m; else if(it->first == "touring") builder = t; else if(it->first == "racing") builder = r; for(size_t i = 0; i < it->second; ++i) bikes.push_back(buildMeABike(tech, builder)); ++it;

531

13800 13801 13802 13803 13804 13805 13806 13807 13808 13809 13810 13811 13812 13813 13814 13815 13816 13817 13818 13819 13820 13821 13822 13823 13824

} delete m; delete t; delete r;

// Display inventory for(size_t i = 0; i < bikes.size(); ++i) cout << "Bicycle: " << *bikes[i] << endl; purge(bikes); }

/* Output: Built a MountainBike Built a MountainBike Built a RacingBike Built a RacingBike Built a RacingBike Built a TouringBike Bicycle: { Frame Wheel Seat Derailleur Handlebar Sprocket Shock } Bicycle: { Frame Wheel Seat Derailleur Handlebar Sprocket Shock } Bicycle: { Frame Wheel Seat Handlebar Sprocket } Bicycle: { Frame Wheel Seat Handlebar Sprocket } Bicycle: { Frame Wheel Seat Handlebar Sprocket }

532

13825 13826 13827

Bicycle: { Frame Wheel Seat Derailleur Handlebar Sprocket Rack } */ ///:~

13828 13829

Listado 9.29. C10/BuildBicycles.cpp

13830

9.13. Observador
//: C10:Observer.h // The Observer interface. #ifndef OBSERVER_H #define OBSERVER_H

13831 13832 13833 13834 13835 13836 13837 13838 13839 13840 13841 13842 13843 13844 13845 13846

class Observable; class Argument {};

class Observer { public: // Called by the observed object, whenever // the observed object is changed: virtual void update(Observable* o, Argument* arg) = 0; virtual ~Observer() {} }; #endif // OBSERVER_H ///:~

13847

Listado 9.30. C10/Observer.h

533
13848

13849 13850 13851 13852 13853 13854 13855 13856 13857 13858 13859 13860 13861 13862 13863 13864 13865 13866 13867 13868 13869 13870 13871 13872

//: C10:Observable.h // The Observable class. #ifndef OBSERVABLE_H #define OBSERVABLE_H #include <set> #include "Observer.h"

class Observable { bool changed; std::set<Observer*> observers; protected: virtual void setChanged() { changed = true; } virtual void clearChanged() { changed = false; } public: virtual void addObserver(Observer& o) { observers.insert(&o); } virtual void deleteObserver(Observer& o) { observers.erase(&o); } virtual void deleteObservers() { observers.clear(); } virtual int countObservers() {

534

13873 13874 13875 13876 13877 13878 13879 13880 13881 13882 13883 13884 13885 13886 13887

return observers.size(); } virtual bool hasChanged() { return changed; } // If this object has changed, notify all // of its observers: virtual void notifyObservers(Argument* arg = 0) { if(!hasChanged()) return; clearChanged(); // Not "changed" anymore std::set<Observer*>::iterator it; for(it = observers.begin();it != observers.end(); it++) (*it)->update(this, arg); } virtual ~Observable() {} }; #endif // OBSERVABLE_H ///:~

13888 13889

Listado 9.31. C10/Observable.h

13890 13891 13892 13893 13894 13895 13896

//: C10:InnerClassIdiom.cpp // Example of the "inner class" idiom. #include <iostream> #include <string> using namespace std;

class Poingable {

535

13897 13898 13899 13900 13901 13902 13903 13904 13905 13906 13907 13908 13909 13910 13911 13912 13913 13914 13915 13916 13917 13918 13919 13920 13921

public: virtual void poing() = 0; };

void callPoing(Poingable& p) { p.poing(); }

class Bingable { public: virtual void bing() = 0; };

void callBing(Bingable& b) { b.bing(); }

class Outer { string name; // Define one inner class: class Inner1; friend class Outer::Inner1; class Inner1 : public Poingable { Outer* parent; public:

536

13922 13923 13924 13925 13926 13927 13928 13929 13930 13931 13932 13933 13934 13935 13936 13937 13938 13939 13940 13941 13942 13943 13944 13945 13946

Inner1(Outer* p) : parent(p) {} void poing() { cout << "poing called for " << parent->name << endl; // Accesses data in the outer class object } } inner1; // Define a second inner class: class Inner2; friend class Outer::Inner2; class Inner2 : public Bingable { Outer* parent; public: Inner2(Outer* p) : parent(p) {} void bing() { cout << "bing called for " << parent->name << endl; } } inner2; public: Outer(const string& nm) : name(nm), inner1(this), inner2(this) {} // Return reference to interfaces // implemented by the inner classes: operator Poingable&() { return inner1; }

537

13947 13948 13949 13950 13951 13952 13953 13954 13955

operator Bingable&() { return inner2; } };

int main() { Outer x("Ping Pong"); // Like upcasting to multiple base types!: callPoing(x); callBing(x); } ///:~

13956 13957 13958

Listado 9.32. C10/InnerClassIdiom.cpp

9.13.1. El ejemplo de observador


//: C10:ObservedFlower.cpp // Demonstration of "observer" pattern. #include <algorithm> #include <iostream> #include <string> #include <vector> #include "Observable.h" using namespace std;

13959 13960 13961 13962 13963 13964 13965 13966 13967 13968 13969 13970

class Flower { bool isOpen; public:

538

13971 13972 13973 13974 13975 13976 13977 13978 13979 13980 13981 13982 13983 13984 13985 13986 13987 13988 13989 13990 13991 13992 13993 13994 13995

Flower() : isOpen(false), openNotifier(this), closeNotifier(this) {} void open() { // Opens its petals isOpen = true; openNotifier.notifyObservers(); closeNotifier.open(); } void close() { // Closes its petals isOpen = false; closeNotifier.notifyObservers(); openNotifier.close(); } // Using the "inner class" idiom: class OpenNotifier; friend class Flower::OpenNotifier; class OpenNotifier : public Observable { Flower* parent; bool alreadyOpen; public: OpenNotifier(Flower* f) : parent(f), alreadyOpen(false) {} void notifyObservers(Argument* arg = 0) { if(parent->isOpen && !alreadyOpen) { setChanged(); Observable::notifyObservers();

539

13996 13997 13998 13999 14000 14001 14002 14003 14004 14005 14006 14007 14008 14009 14010 14011 14012 14013 14014 14015 14016 14017 14018 14019 14020

alreadyOpen = true; } } void close() { alreadyOpen = false; } } openNotifier; class CloseNotifier; friend class Flower::CloseNotifier; class CloseNotifier : public Observable { Flower* parent; bool alreadyClosed; public: CloseNotifier(Flower* f) : parent(f), alreadyClosed(false) {} void notifyObservers(Argument* arg = 0) { if(!parent->isOpen && !alreadyClosed) { setChanged(); Observable::notifyObservers(); alreadyClosed = true; } } void open() { alreadyClosed = false; } } closeNotifier; };

class Bee {

540

14021 14022 14023 14024 14025 14026 14027 14028 14029 14030 14031 14032 14033 14034 14035 14036 14037 14038 14039 14040 14041 14042 14043 14044 14045

string name; // An "inner class" for observing openings: class OpenObserver; friend class Bee::OpenObserver; class OpenObserver : public Observer { Bee* parent; public: OpenObserver(Bee* b) : parent(b) {} void update(Observable*, Argument *) { cout << "Bee " << parent->name << "'s breakfast time!" << endl; } } openObsrv; // Another "inner class" for closings: class CloseObserver; friend class Bee::CloseObserver; class CloseObserver : public Observer { Bee* parent; public: CloseObserver(Bee* b) : parent(b) {} void update(Observable*, Argument *) { cout << "Bee " << parent->name << "'s bed time!" << endl; } } closeObsrv;

541

14046 14047 14048 14049 14050 14051 14052 14053 14054 14055 14056 14057 14058 14059 14060 14061 14062 14063 14064 14065 14066 14067 14068 14069 14070

public: Bee(string nm) : name(nm), openObsrv(this), closeObsrv(this) {} Observer& openObserver() { return openObsrv; } Observer& closeObserver() { return closeObsrv;} };

class Hummingbird { string name; class OpenObserver; friend class Hummingbird::OpenObserver; class OpenObserver : public Observer { Hummingbird* parent; public: OpenObserver(Hummingbird* h) : parent(h) {} void update(Observable*, Argument *) { cout << "Hummingbird " << parent->name << "'s breakfast time!" << endl; } } openObsrv; class CloseObserver; friend class Hummingbird::CloseObserver; class CloseObserver : public Observer { Hummingbird* parent; public:

542

14071 14072 14073 14074 14075 14076 14077 14078 14079 14080 14081 14082 14083 14084 14085 14086 14087 14088 14089 14090 14091 14092 14093 14094 14095

CloseObserver(Hummingbird* h) : parent(h) {} void update(Observable*, Argument *) { cout << "Hummingbird " << parent->name << "'s bed time!" << endl; } } closeObsrv; public: Hummingbird(string nm) : name(nm), openObsrv(this), closeObsrv(this) {} Observer& openObserver() { return openObsrv; } Observer& closeObserver() { return closeObsrv;} };

int main() { Flower f; Bee ba("A"), bb("B"); Hummingbird ha("A"), hb("B"); f.openNotifier.addObserver(ha.openObserver()); f.openNotifier.addObserver(hb.openObserver()); f.openNotifier.addObserver(ba.openObserver()); f.openNotifier.addObserver(bb.openObserver()); f.closeNotifier.addObserver(ha.closeObserver()); f.closeNotifier.addObserver(hb.closeObserver()); f.closeNotifier.addObserver(ba.closeObserver()); f.closeNotifier.addObserver(bb.closeObserver());

543

14096 14097 14098 14099 14100 14101 14102 14103 14104 14105 14106 14107 14108 14109

// Hummingbird B decides to sleep in: f.openNotifier.deleteObserver(hb.openObserver()); // Something changes that interests observers: f.open(); f.open(); // It's already open, no change. // Bee A doesn't want to go to bed: f.closeNotifier.deleteObserver( ba.closeObserver()); f.close(); f.close(); // It's already closed; no change f.openNotifier.deleteObservers(); f.open(); f.close(); } ///:~

14110 14111

Listado 9.33. C10/ObservedFlower.cpp

14112

9.14. Despachado mltiple


//: C10:PaperScissorsRock.cpp // Demonstration of multiple dispatching. #include <algorithm> #include <iostream> #include <iterator> #include <vector>

14113 14114 14115 14116 14117 14118

544

14119 14120 14121 14122 14123 14124 14125 14126 14127 14128 14129 14130 14131 14132 14133 14134 14135 14136 14137 14138 14139 14140 14141 14142 14143

#include <ctime> #include <cstdlib> #include "../purge.h" using namespace std;

class Paper; class Scissors; class Rock;

enum Outcome { WIN, LOSE, DRAW };

ostream& operator<<(ostream& os, const Outcome out) { switch(out) { default: case WIN: return os << "win"; case LOSE: return os << "lose"; case DRAW: return os << "draw"; } }

class Item { public: virtual Outcome compete(const Item*) = 0; virtual Outcome eval(const Paper*) const = 0; virtual Outcome eval(const Scissors*) const= 0;

545

14144 14145 14146 14147 14148 14149 14150 14151 14152 14153 14154 14155 14156 14157 14158 14159 14160 14161 14162 14163 14164 14165 14166 14167 14168

virtual Outcome eval(const Rock*) const = 0; virtual ostream& print(ostream& os) const = 0; virtual ~Item() {} friend ostream& operator<<(ostream& os, const Item* it) { return it->print(os); } };

class Paper : public Item { public: Outcome compete(const Item* it) { return it->eval(this);} Outcome eval(const Paper*) const { return DRAW; } Outcome eval(const Scissors*) const { return WIN; } Outcome eval(const Rock*) const { return LOSE; } ostream& print(ostream& os) const { return os << "Paper } }; ";

class Scissors : public Item { public: Outcome compete(const Item* it) { return it->eval(this);} Outcome eval(const Paper*) const { return LOSE; } Outcome eval(const Scissors*) const { return DRAW; } Outcome eval(const Rock*) const { return WIN; }

546

14169 14170 14171 14172 14173 14174 14175 14176 14177 14178 14179 14180 14181 14182 14183 14184 14185 14186 14187 14188 14189 14190 14191 14192 14193

ostream& print(ostream& os) const { return os << "Scissors"; } };

class Rock : public Item { public: Outcome compete(const Item* it) { return it->eval(this);} Outcome eval(const Paper*) const { return WIN; } Outcome eval(const Scissors*) const { return LOSE; } Outcome eval(const Rock*) const { return DRAW; } ostream& print(ostream& os) const { return os << "Rock } }; ";

struct ItemGen { Item* operator()() { switch(rand() % 3) { default: case 0: return new Scissors; case 1: return new Paper; case 2: return new Rock; } }

547

14194 14195 14196 14197 14198 14199 14200 14201 14202 14203 14204 14205 14206 14207 14208 14209 14210 14211 14212 14213

};

struct Compete { Outcome operator()(Item* a, Item* b) { cout << a << "\t" << b << "\t"; return a->compete(b); } };

int main() { srand(time(0)); // Seed the random number generator const int sz = 20; vector<Item*> v(sz*2); generate(v.begin(), v.end(), ItemGen()); transform(v.begin(), v.begin() + sz, v.begin() + sz, ostream_iterator<Outcome>(cout, "\n"), Compete()); purge(v); } ///:~

14214 14215 14216

Listado 9.34. C10/PaperScissorsRock.cpp

9.14.1. Despachado mltiple con Visitor


//: C10:BeeAndFlowers.cpp

14217

548

14218 14219 14220 14221 14222 14223 14224 14225 14226 14227 14228 14229 14230 14231 14232 14233 14234 14235 14236 14237 14238 14239 14240 14241 14242

// Demonstration of "visitor" pattern. #include <algorithm> #include <iostream> #include <string> #include <vector> #include <ctime> #include <cstdlib> #include "../purge.h" using namespace std;

class Gladiolus; class Renuculus; class Chrysanthemum;

class Visitor { public: virtual void visit(Gladiolus* f) = 0; virtual void visit(Renuculus* f) = 0; virtual void visit(Chrysanthemum* f) = 0; virtual ~Visitor() {} };

class Flower { public: virtual void accept(Visitor&) = 0;

549

14243 14244 14245 14246 14247 14248 14249 14250 14251 14252 14253 14254 14255 14256 14257 14258 14259 14260 14261 14262 14263 14264 14265 14266 14267

virtual ~Flower() {} };

class Gladiolus : public Flower { public: virtual void accept(Visitor& v) { v.visit(this); } };

class Renuculus : public Flower { public: virtual void accept(Visitor& v) { v.visit(this); } };

class Chrysanthemum : public Flower { public: virtual void accept(Visitor& v) { v.visit(this); } };

// Add the ability to produce a string:

550

14268 14269 14270 14271 14272 14273 14274 14275 14276 14277 14278 14279 14280 14281 14282 14283 14284 14285 14286 14287 14288 14289 14290 14291 14292

class StringVal : public Visitor { string s; public: operator const string&() { return s; } virtual void visit(Gladiolus*) { s = "Gladiolus"; } virtual void visit(Renuculus*) { s = "Renuculus"; } virtual void visit(Chrysanthemum*) { s = "Chrysanthemum"; } };

// Add the ability to do "Bee" activities: class Bee : public Visitor { public: virtual void visit(Gladiolus*) { cout << "Bee and Gladiolus" << endl; } virtual void visit(Renuculus*) { cout << "Bee and Renuculus" << endl; } virtual void visit(Chrysanthemum*) {

551

14293 14294 14295 14296 14297 14298 14299 14300 14301 14302 14303 14304 14305 14306 14307 14308 14309 14310 14311 14312 14313 14314 14315 14316 14317

cout << "Bee and Chrysanthemum" << endl; } };

struct FlowerGen { Flower* operator()() { switch(rand() % 3) { default: case 0: return new Gladiolus; case 1: return new Renuculus; case 2: return new Chrysanthemum; } } };

int main() { srand(time(0)); // Seed the random number generator vector<Flower*> v(10); generate(v.begin(), v.end(), FlowerGen()); vector<Flower*>::iterator it; // It's almost as if I added a virtual function // to produce a Flower string representation: StringVal sval; for(it = v.begin(); it != v.end(); it++) { (*it)->accept(sval);

552

14318 14319 14320 14321 14322 14323 14324 14325

cout << string(sval) << endl; } // Perform "Bee" operation on all Flowers: Bee bee; for(it = v.begin(); it != v.end(); it++) (*it)->accept(bee); purge(v); } ///:~

14326 14327

Listado 9.35. C10/BeeAndFlowers.cpp

14328

9.15. Resumen 9.16. Ejercicios

14329

14330 14331 14332 14333 14334 14335 14336 14337 14338 14339 14340 14341 14342 14343 14344 14345

10: Concurrencia
Tabla de contenidos 10.1. Motivacin 10.2. Concurrencia en C++ 10.3. Utilizacin de los hilos 10.4. Comparicin de recursos limitados 10.5. Finalizacin de tareas 10.6. Cooperacin entre hilos 10.7. Bloqueo letal 10.8. Resumen 10.9. Ejercicios Los objetos ofrecen una forma de dividir un programa en diferentes secciones. A menudo, tambin es necesario dividir un programa, independientemente de las subtareas en ejecucin. Utilizando multihilado, un hilo de ejecucin dirige cada una esas subtareas independientes, y puedes programar como si cada hilo tuviera su propia

553
14346 14347 14348 14349 14350 14351 14352 14353 14354 14355 14356 14357 14358 14359 14360 14361 14362 14363 14364 14365 14366 14367 14368 14369 14370 14371 14372 14373 14374 14375 14376 14377 14378 14379 CPU. Un mecanismo interno reparte el tiempo de CPU por ti, pero en general, no necesitas pensar acerca de eso, lo que ayuda a simplificar la programacin con mltiples hilos. Un proceso es un programa autocontenido en ejecucin con su propio espacio de direcciones. Un sistema operativo multitarea puede ejecutar ms de un proceso (programa) en el mismo tiempo, mientras ....., por medio de cambios peridicos de CPU de una tarea a otra. Un hilo es una simple flujo de control secuencial con un proceso. Un proceso puede tener de este modo mltiples hilos en ejecucin concurrentes. Puesto que los hilos se ejecutan con un proceso simple, pueden compartir memoria y otros recursos. La dificultad fundamental de escribir programas multihilados est en coordinar el uso de esos recursos entre los diferentes hilos. Hay muchas aplicaciones posibles para el multihilado, pero lo ms usual es querer usarlo cuando tienes alguna parte de tu programa vinculada a un evento o recurso particular. Para evitar bloquear el resto de tu programa, creas un hilo asociado a ese evento o recurso y le permites ejecutarse independientemente del programa principal. La programacin concurrente se como caminar en un mundo completamente nuevo y aprender un nuevo lenguaje de programacin, o por lo menos un nuevo conjunto de conceptos del lenguaje. Con la aparicin del soporte para los hilos en la mayora de los sistemas operativos para microcomputadores, han aparecido tambin en los lenguajes de programacin o libreras extensiones para los hilos. En cualquier caso, programacin hilada: 1. Parece misteriosa y requiere un esfuerzo en la forma de pensar acerca de la programacin. 2. En otros lenguajes el soporte a los hilos es similar. Cuando entiendas los hilos, comprenders una jerga comn. Comprender la programacin concurrente est al mismo nivel de dificultad que comprender el polimorfismo. Si pones un poco de esfuerzo, podrs entender el mecanismo bsico, pero generalmente necesitar de un entendimiento y estudio profundo para desarrollar una comprensin autntica sobre el tema. La meta de este captulo es darte una base slida en los principios de concurrencia para que puedas entender los conceptos y escribir

554
14380 14381 14382 14383 14384 14385 14386 14387 14388 14389 14390 14391 14392 14393 14394 14395 14396 14397 14398 14399 14400 14401 14402 14403 14404 14405 14406 14407 14408 14409 14410 14411 14412 programas multihilados razonables. S consciente de que puedes confiarte fcilmente. Si vas a escribir algo complejo, necesitars estudiar libros especficos sobre el tema.

10.1. Motivacin
Una de las razones ms convincentes para usar concurrencia es crear una interfaz sensible al usuario. Considera un programa que realiza una operacin de CPU intensiva y, de esta forma, termina ignorando la entrada del usuario y comienza a no responder. El programa necesita continuar controlandor sus operaciones, y al mismo tiempo necesita devolver el control al botn de la interfaz de usuario para que el programa pueda responder al usuario. Si tienes un botn de "Salir", no querrs estar forzado a sondearlo en todas las partes de cdigo que escribas en tu programa. (Esto acoplara tu botn de salir a lo largo del programa y sera un quebradero de cabeza a la hora de mantenerlo). ..... Una funcin convencional no puede continuar realizando sus operaciones y al mismo tiempo devolver el control al resto del programa. De hecho, suena a imposible, como si la CPU estuviera en dos lugares a la vez, pero esto es precisamente la "ilusin" que la concurrencia permite (en el caso de un sistema multiprocesador, debe haber ms de una "ilusin"). Tambin puedes usar concurrencia para optimizar la carga de trabajo. Por ejemplo, podras necesitar hacer algo importante mientras ests estancado esperando la llegada de una entrada del puerto I/O. Sin hilos, la nica solucin razonable es sondear los puertos I/O, que es costoso y puede ser difcil. Si tienes una mquina multiprocesador, los mltiples hilos pueden ser distribudos a lo largo de los mltiples procesadores, pudiendo mejorar considerablemente la carga de trabajo. Este es el tpico caso de los potentes servidores web multiprocesdor, que pueden distribuir un gran nmero de peticiones de usuario por todas las CPUs en un programa que asigna un hilo por peticin. Un programa que usa hilos en una mquina monoprocesador har una cosa en un tiempo dado, por lo que es tericamente posible escribir el mismo programa sin el uso de hilos. Sin embargo, el multihilado proporciona un

555
14413 14414 14415 14416 14417 14418 14419 14420 14421 14422 14423 14424 14425 14426 14427 14428 14429 14430 14431 14432 14433 14434 14435 beneficio de optimizacin importante: El diseo de un programa puede ser maravillosamente simple. Algunos tipos de problemas, como la simulacin un video juego, por ejemplo - son difciles de resolver sin el soporte de la concurrencia. El modelo hilado es una comodidad de la programacin para simplificar el manejo de muchas operaciones al mismo tiempo con un simple programa: La CPU desapilar y dar a cada hilo algo de su tiempo. Cada hilo tiene consciencia de que tiene un tiempo constante de uso de CPU, pero el tiempo de CPU est actualmente repartido entre todo los hilos. La excepcin es un programa que se ejecuta sobre mltiples CPU's. Pero una de las cosas fabulosas que tiene el hilado es que te abstrae de esta capa, por lo que tu cdigo no necesita saber si est ejecutndose sobre una sla CPU o sobre varias.[149] De este modo, usar hilos es una manera de crear programas escalables de forma transparente - si un programa se est ejecutando demasiado despacio, puedes acelerarlo fcilmente aadiendo CPUs a tu ordenador. La multitarea y el multihilado tienden a ser las mejores opciones a utilizar en un sistema multiprocesador. El uso de hilos puede reducir la eficiencia computacional un poco, pero el aumento neto en el diseo del programa, balanceo de recursos, y la comodidad del usuario a menudo es ms valorado. En general, los hilos te permiten crear diseor ms desacoplados; de lo contrario, las partes de tu cdigo estara obligadas a prestar atencin a tareas que podras manejarlas con hilos normalmente.

14436 14437 14438 14439 14440 14441 14442 14443 14444 14445

10.2. Concurrencia en C++


Cuando el Comit de Estndares de C++ estaba creando el estndar inicial de C++, el mecanismo de concurrencia fue excludo de forma explcita porque C no tena uno y tambin porque haba varos enfoques rivales acerca de su implementacin. Pareca demasiado restrictivo forzar a los programadores a usar una sola alternativa. Sin embargo, la alternativa result ser peor. Para usar concurriencia, tenas que encontrar y aprender una librera y ocuparte de su indiosincrasia y las incertidumbres de trabajar con un vendedor particular. Adems, no haba garanta de que una librera funcionara en diferentes compiladores o en

556
14446 14447 14448 14449 14450 14451 14452 14453 14454 14455 14456 14457 14458 14459 14460 14461 14462 14463 14464 14465 14466 14467 14468 14469 14470 14471 14472 14473 14474 14475 14476 14477 14478 distintas plataformas. Tambin, desde que la concurrencia no formaba parte del estndar del lenguaje, fue ms difcil encontrar programadores C++ que tambin entendieran la programacin concurrente. Otra influencia pudo ser el lenguaje Java, que incluy concurrencia en el ncleo del lenguaje. Aunque el multihilado is an complicado, los programadores de Java tienden a empezar a aprenderlo y usarlo desde el principio. El Comit de Estndares de C++ est considerando incluir el soporte a la concurrencia en la siguiente iteracin de C++, pero en el momento de este escrito no estaba claro qu aspecto tendr la librera. Decidimos usar la librera ZThread como base para este captulo. La escogimos por su diseo, y es open-source y gratuitamente descargable desde http://zthread.sourceforge.net. Eric Crahen de IBM, el autor de la librera ZThread, fue decisivo para crear este captulo.[150] Este captulo utiliza slo un subgrupo de la librera ZThread, de acuerdo con el convenio de ideas fundamentales sobre los hilos. La librera ZThread contiene un soporte a los hilos significativamente ms sofisticado que el que se muestra aqu, y deberas estudiar esa librera ms profundamente para comprender completamente sus posibilidades.

10.2.1. Instalacin de ZThreads


Por favor, note que la librera ZThread es un proyecto independiente y no est soportada por el autor de este libro; simplemente estamos usando la librera en este captulo y no podemos dar soporte tcnico a las caractersticas de la instalacin. Mira el sitio web de ZThread para obtener soporte en la instalacin y reporte de errores. La librera ZThread se distribuye como cdigo fuente. Despus de descargarla (versin 2.3 o superior) desde la web de ZThread, debes compilar la librera primero, y despus configurar tu proyecto para que use la librera. --> El mtodo habitual para compilar la librera ZThreads para los distintos sabores de UNIX (Linux, SunOS, Cygwin, etc) es usar un script de configuracin. Despus de desempaquetar los archivos (usando tar), simplemente ejecuta: ./configure && make install

557
14479 14480 14481 14482 14483 14484 14485 14486 14487 14488 14489 14490 14491 14492 14493 14494 14495 en el directorio principal de ZThreads para compilar e instalar una copia de la librera en directorio /usr/local. Puedes personalizar algunas opciones cuando uses el script, includa la localizacin de los ficheros. Para ms detalles, utiliza este comando: ./configure ?help El cdigo de ZThreads est estructurado para simplificar la compilacin para otras plataformas (como Borland, Microsoft y Metrowerks). Para hacer esto, crea un nuevo proyecto y aade todos los archivos .cxx en el directorio src de ZThreads a la lista de archivos a compilar. Adems, asegrate de incluir el directorio includo del archivo en la ruta de bsqueda de la cabecera para tu proyecto???. Los detalles exactos variarn de compilador en compilador, por lo que necesitars estar algo familiarizado con tu conjunto de herramientas para ser capaz de utilizar esta opcin. Una vez la compilacin ha finalizado con xito, el siguiente paso es crear un proyecto que use la nueva librera compilada. Primero, permite al compilador saber donde estn localizadas las cabeceras, por lo que tu instruccin #include funcionar correctamente. Habitualmente, necesitars en tu proyecto una opcin como se muestra:

14496

-I/path/to/installation/include

14497 14498 14499 14500 14501 14502 14503

Si utilizaste el script de configuracin, la ruta de instalacin ser el prefijo de la que definiste (por defecto, /usr/local). Si utilizaste uno de los archivos de proyecto en la creacin del directorio, la ruta instalacin debera ser simplemente la ruta al directorio principal del archivo ZThreads. Despus, necesitars aadir una opcin a tu proyecto que permitir al enlazador saber donde est la librera. Si usaste el script de configuracin, se parecer a lo siguiente:

14504

-L/path/to/installation/lib ?lZThread

14505

Si usaste uno de los archivos del proyecto proporcionados, ser similar a:

558

14506

-L/path/to/installation/Debug ZThread.lib

14507 14508 14509 14510 14511 14512 14513 14514 14515 14516

De nuevo, si usaste el script de configuracin, la ruta de instalacin s ser el prefijo de la que definistes. Si su utilizaste un archivo del proyecto, la ruta ser la misma que la del directorio principal de ZThreads. Nota que si ests utilizando Linux, o Cygwin (www.cygwin.com) bajo Windows, no deberas necesitar modificar la ruta de include o de la librera; el proceso por defecto de instalacin tendr cuidado para hacerlo por ti, normalmente. En GNU/Linux, es posible que necesites aadir lo siguiente a tu .bashrc para que el sistema pueda encontrar la el archivo de la librera compartida LibZThread-x.x.so.0 cuando ejecute programas de este captulo.

14517

export LD_LIBRARY_PATH=/usr/local/lib:${LD_LIBRARY_PATH}

14518 14519 14520

(Asumiendo que utilizas el proceso de instalacin por defecto y la librera compartida acaba en /user/local/lib/; en otro caso, cambia la ruta por tu localizacin.

14521 14522 14523 14524 14525 14526

10.2.2. Definicin de tareas


Un hilo cumple con una tarea, por lo que necesitas un manera de describir esa tarea. La clase Runnable proporciona unas interfaces comunes a ejecutar para cualquier tarea. Aqu est el ncleo de las clase Runnable de ZThread, que la encontrars en el archivo Runnable.h dentro del directorio includo, despus de instalar la librera ZThread:

14527 14528 14529 14530

class Runnable { public: virtual void run() = 0; virtual ~Runnable() {}

559

14531

};

14532 14533 14534 14535 14536 14537

Al hacerla una clase base abstracta, Runnable es fcilmente combinable con una clase bsica u otras clases. Para definir una tarea, simplemente hereda de la clase Runnable y sobreescribe run( ) para que la tarea haga lo que quieres. Por ejecomplo, la tarea LiftOff siguiente muestra la cuenta atrs antes de despegar:

14538 14539 14540 14541 14542 14543 14544 14545 14546 14547 14548 14549 14550 14551 14552 14553 14554 14555

//: C11:LiftOff.h // Demonstration of the Runnable interface. #ifndef LIFTOFF_H #define LIFTOFF_H #include <iostream> #include "zthread/Runnable.h"

class LiftOff : public ZThread::Runnable { int countDown; int id; public: LiftOff(int count, int ident = 0) : countDown(count), id(ident) {} ~LiftOff() { std::cout << id << " completed" << std::endl; } void run() { while(countDown--)

560

14556 14557 14558 14559 14560

std::cout << id << ":" << countDown << std::endl; std::cout << "Liftoff!" << std::endl; } }; #endif // LIFTOFF_H ///:~

14561 14562 14563 14564 14565 14566 14567 14568

Listado 10.1. C11/LiftOff.h El identificador id sirve como distincin entre multiples instancias de la tarea. Si slo quieres hacer una instancia, debes utilizar el valor por defecto para identificarla. El destructor te permitir ver que tarea est destruda correctamente. En el siguiente ejemplo, las tareas de run( ) no estn dirigidas por hilos separados; directamente es una simple llamada en main( ):

14569 14570 14571 14572 14573 14574 14575

//: C11:NoThread.cpp #include "LiftOff.h"

int main() { LiftOff launch(10); launch.run(); } ///:~

14576 14577 14578 14579 14580 14581

Listado 10.2. C11/NoThread.cpp Cuando una clase deriva de Runnable, debe tene una funcin run( ), pero no tiene nada de especial - no produce ninguna habibilidad innata en el hilo. Para llevar a cabo el funcionamiento de los hilos, debes utilizas la clase Thread.

561
14582 14583 14584 14585 14586 14587 14588

10.3. Utilizacin de los hilos


Para controlar un objeto Runnable con un hilo, crea un objeto Thread separado y utiliza un pontero Runnable al constructor de Thread. Esto lleva a cabo la inicializacin del hilo y, despus, llama a run ( ) de Runnable como un hilo capaz de ser interrumpido. Manejando LiftOff con un hilo, el ejemplo siguiente muestra como cualquier tarea puede ser ejecutada en el contexto de cualquier otro hilo:

14589 14590 14591 14592 14593 14594 14595 14596 14597 14598 14599 14600 14601 14602 14603 14604 14605

//: C11:BasicThreads.cpp // The most basic use of the Thread class. //{L} ZThread #include <iostream> #include "LiftOff.h" #include "zthread/Thread.h" using namespace ZThread; using namespace std;

int main() { try { Thread t(new LiftOff(10)); cout << "Waiting for LiftOff" << endl; } catch(Synchronization_Exception& e) { cerr << e.what() << endl; } } ///:~

14606

Listado 10.3. C11/BasicThreads.cpp

562
14607 14608 14609 14610 14611 14612 14613 14614 14615

Synchronization_Exception forma parte de la librera ZThread y la clase base para todas las excepciones de ZThread. Se lanzar si hay un error al crear o usar un hilo. Un constructor de Thread slo necesita un puntero a un objeto Runnable. Al crear un objeto Thread se efectuar la incializacin necesaria del hilo y despus se llamar a Runnable::run() Puede aadir ms hilos fcilmente para controlar ms tareas. A continuacin, puede ver cmo los hilos se ejecutan con algn otro:

14616 14617 14618 14619 14620 14621 14622 14623 14624 14625 14626 14627 14628 14629 14630 14631 14632 14633

//: C11:MoreBasicThreads.cpp // Adding more threads. //{L} ZThread #include <iostream> #include "LiftOff.h" #include "zthread/Thread.h" using namespace ZThread; using namespace std;

int main() { const int SZ = 5; try { for(int i = 0; i < SZ; i++) Thread t(new LiftOff(10, i)); cout << "Waiting for LiftOff" << endl; } catch(Synchronization_Exception& e) { cerr << e.what() << endl; }

563

14634

} ///:~

14635 14636 14637 14638 14639 14640 14641 14642 14643 14644 14645 14646 14647 14648 14649 14650 14651 14652 14653 14654 14655 14656 14657 14658 14659 14660 14661 14662 14663 14664

Listado 10.4. C11/MoreBasicThreads.cpp El segundo argumento del constructor de LiftOff identifica cada tarea. Cuando ejecute el programa, ver que la ejecucin de las distintas tareas se mezclan a medida que los hilos entran y salen de su ejecucin. Este intercambio est controlado automticamente por el planificador de hilos. Si tiene mltiples procesadores en su mquina, el planificador de hilos distribuir los hilos entre los procesadores de forma transparente. El bucle for puede parecer un poco extrao a priori ya que se crea localmente dentro del bucle for e inmediatamente despus sale del mbito y es destrudo. Esto hace que parezca que el hilo propiamente dicho pueda perderse inmediatamente, pero puede ver por la salida que los hilos, en efecto, estn en ejecucin hasta su finalizacin. Cuando crea un objeto
Thread,

el hilo asociado se registra en el sistema de hilos, que lo mantiene

vivo. A pesar de que el objeto Thread local se pierde, el hilo sigue vivo hasta que su tarea asociada termina. Aunque puede ser poco intuitivo desde el punto de vista de C++, el concepto de hilos es la excepcin de la regla: un hilo crea un hilo de ejecucin separado que persiste despus de que la llamada a funcin finalice. Esta excepcin se refleja en la persistencia del hilo subyacente despus de que el objeto desaparezca.

10.3.1. Creacin de interfaces de usuarios interactivas


Como se dijo anteriormente, uno de las motivaciones para usar hilos es crear interfaces de usuario interactivas. Aunque en este libro no cubriremos las interfaces grficas de usuario, ver un ejemplo sencillo de una interfaz de usuario basada en consola. El siguiente ejemplo lee lneas de un archivo y las imprime a la consola, durmindose (suspender el hilo actual) durante un segundo despus de que cada lnea sea mostrada. (Aprender ms sobre el proceso de dormir hilos en el captulo.) Durante este proceso, el programa no busca la entrada del usuario, por lo que la IU no es interactiva:

564

14665 14666 14667 14668 14669 14670 14671 14672 14673 14674 14675 14676 14677 14678 14679 14680 14681 14682 14683 14684 14685 14686

//: C11:UnresponsiveUI.cpp {RunByHand} // Lack of threading produces an unresponsive UI. //{L} ZThread #include <iostream> #include <fstream> #include <string> #include "zthread/Thread.h" using namespace std; using namespace ZThread;

int main() { cout << "Press <Enter> to quit:" << endl; ifstream file("UnresponsiveUI.cpp"); string line; while(getline(file, line)) { cout << line << endl; Thread::sleep(1000); // Time in milliseconds } // Read input from the console cin.get(); cout << "Shutting down..." << endl; } ///:~

14687 14688

Listado 10.5. C11/UnresponsiveUI.cpp

565
14689 14690 14691 Para hacer este programa interactivo, puede ejecutar una tarea que muestre el archivo en un hilo separado. De esta forma, el hilo principal puede leer la entrada del usuario, por lo que el programa se vuelve interactivo:

14692 14693 14694 14695 14696 14697 14698 14699 14700 14701 14702 14703 14704 14705 14706 14707 14708 14709 14710 14711 14712 14713

//: C11:ResponsiveUI.cpp {RunByHand} // Threading for a responsive user interface. //{L} ZThread #include <iostream> #include <fstream> #include <string> #include "zthread/Thread.h" using namespace ZThread; using namespace std;

class DisplayTask : public Runnable { ifstream in; string line; bool quitFlag; public: DisplayTask(const string& file) : quitFlag(false) { in.open(file.c_str()); } ~DisplayTask() { in.close(); } void run() { while(getline(in, line) && !quitFlag) { cout << line << endl;

566

14714 14715 14716 14717 14718 14719 14720 14721 14722 14723 14724 14725 14726 14727 14728 14729 14730 14731

Thread::sleep(1000); } } void quit() { quitFlag = true; } };

int main() { try { cout << "Press <Enter> to quit:" << endl; DisplayTask* dt = new DisplayTask("ResponsiveUI.cpp"); Thread t(dt); cin.get(); dt->quit(); } catch(Synchronization_Exception& e) { cerr << e.what() << endl; } cout << "Shutting down..." << endl; } ///:~

14732 14733 14734 14735 14736 14737 14738 14739

Listado 10.6. C11/ResponsiveUI.cpp Ahora el hilo main() puede responder inmediatamente cuando pulse Return e invocar quit() sobre DisplayTask. Este ejemplo tambin muestra la necesidad de una comunicacin entre tareas - la tarea en el hilo main() necesita parar al DisplayTask. Dado que tenemos un puntero a DisplayTask, puede pensar que bastara con llamar al destructor de ese puntero para matar la tarea, pero esto hace que los

567
14740 14741 14742 14743 14744 14745 14746 14747 14748 14749 14750 14751 programas sean poco fiables. El problema es que la tarea podra estar en mitad de algo importante cuando lo destruye y, por lo tanto, es probable que ponga el programa en un estado inestable. En este sentido, la propia tarea decide cuando es seguro terminar. La manera ms sencilla de hacer esto es simplemente notificar a la tarea que desea detener mediante una bandera booleana. Cuando la tarea se encuentre en un punto estable puede consultar esa bandera y hacer lo que sea necesario para limpiar el estado despus de regresar de run(). Cuando la tarea vuelve de run(), Thread sabe que la tarea se ha completado. Aunque este programa es lo suficientemente simple para que no haya problemas, hay algunos pequeos defectos respecto a la comunicacin entre pilas. Es un tema importante que se cubrir ms tarde en este captulo.

14752 14753 14754 14755 14756 14757 14758 14759 14760 14761 14762

10.3.2. Simplificacin con Ejecutores


Utilizando los Ejecutores de ZThread, puede simplificar su cdigo. Los Ejecutores proporcionan una capa de indireccin entre un cliente y la ejecucin de una tarea; a diferencia de un cliente que ejecuta una tarea directamente, un objeto intermediario ejecuta la tarea. Podemos verlo utilizando un Ejecutor en vez de la creacin explcita de objetos Thread en MoreBasicThreads.cpp. Un objeto LiftOff conoce cmo ejecutar una tarea especfica; como el patrn Command, expone una nica funcin a ejecutar. Un objeto Executor conoce como construir el contexto apropiado para lanzar objetos Runnable. En el siguiente ejemplo,
ThreadedExecutor

crea un hilo por tarea:

14763 14764 14765 14766 14767 14768

//: c11:ThreadedExecutor.cpp //{L} ZThread #include <iostream> #include "zthread/ThreadedExecutor.h" #include "LiftOff.h" using namespace ZThread;

568

14769 14770 14771 14772 14773 14774 14775 14776 14777 14778 14779

using namespace std;

int main() { try { ThreadedExecutor executor; for(int i = 0; i < 5; i++) executor.execute(new LiftOff(10, i)); } catch(Synchronization_Exception& e) { cerr << e.what() << endl; } } ///:~

14780 14781 14782 14783 14784 14785 14786 14787 14788 14789 14790 14791 14792 14793 14794 14795 14796 14797

Listado 10.7. C11/ThreadedExecutor.cpp Note que algunos casos un Executor individual puede ser usado para crear y gestionar todo los hilos en su sistema. Debe colocar el cdigo correspondiente a los hilos dentro de un bloque try porque el mtodo execute() de un Executor puede lanzar una Synchronization_Exception si algo va mal. Esto es vlido para cualquier funcin que implique cambiar el estado de un objeto de sincronizacin (arranque de hilos, la adquisicin de mutexes, esperas en condiciones, etc.), tal y como aprender ms adelante en este captulo. El programa finalizar cuando todas las tareas en el Executor hayan concluido. En el siguiente ejemplo, ThreadedExecutor crea un hilo para cada tarea que quiera ejecutar, pero puede cambiar fcilmente la forma en la que esas tareas son ejecutadas reemplazando el ThreadedExecutor por un tipo diferente de Executor. En este captulo, usar un ThreadedExecutor est bien, pero para cdigo en produccin puede resultar excesivamente costoso para la creacin de muchos hilos. En ese caso, puede reemplazarlo por un

569
14798 14799
PoolExecutor,

que utilizar un conjunto limitado de hilos para lanzar las tareas registradas en paralelo:

14800 14801 14802 14803 14804 14805 14806 14807 14808 14809 14810 14811 14812 14813 14814 14815 14816 14817

//: C11:PoolExecutor.cpp //{L} ZThread #include <iostream> #include "zthread/PoolExecutor.h" #include "LiftOff.h" using namespace ZThread; using namespace std;

int main() { try { // Constructor argument is minimum number of threads: PoolExecutor executor(5); for(int i = 0; i < 5; i++) executor.execute(new LiftOff(10, i)); } catch(Synchronization_Exception& e) { cerr << e.what() << endl; } } ///:~

14818 14819 14820 14821 14822 14823

Listado 10.8. C11/PoolExecutor.cpp Con PoolExecutor puede realizar una asignacin inicial de hilos costosa de una sola vez, por adelantado, y los hilos se reutilizan cuando sea posible. Esto ahorra tiempo porque no est pagando el gasto de la creacin de hilos por cada tarea individual de forma constante. Adems, en un sistema dirigido

570
14824 14825 14826 14827 14828 14829 14830 14831 14832 14833 14834 14835 14836 14837 14838 14839 14840 por eventos, los eventos que requieren hilos para manejarlos puede ser generados tan rpido como quiera, basta con traerlos del pool. No exceder los recursos disponibles porque PoolExecutor utiliza un nmero limitado de objetos Thread. As, aunque en este libro se utilizar ThreadedExecutors, tenga en cuenta utilizar PoolExecutor para cdigo en produccin.
ConcurrentExecutor

es como PoolExecutor pero con un tamao fijo de

hilos. Es til para cualquier cosa que quiera lanzar en otro hilo de forma continua (una tarea de larga duracin), como una tare que escucha conexiones entrantes en un socket. Tambin es til para tareas cortas que quiera lanzar en un hilo, por ejemplo, pequeas tareas que actualizar un log local o remoto, o para un hilo que atienda a eventos. Si hay ms de una tarea registrada en un ConcurrentExecutor, cada una de ellas se ejecutar completamente hasta que la siguiente empiece; todas utilizando el mismo hilo. En el ejemplo siguiente, ver que cada tarea se completa, en el orden en el que fue registrada, antes de que la siguiente comience. De esta forma, un ConcurrentExecutor serializa las tareas que le fueron asignadas.

14841 14842 14843 14844 14845 14846 14847 14848 14849 14850 14851 14852

//: C11:ConcurrentExecutor.cpp //{L} ZThread #include <iostream> #include "zthread/ConcurrentExecutor.h" #include "LiftOff.h" using namespace ZThread; using namespace std;

int main() { try { ConcurrentExecutor executor; for(int i = 0; i < 5; i++)

571

14853 14854 14855 14856 14857

executor.execute(new LiftOff(10, i)); } catch(Synchronization_Exception& e) { cerr << e.what() << endl; } } ///:~

14858 14859 14860 14861 14862 14863 14864 14865 14866 14867 14868 14869 14870 14871 14872 14873 14874 14875 14876 14877 14878

Listado 10.9. C11/ConcurrentExecutor.cpp Como un ConcurrentExecutor, un SynchronousExecutor se usa cuando quiera una nica tarea se ejecute al mismo tiempo, en serie en lugar de concurrente. A diferencia de ConcurrentExecutor, un SynchronousExecutor no crea ni gestiona hilos sobre si mismo. Utiliza el hilo que aadi la tarea y, as, nicamente acta como un punto focal para la sincronizacin. Si tiene n tareas registradas en un SynchronousExecutor, nunca habr 2 tareas que se ejecuten a la vez. En lugar de eso, cada una se ejecutar hasta su finalizacin y la siguiente en la cola comenzar. Por ejemplo, suponga que tiene un nmero de hilos ejecutando tareas que usan un sistema de archivos, pero est escribiendo cdigo portable luego no quiere utilizar flock() u otra llamada al sistema operativo especfica para bloquear un archivo. Puede lanzar esas tareas con un SynchronousExecutor para asegurar que solamente una de ellas, en un tiempo determinado, est ejecutndose desde cualquier hilo. De esta manera, no necesita preocuparse por la sincronizacin del recurso compartido (y, de paso, no se cargar el sistema de archivos). Una mejor solucin pasa por sincronizar el recurso (lo cual aprender ms adelante en este captulo), sin embargo un
SynchronousExecutor

le permite evitar las molestias de obtener una

coordinacin adecuada para prototipar algo.

14879 14880 14881

//: C11:SynchronousExecutor.cpp //{L} ZThread #include <iostream>

572

14882 14883 14884 14885 14886 14887 14888 14889 14890 14891 14892 14893 14894 14895

#include "zthread/SynchronousExecutor.h" #include "LiftOff.h" using namespace ZThread; using namespace std;

int main() { try { SynchronousExecutor executor; for(int i = 0; i < 5; i++) executor.execute(new LiftOff(10, i)); } catch(Synchronization_Exception& e) { cerr << e.what() << endl; } } ///:~

14896 14897 14898 14899 14900 14901 14902 14903 14904

Listado 10.10. C11/SynchronousExecutor.cpp Cuando ejecuta el programa ver que las tareas son lanzadas en el orden en el que fueron registradas, y cada tarea se ejecuta completamente antes de que la siguiente empiece. Qu es lo que no ve y que hace que no se creen nuevos hilos? El hilo main() se usa para cada tarea, y debido a este ejemplo, ese es el hilo que registra todas las tareas. Podra no utilizar un
SynchronousExecutor

en cdigo en produccin porque, principalmente, es para prototipado.

14905 14906 14907 14908

10.3.3. Ceder el paso


Si sabe que ha logrado realizar lo que necesita durante una pasada a travs de un bucle en su funcin run() (la mayora de las funciones run() suponen un periodo largo de tiempo de ejecucin), puede darle un toque al

573
14909 14910 14911 14912 14913 14914 mecanismo de planificacin de hilos, decirle que ya ha hecho suficiente y que algn otro hilo puede tener la CPU. Este toque (y es un toque - no hay garanta de que su implementacin vaya a escucharlo) adopta la forma de la funcin yield(). Podemos construir una versin modificada de los ejemplos de LiftOff cediendo el paso despus de cada bucle:

14915 14916 14917 14918 14919 14920 14921 14922 14923 14924 14925 14926 14927 14928 14929 14930 14931 14932 14933 14934

//: C11:YieldingTask.cpp // Suggesting when to switch threads with yield(). //{L} ZThread #include <iostream> #include "zthread/Thread.h" #include "zthread/ThreadedExecutor.h" using namespace ZThread; using namespace std;

class YieldingTask : public Runnable { int countDown; int id; public: YieldingTask(int ident = 0) : countDown(5), id(ident) {} ~YieldingTask() { cout << id << " completed" << endl; } friend ostream& operator<<(ostream& os, const YieldingTask& yt) { return os << "#" << yt.id << ": " << yt.countDown;

574

14935 14936 14937 14938 14939 14940 14941 14942 14943 14944 14945 14946 14947 14948 14949 14950 14951 14952 14953

} void run() { while(true) { cout << *this << endl; if(--countDown == 0) return; Thread::yield(); } } };

int main() { try { ThreadedExecutor executor; for(int i = 0; i < 5; i++) executor.execute(new YieldingTask(i)); } catch(Synchronization_Exception& e) { cerr << e.what() << endl; } } ///:~

14954 14955 14956 14957 14958 14959

Listado 10.11. C11/YieldingTask.cpp Puede ver que la tarea del mtodo run() es en un bucle infinito en su totalidad. Utilizando yield(), la salida se equilibra bastante que en el caso en el que no se cede el paso. Pruebe a comentar la llamada a Thread::yield() para ver la diferencia. Sin embargo, en general, yield() es til en raras

575
14960 14961 ocasiones, y no puede contar con ella para realizar un afinamiento serio sobre su aplicacin.

14962 14963 14964 14965 14966

10.3.4. Dormido
Otra forma con la que puede tener control sobre el comportamiento de su hilos es llamando a sleep() para cesar la ejecucin de uno de ellos durante un nmero de milisegundos dado. En el ejemplo que viene a continuacin, si cambia la llamada a yield() por una a sleep(), obtendr lo siguiente:

14967 14968 14969 14970 14971 14972 14973 14974 14975 14976 14977 14978 14979 14980 14981 14982 14983 14984

//: C11:SleepingTask.cpp // Calling sleep() to pause for awhile. //{L} ZThread #include <iostream> #include "zthread/Thread.h" #include "zthread/ThreadedExecutor.h" using namespace ZThread; using namespace std;

class SleepingTask : public Runnable { int countDown; int id; public: SleepingTask(int ident = 0) : countDown(5), id(ident) {} ~SleepingTask() { cout << id << " completed" << endl; } friend ostream&

576

14985 14986 14987 14988 14989 14990 14991 14992 14993 14994 14995 14996 14997 14998 14999 15000 15001 15002 15003 15004 15005 15006 15007 15008 15009

operator<<(ostream& os, const SleepingTask& st) { return os << "#" << st.id << ": " << st.countDown; } void run() { while(true) { try { cout << *this << endl; if(--countDown == 0) return; Thread::sleep(100); } catch(Interrupted_Exception& e) { cerr << e.what() << endl; } } } };

int main() { try { ThreadedExecutor executor; for(int i = 0; i < 5; i++) executor.execute(new SleepingTask(i)); } catch(Synchronization_Exception& e) { cerr << e.what() << endl; } } ///:~

577
15010 15011 15012 15013 15014 15015 15016 15017 15018 15019 15020 15021 15022 15023 15024 15025 15026 15027 15028 15029 15030 15031 Listado 10.12. C11/SleepingTask.cpp Thread::sleep() puede lanzar una Interrupted_Exception(sobre las interrupciones aprender ms adelante), y puede ver que esta excepcin se captura en run(). Sin embargo, la tarea se crea y se ejecuta dentro de un bloque try en main() que captura Interrupted_Exception (la clase base para todas las excepciones de ZThread), por lo que no sera posible ignorar la excepcin en run() y asumir que se propagar al manejador en main()?. Esto no funcionar porque las excepciones no se propagarn a lo largo de los hilos para volver hacia main(). De esta forma, debe manejar cualquier excepcin que pueda ocurrir dentro de una tarea de forma local. Notar que los hilos tienden a ejecutarse en cualquier orden, lo que quiere decir que sleep() tampoco es una forma de controlar el orden de la ejecucin de los hilos. Simplemente para la ejecucin del hilo durante un rato. La nica garanta que tiene es que el hilo se dormir durante, al menos, 100 milisegundos (en este ejemplo), pero puede que tarde ms despus de que el hilo reinicie la ejecucin ya que el planificador de hilos tiene que volver a l tras haber expirado el intervalo. Si debe tener control sobre el orden de la ejecucin de hilos, su mejor baza es el uso de controles de sincronizacin (descritos ms adelante) o, en algunos casos, no usar hilos en todo, FIXMEbut instead to write your own cooperative routines that hand control to each other in a specified order.

15032 15033 15034 15035 15036 15037 15038 15039 15040 15041 15042

10.3.5. Prioridad
La prioridad de un hilo representa la importancia de ese hilo para el planificador. Pese a que el orden en que la CPU ejecuta un conjunto de hilos es indeterminado, el planificador tender a ejecutar el hilo con mayor prioridad de los que estn esperando. Sin embargo, no quiere decir que hilos con menos prioridad no se ejecutarn (es decir, no tendr bloqueo de un hilo a causa de las prioridades). Simplemente, los hilos con menos prioridad tendern a ejecutarse menos frecuentemente. Se ha modificado MoreBasicThreads.cpp para mostrar los niveles de prioridad. Las prioridades se ajustan utilizando la funcin setPriority() de
Thread.

578

15043 15044 15045 15046 15047 15048 15049 15050 15051 15052 15053 15054 15055 15056 15057 15058 15059 15060 15061 15062 15063 15064 15065 15066 15067

//: C11:SimplePriorities.cpp // Shows the use of thread priorities. //{L} ZThread #include <iostream> #include "zthread/Thread.h" using namespace ZThread; using namespace std;

const double pi = 3.14159265358979323846; const double e = 2.7182818284590452354;

class SimplePriorities : public Runnable { int countDown; volatile double d; // No optimization int id; public: SimplePriorities(int ident=0): countDown(5), id(ident) {} ~SimplePriorities() { cout << id << " completed" << endl; } friend ostream& operator<<(ostream& os, const SimplePriorities& sp) { return os << "#" << sp.id << " priority: " << Thread().getPriority() << " count: "<< sp.countDown;

579

15068 15069 15070 15071 15072 15073 15074 15075 15076 15077 15078 15079 15080 15081 15082 15083 15084 15085 15086 15087 15088 15089 15090 15091

} void run() { while(true) { // An expensive, interruptable operation: for(int i = 1; i < 100000; i++) d = d + (pi + e) / double(i); cout << *this << endl; if(--countDown == 0) return; } } };

int main() { try { Thread high(new SimplePriorities); high.setPriority(High); for(int i = 0; i < 5; i++) { Thread low(new SimplePriorities(i)); low.setPriority(Low); } } catch(Synchronization_Exception& e) { cerr << e.what() << endl; } } ///:~

580
15092 15093 15094 15095 15096 15097 15098 15099 15100 15101 15102 15103 15104 15105 15106 15107 15108 15109 15110 15111 15112 15113 15114 15115 15116 Listado 10.13. C11/SimplePriorities.cpp En este ejemplo, el operador <<() se sobreescribe para mostrar el identificador, la prioridad y el valor de countDown de la tarea. Puede ver que el nivel de prioridad del hilo es el ms alto, y que el resto de hilos tienen el nivel ms bajo. No utilizamos Executor en este ejemplo porque necesitamos acceder directamente al hilo para configurar sus propiedades. Dentro de SimplePriorities::run() se ejecutan 100,000 veces un costoso conjunto de clculos en punto flotante. La variable d es voltil para intentar garantizar que ningn compilador hace optimizaciones. Sin este clculo, no comprobar el efecto de la configuracin de los niveles de prioridad. (Prubelo: comente el bucle for que contiene los clculos en doble precisin.) Con el clculo puede ver que el planificador de hilos al hilo high se le da ms preferencia. (Al menos, este fue el comportamiento sobre una mquina Windows). El clculo tarda lo suficiente para que el mecanismo de planificacin de hilos lo salte, cambie hilos y tome en cuenta las prioridades para que el hilo high tenga preferencia. Tambin, puede leer la prioridad de un hilo existente con getPriority() y cambiarla en cualquier momento (no slo antes de que el hilo se ejecute, como en SimplePriorities.cpp) con setPriority(). La correspondencia de las prioridades con el sistema operativo es un problema. Por ejemplo, Windows 2000 tiene siete niveles de prioridades, mientras que Solaris de Sun tiene 231. El nico enfoque portable es ceirse a los niveles discretos de prioridad, como Low, Medium y High utilizados en la librera ZThread.

15117 15118 15119 15120 15121 15122 15123

10.4. Comparicin de recursos limitados


Piense en un programa con un nico hilo como una solitaria entidad movindose a lo largo del espacio de su problema y haciendo una cosa en cada instante. Debido a que slo hay una entidad, no tiene que preocuparse por el problema de dos entidades intentando usar el mismo recurso al mismo tiempo: problemas como dos personas intentando aparcar en el mismo sitio, pasar por una puerta al mismo tiempo o incluso hablar al mismo tiempo.

581
15124 15125 15126 15127 15128 15129 15130 15131 15132 15133 15134 15135 15136 15137 15138 15139 15140 15141 15142 15143 15144 15145 15146 15147 15148 15149 15150 15151 15152 15153 15154 15155 15156 Con multihilado las cosas ya no son solitarias, pero ahora tiene la posibilidad de tener dos o ms hilos intentando utilizar un mismo recurso a la vez. Esto puede causar dos problemas distintos. El primero es que el recurso necesario podra no existir. En C++, el programador tiene un control total sobre la vida de los objetos, y es fcil crear hilos que intenten usar objetos que han sido destruidos antes de que esos hilos hayan finalizado. El segundo problema es que dos o ms hilos podran chocar cuando intenten acceder al mismo dispositivo al mismo tiempo. Si no previene esta colisin, tendr dos hilos intentando acceder a la misma cuenta bancaria al mismo tiempo, imprimir en la misma impresora, ajustar la misma vlvula, etc. Esta seccin presenta el problema de los objetos que desaparecen mientras las tareas an estn usndolos y el problema del choque entre tareas sobre recursos compartidos. Aprender sobre las herramientas que se usan para solucionar esos problemas.

10.4.1. Aseguramiento de la existencia de objetos


La gestin de memoria y recursos son las principales preocupaciones en C++. Cuando crea cualquier programa en C++, tiene la opcin de crear objetos en la pila o en el heap (utilizando new). En un programa con un solo hilo, normalmente es sencillo seguir la vida de los objetos con el fin de que no tenga que utilizar objetos que ya estn destruidos. Los ejemplos mostrados en este captulo crean objetos Runnable en el heap utilizando new, se dar cuenta que esos objetos nunca son destruidos explcitamente. Sin embargo, podr por la salida cuando ejecuta el programa que la biblioteca de hilos sigue la pista a cada tarea y, eventualmente, las destruye. Esto ocurre cuando el mtodo Runnable::run() finaliza - volver de run() indica que la tarea ha finalizado. Recargar el hilo al destruir una tarea es un problema. Ese hilo sabe necesariamente si otro necesita hacer referencia a ese Runnable, y por ello el Runnable podra ser destruido prematuramente. Para ocuparse de este problema, el mecanismo de la biblioteca ZThread mantiene un conteo de referencias sobre las tareas. Una tarea se mantiene viva hasta que su contador de referencias se pone a cero, en este punto la tarea se destruye. Esto quiere decir que las tareas tienen que ser destruidas dinmicamente

582
15157 15158 15159 15160 15161 15162 15163 15164 15165 siempre, por lo que no pueden ser creadas en la pila. En vez de eso, las tareas deben ser creadas utilizando new, tal y como puede ver en todos los ejemplos de este captulo. tareas in ZThreads Adems, tambin debe asegurar que los objetos que no son tareas estarn vivos tanto tiempo como el que las tareas necesiten de ellos. Por otro lado, resulta sencillo para los objetos utilizados por las tareas salir del mbito antes de que las tareas hayan concluido. Si ocurre esto, las tareas intentarn acceder zonas de almacenamiento ilegales y provocar que el programa falle. He aqu un simple ejemplo:

15166 15167 15168 15169 15170 15171 15172 15173 15174 15175 15176 15177 15178 15179 15180 15181 15182 15183

//: C11:Incrementer.cpp {RunByHand} // Destroying objects while threads are still // running will cause serious problems. //{L} ZThread #include <iostream> #include "zthread/Thread.h" #include "zthread/ThreadedExecutor.h" using namespace ZThread; using namespace std;

class Count { enum { SZ = 100 }; int n[SZ]; public: void increment() { for(int i = 0; i < SZ; i++) n[i]++; }

583

15184 15185 15186 15187 15188 15189 15190 15191 15192 15193 15194 15195 15196 15197 15198 15199 15200 15201 15202 15203 15204 15205 15206 15207

};

class Incrementer : public Runnable { Count* count; public: Incrementer(Count* c) : count(c) {} void run() { for(int n = 100; n > 0; n--) { Thread::sleep(250); count->increment(); } } };

int main() { cout << "This will cause a segmentation fault!" << endl; Count count; try { Thread t0(new Incrementer(&count)); Thread t1(new Incrementer(&count)); } catch(Synchronization_Exception& e) { cerr << e.what() << endl; } } ///:~

584
15208 15209 15210 15211 15212 15213 15214 15215 15216 15217 15218 15219 15220 15221 15222 15223 15224 15225 15226 15227 15228 15229 15230 15231 15232 15233 15234 15235 15236 15237
Incrementer

Listado 10.14. C11/Incrementer.cpp Podra parecer a priori que la clase Count es excesiva, pero si nicamente n es un int (en lugar de una matriz), el compilador puede ponerlo dentro de un registro y ese almacenamiento seguir estando disponible (aunque tcnicamente es ilegal) despus de que el objeto Count salga del mbito. Es difcil detectar la violacin de memoria en este caso. Sus resultados podran variar dependiendo de su compilador y de su sistema operativo, pero pruebe a que n sea un int y ver qu ocurre. En cualquier evento, si Count contiene una matriz de ints y como antes, el compilador est obligado a ponerlo en la pila y no en un registro. es una tarea sencilla que utiliza un objeto Count. En main(),

puede ver que las tareas Incrementer se ejecutan el tiempo suficiente para que el salga del mbito, por lo que la tarea intentar acceder a un objeto que no existe. Esto produce un fallo en el programa. objeto Count Para solucionar este problema, debemos garantizar que cualquiera de los objetos compartidos entre tareas estarn accesibles tanto tiempo como las tareas los necesiten. (Si los objetos no fueran compartidos, podran estar directamente dentro de las clases de las tareas y, as, unir su tiempo de vida a la tarea.) Dado que no queremos que el propio mbito esttico del programa controle el tiempo de vida del objeto, pondremos el en heap. Y para asegurar que el objeto no se destruye hasta que no haya objetos (tareas, en este caso) que lo estn utilizando, utilizaremos el conteo de referencias. El conteo de referencias se ha explicado a lo largo del volumen uno de este libro y adems se revisar en este volumen. La librera ZThread incluye una plantilla llamada CountedPtr que automticamente realiza el conteo de referencias y destruye un objeto cuando su contador de referencias vale cero. A continuacin, se ha modificado el programa para que utilice
CountedPtr

para evitar el fallo:

15238 15239

//: C11:ReferenceCounting.cpp // A CountedPtr prevents too-early destruction.

585

15240 15241 15242 15243 15244 15245 15246 15247 15248 15249 15250 15251 15252 15253 15254 15255 15256 15257 15258 15259 15260 15261 15262 15263 15264

//{L} ZThread #include <iostream> #include "zthread/Thread.h" #include "zthread/CountedPtr.h" using namespace ZThread; using namespace std;

class Count { enum { SZ = 100 }; int n[SZ]; public: void increment() { for(int i = 0; i < SZ; i++) n[i]++; } };

class Incrementer : public Runnable { CountedPtr<Count> count; public: Incrementer(const CountedPtr<Count>& c ) : count(c) {} void run() { for(int n = 100; n > 0; n--) { Thread::sleep(250); count->increment();

586

15265 15266 15267 15268 15269 15270 15271 15272 15273 15274 15275 15276 15277

} } };

int main() { CountedPtr<Count> count(new Count); try { Thread t0(new Incrementer(count)); Thread t1(new Incrementer(count)); } catch(Synchronization_Exception& e) { cerr << e.what() << endl; } } ///:~

15278 15279 15280 15281 15282 15283 15284 15285 15286 15287 15288 15289 15290

Listado 10.15. C11/ReferenceCounting.cpp Ahora Incrementer contiene un objeto CountedPtr, que gestiona un Count. En la funcin main(), los objetos CountedPtr se pasan a los dos objetos
Incrementer

por valor, por lo que se llama el constructor de copia,

incrementando el conteo de referencias. Mientras la tarea est ejecutndose, el contador de referencias no valdr cero, por lo que el objeto Count utilizado por CountedPtr no ser destrudo. Solamente cuando todas las tareas que utilice el Count terminen se llamar al destructor (automticamente) sobre el objeto Count por el CountedPtr. Siempre que tenga una tarea que utilice ms de un objeto, casi siempre necesitar controlar aquellos objetos utilizando la plantilla CountedPtr para evitar problemas derivados del tiempo de vida de los objetos.

15291

10.4.2. Acceso no apropiado a recursos

587
15292 15293 15294 15295 15296 15297 15298 15299 15300 Considere el siguiente ejemplo, donde una tarea genera nmeros constantes y otras tareas consumen esos nmeros. Ahora, el nico trabajo de los hilos consumidores es probar la validez de los nmeros constantes. Primeramente, definiremos EvenChecker, el hilo consumidor, puesto que ser reutilizado en todos los ejemplos siguientes. Para desacoplar
EvenChecker

de los varios tipos de generadores con los que

experimentaremos, crearemos una interfaz llamada Generator que contiene el nmero mnimo de funciones que EvenChecker necesita conocer: por lo que tiene una funcin nextValue() y que puede ser cancelada.

15301 15302 15303 15304 15305 15306 15307 15308 15309 15310 15311 15312 15313 15314 15315 15316 15317 15318

//: C11:EvenChecker.h #ifndef EVENCHECKER_H #define EVENCHECKER_H #include <iostream> #include "zthread/CountedPtr.h" #include "zthread/Thread.h" #include "zthread/Cancelable.h" #include "zthread/ThreadedExecutor.h"

class Generator : public ZThread::Cancelable { bool canceled; public: Generator() : canceled(false) {} virtual int nextValue() = 0; void cancel() { canceled = true; } bool isCanceled() { return canceled; } };

588

15319 15320 15321 15322 15323 15324 15325 15326 15327 15328 15329 15330 15331 15332 15333 15334 15335 15336 15337 15338 15339 15340 15341 15342 15343

class EvenChecker : public ZThread::Runnable { ZThread::CountedPtr<Generator> generator; int id; public: EvenChecker(ZThread::CountedPtr<Generator>& g, int ident) : generator(g), id(ident) {} ~EvenChecker() { std::cout << "~EvenChecker " << id << std::endl; } void run() { while(!generator->isCanceled()) { int val = generator->nextValue(); if(val % 2 != 0) { std::cout << val << " not even!" << std::endl; generator->cancel(); // Cancels all EvenCheckers } } } // Test any type of generator: template<typename GenType> static void test(int n = 10) { std::cout << "Press Control-C to exit" << std::endl; try { ZThread::ThreadedExecutor executor; ZThread::CountedPtr<Generator> gp(new GenType); for(int i = 0; i < n; i++)

589

15344 15345 15346 15347 15348 15349 15350

executor.execute(new EvenChecker(gp, i)); } catch(ZThread::Synchronization_Exception& e) { std::cerr << e.what() << std::endl; } } }; #endif // EVENCHECKER_H ///:~

15351 15352 15353 15354 15355 15356 15357 15358 15359 15360 15361 15362 15363 15364 15365 15366 15367 15368 15369 15370 15371 15372 15373

Listado 10.16. C11/EvenChecker.h La clase Generator presenta la clase abstracta Cancelable, que es parte de la biblioteca de ZThread. El propsito de Cancelable es proporcionar una interfaz consistente para cambiar el estado de un objeto via cancel() y ver si el objeto ha sido cancelado con la funcin isCanceled(). Aqu utilizamos el enfoque simple de una bandera de cancelacin booleana similar a quitFlag, vista previamente en ResponsiveUI.cpp. Note que en este ejemplo la clase que es Cancelable no es Runnable. En su lugar, toda tarea EvenChecker que dependa de un objeto Cancelable (el Generator) lo comprueba para ver que ha sido cancelado, como puede ver en run(). De esta manera, las tareas que comparten recursos comunes (el Cancelable Generator) estn atentos a la seal de ese recurso para terminar. Esto elimina la tambin conocida condicin de carrera, donde dos o ms tareas compiten por responder una condicin y, as, colisionar o producir resultados inconsistentes. Debe pensar sobre esto cuidadosamente y protegerse de todas las formas posible de los fallos de un sistema concurrente. Por ejemplo, una tarea no puede depender de otra porque el orden de finalizacin de las tareas no est garantizado. En este sentido, eliminamos la potencial condicin de carrera haciendo que las tareas dependan de objetos que no son tareas (que son contados referencialmente utilizando CountedPtr. En las secciones posteriores, ver que la librera ZThread contiene ms mecanismos generales para la terminacin de hilos.

590
15374 15375 15376 15377 15378 15379 15380 15381 15382 15383 15384 15385 15386 15387 15388 15389 15390
EvenGenerator

Debido a que muchos objetos EvenChecker podran terminar compartiendo un Generator, la plantilla CountedPtr se usa para contar las referencias de los objetos Generator. El ltimo mtodo en EvenChecker es un miembro esttico de la plantilla que configura y realiza una comprobacin de los tipos de Generator creando un
CountedPtr

dentro y, seguidamente, lanzar un nmero de EvenCheckers que

usan ese Generator. Si el Generator provoca un fallo, test() lo reportar y volver; en otro caso, deber pulsar Control-C para finalizarlo. Las tareas EvenChecker leen constantemente y comprueban que los valores de sus Generators asociados. Vea que si generator->isCanceled() es verdadero, run() retorna, con lo que se le dice al Executor de EvenChecker::test() que la tarea se ha completado. Cualquier tarea
EvenChecker

puede llamar a cancel() sobre su Generator asociado, lo que finalicen con elegancia. es simple - nextValue() produce el siguiente valor constante:

causar que todos los dems EvenCheckers que utilicen ese Generator

15391 15392 15393 15394 15395 15396 15397 15398 15399 15400 15401

//: C11:EvenGenerator.cpp // When threads collide. //{L} ZThread #include <iostream> #include "EvenChecker.h" #include "zthread/ThreadedExecutor.h" using namespace ZThread; using namespace std;

class EvenGenerator : public Generator { unsigned int currentEvenValue; // Unsigned can't overflow

591

15402 15403 15404 15405 15406 15407 15408 15409 15410 15411 15412 15413 15414

public: EvenGenerator() { currentEvenValue = 0; } ~EvenGenerator() { cout << "~EvenGenerator" << endl; } int nextValue() { ++currentEvenValue; // Danger point here! ++currentEvenValue; return currentEvenValue; } };

int main() { EvenChecker::test<EvenGenerator>(); } ///:~

15415 15416 15417 15418 15419 15420 15421 15422 15423 15424 15425 15426 15427 15428

Listado 10.17. C11/EvenGenerator.cpp Es posible que un hilo llame a nextValue() despus de el primer incremento de currentEvenValue y antes del segundo (en el lugar "Danger point here!" del cdigo comentado), que pone el valor en un estado "incorrecto". Para probar que esto puede ocurrir, EventChecker::test() crea un grupo de objetos EventChecker para leer continuamente la salida de un
EvenGenerator

y ver si cada valor es constante. Si no es as, el error se reporta y el programa finaliza.

Este programa podra no detectar el problema hasta que EvenGenerator ha completado varios ciclos, dependiendo de las particuliaridades de su sistema operativo y otros detalles de implementacin. Si quiere ver que falla mucho ms rpido, pruebe a poner una llamada a yield() entre el primero y segundo incremento. En algn evento, fallar puntualmente a causa de que los hilos

592
15429 15430
EvenChecker

pueden acceder a la informacin en EvenGenerator mientras se encuentra en un estado "incorrecto".

15431 15432 15433 15434 15435 15436 15437 15438 15439 15440 15441 15442 15443 15444 15445 15446 15447 15448 15449 15450 15451 15452 15453 15454 15455 15456 15457 15458 15459

10.4.3. Control de acceso


En el ejemplo anterior se muestra el problema fundamental a la hora de utilizar hilos: nunca sabr cundo un hilo puede ser ejecutado. Imagnese sentado en la mesa con un tenedor, a punto de coger el ltimo pedazo de comida de un plato y tan pronto como su tenedor lo alcanza, de repente, la comida se desvanece (debido a que su hilo fue suspendido y otro vino y se comi la comida). Ese es el problema que estamos tratando a la hora de escribir programas concurrentes. En ocasiones no le importar si un recurso est siendo accedido a la vez que intenta usarlo. Pero en la mayora de los casos s, y para trabajar con mltiples hilos necesitar alguna forma de evitar que dos hilos accedan al mismo recurso, al menos durante perodos crticos. Para prevenir este tipo de colisiones existe una manera sencilla que consiste en poner un bloqueo sobre un recursos cuando un hilo trata de usarlo. El primer hilo que accede al recursos lo bloquea y, as, otro hilo no puede acceder al recurso hasta que no sea desbloqueado, momento en el que este hilo lo vuelve a bloquear y lo vuelve a usar, y as sucesivamente. De esta forma, tenemos que ser capaces de evitar cualquier tarea de acceso a memoria mientras ese almacenamiento no est en un estado adecuado. Esto es, necesitamos tener un mecanismo que excluya una segunda tarea sobre el acceso a memoria cuando una primera tarea ya est usndola. Esta idea es fundamental para todo sistema multihilado y se conoce como exclusin mutua; abreviado como mutex. La biblioteca ZThread tiene un mecanismo de mutex en el fichero de cabecera Mutex.h. Para solucionar el problema en el programa anterior, identificaremos las secciones crticas donde debe aplicarse la exclusin mutua; posteriormente, antes de entrar en la seccin crtica adquiriremos el mutex y lo liberaremos cuando finalice la seccin crtica. nicamente un hilo podr adquirir el mutex al mismo tiempo, por lo que se logra exclusin mutua:

593

15460 15461 15462 15463 15464 15465 15466 15467 15468 15469 15470 15471 15472 15473 15474 15475 15476 15477 15478 15479 15480 15481 15482 15483 15484

//: C11:MutexEvenGenerator.cpp {RunByHand} // Preventing thread collisions with mutexes. //{L} ZThread #include <iostream> #include "EvenChecker.h" #include "zthread/ThreadedExecutor.h" #include "zthread/Mutex.h" using namespace ZThread; using namespace std;

class MutexEvenGenerator : public Generator { unsigned int currentEvenValue; Mutex lock; public: MutexEvenGenerator() { currentEvenValue = 0; } ~MutexEvenGenerator() { cout << "~MutexEvenGenerator" << endl; } int nextValue() { lock.acquire(); ++currentEvenValue; Thread::yield(); // Cause failure faster ++currentEvenValue; int rval = currentEvenValue; lock.release();

594

15485 15486 15487 15488 15489 15490 15491

return rval; } };

int main() { EvenChecker::test<MutexEvenGenerator>(); } ///:~

15492 15493 15494 15495 15496 15497 15498 15499 15500 15501 15502 15503 15504 15505 15506 15507 15508 15509 15510

Listado 10.18. C11/MutexEvenGenerator.cpp


MutexEvenGenerator

aade un Mutex llamado lock y utiliza acquire() y

release() para crear una seccin crtica con nextValue(). Adems, se ha insertado una llamada a Thread::yield() entre los dos incrementos, para aumentar la probabilidad de que haya un cambio de contexto mientras currentEvenValue se encuentra en un estado extrao. Este hecho no producir un fallo ya que el mutex evita que ms de un hilo est en la seccin crtica al mismo tiempo, pero llamar a yield() es una buena forma de provocar un fallo si este ocurriera. Note que nextValue() debe capturar el valor de retorno dentro de la seccin crtica porque si lo devolviera dentro de la seccin critica no liberara lock y as evitar que fuera adquirido. (Normalmente, esto conllevara un interbloqueo, de lo cual aprender sobre ello al final de este captulo.) El primer hilo que entre en nextValue() adquirir lock y cualquier otro hilo que intente adquirirlo ser bloqueado hasta que el primer hilo libere lock. En ese momento, el mecanismo de planificacin selecciona otro hilo que est esperando en lock. De esta manera, solo un hilo puede pasar a travs del cdigo custodiado por el mutex al mismo tiempo.

15511 15512 15513

10.4.4. Cdigo simplificado mediante guardas


El uso de mutexes se convierte rpidamente complicado cuando se introducen excepciones. Para estar seguro de que el mutes siempre se

595
15514 15515 15516 15517 15518 15519 15520 15521 15522 15523 15524 15525 15526 libera, debe asegurar que cualquier camino a una excepcin incluya una llamada a release(). Adems, cualquier funcin que tenga mltiples caminos para retornar debe asegurar cuidadosamente que se llama a release() en el momento adecuado. Esos problemas pueden se fcilmente solucionados utilizando el hecho de que los objetos de la pila (automticos) tiene un destructor que siempre se llama sea cual sea la forma en que salga del mbito de la funcin. En la librera ZThread, esto se implementa en la plantilla Guard. La plantilla Guard crea objetos que adquieren un objeto Lockable cuando se construyen y lo liberan cuando son destruidos. Los objetos Guard creados en la pila local sern eliminados automticamente independientemente de la forma en el que la funcin finalice y siempre desbloquear el objeto Lockable. A continuacin, el ejemplo anterior reimplementado para utilizar Guards:

15527 15528 15529 15530 15531 15532 15533 15534 15535 15536 15537 15538 15539 15540 15541

//: C11:GuardedEvenGenerator.cpp {RunByHand} // Simplifying mutexes with the Guard template. //{L} ZThread #include <iostream> #include "EvenChecker.h" #include "zthread/ThreadedExecutor.h" #include "zthread/Mutex.h" #include "zthread/Guard.h" using namespace ZThread; using namespace std;

class GuardedEvenGenerator : public Generator { unsigned int currentEvenValue; Mutex lock; public:

596

15542 15543 15544 15545 15546 15547 15548 15549 15550 15551 15552 15553 15554 15555 15556 15557

GuardedEvenGenerator() { currentEvenValue = 0; } ~GuardedEvenGenerator() { cout << "~GuardedEvenGenerator" << endl; } int nextValue() { Guard<Mutex> g(lock); ++currentEvenValue; Thread::yield(); ++currentEvenValue; return currentEvenValue; } };

int main() { EvenChecker::test<GuardedEvenGenerator>(); } ///:~

15558 15559 15560 15561 15562 15563 15564 15565 15566

Listado 10.19. C11/GuardedEvenGenerator.cpp Note que el valor de retorno temporal ya no es necesario en nextValue(). En general, hay menos cdigo que escribir y la probabilidad de errores por parte del usuario se reduce en gran medida. Una caracterstica interesante de la plantilla Guard es que puede ser usada para manipular otros elementos de seguridad. Por ejemplo, un segundo
Guard

puede ser utilizado temporalmente para desbloquear un elemento de seguridad:

15567

//: C11:TemporaryUnlocking.cpp

597

15568 15569 15570 15571 15572 15573 15574 15575 15576 15577 15578 15579 15580 15581 15582 15583 15584 15585 15586 15587 15588 15589 15590 15591 15592

// Temporarily unlocking another guard. //{L} ZThread #include "zthread/Thread.h" #include "zthread/Mutex.h" #include "zthread/Guard.h" using namespace ZThread;

class TemporaryUnlocking { Mutex lock; public: void f() { Guard<Mutex> g(lock); // lock is acquired // ... { Guard<Mutex, UnlockedScope> h(g); // lock is released // ... // lock is acquired } // ... // lock is released } };

598

15593 15594 15595 15596

int main() { TemporaryUnlocking t; t.f(); } ///:~

15597 15598 15599 15600

Listado 10.20. C11/TemporaryUnlocking.cpp Un Guard tambin puede utilizarse para adquirir un lock durante un determinado tiempo y, despus, liberarlo:

15601 15602 15603 15604 15605 15606 15607 15608 15609 15610 15611 15612 15613 15614 15615 15616

//: C11:TimedLocking.cpp // Limited time locking. //{L} ZThread #include "zthread/Thread.h" #include "zthread/Mutex.h" #include "zthread/Guard.h" using namespace ZThread;

class TimedLocking { Mutex lock; public: void f() { Guard<Mutex, TimedLockedScope<500> > g(lock); // ... } };

599

15617 15618 15619 15620 15621


int main() { TimedLocking t; t.f(); } ///:~

15622 15623 15624 15625 15626 15627 15628 15629 15630

Listado 10.21. C11/TimedLocking.cpp En este ejemplo, se lanzar una Timeout_Exception si el lock no puede ser adquirido en 500 milisegundos. Sincronizacin de clases completas La librera ZThread tambin proporciona la plantilla GuardedClass para crear automticamente un recubrimiento de sincronizacin para toda una clase. Esto quiere decir que cualquier mtodo de una clase estar automticamente protegido:

15631 15632 15633 15634 15635 15636 15637 15638 15639 15640 15641

//: C11:SynchronizedClass.cpp {-dmc} //{L} ZThread #include "zthread/GuardedClass.h" using namespace ZThread;

class MyClass { public: void func1() {} void func2() {} };

600

15642 15643 15644 15645 15646 15647 15648 15649 15650

int main() { MyClass a; a.func1(); // Not synchronized a.func2(); // Not synchronized GuardedClass<MyClass> b(new MyClass); // Synchronized calls, only one thread at a time allowed: b->func1(); b->func2(); } ///:~

15651 15652 15653 15654 15655 15656 15657 15658 15659 15660 15661 15662 15663 15664 15665 15666 15667

Listado 10.22. C11/SynchronizedClass.cpp El objeto a no est sincronizado, por lo que func1() y func2() pueden ser llamadas en cualquier momento por cualquier nmero de hilos. El objeto b est protegido por el recubrimiento GuardedClass, as que cada mtodo se sincroniza automticamente y solo se puede llamar a una funcin por objeto en cualquier instante. El recubrimiento bloquea un tipo de nivel de granularidad, que podra afectar al rendimiento.[151] Si una clase contiene funciones no vinculadas, puede ser mejor sincronizarlas internamente con 2 locks diferentes. Sin embargo, si se encuentra haciendo esto, significa que la clase contiene grupos de datos que puede no estar fuertemente asociados. Considere dividir la clase en dos. Proteger todos los mtodos de una clase con un mutex no hace que esa clase sea segura automticamente cuando se utilicen hilos. Debe tener cuidado con estas cuestiones para garantizar la seguridad cuando se usan hilos.

15668

10.4.5. Almacenamiento local al hilo

601
15669 15670 15671 15672 15673 15674 15675 15676 15677 Una segunda forma de eliminar el problema de colisin de tareas sobre recursos compartidos es la eliminacin de las variables compartidas, lo cual puede realizarse mediante la creacin de diferentes almacenamientos para la misma variable, uno por cada hilo que use el objeto. De esta forma, si tiene cinco hilos que usan un objeto con una variable x, el almacenamiento local al hilo genera automticamente cinco porciones de memoria distintas para almacenar x. Afortunadamente, la creacin y gestin del almacenamiento local al hilo la lleva a cabo una plantilla de ZThread llamada ThreadLocal, tal y como se puede ver aqu:

15678 15679 15680 15681 15682 15683 15684 15685 15686 15687 15688 15689 15690 15691 15692 15693 15694 15695

//: C11:ThreadLocalVariables.cpp {RunByHand} // Automatically giving each thread its own storage. //{L} ZThread #include <iostream> #include "zthread/Thread.h" #include "zthread/Mutex.h" #include "zthread/Guard.h" #include "zthread/ThreadedExecutor.h" #include "zthread/Cancelable.h" #include "zthread/ThreadLocal.h" #include "zthread/CountedPtr.h" using namespace ZThread; using namespace std;

class ThreadLocalVariables : public Cancelable { ThreadLocal<int> value; bool canceled; Mutex lock;

602

15696 15697 15698 15699 15700 15701 15702 15703 15704 15705 15706 15707 15708 15709 15710 15711 15712 15713 15714 15715 15716 15717 15718 15719 15720

public: ThreadLocalVariables() : canceled(false) { value.set(0); } void increment() { value.set(value.get() + 1); } int get() { return value.get(); } void cancel() { Guard<Mutex> g(lock); canceled = true; } bool isCanceled() { Guard<Mutex> g(lock); return canceled; } };

class Accessor : public Runnable { int id; CountedPtr<ThreadLocalVariables> tlv; public: Accessor(CountedPtr<ThreadLocalVariables>& tl, int idn) : id(idn), tlv(tl) {} void run() { while(!tlv->isCanceled()) { tlv->increment();

603

15721 15722 15723 15724 15725 15726 15727 15728 15729 15730 15731 15732 15733 15734 15735 15736 15737 15738 15739 15740 15741 15742 15743 15744

cout << *this << endl; } } friend ostream& operator<<(ostream& os, Accessor& a) { return os << "#" << a.id << ": " << a.tlv->get(); } };

int main() { cout << "Press <Enter> to quit" << endl; try { CountedPtr<ThreadLocalVariables> tlv(new ThreadLocalVariables); const int SZ = 5; ThreadedExecutor executor; for(int i = 0; i < SZ; i++) executor.execute(new Accessor(tlv, i)); cin.get(); tlv->cancel(); // All Accessors will quit } catch(Synchronization_Exception& e) { cerr << e.what() << endl; } } ///:~

604
15745 15746 15747 15748 15749 15750 15751 15752 15753 15754 15755 15756 15757 15758 Listado 10.23. C11/ThreadLocalVariables.cpp Cuando crea un objeto ThreadLocal instanciando la plantilla, nicamente puede acceder al contenido del objeto utilizando los mtodos set() y get(). El mtodo get() devuelve una copia del objeto que est asociado a ese hilo, y set() inserta su argumento dentro del objeto almacenado para ese hilo, devolviendo el objeto antiguo que se encontraba almacenado. Puede comprobar que esto se utiliza en increment() y get() de
ThreadLocalVariables.

Ya que tlv se comparte en mltiples objetos Accessor, est escrito como un


Cancelable,

por lo que los Accessors puede recibir seales cuando queramos parar el sistema.

Cuando ejecute este programa se evidenciar que se reserva para cada hilo su propio almacenamiento.

15759 15760 15761 15762 15763 15764 15765 15766 15767 15768 15769 15770 15771 15772 15773 15774 15775

10.5. Finalizacin de tareas


En los ejemplos anteriores, hemos visto el uso de una "bandera de terminacin" o de la interfaz Cancelable para finalizar una tarea. Este es un enfoque razonable para el problema. Sin embargo, en algunas ocasiones la tarea tiene que ser finalizada ms abruptamente. En esta seccin, aprender sobre las cuestiones y problemas de este tipo de finalizacin. Primeramente, veamos en un ejemplo que no slo demuestra el problema de la finalizacin sino que, adems, es un ejemplo adicional de comparicin de recursos. Para mostrar el ejemplo, primero necesitaremos resolver el problema de la colisin de iostream.

10.5.1. Prevencin de coliciones en iostream


FIXME: dos versiones: Podra haberse dado cuenta en los anteriores ejemplos que la salida es confusa en algunas ocasiones. Los iostreams de C++ no fueron creados pensando en el sistema de hilos, por lo que no hay Puede haberse dado cuenta en los ejemplos anteriores que la salida es FIXMEconfusa. El sistema iostream de C++ no fue creado con el sistema de hilos en mente, por no lo que no hay nada que prevenga que la salida de un

605
15776 15777 15778 15779 15780 15781 15782 15783 hilo interfiera con la salida de otro hilo. Por ello, debe escribir aplicaciones de tal forma que sincronicen el uso de iostreams. Para solucionar el problema, primero necesitamos crear un paquete completo de salida y, despus, decidir explcitamente cuando intentamos mandarlo a la consola. Una sencilla solucin pasa por escribir la informacion en un ostringstream y posteriormente utilizar un nico objeto con un mutex como punto de salida de todos los hilos, para evitar que ms de un hilo escriba al mismo tiempo:

15784 15785 15786 15787 15788 15789 15790 15791 15792 15793 15794 15795 15796 15797 15798 15799 15800 15801

//: C11:Display.h // Prevents ostream collisions. #ifndef DISPLAY_H #define DISPLAY_H #include <iostream> #include <sstream> #include "zthread/Mutex.h" #include "zthread/Guard.h"

class Display { // Share one of these among all threads ZThread::Mutex iolock; public: void output(std::ostringstream& os) { ZThread::Guard<ZThread::Mutex> g(iolock); std::cout << os.str(); } }; #endif // DISPLAY_H ///:~

606
15802 15803 15804 15805 15806 15807 15808 15809 15810 15811 15812 15813 Listado 10.24. C11/Display.h De esta manera, predefinimos la funcin estandar operator<<() y el objeto puede ser construido en memoria utilizando operadores habituales de ostream. Cuando una tarea quiere mostrar una salida, crea un objeto ostringstream temporal que utiliza FIXME. Cuando llama a output(), el mutex evita que varios hilos escriban a este objeto Display. (Debe usar solo un objeto Display en su programa, tal y como ver en los siguientes ejemplos.) Todo esto muestra la idea bsica pero, si es necesario, puede construir un entorno ms elaborado. Por ejemplo, podra forzar el requisito de que solo haya un objeto Display en un programa hacindolo Singleton. (La librera ZThread tiene una plantilla Singleton para dar soporte a Singletons).

15814 15815 15816 15817 15818 15819

10.5.2. El jardn ornamental


En esta simulacin, al comit del jardn le gustara saber cuanta gente entra en el jardn cada da a travs de distintas puertas. Cada puerta tiene un FIXMEturnstile o algn otro tipo de contador, y despus de que el contador FIXMEturnstile se incrementa, aumenta una cuenta compartida que representa el nmero total de gente en el jardn.

15820 15821 15822 15823 15824 15825 15826 15827 15828 15829

//: C11:OrnamentalGarden.cpp {RunByHand} //{L} ZThread #include <vector> #include <cstdlib> #include <ctime> #include "Display.h" #include "zthread/Thread.h" #include "zthread/FastMutex.h" #include "zthread/Guard.h" #include "zthread/ThreadedExecutor.h"

607

15830 15831 15832 15833 15834 15835 15836 15837 15838 15839 15840 15841 15842 15843 15844 15845 15846 15847 15848 15849 15850 15851 15852 15853 15854

#include "zthread/CountedPtr.h" using namespace ZThread; using namespace std;

class Count : public Cancelable { FastMutex lock; int count; bool paused, canceled; public: Count() : count(0), paused(false), canceled(false) {} int increment() { // Comment the following line to see counting fail: Guard<FastMutex> g(lock); int temp = count ; if(rand() % 2 == 0) // Yield half the time Thread::yield(); return (count } int value() { Guard<FastMutex> g(lock); return count; } void cancel() { Guard<FastMutex> g(lock); canceled = true; = ++temp);

608

15855 15856 15857 15858 15859 15860 15861 15862 15863 15864 15865 15866 15867 15868 15869 15870 15871 15872 15873 15874 15875 15876 15877 15878 15879

} bool isCanceled() { Guard<FastMutex> g(lock); return canceled; } void pause() { Guard<FastMutex> g(lock); paused = true; } bool isPaused() { Guard<FastMutex> g(lock); return paused; } };

class Entrance : public Runnable { CountedPtr<Count> count; CountedPtr<Display> display; int number; int id; bool waitingForCancel; public: Entrance(CountedPtr<Count>& cnt, CountedPtr<Display>& disp, int idn) : count(cnt), display(disp), number(0), id(idn),

609

15880 15881 15882 15883 15884 15885 15886 15887 15888 15889 15890 15891 15892 15893 15894 15895 15896 15897 15898 15899 15900 15901 15902 15903 15904

waitingForCancel(false) {} void run() { while(!count->isPaused()) { ++number; { ostringstream os; os << *this << " Total: " << count->increment() << endl; display->output(os); } Thread::sleep(100); } waitingForCancel = true; while(!count->isCanceled()) // Hold here... Thread::sleep(100); ostringstream os; os << "Terminating " << *this << endl; display->output(os); } int getValue() { while(count->isPaused() && !waitingForCancel) Thread::sleep(100); return number; } friend ostream&

610

15905 15906 15907 15908 15909 15910 15911 15912 15913 15914 15915 15916 15917 15918 15919 15920 15921 15922 15923 15924 15925 15926 15927 15928 15929

operator<<(ostream& os, const Entrance& e) { return os << "Entrance " << e.id << ": " << e.number; } };

int main() { srand(time(0)); // Seed the random number generator cout << "Press <ENTER> to quit" << endl; CountedPtr<Count> count(new Count); vector<Entrance*> v; CountedPtr<Display> display(new Display); const int SZ = 5; try { ThreadedExecutor executor; for(int i = 0; i < SZ; i++) { Entrance* task = new Entrance(count, display, i); executor.execute(task); // Save the pointer to the task: v.push_back(task); } cin.get(); // Wait for user to press <Enter> count->pause(); // Causes tasks to stop counting int sum = 0; vector<Entrance*>::iterator it = v.begin(); while(it != v.end()) {

611

15930 15931 15932 15933 15934 15935 15936 15937 15938 15939 15940 15941

sum += (*it)->getValue(); ++it; } ostringstream os; os << "Total: " << count->value() << endl << "Sum of Entrances: " << sum << endl; display->output(os); count->cancel(); // Causes threads to quit } catch(Synchronization_Exception& e) { cerr << e.what() << endl; } } ///:~

15942 15943 15944 15945 15946 15947 15948 15949 15950 15951 15952 15953 15954 15955 15956
Count

Listado 10.25. C11/OrnamentalGarden.cpp es la clase que conserva el contador principal de los visitantes del

jardn. El objeto nico Count definido en main() como contador FIXME is held como un CountedPtr en Entrance y, as, se comparte entre todos los objetos
Entrance. Mutex

En este ejemplo, se utiliza un FastMutex llamado lock en vez de un ordinario ya que un FastMutex usa el mutex nativo del sistema

operativo y, por ello, aportar resultados ms interesantes. Se utiliza un Guard con bloqueo en increment() para sincronizar el acceso a count. Esta funcin usa rand() para realizar una carga de trabajo alta (mediante yield()) FIXME la mitad del tiempo, .... La clase Entrance tambin mantiene una variable local numbre con el nmero de visitantes que han pasado a travs de una entrada concreta. Esto proporciona un chequeo doble contra el objeto count para asegurar que el verdadero nmero de visitantes es el que se est almacenando.

612
15957 15958 15959 15960 15961 15962 15963 15964 15965 15966 15967 15968 15969 15970 15971 15972 15973 15974 15975 15976 15977 15978 15979 15980 15981 15982 15983 15984 15985 15986 15987 15988 15989 15990 Entrance::run() simplemente incrementa number y el objeto count y se duerme durante 100 milisegundos. En main, se carga un vector<Entrance*> con cada Entrance que se crean. Despus de que el usuario pulse Enter, el vector se utiliza para iterar sobre el valor de cada Entrance y calcular el total. FIXMEThis program goes to quite a bit of extra trouble to shut everything down in a stable fashion. Toda la comunicacin entre los objetos Entrance ocurre a travs de un nico objeto Count. Cuando el usuario pulsa Enter, main() manda el mensaje pause() a count. Como cada Entrance::run() est vigilando a que el objeto count est pausado, esto hace que cada Entrance se mueva al estado waitingForCancel, donde no se cuenta ms, pero an sigue vivo. Esto es esencial porque main() debe poder seguir iterando de forma segura sobre los objetos del vector<Entrace*>. Note que debido a que existe una FIXMEpequea posibilidad que la iteracin pueda ocurrir antes de que un
Entrance

haya terminado de contar y haya ido al estado de

waitingForCancel, la funcin getValue() itera a lo largo de las llamadas a sleep() hasta que el objeto vaya al estado de waitingForCancel. (Esta es una forma, que se conoce como espera activa, y es indeseable. Ver un enfoque ms apropiado utilizando wait(), ms adelante en el captulo). Una vez que main() completa una iteracin a lo largo del vector<Entrance*>, se manda el mensaje de cancel() al objeto count, y de nuevo todos los objetos Entrance esperan a este cambio de estado. En ese instante, imprimen un mensaje de finalizacin y salen de run(), por lo que el mecanismo de hilado destruye cada tarea. Tal y como este programa se ejecuta, ver que la cuenta total y la de cada una de las entradas se muestran a la vez que la gente pasa a travs de un FIXMEturnstile. Si comenta el objeto Guard en Count::increment(), se dar cuenta que el nmero total de personas no es el que espera que sea. El nmero de personas contadas por cada FIXMEturnstile ser diferente del valor de count. Tan pronto como haya un Mutex para sincronizar el acceso al
Counter,

las cosas funcionarn correctamente. Tenga en cuenta que

Count::increment() exagera la situacin potencial de fallo que supone utilizar temp y yield(). En problemas reales de hilado, la probabilidad de fallo puede

613
15991 15992 15993 15994 15995 15996 15997 15998 15999 16000 16001 16002 16003 16004 16005 16006 16007 16008 16009 16010 16011 16012 16013 16014 16015 16016 16017 16018 16019 16020 16021 16022 16023 ser estadsticamente menor, por lo que puede caer fcilmente en la trampa de creer que las cosas funcionan correctamente. Tal y como muestra el problema anterior, existen FIXMElikely problemas ocultos que no le han ocurrido, por lo que debe ser excepcionalmente diligente cuando revise cdigo concurrente. Operaciones atmicas Note que Count::value() devuelve el valor de count utilizando un objeto
Guard

para la sincronizacin. Esto ofrece un aspecto interesante porque este

cdigo probablemente funcionar bien con la mayora de los compiladores y sistemas sin sincronizacin. El motivo es que, en general, una operacin simple como devolver un int ser una operacin atmica, que quiere decir que probablemente se llevar a cabo con una nica instruccin de microprocesador y no ser interrumpida. (El mecanismo de multihilado no puede parar un hilo en mitad de una instruccin de microprocesador.) Esto es, las operaciones atmicas no son interrumpibles por el mecanismo de hilado y, as, no necesitan ser protegidas.[152] De hecho, si eliminramos la asignacin de count en temp y quitramos yield(), y en su lugar simplemente incrementramos count directamente, probablemente no necesitaramos un lock ya que la operacin de incremento es, normalmente, atmica. [153] El problema es que el estndar de C++ no garantiza la atomicidad para ninguna de esas operaciones. Sin embargo, pese a que operaciones como devolver un int e incrementar un int son atmicas en la mayora de las mquinas no hay garantas. Y puesto que no hay garanta, debe asumir lo peor. En algunas ocasiones podra investigar el funcionamiento de la atomicidad para una mquina en particular (normalmente mirando el lenguaje ensamblador) y escribir cdigo basado en esas asunciones. Esto es siempre peligroso y FIXMEill-advised. Es muy fcil que esta informacin se pierda o est oculta, y la siguiente persona que venga podra asumir que el cdigo puede ser portado a otra mquina y, por ello, volverse loco siguiendo la pista al FIXMEoccsional glitch provocado por la colisin de hilos. Por ello, aunque quitar el guarda en Count::value() parezca que funciona no es FIXMEairtight y, as, en algunas mquinas puede ver un comportamiento aberrante.

614
16024 16025 16026 16027 16028 16029 16030 16031 16032 16033 16034 16035 16036 16037 16038 16039 16040 16041 16042 16043 16044 16045 16046 16047 16048 16049 16050 16051 16052 16053 16054 16055 16056

10.5.3. FIXME:Teminacin al bloquear


En el ejemplo anterior, Entrance::run() incluye una llamada a sleep() en el bucle principal. Sabemos que sleep() se despertar eventualmente y que la tarea llegar al principio del bucle donde tiene una oportunidad para salir de ese bucle chequeando el estado isPaused(). Sin embargo, sleep() es simplemente una situacin donde un hilo en ejecucin se bloquea, y a veces necesita terminar una tarea que est bloqueada. Un hilo puede estar en alguno de los cuatro estados: 1. Nuevo: Un hilo permanece en este estado solamente de forma momentnea, tan solo cuando se crea. Reserva todos los recursos del sistema necesarios y ejecuta la inicializacin. En este momento se convierte en FIXMEcandidato para recibir tiempo de CPU. A continuacin, el planificador llevar a este hilo al estado de ejecucin o de bloqueo. 2. Ejecutable: Esto significa que un hilo puede ser ejecutado cuando el mecanismo de fraccionador de tiempo tenga ciclos de CPU disponibles para el hilo. As, el hilo podra o no ejecutarse en cualquier momento, pero no hay nada que evite FIXME 3. Bloqueado: El hilo pudo ser ejecutado, pero algo lo impidi. (Podra estar esperando a que se complete una operacin de entrada/salida, por ejemplo.) Mientras un hilo est en el estado de bloqueo, el planificador simplemente lo ignorar y no le dar tiempo de CPU. Hasta que un hilo no vuelva a entrar en el estado de ejecucin, no ejecutar ninguna operacin. 4. FIXMEMuerte: Un hilo en el estado de muerto no ser planificable y no recibir tiempo de CPU. Sus tareas han finalizado, y no ser ejecutable nunca ms. La forma normal que un hilo tiene para morir es volviendo de su funcin run(). Un hilo est bloqueado cuando no puede continuar su ejecucin. Un hilo puede bloquearse debido a los siguientes motivos: Puso el hilo a dormir llamando a sleep(milisegundos), en cuyo caso no ser ejecutado durante el tiempo especificado. Suspendi la ejecucin del hilo con wait(). No volver a ser ejecutable hasta que el hilo no obtenga el mensaje signal() o broadcast(). Estudiaremos esto en una seccin ms adelante.

615
16057 16058 16059 16060 16061 16062 16063 16064 El hilo est esperando a que una operacin de entrada/salida finalice. El hilo est intentando entrar en un bloque de cdigo controlado por un mutex, y el mutex ha sido ya adquirido por otro hilo. El problema que tenemos ahora es el siguiente: algunas veces quiere terminar un hilo que est en el estado de bloqueo. Si no puede esperar a que el hilo llegue a un punto en el cdigo donde pueda comprobar el valor del estado y decidir si terminar por sus propios medios, debe forzar a que el hilo salga de su estado de bloqueo.

16065 16066 16067 16068 16069 16070 16071 16072 16073 16074 16075 16076 16077 16078 16079 16080 16081 16082 16083 16084 16085 16086 16087

10.5.4. Interrupcin
Tal y como podra imaginar, es mucho ms FIXMEmessier salir de forma brusca en mitad de la funcin Runnable::run() que si espera a que esa funcin llegue al test de isCanceled() (o a algn otro lugar donde el programador est preparado para salir de la funcin). Cuando sale de una tarea bloqueada, podra necesitar destruir objetos y liberar recursos. Debido a esto, salir en mitad de un run() de una tarea, ms que otra cosa, consiste en lanzar una excepcin, por lo que en ZThreads, las excepciones se utilizan para este tipo de terminacin. (Esto roza el lmite de un uso inapropiado de las excepciones, porque significa que las utiliza para el control de flujo.)[154] Para volver de esta manera a un buen estado conocido a la hora de terminar una tarea, tenga en cuenta cuidadosamente los caminos de ejecucin de su cdigo y libere todo correctamente dentro de los bloques catch. Veremos esta tcnica en la presente seccin. Para finalizar un hilo bloqueado, la librera ZThread proporciona la funcin Thread::interrupted(). Esta configura el estado de interrupcin para ese hilo. Un hilo con su estado de interrupcin configurado lanzar una Interrupted_Exception si est bloqueado o si espera una operacin bloqueante. El estado de interrupcin ser restaurado cuando se haya lanzado la excepcin o si la tarea llama a Thread::interrupted(). Como puede ver, Thread::interrupted() proporciona otra forma de salir de su bucle run(), sin lanzar una excepcin. A continuacin, un ejemplo que ilustra las bases de interrupt():

616

16088 16089 16090 16091 16092 16093 16094 16095 16096 16097 16098 16099 16100 16101 16102 16103 16104 16105 16106 16107 16108 16109 16110 16111 16112

//: C11:Interrupting.cpp // Interrupting a blocked thread. //{L} ZThread #include <iostream> #include "zthread/Thread.h" using namespace ZThread; using namespace std;

class Blocked : public Runnable { public: void run() { try { Thread::sleep(1000); cout << "Waiting for get() in run():"; cin.get(); } catch(Interrupted_Exception&) { cout << "Caught Interrupted_Exception" << endl; // Exit the task } } };

int main(int argc, char* argv[]) { try { Thread t(new Blocked);

617

16113 16114 16115 16116 16117 16118 16119

if(argc > 1) Thread::sleep(1100); t.interrupt(); } catch(Synchronization_Exception& e) { cerr << e.what() << endl; } } ///:~

16120 16121 16122 16123 16124 16125 16126 16127 16128 16129 16130 16131 16132 16133 16134 16135 16136 16137 16138 16139 16140 16141 16142 16143

Listado 10.26. C11/Interrupting.cpp Puede ver que, adems de la insercin de cout, run() tiene dos puntos donde puede ocurrir el bloqueo: la llamada a Thread::sleep(1000) y la llamada a cin.get(). Dando cualquier argumento por lnea de comandos al programa, dir a main() que se duerma lo suficiente para que la tarea finalice su sleep() y llame a cin.get().[155] Si no le da un argumento, el sleep() de main() se ignora. Ahora, la llamada a interrupt() ocurrir mientras la tarea est dormida, y ver que esto provoca que una Interrupted_Exception se lance. Si le da un argumento por lnea de comandos al programa, descubrir que la tarea no puede ser interrumpida si est bloqueada en la entrada/salida. Esto es, puede interrumpir cualquier operacin bloqueante a excepcin de una entrada/salida.[156] Esto es un poco desconcertante si est creando un hilo que ejecuta entrada/salida porque quiere decir que la entrada/salida tiene posibilidades de bloquear su programa multihilado. El problema es que, de nuevo, C++ no fue diseado con el sistema de hilos en mente; muy al contrario, FIXMEpresupone que el hilado no existe. Por ello, la librera iostream no ses thread-friendly. Si el nuevo estndar de C++ decide aadir soporte a hilos, la librera iostream podra necesitar ser reconsiderada en el proceso. Bloqueo debido a un mutex. Si intenta llamar a una funcin cuyo mutes ha sido adquirido, la tarea que llama ser suspendida hasta que el mutex est accesible. El siguiente ejemplo comprueba si este tipo de bloqueo es interrumpible:

618

16144 16145 16146 16147 16148 16149 16150 16151 16152 16153 16154 16155 16156 16157 16158 16159 16160 16161 16162 16163 16164 16165 16166 16167 16168

//: C11:Interrupting2.cpp // Interrupting a thread blocked // with a synchronization guard. //{L} ZThread #include <iostream> #include "zthread/Thread.h" #include "zthread/Mutex.h" #include "zthread/Guard.h" using namespace ZThread; using namespace std;

class BlockedMutex { Mutex lock; public: BlockedMutex() { lock.acquire(); } void f() { Guard<Mutex> g(lock); // This will never be available } };

class Blocked2 : public Runnable { BlockedMutex blocked;

619

16169 16170 16171 16172 16173 16174 16175 16176 16177 16178 16179 16180 16181 16182 16183 16184 16185 16186 16187 16188

public: void run() { try { cout << "Waiting for f() in BlockedMutex" << endl; blocked.f(); } catch(Interrupted_Exception& e) { cerr << e.what() << endl; // Exit the task } } };

int main(int argc, char* argv[]) { try { Thread t(new Blocked2); t.interrupt(); } catch(Synchronization_Exception& e) { cerr << e.what() << endl; } } ///:~

16189 16190 16191 16192 16193 16194

Listado 10.27. C11/Interrupting2.cpp La clase BlockedMutex tiene un constructor que adquiere su propio objeto
Mutex

y nunca lo libera. Por esa razn, si intenta llamara a f(), siempre ser

bloqueado porque el Mutex no puede ser adquirido. En Blocked2, la funcin run() se parar en la llamada blocked.f(). Cuando ejecute el programa ver

620
16195 16196 16197 16198 16199 16200 16201 que, a diferencia de la llamada a iostream, interrupt() puede salir de una llamada que est bloqueada por un mutex.[157] Comprobacin de una una interrupcin. Note que cuando llama a interrupt() sobre un hilo, la nica vez que ocurre la interrupcin es cuando la tarea entra, o ya est dentro, de una operacin bloqueante (a excepcin, como ya ha visto, del caso de la entrada/salida, donde simplemente

16202 16203 16204 16205 16206 16207 16208 16209 16210 16211 16212 16213 16214 16215 16216 16217 16218 16219 16220

//: C11:Interrupting3.cpp {RunByHand} // General idiom for interrupting a task. //{L} ZThread #include <iostream> #include "zthread/Thread.h" using namespace ZThread; using namespace std;

const double PI = 3.14159265358979323846; const double E = 2.7182818284590452354;

class NeedsCleanup { int id; public: NeedsCleanup(int ident) : id(ident) { cout << "NeedsCleanup " << id << endl; } ~NeedsCleanup() { cout << "~NeedsCleanup " << id << endl;

621

16221 16222 16223 16224 16225 16226 16227 16228 16229 16230 16231 16232 16233 16234 16235 16236 16237 16238 16239 16240 16241 16242 16243 16244 16245

} };

class Blocked3 : public Runnable { volatile double d; public: Blocked3() : d(0.0) {} void run() { try { while(!Thread::interrupted()) { point1: NeedsCleanup n1(1); cout << "Sleeping" << endl; Thread::sleep(1000); point2: NeedsCleanup n2(2); cout << "Calculating" << endl; // A time-consuming, non-blocking operation: for(int i = 1; i < 100000; i++) d = d + (PI + E) / (double)i; } cout << "Exiting via while() test" << endl; } catch(Interrupted_Exception&) { cout << "Exiting via Interrupted_Exception" << endl; }

622

16246 16247 16248 16249 16250 16251 16252 16253 16254 16255 16256 16257 16258 16259 16260 16261 16262 16263

} };

int main(int argc, char* argv[]) { if(argc != 2) { cerr << "usage: " << argv[0] << " delay-in-milliseconds" << endl; exit(1); } int delay = atoi(argv[1]); try { Thread t(new Blocked3); Thread::sleep(delay); t.interrupt(); } catch(Synchronization_Exception& e) { cerr << e.what() << endl; } } ///:~

16264 16265

Listado 10.28. C11/Interrupting3.cpp

16266 16267

10.6. Cooperacin entre hilos


10.6.1. Wait y signal
//: C11:WaxOMatic.cpp {RunByHand}

16268

623

16269 16270 16271 16272 16273 16274 16275 16276 16277 16278 16279 16280 16281 16282 16283 16284 16285 16286 16287 16288 16289 16290 16291 16292 16293

// Basic thread cooperation. //{L} ZThread #include <iostream> #include <string> #include "zthread/Thread.h" #include "zthread/Mutex.h" #include "zthread/Guard.h" #include "zthread/Condition.h" #include "zthread/ThreadedExecutor.h" using namespace ZThread; using namespace std;

class Car { Mutex lock; Condition condition; bool waxOn; public: Car() : condition(lock), waxOn(false) {} void waxed() { Guard<Mutex> g(lock); waxOn = true; // Ready to buff condition.signal(); } void buffed() { Guard<Mutex> g(lock);

624

16294 16295 16296 16297 16298 16299 16300 16301 16302 16303 16304 16305 16306 16307 16308 16309 16310 16311 16312 16313 16314 16315 16316 16317 16318

waxOn = false; // Ready for another coat of wax condition.signal(); } void waitForWaxing() { Guard<Mutex> g(lock); while(waxOn == false) condition.wait(); } void waitForBuffing() { Guard<Mutex> g(lock); while(waxOn == true) condition.wait(); } };

class WaxOn : public Runnable { CountedPtr<Car> car; public: WaxOn(CountedPtr<Car>& c) : car(c) {} void run() { try { while(!Thread::interrupted()) { cout << "Wax On!" << endl; Thread::sleep(200); car->waxed();

625

16319 16320 16321 16322 16323 16324 16325 16326 16327 16328 16329 16330 16331 16332 16333 16334 16335 16336 16337 16338 16339 16340 16341 16342 16343

car->waitForBuffing(); } } catch(Interrupted_Exception&) { /* Exit */ } cout << "Ending Wax On process" << endl; } };

class WaxOff : public Runnable { CountedPtr<Car> car; public: WaxOff(CountedPtr<Car>& c) : car(c) {} void run() { try { while(!Thread::interrupted()) { car->waitForWaxing(); cout << "Wax Off!" << endl; Thread::sleep(200); car->buffed(); } } catch(Interrupted_Exception&) { /* Exit */ } cout << "Ending Wax Off process" << endl; } };

int main() {

626

16344 16345 16346 16347 16348 16349 16350 16351 16352 16353 16354 16355

cout << "Press <Enter> to quit" << endl; try { CountedPtr<Car> car(new Car); ThreadedExecutor executor; executor.execute(new WaxOff(car)); executor.execute(new WaxOn(car)); cin.get(); executor.interrupt(); } catch(Synchronization_Exception& e) { cerr << e.what() << endl; } } ///:~

16356 16357 16358

Listado 10.29. C11/WaxOMatic.cpp

10.6.2. Relacin de productor/consumidor


//: C11:ToastOMatic.cpp {RunByHand} // Problems with thread cooperation. //{L} ZThread #include <iostream> #include <cstdlib> #include <ctime> #include "zthread/Thread.h" #include "zthread/Mutex.h"

16359 16360 16361 16362 16363 16364 16365 16366

627

16367 16368 16369 16370 16371 16372 16373 16374 16375 16376 16377 16378 16379 16380 16381 16382 16383 16384 16385 16386 16387 16388 16389 16390 16391

#include "zthread/Guard.h" #include "zthread/Condition.h" #include "zthread/ThreadedExecutor.h" using namespace ZThread; using namespace std;

// Apply jam to buttered toast: class Jammer : public Runnable { Mutex lock; Condition butteredToastReady; bool gotButteredToast; int jammed; public: Jammer() : butteredToastReady(lock) { gotButteredToast = false; jammed = 0; } void moreButteredToastReady() { Guard<Mutex> g(lock); gotButteredToast = true; butteredToastReady.signal(); } void run() { try { while(!Thread::interrupted()) {

628

16392 16393 16394 16395 16396 16397 16398 16399 16400 16401 16402 16403 16404 16405 16406 16407 16408 16409 16410 16411 16412 16413 16414 16415 16416

{ Guard<Mutex> g(lock); while(!gotButteredToast) butteredToastReady.wait(); ++jammed; } cout << "Putting jam on toast " << jammed << endl; { Guard<Mutex> g(lock); gotButteredToast = false; } } } catch(Interrupted_Exception&) { /* Exit */ } cout << "Jammer off" << endl; } };

// Apply butter to toast: class Butterer : public Runnable { Mutex lock; Condition toastReady; CountedPtr<Jammer> jammer; bool gotToast; int buttered; public:

629

16417 16418 16419 16420 16421 16422 16423 16424 16425 16426 16427 16428 16429 16430 16431 16432 16433 16434 16435 16436 16437 16438 16439 16440 16441

Butterer(CountedPtr<Jammer>& j) : toastReady(lock), jammer(j) { gotToast = false; buttered = 0; } void moreToastReady() { Guard<Mutex> g(lock); gotToast = true; toastReady.signal(); } void run() { try { while(!Thread::interrupted()) { { Guard<Mutex> g(lock); while(!gotToast) toastReady.wait(); ++buttered; } cout << "Buttering toast " << buttered << endl; jammer->moreButteredToastReady(); { Guard<Mutex> g(lock); gotToast = false; }

630

16442 16443 16444 16445 16446 16447 16448 16449 16450 16451 16452 16453 16454 16455 16456 16457 16458 16459 16460 16461 16462 16463 16464 16465 16466

} } catch(Interrupted_Exception&) { /* Exit */ } cout << "Butterer off" << endl; } };

class Toaster : public Runnable { CountedPtr<Butterer> butterer; int toasted; public: Toaster(CountedPtr<Butterer>& b) : butterer(b) { toasted = 0; } void run() { try { while(!Thread::interrupted()) { Thread::sleep(rand()/(RAND_MAX/5)*100); // ... // Create new toast // ... cout << "New toast " << ++toasted << endl; butterer->moreToastReady(); } } catch(Interrupted_Exception&) { /* Exit */ } cout << "Toaster off" << endl;

631

16467 16468 16469 16470 16471 16472 16473 16474 16475 16476 16477 16478 16479 16480 16481 16482 16483 16484 16485

} };

int main() { srand(time(0)); // Seed the random number generator try { cout << "Press <Return> to quit" << endl; CountedPtr<Jammer> jammer(new Jammer); CountedPtr<Butterer> butterer(new Butterer(jammer)); ThreadedExecutor executor; executor.execute(new Toaster(butterer)); executor.execute(butterer); executor.execute(jammer); cin.get(); executor.interrupt(); } catch(Synchronization_Exception& e) { cerr << e.what() << endl; } } ///:~

16486 16487 16488

Listado 10.30. C11/ToastOMatic.cpp

10.6.3. Resolucin de problemas de hilos mediante colas


//: C11:TQueue.h

16489

632

16490 16491 16492 16493 16494 16495 16496 16497 16498 16499 16500 16501 16502 16503 16504 16505 16506 16507 16508 16509 16510 16511 16512 16513 16514

#ifndef TQUEUE_H #define TQUEUE_H #include <deque> #include "zthread/Thread.h" #include "zthread/Condition.h" #include "zthread/Mutex.h" #include "zthread/Guard.h"

template<class T> class TQueue { ZThread::Mutex lock; ZThread::Condition cond; std::deque<T> data; public: TQueue() : cond(lock) {} void put(T item) { ZThread::Guard<ZThread::Mutex> g(lock); data.push_back(item); cond.signal(); } T get() { ZThread::Guard<ZThread::Mutex> g(lock); while(data.empty()) cond.wait(); T returnVal = data.front(); data.pop_front();

633

16515 16516 16517 16518

return returnVal; } }; #endif // TQUEUE_H ///:~

16519 16520

Listado 10.31. C11/TQueue.h

16521 16522 16523 16524 16525 16526 16527 16528 16529 16530 16531 16532 16533 16534 16535 16536 16537 16538

//: C11:TestTQueue.cpp {RunByHand} //{L} ZThread #include <string> #include <iostream> #include "TQueue.h" #include "zthread/Thread.h" #include "LiftOff.h" using namespace ZThread; using namespace std;

class LiftOffRunner : public Runnable { TQueue<LiftOff*> rockets; public: void add(LiftOff* lo) { rockets.put(lo); } void run() { try { while(!Thread::interrupted()) { LiftOff* rocket = rockets.get();

634

16539 16540 16541 16542 16543 16544 16545 16546 16547 16548 16549 16550 16551 16552 16553 16554 16555 16556 16557 16558 16559

rocket->run(); } } catch(Interrupted_Exception&) { /* Exit */ } cout << "Exiting LiftOffRunner" << endl; } };

int main() { try { LiftOffRunner* lor = new LiftOffRunner; Thread t(lor); for(int i = 0; i < 5; i++) lor->add(new LiftOff(10, i)); cin.get(); lor->add(new LiftOff(10, 99)); cin.get(); t.interrupt(); } catch(Synchronization_Exception& e) { cerr << e.what() << endl; } } ///:~

16560 16561

Listado 10.32. C11/TestTQueue.cpp

16562

//: C11:ToastOMaticMarkII.cpp {RunByHand}

635

16563 16564 16565 16566 16567 16568 16569 16570 16571 16572 16573 16574 16575 16576 16577 16578 16579 16580 16581 16582 16583 16584 16585 16586 16587

// Solving the problems using TQueues. //{L} ZThread #include <iostream> #include <string> #include <cstdlib> #include <ctime> #include "zthread/Thread.h" #include "zthread/Mutex.h" #include "zthread/Guard.h" #include "zthread/Condition.h" #include "zthread/ThreadedExecutor.h" #include "TQueue.h" using namespace ZThread; using namespace std;

class Toast { enum Status { DRY, BUTTERED, JAMMED }; Status status; int id; public: Toast(int idn) : status(DRY), id(idn) {} #ifdef __DMC__ // Incorrectly requires default Toast() { assert(0); } // Should never be called #endif void butter() { status = BUTTERED; }

636

16588 16589 16590 16591 16592 16593 16594 16595 16596 16597 16598 16599 16600 16601 16602 16603 16604 16605 16606 16607 16608 16609 16610 16611 16612

void jam() { status = JAMMED; } string getStatus() const { switch(status) { case DRY: return "dry"; case BUTTERED: return "buttered"; case JAMMED: return "jammed"; default: return "error"; } } int getId() { return id; } friend ostream& operator<<(ostream& os, const Toast& t) { return os << "Toast " << t.id << ": " << t.getStatus(); } };

typedef CountedPtr< TQueue<Toast> > ToastQueue;

class Toaster : public Runnable { ToastQueue toastQueue; int count; public: Toaster(ToastQueue& tq) : toastQueue(tq), count(0) {} void run() { try { while(!Thread::interrupted()) {

637

16613 16614 16615 16616 16617 16618 16619 16620 16621 16622 16623 16624 16625 16626 16627 16628 16629 16630 16631 16632 16633 16634 16635 16636 16637

int delay = rand()/(RAND_MAX/5)*100; Thread::sleep(delay); // Make toast Toast t(count++); cout << t << endl; // Insert into queue toastQueue->put(t); } } catch(Interrupted_Exception&) { /* Exit */ } cout << "Toaster off" << endl; } };

// Apply butter to toast: class Butterer : public Runnable { ToastQueue dryQueue, butteredQueue; public: Butterer(ToastQueue& dry, ToastQueue& buttered) : dryQueue(dry), butteredQueue(buttered) {} void run() { try { while(!Thread::interrupted()) { // Blocks until next piece of toast is available: Toast t = dryQueue->get(); t.butter();

638

16638 16639 16640 16641 16642 16643 16644 16645 16646 16647 16648 16649 16650 16651 16652 16653 16654 16655 16656 16657 16658 16659 16660 16661 16662

cout << t << endl; butteredQueue->put(t); } } catch(Interrupted_Exception&) { /* Exit */ } cout << "Butterer off" << endl; } };

// Apply jam to buttered toast: class Jammer : public Runnable { ToastQueue butteredQueue, finishedQueue; public: Jammer(ToastQueue& buttered, ToastQueue& finished) : butteredQueue(buttered), finishedQueue(finished) {} void run() { try { while(!Thread::interrupted()) { // Blocks until next piece of toast is available: Toast t = butteredQueue->get(); t.jam(); cout << t << endl; finishedQueue->put(t); } } catch(Interrupted_Exception&) { /* Exit */ } cout << "Jammer off" << endl;

639

16663 16664 16665 16666 16667 16668 16669 16670 16671 16672 16673 16674 16675 16676 16677 16678 16679 16680 16681 16682 16683 16684 16685 16686 16687

} };

// Consume the toast: class Eater : public Runnable { ToastQueue finishedQueue; int counter; public: Eater(ToastQueue& finished) : finishedQueue(finished), counter(0) {} void run() { try { while(!Thread::interrupted()) { // Blocks until next piece of toast is available: Toast t = finishedQueue->get(); // Verify that the toast is coming in order, // and that all pieces are getting jammed: if(t.getId() != counter++ || t.getStatus() != "jammed") { cout << ">>>> Error: " << t << endl; exit(1); } else cout << "Chomp! " << t << endl; } } catch(Interrupted_Exception&) { /* Exit */ }

640

16688 16689 16690 16691 16692 16693 16694 16695 16696 16697 16698 16699 16700 16701 16702 16703 16704 16705 16706 16707 16708 16709 16710

cout << "Eater off" << endl; } };

int main() { srand(time(0)); // Seed the random number generator try { ToastQueue dryQueue(new TQueue<Toast>), butteredQueue(new TQueue<Toast>), finishedQueue(new TQueue<Toast>); cout << "Press <Return> to quit" << endl; ThreadedExecutor executor; executor.execute(new Toaster(dryQueue)); executor.execute(new Butterer(dryQueue,butteredQueue)); executor.execute( new Jammer(butteredQueue, finishedQueue)); executor.execute(new Eater(finishedQueue)); cin.get(); executor.interrupt(); } catch(Synchronization_Exception& e) { cerr << e.what() << endl; } } ///:~

16711

Listado 10.33. C11/ToastOMaticMarkII.cpp

641
16712 16713

10.6.4. Broadcast
//: C11:CarBuilder.cpp {RunByHand} // How broadcast() works. //{L} ZThread #include <iostream> #include <string> #include "zthread/Thread.h" #include "zthread/Mutex.h" #include "zthread/Guard.h" #include "zthread/Condition.h" #include "zthread/ThreadedExecutor.h" #include "TQueue.h" using namespace ZThread; using namespace std;

16714 16715 16716 16717 16718 16719 16720 16721 16722 16723 16724 16725 16726 16727 16728 16729 16730 16731 16732 16733 16734 16735 16736

class Car { int id; bool engine, driveTrain, wheels; public: Car(int idn) : id(idn), engine(false), driveTrain(false), wheels(false) {} // Empty Car object: Car() : id(-1), engine(false), driveTrain(false), wheels(false) {}

642

16737 16738 16739 16740 16741 16742 16743 16744 16745 16746 16747 16748 16749 16750 16751 16752 16753 16754 16755 16756 16757 16758 16759 16760 16761

// Unsynchronized -- assumes atomic bool operations: int getId() { return id; } void addEngine() { engine = true; } bool engineInstalled() { return engine; } void addDriveTrain() { driveTrain = true; } bool driveTrainInstalled() { return driveTrain; } void addWheels() { wheels = true; } bool wheelsInstalled() { return wheels; } friend ostream& operator<<(ostream& os, const Car& c) { return os << "Car " << c.id << " [" << " engine: " << c.engine << " driveTrain: " << c.driveTrain << " wheels: " << c.wheels << " ]"; } };

typedef CountedPtr< TQueue<Car> > CarQueue;

class ChassisBuilder : public Runnable { CarQueue carQueue; int counter; public: ChassisBuilder(CarQueue& cq) : carQueue(cq),counter(0) {} void run() { try {

643

16762 16763 16764 16765 16766 16767 16768 16769 16770 16771 16772 16773 16774 16775 16776 16777 16778 16779 16780 16781 16782 16783 16784 16785 16786

while(!Thread::interrupted()) { Thread::sleep(1000); // Make chassis: Car c(counter++); cout << c << endl; // Insert into queue carQueue->put(c); } } catch(Interrupted_Exception&) { /* Exit */ } cout << "ChassisBuilder off" << endl; } };

class Cradle { Car c; // Holds current car being worked on bool occupied; Mutex workLock, readyLock; Condition workCondition, readyCondition; bool engineBotHired, wheelBotHired, driveTrainBotHired; public: Cradle() : workCondition(workLock), readyCondition(readyLock) { occupied = false; engineBotHired = true; wheelBotHired = true;

644

16787 16788 16789 16790 16791 16792 16793 16794 16795 16796 16797 16798 16799 16800 16801 16802 16803 16804 16805 16806 16807 16808 16809 16810 16811

driveTrainBotHired = true; } void insertCar(Car chassis) { c = chassis; occupied = true; } Car getCar() { // Can only extract car once if(!occupied) { cerr << "No Car in Cradle for getCar()" << endl; return Car(); // "Null" Car object } occupied = false; return c; } // Access car while in cradle: Car* operator->() { return &c; } // Allow robots to offer services to this cradle: void offerEngineBotServices() { Guard<Mutex> g(workLock); while(engineBotHired) workCondition.wait(); engineBotHired = true; // Accept the job } void offerWheelBotServices() { Guard<Mutex> g(workLock);

645

16812 16813 16814 16815 16816 16817 16818 16819 16820 16821 16822 16823 16824 16825 16826 16827 16828 16829 16830 16831 16832 16833 16834 16835 16836

while(wheelBotHired) workCondition.wait(); wheelBotHired = true; // Accept the job } void offerDriveTrainBotServices() { Guard<Mutex> g(workLock); while(driveTrainBotHired) workCondition.wait(); driveTrainBotHired = true; // Accept the job } // Tell waiting robots that work is ready: void startWork() { Guard<Mutex> g(workLock); engineBotHired = false; wheelBotHired = false; driveTrainBotHired = false; workCondition.broadcast(); } // Each robot reports when their job is done: void taskFinished() { Guard<Mutex> g(readyLock); readyCondition.signal(); } // Director waits until all jobs are done: void waitUntilWorkFinished() {

646

16837 16838 16839 16840 16841 16842 16843 16844 16845 16846 16847 16848 16849 16850 16851 16852 16853 16854 16855 16856 16857 16858 16859 16860 16861

Guard<Mutex> g(readyLock); while(!(c.engineInstalled() && c.driveTrainInstalled() && c.wheelsInstalled())) readyCondition.wait(); } };

typedef CountedPtr<Cradle> CradlePtr;

class Director : public Runnable { CarQueue chassisQueue, finishingQueue; CradlePtr cradle; public: Director(CarQueue& cq, CarQueue& fq, CradlePtr cr) : chassisQueue(cq), finishingQueue(fq), cradle(cr) {} void run() { try { while(!Thread::interrupted()) { // Blocks until chassis is available: cradle->insertCar(chassisQueue->get()); // Notify robots car is ready for work cradle->startWork(); // Wait until work completes cradle->waitUntilWorkFinished(); // Put car into queue for further work

647

16862 16863 16864 16865 16866 16867 16868 16869 16870 16871 16872 16873 16874 16875 16876 16877 16878 16879 16880 16881 16882 16883 16884 16885 16886

finishingQueue->put(cradle->getCar()); } } catch(Interrupted_Exception&) { /* Exit */ } cout << "Director off" << endl; } };

class EngineRobot : public Runnable { CradlePtr cradle; public: EngineRobot(CradlePtr cr) : cradle(cr) {} void run() { try { while(!Thread::interrupted()) { // Blocks until job is offered/accepted: cradle->offerEngineBotServices(); cout << "Installing engine" << endl; (*cradle)->addEngine(); cradle->taskFinished(); } } catch(Interrupted_Exception&) { /* Exit */ } cout << "EngineRobot off" << endl; } };

648

16887 16888 16889 16890 16891 16892 16893 16894 16895 16896 16897 16898 16899 16900 16901 16902 16903 16904 16905 16906 16907 16908 16909 16910 16911

class DriveTrainRobot : public Runnable { CradlePtr cradle; public: DriveTrainRobot(CradlePtr cr) : cradle(cr) {} void run() { try { while(!Thread::interrupted()) { // Blocks until job is offered/accepted: cradle->offerDriveTrainBotServices(); cout << "Installing DriveTrain" << endl; (*cradle)->addDriveTrain(); cradle->taskFinished(); } } catch(Interrupted_Exception&) { /* Exit */ } cout << "DriveTrainRobot off" << endl; } };

class WheelRobot : public Runnable { CradlePtr cradle; public: WheelRobot(CradlePtr cr) : cradle(cr) {} void run() { try { while(!Thread::interrupted()) {

649

16912 16913 16914 16915 16916 16917 16918 16919 16920 16921 16922 16923 16924 16925 16926 16927 16928 16929 16930 16931 16932 16933 16934 16935 16936

// Blocks until job is offered/accepted: cradle->offerWheelBotServices(); cout << "Installing Wheels" << endl; (*cradle)->addWheels(); cradle->taskFinished(); } } catch(Interrupted_Exception&) { /* Exit */ } cout << "WheelRobot off" << endl; } };

class Reporter : public Runnable { CarQueue carQueue; public: Reporter(CarQueue& cq) : carQueue(cq) {} void run() { try { while(!Thread::interrupted()) { cout << carQueue->get() << endl; } } catch(Interrupted_Exception&) { /* Exit */ } cout << "Reporter off" << endl; } };

650

16937 16938 16939 16940 16941 16942 16943 16944 16945 16946 16947 16948 16949 16950 16951 16952 16953 16954 16955 16956 16957

int main() { cout << "Press <Enter> to quit" << endl; try { CarQueue chassisQueue(new TQueue<Car>), finishingQueue(new TQueue<Car>); CradlePtr cradle(new Cradle); ThreadedExecutor assemblyLine; assemblyLine.execute(new EngineRobot(cradle)); assemblyLine.execute(new DriveTrainRobot(cradle)); assemblyLine.execute(new WheelRobot(cradle)); assemblyLine.execute( new Director(chassisQueue, finishingQueue, cradle)); assemblyLine.execute(new Reporter(finishingQueue)); // Start everything running by producing chassis: assemblyLine.execute(new ChassisBuilder(chassisQueue)); cin.get(); assemblyLine.interrupt(); } catch(Synchronization_Exception& e) { cerr << e.what() << endl; } } ///:~

16958 16959

Listado 10.34. C11/CarBuilder.cpp

16960

10.7. Bloqueo letal

651

16961 16962 16963 16964 16965 16966 16967 16968 16969 16970 16971 16972 16973 16974 16975 16976 16977 16978 16979 16980 16981 16982 16983 16984 16985

//: C11:DiningPhilosophers.h // Classes for Dining Philosophers. #ifndef DININGPHILOSOPHERS_H #define DININGPHILOSOPHERS_H #include <string> #include <iostream> #include <cstdlib> #include "zthread/Condition.h" #include "zthread/Guard.h" #include "zthread/Mutex.h" #include "zthread/Thread.h" #include "Display.h"

class Chopstick { ZThread::Mutex lock; ZThread::Condition notTaken; bool taken; public: Chopstick() : notTaken(lock), taken(false) {} void take() { ZThread::Guard<ZThread::Mutex> g(lock); while(taken) notTaken.wait(); taken = true; }

652

16986 16987 16988 16989 16990 16991 16992 16993 16994 16995 16996 16997 16998 16999 17000 17001 17002 17003 17004 17005 17006 17007 17008 17009 17010

void drop() { ZThread::Guard<ZThread::Mutex> g(lock); taken = false; notTaken.signal(); } };

class Philosopher : public ZThread::Runnable { Chopstick& left; Chopstick& right; int id; int ponderFactor; ZThread::CountedPtr<Display> display; int randSleepTime() { if(ponderFactor == 0) return 0; return rand()/(RAND_MAX/ponderFactor) * 250; } void output(std::string s) { std::ostringstream os; os << *this << " " << s << std::endl; display->output(os); } public: Philosopher(Chopstick& l, Chopstick& r, ZThread::CountedPtr<Display>& disp, int ident,int ponder)

653

17011 17012 17013 17014 17015 17016 17017 17018 17019 17020 17021 17022 17023 17024 17025 17026 17027 17028 17029 17030 17031 17032 17033 17034 17035

: left(l), right(r), id(ident), ponderFactor(ponder), display(disp) {} virtual void run() { try { while(!ZThread::Thread::interrupted()) { output("thinking"); ZThread::Thread::sleep(randSleepTime()); // Hungry output("grabbing right"); right.take(); output("grabbing left"); left.take(); output("eating"); ZThread::Thread::sleep(randSleepTime()); right.drop(); left.drop(); } } catch(ZThread::Synchronization_Exception& e) { output(e.what()); } } friend std::ostream& operator<<(std::ostream& os, const Philosopher& p) { return os << "Philosopher " << p.id; }

654

17036 17037

}; #endif // DININGPHILOSOPHERS_H ///:~

17038 17039

Listado 10.35. C11/DiningPhilosophers.h

17040 17041 17042 17043 17044 17045 17046 17047 17048 17049 17050 17051 17052 17053 17054 17055 17056 17057 17058 17059

//: C11:DeadlockingDiningPhilosophers.cpp {RunByHand} // Dining Philosophers with Deadlock. //{L} ZThread #include <ctime> #include "DiningPhilosophers.h" #include "zthread/ThreadedExecutor.h" using namespace ZThread; using namespace std;

int main(int argc, char* argv[]) { srand(time(0)); // Seed the random number generator int ponder = argc > 1 ? atoi(argv[1]) : 5; cout << "Press <ENTER> to quit" << endl; enum { SZ = 5 }; try { CountedPtr<Display> d(new Display); ThreadedExecutor executor; Chopstick c[SZ]; for(int i = 0; i < SZ; i++) { executor.execute(

655

17060 17061 17062 17063 17064 17065 17066 17067 17068

new Philosopher(c[i], c[(i+1) % SZ], d, i,ponder)); } cin.get(); executor.interrupt(); executor.wait(); } catch(Synchronization_Exception& e) { cerr << e.what() << endl; } } ///:~

17069 17070

Listado 10.36. C11/DeadlockingDiningPhilosophers.cpp

17071 17072 17073 17074 17075 17076 17077 17078 17079 17080 17081 17082 17083

//: C11:FixedDiningPhilosophers.cpp {RunByHand} // Dining Philosophers without Deadlock. //{L} ZThread #include <ctime> #include "DiningPhilosophers.h" #include "zthread/ThreadedExecutor.h" using namespace ZThread; using namespace std;

int main(int argc, char* argv[]) { srand(time(0)); // Seed the random number generator int ponder = argc > 1 ? atoi(argv[1]) : 5; cout << "Press <ENTER> to quit" << endl;

656

17084 17085 17086 17087 17088 17089 17090 17091 17092 17093 17094 17095 17096 17097 17098 17099 17100 17101 17102 17103

enum { SZ = 5 }; try { CountedPtr<Display> d(new Display); ThreadedExecutor executor; Chopstick c[SZ]; for(int i = 0; i < SZ; i++) { if(i < (SZ-1)) executor.execute( new Philosopher(c[i], c[i + 1], d, i, ponder)); else executor.execute( new Philosopher(c[0], c[i], d, i, ponder)); } cin.get(); executor.interrupt(); executor.wait(); } catch(Synchronization_Exception& e) { cerr << e.what() << endl; } } ///:~

17104 17105

Listado 10.37. C11/FixedDiningPhilosophers.cpp

17106

10.8. Resumen

657
17107

10.9. Ejercicios

Вам также может понравиться