Pensar en C++ (Volumen 2)

657
1 Pensar en C++ (Volumen 2) 1 Traducción (INACABADA) del libro Thinking in C++, 2 Volumen 2 3 4 Tabla de contenidos 5 6 1: Introducción 7 Tabla de contenidos 8 1.1. Objetivos 9 1.2. Capítulos 10 1.3. Ejercicios 11 1.4. Código fuente 12 1.5. Compiladores 13 1.6. Estándares del lenguaje 14 1.7. Seminarios, CD-ROMs y consultoría 15 1.8. Errores 16 1.9. Sobre la portada 17 1.10. Agradecimientos 18 En el volumen 1 de este libro, aprendió los fundamentos de C y C++. En 19 este volumen, veremos características más avanzadas, con miras hacia 20 técnicas e ideas para realizar programas robustos en C++. 21 Asumimos que está familiarizado con el material presentado en el Volumen 22 1. 23 1.1. Objetivos 24 Nuestros objetivos en este libros son: 25 1. Presentar el material como un sencillo paso cada vez, de este modo el 26 lector puede asimilar cada concepto antes de seguir adelante. 27 2. Enseñar técnicas de "programación práctica" que puede usar en la base 28 del día a día. 29 3. Darle lo que pensamos que es importante para que entienda el lenguaje, 30 más bien que todo lo que sabemos. Creemos que hay una "jerarquía de 31

Transcript of Pensar en C++ (Volumen 2)

Page 1: Pensar en C++ (Volumen 2)

1

Pensar en C++ (Volumen 2) 1

Traducción (INACABADA) del libro Thinking in C++, 2

Volumen 2 3

4

Tabla de contenidos 5

6

1: Introducción 7

Tabla de contenidos 8

1.1. Objetivos 9

1.2. Capítulos 10

1.3. Ejercicios 11

1.4. Código fuente 12

1.5. Compiladores 13

1.6. Estándares del lenguaje 14

1.7. Seminarios, CD-ROMs y consultoría 15

1.8. Errores 16

1.9. Sobre la portada 17

1.10. Agradecimientos 18

En el volumen 1 de este libro, aprendió los fundamentos de C y C++. En 19

este volumen, veremos características más avanzadas, con miras hacia 20

técnicas e ideas para realizar programas robustos en C++. 21

Asumimos que está familiarizado con el material presentado en el Volumen 22

1. 23

1.1. Objetivos 24

Nuestros objetivos en este libros son: 25

1. Presentar el material como un sencillo paso cada vez, de este modo el 26

lector puede asimilar cada concepto antes de seguir adelante. 27

2. Enseñar técnicas de "programación práctica" que puede usar en la base 28

del día a día. 29

3. Darle lo que pensamos que es importante para que entienda el lenguaje, 30

más bien que todo lo que sabemos. Creemos que hay una "jerarquía de 31

Page 2: Pensar en C++ (Volumen 2)

2

importancia de la información", y hay algunos hechos que el 95% de los 32

programadores nunca necesitarán conocer, sino que sólo confundiría a la 33

gente y añade complejidad a su percepción del lenguaje. Tomando un 34

ejemplo de C, si memoriza la tabla de prioridad de operadores (nosotros 35

nunca lo hicimos) puede escribir código ingenioso. Pero si debe pensarlo, 36

confundirá al lector/mantenedor de ese código. De modo que olvide las 37

precedencias y use paréntesis cuando las cosas no estén claras. 38

4. Manténgase suficientemente centrado en cada sección de modo que el 39

tiempo de lectura -y el tiempo entre ejercicios- sea pequeño. Esto no sólo 40

hace mantener las mentes de los lectores más activas e involucradas 41

durante un seminario práctico sino que da al lector un mayor sentido de éxito. 42

Hemos procurado no usar ninguna versión de un vendedor particular de 43

C++. Hemos probado el código sobre todas las implementaciones que 44

pudimos (descriptas posteriormente en esta introducción), y cuando una 45

implementación no funciona en absoluto porque no cumple el Estándar C++, 46

lo hemos señalado en el ejemplo(verá las marcas en el código fuente) para 47

excluirlo del proceso de construcción. 48

6. Automatizar la compilación y las pruebas del código en el libro. Hemos 49

descubierto que el código que no está compilado y probado probablemente 50

no funcione correctamente, de este modo en este volumen hemos provisto a 51

los ejemplos con código de pruebas. Además, el código que puede 52

descargar desde http://www.MindView.net ha sido extraído directamente del 53

texto del libro usando programas que automáticamente crean makefiles para 54

compilar y ejecutar las pruebas. De esta forma sabemos que el código en el 55

libro es correcto. 56

1.2. Capítulos 57

Aquí está una breve descripción de los capítulos que contiene este libro: 58

Parte 1: Construcción de sistemas estables 59

1. Manejar excepciones. Manejar errores ha sido siempre un problema en 60

programación. Incluso si obedientemente devuelve información del error o 61

pone una bandera, la función que llama puede simplemente ignorarlo. 62

Manejar excepciones es una cualidad primordial en C++ que soluciona este 63

Page 3: Pensar en C++ (Volumen 2)

3

problema permitiéndole "lanzar" un objeto fuera de su función cuando ocurre 64

un error crítico. Tire diferentes tipos de objetos para diferentes errores, y la 65

función que llama "coge" estos objetos en rutinas separadas de gestión de 66

errores. Si lanza una excepción, no puede ser ignorada, de modo que puede 67

garantizar que algo ocurrirá en respuesta a su error. La decisión de usar 68

excepciones afecta al diseño del código positivamente, de modo 69

fundamental. 70

2. Programación defensiva. Muchos problemas de software pueden ser 71

prevenidos. Programar de forma defensiva es realizar cuidadosamente 72

código de tal modo que los bugs son encontrados y arreglados pronto antes 73

que puedan dañar en el campo. Usar aserciones es la única y más 74

importante forma para validar su código durante el desarrollo, dejando al 75

mismo tiempo seguimiento ejecutable de la documentación en su código que 76

muestra sus pensamientos mientras escribe el código en primer lugar. 77

Pruebe rigurosamente su código antes de darlo a otros. Un marco de trabajo 78

de pruebas unitario automatizado es una herramienta indispensable para el 79

éxito, en el desarrollo diario de software. 80

Parte 2: La biblioteca estándar de C++ 81

3. Cadenas en profundidad. La actividad más común de programación es 82

el procesamiento de texto. La clase string de C++ libera al programador de 83

los temas de gestión de memoria, mientras al mismo tiempo proporciona una 84

fuente de recursos para el procesamiento de texto. C++ también facilita el 85

uso de una gran variedad de caracteres y locales para las aplicaciones de 86

internacionalización. 87

4. Iostreams. Una de las bibliotecas original de C++-la que proporciona la 88

facilidad básica de I/O-es llamada iostreams. Iostreams está destinado a 89

reemplazar stdio.h de C con una biblioteca I/O que es más fácil de usar, más 90

flexible, y extensible- que puede adaptarla para trabajar con sus nuevas 91

clases. Este capítulo le enseña cómo hacer el mejor uso de la biblioteca 92

actual I/O, fichero I/O, y formateo en memoria. 93

5. Plantillas en profundidad. La distintiva cualidad del "C++ moderno" es el 94

extenso poder de las plantillas. Las plantillas hacen algo más que sólo crear 95

contenedores genéricos. Sostienen el desarrollo de bibliotecas robustas, 96

genéricas y de alto rendimiento.Hay mucho por saber sobre plantillas-97

Page 4: Pensar en C++ (Volumen 2)

4

constituyen, como fue, un sub-lenguaje dentro del lenguaje C++, y da al 98

programador un grado impresionante de control sobre el proceso de 99

compilación. No es una exageración decir que las plantillas han 100

revolucionado la programación de C++. 101

6. Algoritmos genéricos. Los algoritmos son el corazón de la informática, y 102

C++, por medio de la facilidad de las plantillas, facilita un impresionante 103

entorno de poder, eficiencia, y facilidad de uso de algoritmos genéricos. Los 104

algoritmos estándar son también personalizables a través de objetos de 105

función. Este capítulo examina cada algoritmo de la biblioteca. (Capítulos 6 y 106

7 abarcan esa parte de la biblioteca Estándar de C++ comúnmente conocida 107

como Biblioteca de Plantilla Estándar, o STL.) 108

7. Contenedores genéricos e Iteradores. C++ proporciona todas las 109

estructuras comunes de datos de modo de tipado fuerte. Nunca necesita 110

preocuparse sobre qué tiene tal contenedor. La homogenidad de sus objetos 111

está garantizada. Separar la #FIXME traversing de un contenedor del propio 112

contenedor, otra realización de plantillas, se hace posible por medio de los 113

iteradores. Este ingenioso arreglo permite una aplicación flexible de 114

algoritmos a contenedores usando el más sencillo de los diseños. 115

Parte 3: Temas especiales 116

8. Identificación de tipo en tiempo de ejecución. La identificación de tipo en 117

tiempo de ejecución (RTTI) encuentra el tipo exacto de un objeto cuando sólo 118

tiene un puntero o referencia al tipo base. Normalmente, tendrá que ignorar a 119

propósito el tipo exacto de un objeto y permitir al mecanismo de función 120

virtual implementar el comportamiento correcto para ese tipo. Pero 121

ocasionalmente (como las herramientas de escritura de software tales como 122

los depuradores) es útil para conocer el tipo exacto de un objeto-con su 123

información, puede realizar con frecuencia una operación en casos 124

especiales de forma más eficiente. Este capítulo explica para qué es RTTT y 125

como usarlo. 126

9. Herencia múltiple. Parece sencillo al principio: Una nueva clase hereda 127

de más de una clase existente. Sin embargo, puede terminar con copias 128

ambiguas y múltiples de objetos de la clase base. Ese problema está 129

resuelto con clases bases virtuales, pero la mayor cuestión continua: 130

¿Cuándo usarla? La herencia múltiple es sólo imprescindible cuando 131

Page 5: Pensar en C++ (Volumen 2)

5

necesite manipular un objeto por medio de más de un clase base común. 132

Este capítulo explica la sintaxis para la herencia múltiple y muestra enfoques 133

alternativos- en particular, como las plantillas solucionan un problema típico. 134

Usar herencia múltiple para reparar un interfaz de clase "dañada" demuestra 135

un uso valioso de esta cualidad. 136

10. Patrones de diseño. El más revolucionario avance en programación 137

desde los objetos es la introducción de los patrones de diseño. Un patrón de 138

diseño es una codificación independiente del lenguaje de una solución a un 139

problema de programación común, expresado de tal modo que puede 140

aplicarse a muchos contextos. Los patrones tales como Singleton, Factory 141

Method, y Visitor ahora tienen lugar en discusiones diarias alrededor del 142

teclado. Este capítulo muestra como implementar y usar algunos de los 143

patrones de diseño más usados en C++. 144

11. Programación concurrente. La gente ha llegado a esperar interfaces de 145

usuario sensibles que (parece que) procesan múltiples tareas 146

simultáneamente. Los sistemas operativos modernos permiten a los 147

procesos tener múltiples hilos que comparten el espacio de dirección del 148

proceso. La programación multihilo requiere una perspectiva diferente, sin 149

embargo, y viene con su propio conjunto de dificultades. Este capítulo utiliza 150

un biblioteca disponible gratuitamente (la biblioteca ZThread por Eric Crahen 151

de IBM) para mostrar como gestionar eficazmente aplicaciones multihilo en 152

C++. 153

1.3. Ejercicios 154

Hemos descubierto que los ejercicios sencillos son excepcionalmente 155

útiles durante un seminario para completar la comprensión de un estudiante. 156

Encontrará una colección al final de cada capítulo. 157

Estos son bastante sencillos, de modo que puedan ser acabados en una 158

suma de tiempo razonable en una situación de clase mientras el profesor 159

observa, asegurándose que todos los estudiantes están absorbiendo el 160

material. Algunos ejercicios son un poco más exigentes para mantener 161

entretenidos a los estudiantes avanzados. Están todos diseñados para ser 162

resueltos en un tiempo corto y sólo están allí para probar y refinar su 163

Page 6: Pensar en C++ (Volumen 2)

6

conocimiento más bien que presentar retos mayores (presumiblemente, 164

podrá encontrarlos o más probablemente ellos le encontrarán a usted). 165

1.3.1. Soluciones de los ejercicios 166

Las soluciones a los ejercicios pueden encontrarse en el documento 167

electrónico La Guía de Soluciones Comentada de C++, Volumen 2, 168

disponible por una cuota simbólica en http://www.MindView.net. 169

1.4. Código fuente 170

El código fuente para este libro está autorizado como software gratuito, 171

distribuido por medio del sitio web http://www.MindView.net. Los derechos de 172

autor le impiden volver a publicar el código impreso sin permiso. 173

En el directorio inicial donde desempaqueta el código encontrará el 174

siguiente aviso de derechos de autor: 175

Puede usar el código en sus proyectos y en clase siempre que el aviso de 176

los derechos de autor se conserve. 177

1.5. Compiladores 178

Su compilador podría no soportar todas las cualidades discutidas en este 179

libro, especialmente si no tiene la versión más nueva de su compilador. 180

Implementar un lenguaje como C++ es un tarea Hercúlea, y puede suponer 181

que las cualidades aparecen por partes en lugar de todas juntas. Pero si 182

intenta uno de los ejemplos del libro y obtiene muchos errores del 183

compilador, no es necesariamente un error en el código o en el compilador-184

puede que sencillamente no esté implementado todavía en su compilador 185

concreto. 186

Empleamos un número de compiladores para probar el código de este 187

libro, en un intento para asegurar que nuestro código cumple el Estándar 188

C++ y funcionará con todos los compiladores posibles. Desafortunadamente, 189

no todos los compiladores cumplen el Estándar C++, y de este modo 190

tenemos un modo de excluir ciertos ficheros de la construcción con esos 191

compiladores. Estas exclusiones se reflejadas automáticamente en los 192

Page 7: Pensar en C++ (Volumen 2)

7

makefiles creados para el paquete de código para este libro que puede 193

descargar desde www.MindView.net. Puede ver las etiquetas de exclusión 194

incrustadas en los comentarios al inicio de cada listado, de este modo sabrá 195

si exigir a un compilador concreto que funcione con ese código (en pocos 196

casos, el compilador realmente compilará el código pero el comportamiento 197

de ejecución es erróneo, y excluimos esos también). 198

Aquí están las etiquetas y los compiladores que se excluyen de la 199

construcción. 200

· {-dmc} El Compilador de Mars Digital de Walter Bright para Windows, 201

descargable gratuitamente en www.DigitalMars.com. Este compilador es muy 202

tolerante y así no verá casi ninguna de estas etiquetas en todo el libro. 203

· {-g++} La versión libre Gnu C++ 3.3.1, que viene preinstalada en la 204

mayoría de los paquetes Linux y Macintosh OSX. También es parte de 205

Cygwin para Windows (ver abajo). Está disponible para la mayoría de las 206

plataformas en gcc.gnu.org. 207

· {-msc} Microsoft Version 7 con Visual C++ .NET (viene sólo con Visual 208

Studio .NET; no descargable gratuitamente). 209

· {-bor} Borland C++ Version 6 (no la versión gratuita; éste está más 210

actualizado). 211

· {-edg} Edison Design Group (EDG) C++. Este es el compilador de 212

referencia para la conformidad con los estándares. Esta etiqueta existe a 213

causa de los temas de biblioteca, y porque estábamos usando un copia 214

gratis de la interfaz EDG con una implementación de la biblioteca gratuita de 215

Dinkumware, Ltd. No aparecieron errores de compilación a causa sólo del 216

compilador. 217

· {-mwcc} Metrowerks Code Warrior para Macintosh OS X. Fíjese que OS 218

X viene con Gnu C++ preinstalado, también. 219

Si descarga y desempaqueta el paquete de código de este libro de 220

www.MindView.net, encontrará los makefiles para construir el código para los 221

compiladores de más arriba. Usabamos GNU-make disponible gratuitamente, 222

que viene con Linux, Cygwin (una consola gratis de Unix que corre encima 223

de Windows; ver www.Cygwin.com), o puede instalar en su plataforma, ver 224

www.gnu.org/software/make. (Otros makes pueden o no funcionar con estos 225

Page 8: Pensar en C++ (Volumen 2)

8

ficheros, pero no están soportados.) Una vez que instale make, si teclea 226

make en la línea de comando obtendrá instrucciones de cómo construir el 227

código del libro para los compiladores de más arriba. 228

Fíjese que la colocación de estas etiquetas en los ficheros en este libro 229

indica el estado de la versión concreta del compilador en el momento que lo 230

probamos. Es posible y probable que el vendedor del compilador haya 231

mejorado el compilador desde la publicación de este libro. Es posible 232

también que mientras que realizamos el libro con tantos compiladores, 233

hayamos desconfigurado un compilador en concreto que en otro caso habría 234

compilado el código correctamente. Por consiguiente, debería probar el 235

código usted mismo con su compilador, y comprobar también el código 236

descargado de www.MindView.net para ver que es actual. 237

1.6. Estándares del lenguaje 238

A lo largo de este libro, cuando se hace referencia a la conformidad con el 239

ANSI/ISO C estándar, estaremos haciendo referencia al estándar de 1989, y 240

de forma general diremos solamente 'C.'. Sólo si es necesario distinguir entre 241

el Estándar de C de 1989 y anteriores, versiones pre-Estándares de C 242

haremos la distinción. No hacemos referencia a c99 en este libro. 243

El Comité de ANSI/ISO C++ hace mucho acabó el trabajo sobre el primer 244

Estándar C++, comunmente conocido como C++98. Usaremos el término 245

Standard C++ para referirnos a este lenguaje normalizado. Si nos referimos 246

sencillamente a C++, asuma que queremos decir "Standard C++". El Comité 247

de Estándares de C++ continua dirigiendo cuestiones importantes para la 248

comunidad de C++ que se convertirá en C++0x, un futuro Estándar de C++ 249

que no estará probablemente disponible durante muchos años. 250

1.7. Seminarios, CD-ROMs y consultoría 251

La compañía de Bruce Eckel, MindView, Inc., proporciona seminarios 252

públicos prácticos de formación basados en el material de este libro, y 253

también para temas avanzados. El material seleccionado de cada capítulo 254

representa una lección, que es sucedida por un periodo de ejercicios guiado 255

de tal modo que cada estudiante recibe atención personalizada. También 256

Page 9: Pensar en C++ (Volumen 2)

9

facilitamos formación en el lugar, consultoría, tutoría y comprobación de 257

diseño y código. La información y los formularios de inscripción para los 258

seminarios próximos y otra información de contacto se puede encontrar en 259

http://www.MindView.net. 260

1.8. Errores 261

No importa cuantos trucos usen los escritores para detectar errores, 262

algunos siempre pasan desapercibidos y éstos a menudo destacan para un 263

nuevo lector. Si descubre algo que cree ser un error, por favor use el sistema 264

de respuesta incorporado en la versión electrónica de este libro, que 265

encontrará en http://www.MindView.net. Su ayuda se valora. 266

Parte I. Construcción de Sistemas 267

estables 268

Los ingenieros de software gastan tanto tiempo en validar código como 269

el que tardan en crearlo. La calidad es, o debería ser, el objetivo de todo 270

programador, y se puede recorrer un largo camino hacia ese objetivo 271

eliminando problemas antes de que aparezcan. Además, los sistemas 272

software deberían ser lo suficientemente robustos como para comportarse 273

razonablemente en presencia de problemas imprevistos. 274

Las excepciones se introdujeron en C++ para facilitar una gestión de 275

errores sofisticada sin trocear el código con una innumerable cantidad de 276

lógica de error. El Capítulo 1 explica cómo el uso apropiado de las 277

excepciones puede hacer software FIXME:well-behaved, y también introduce 278

los principios de diseño que subyacen al código seguro. En el Capítulo 2 279

cubrimos las pruebas unitarias y las técnicas de depuración que prevén 280

maximizar la calidad del código antes de ser entregado. El uso de aserciones 281

para expresar y reforzar las invariantes de un programa es una señal 282

inequívoca de un ingeniero de software experimentado. También 283

introducimos un entorno simple para dar soporte a las pruebas unitarias. 284

Tabla de contenidos 285

2. Tratamiento de excepciones 286

Page 10: Pensar en C++ (Volumen 2)

10

2.1. Tratamiento tradicional de errores 287

2.2. Lanzar una excepción 288

2.3. Capturar una excepción 289

2.4. 290

2.5. Limpieza 291

2.6. Excepciones estándar 292

2.7. Especificaciones de excepciones 293

2.8. Seguridad de la excepción 294

2.9. Programar con excepciones 295

2.10. Sobrecarga 296

2.11. Resumen 297

2.12. Ejercicios 298

3. Programación defensiva 299

3.1. Aserciones 300

3.2. Un framework de pruebas unitarias sencillo 301

3.3. Técnicas de depuración 302

3.4. Resumen 303

3.5. Ejercicios 304

2: Tratamiento de excepciones 305

Tabla de contenidos 306

2.1. Tratamiento tradicional de errores 307

2.2. Lanzar una excepción 308

2.3. Capturar una excepción 309

2.4. 310

2.5. Limpieza 311

2.6. Excepciones estándar 312

2.7. Especificaciones de excepciones 313

2.8. Seguridad de la excepción 314

2.9. Programar con excepciones 315

2.10. Sobrecarga 316

2.11. Resumen 317

2.12. Ejercicios 318

Mejorar la recuperación de errores es una de las maneras más potentes de 319

incrementar la robustez de su código. 320

Una de las principales características de C++ es el tratamiento o manejo 321

de excepciones, el cual es una manera mejor de pensar acerca de los 322

errores y su tratamiento. Con el tratamiento de excepciones: 323

1. El código de manejo de errores no resulta tan tedioso de escribir y no se 324

entremezcla con su código «normal». Usted escribe el código que desea que 325

se ejecute, y más tarde, en una sección aparte, el código que se encarga de 326

los problemas. Si realiza varias llamadas a la misma función, el manejo de 327

errores de esa función se hará una sola vez, en un solo lugar. 328

Page 11: Pensar en C++ (Volumen 2)

11

2. Los errores no pueden ser ignorados. Si una función necesita enviar un 329

mensaje de error al invocador de esa función, ésta «lanza» un objeto que 330

representa a ese error fuera de la función. Si el invocador no «captura» el 331

error y lo trata, éste pasa al siguiente ámbito abarcador, y así hasta que el 332

error es capturado o el programa termina al no existir un manejador 333

adecuado para ese tipo de excepción. 334

2.1. Tratamiento tradicional de errores 335

En la mayoría de ejemplos de estos volúmenes, usamos la función assert() 336

para lo que fue concebida: para la depuración durante el desarrollo 337

insertando código que puede deshabilitarse con #define NDEBUG en un 338

producto comercial. Para la comprobación de errores en tiempo de ejecución 339

se utilizan las funciones de require.h (assure( ) y require( )) desarrolladas en 340

el capítulo 9 del Volumen 1 y repetidas aquí en el Apéndice B. Estas 341

funciones son un modo conveniente de decir, «Hay un problema aquí que 342

probablemente quiera manejar con un código algo más sofisticado, pero no 343

es necesario que se distraiga con eso en este ejemplo.» Las funciones de 344

require.h pueden parecer suficientes para programas pequeños, pero para 345

productos complicados deseará escribir un código de manejo de errores más 346

sofisticado. 347

El tratamiento de errores es bastante sencillo cuando uno sabe 348

exactamente qué hacer, puesto que se tiene toda la información necesaria 349

en ese contexto. Simplemente se trata el error en ese punto. 350

El problema ocurre cuando no se tiene suficiente información en ese 351

contexto, y se necesita pasar la información sobre el error a un contexto 352

diferente donde esa información sí que existe. En C, esta situación puede 353

tratarse usando tres enfoques: 354

2. Usar el poco conocido sistema de manejo de señales de la biblioteca 355

estándar de C, implementado en las funciones signal( ) (para determinar lo 356

que ocurre cuando se presenta un evento) y raise( ) (para generar un 357

evento). De nuevo, esta alternativa supone un alto acoplamiento debido a 358

que requiere que el usuario de cualquier biblioteca que genere señales 359

entienda e instale el mecanismo de manejo de señales adecuado. En 360

Page 12: Pensar en C++ (Volumen 2)

12

proyectos grandes los números de las señales de las diferentes bibliotecas 361

puede llegar a entrar en conflicto. 362

Cuando se consideran los esquemas de tratamiento de errores para C++, 363

hay un problema adicional que es crítico: Las técnicas de C de señales y 364

setjmp( )/longjmp( ) no llaman a los destructores, por lo que los objetos no se 365

limpian adecuadamente. (De hecho, si longjmp( ) salta más allá del final de 366

un ámbito donde los destructores deben ser llamados, el comportamiento del 367

programa es indefinido.) Esto hace casi imposible recuperarse efectivamente 368

de una condición excepcional, puesto que siempre se están dejando objetos 369

detrás sin limpiar y a los que ya no se tiene acceso. El siguiente ejemplo lo 370

demuestra con setjmp/longjmp: 371

//: C01:Nonlocal.cpp 372

// setjmp() & longjmp(). 373

#include <iostream> 374

#include <csetjmp> 375

using namespace std; 376

377

class Rainbow { 378

public: 379

Rainbow() { cout << "Rainbow()" << endl; } 380

~Rainbow() { cout << "~Rainbow()" << endl; } 381

}; 382

383

jmp_buf kansas; 384

385

void oz() { 386

Rainbow rb; 387

Page 13: Pensar en C++ (Volumen 2)

13

for(int i = 0; i < 3; i++) 388

cout << "there's no place like home" << endl; 389

longjmp(kansas, 47); 390

} 391

392

int main() { 393

if(setjmp(kansas) == 0) { 394

cout << "tornado, witch, munchkins..." << endl; 395

oz(); 396

} else { 397

cout << "Auntie Em! " 398

<< "I had the strangest dream..." 399

<< endl; 400

} 401

} ///:~ 402

Listado 2.1. C01/Nonlocal.cpp 403

404

El problema con C++ es que longjmp( ) no respeta los objetos; en 405

particular no llama a los destructores cuando salta fuera de un ámbito.[1] 406

Puesto que las llamadas a los destructores son esenciales, esta propuesta 407

no es válida para C++. De hecho, el estándar de C++ aclara que saltar a un 408

ámbito con goto (pasando por alto las llamadas a los constructores), o saltar 409

fuera de un ámbito con longjmp( ) donde un objeto en la pila posee un 410

destructor, constituye un comportamiento indefinido. 411

2.2. Lanzar una excepción 412

Page 14: Pensar en C++ (Volumen 2)

14

Si usted se encuentra en su código con una situación excepcional-es decir, 413

si no tiene suficiente información en el contexto actual para decidir lo que 414

hacer- puede enviar información acerca del error a un contexto mayor 415

creando un objeto que contenga esa información y «lanzándolo» fuera de su 416

contexto actual. Esto es lo que se llama lanzar una excepción. Este es el 417

aspecto que tiene: 418

//: C01:MyError.cpp {RunByHand} 419

420

class MyError { 421

const char* const data; 422

public: 423

MyError(const char* const msg = 0) : data(msg) {} 424

}; 425

426

void f() { 427

// Here we "throw" an exception object: 428

throw MyError("something bad happened"); 429

} 430

431

int main() { 432

// As you'll see shortly, we'll want a "try block" here: 433

f(); 434

} ///:~ 435

Listado 2.2. C01/MyError.cpp 436

437

Page 15: Pensar en C++ (Volumen 2)

15

MyError es una clase normal, que en este caso acepta un char* como 438

argumento del constructor. Usted puede usar cualquier tipo para lanzar 439

(incluyendo los tipos predefinidos), pero normalmente creará clases especial 440

para lanzar excepciones. 441

La palabra clave throw hace que suceda una serie de cosas relativamente 442

mágicas. En primer lugar se crea una copia del objeto que se está lanzando 443

y se «devuelve» desde la función que contiene la expresión throw, aun 444

cuando ese tipo de objeto no es lo que normalmente la función está diseñada 445

para devolver. Un modo simplificado de pensar acerca del tratamiento de 446

excepciones es como un mecanismo alternativo de retorno (aunque llegará a 447

tener problemas si lleva esta analogía demasiado lejos). También es posible 448

salir de ámbitos normales lanzando una excepción. En cualquier caso se 449

devuelve un valor y se sale de la función o ámbito. 450

Además es posible lanzar tantos tipos de objetos diferentes como se 451

quiera. Típicamente, para cada categoría de error se lanzará un tipo 452

diferente. La idea es almacenar la información en el objeto y en el nombre de 453

la clase con el fin de quien esté en el contexto invocador pueda averiguar lo 454

que hacer con esa excepción. 455

2.3. Capturar una excepción 456

2.3.1. El bloque try 457

try { 458

// Code that may generate exceptions 459

} 460

2.3.2. Manejadores de excepción 461

try { 462

// Code that may generate exceptions 463

} catch(type1 id1) { 464

Page 16: Pensar en C++ (Volumen 2)

16

// Handle exceptions of type1 465

} catch(type2 id2) { 466

// Handle exceptions of type2 467

} catch(type3 id3) 468

// Etc... 469

} catch(typeN idN) 470

// Handle exceptions of typeN 471

} 472

// Normal execution resumes here... 473

//: C01:Nonlocal2.cpp 474

// Illustrates exceptions. 475

#include <iostream> 476

using namespace std; 477

478

class Rainbow { 479

public: 480

Rainbow() { cout << "Rainbow()" << endl; } 481

~Rainbow() { cout << "~Rainbow()" << endl; } 482

}; 483

484

void oz() { 485

Rainbow rb; 486

for(int i = 0; i < 3; i++) 487

cout << "there's no place like home" << endl; 488

Page 17: Pensar en C++ (Volumen 2)

17

throw 47; 489

} 490

491

int main() { 492

try { 493

cout << "tornado, witch, munchkins..." << endl; 494

oz(); 495

} catch(int) { 496

cout << "Auntie Em! I had the strangest dream..." 497

<< endl; 498

} 499

} ///:~ 500

Listado 2.3. C01/Nonlocal2.cpp 501

502

2.3.3. 503

2.4. 504

//: C01:Autoexcp.cpp 505

// No matching conversions. 506

#include <iostream> 507

using namespace std; 508

509

class Except1 {}; 510

511

Page 18: Pensar en C++ (Volumen 2)

18

class Except2 { 512

public: 513

Except2(const Except1&) {} 514

}; 515

516

void f() { throw Except1(); } 517

518

int main() { 519

try { f(); 520

} catch(Except2&) { 521

cout << "inside catch(Except2)" << endl; 522

} catch(Except1&) { 523

cout << "inside catch(Except1)" << endl; 524

} 525

} ///:~ 526

Listado 2.4. C01/Autoexcp.cpp 527

528

//: C01:Basexcpt.cpp 529

// Exception hierarchies. 530

#include <iostream> 531

using namespace std; 532

533

class X { 534

public: 535

Page 19: Pensar en C++ (Volumen 2)

19

class Trouble {}; 536

class Small : public Trouble {}; 537

class Big : public Trouble {}; 538

void f() { throw Big(); } 539

}; 540

541

int main() { 542

X x; 543

try { 544

x.f(); 545

} catch(X::Trouble&) { 546

cout << "caught Trouble" << endl; 547

// Hidden by previous handler: 548

} catch(X::Small&) { 549

cout << "caught Small Trouble" << endl; 550

} catch(X::Big&) { 551

cout << "caught Big Trouble" << endl; 552

} 553

} ///:~ 554

Listado 2.5. C01/Basexcpt.cpp 555

556

2.4.1. Capturar cualquier excepción 557

catch(...) { 558

cout << "an exception was thrown" << endl; 559

Page 20: Pensar en C++ (Volumen 2)

20

} 560

2.4.2. Relanzar una excepción 561

catch(...) { 562

cout << "an exception was thrown" << endl; 563

// Deallocate your resource here, and then rethrow 564

throw; 565

} 566

2.4.3. Excepciones no capturadas 567

//: C01:Terminator.cpp 568

// Use of set_terminate(). Also shows uncaught exceptions. 569

#include <exception> 570

#include <iostream> 571

using namespace std; 572

573

void terminator() { 574

cout << "I'll be back!" << endl; 575

exit(0); 576

} 577

578

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

580

class Botch { 581

Page 21: Pensar en C++ (Volumen 2)

21

public: 582

class Fruit {}; 583

void f() { 584

cout << "Botch::f()" << endl; 585

throw Fruit(); 586

} 587

~Botch() { throw 'c'; } 588

}; 589

590

int main() { 591

try { 592

Botch b; 593

b.f(); 594

} catch(...) { 595

cout << "inside catch(...)" << endl; 596

} 597

} ///:~ 598

Listado 2.6. C01/Terminator.cpp 599

600

2.5. Limpieza 601

//: C01:Cleanup.cpp 602

// Exceptions clean up complete objects only. 603

#include <iostream> 604

Page 22: Pensar en C++ (Volumen 2)

22

using namespace std; 605

606

class Trace { 607

static int counter; 608

int objid; 609

public: 610

Trace() { 611

objid = counter++; 612

cout << "constructing Trace #" << objid << endl; 613

if(objid == 3) throw 3; 614

} 615

~Trace() { 616

cout << "destructing Trace #" << objid << endl; 617

} 618

}; 619

620

int Trace::counter = 0; 621

622

int main() { 623

try { 624

Trace n1; 625

// Throws exception: 626

Trace array[5]; 627

Trace n2; // Won't get here. 628

} catch(int i) { 629

Page 23: Pensar en C++ (Volumen 2)

23

cout << "caught " << i << endl; 630

} 631

} ///:~ 632

Listado 2.7. C01/Cleanup.cpp 633

634

constructing Trace #0 635 constructing Trace #1 636 constructing Trace #2 637 constructing Trace #3 638 destructing Trace #2 639 destructing Trace #1 640 destructing Trace #0 641

caught 3 642

2.5.1. Gestión de recursos 643

//: C01:Rawp.cpp 644

// Naked pointers. 645

#include <iostream> 646

#include <cstddef> 647

using namespace std; 648

649

class Cat { 650

public: 651

Cat() { cout << "Cat()" << endl; } 652

~Cat() { cout << "~Cat()" << endl; } 653

}; 654

655

class Dog { 656

public: 657

Page 24: Pensar en C++ (Volumen 2)

24

void* operator new(size_t sz) { 658

cout << "allocating a Dog" << endl; 659

throw 47; 660

} 661

void operator delete(void* p) { 662

cout << "deallocating a Dog" << endl; 663

::operator delete(p); 664

} 665

}; 666

667

class UseResources { 668

Cat* bp; 669

Dog* op; 670

public: 671

UseResources(int count = 1) { 672

cout << "UseResources()" << endl; 673

bp = new Cat[count]; 674

op = new Dog; 675

} 676

~UseResources() { 677

cout << "~UseResources()" << endl; 678

delete [] bp; // Array delete 679

delete op; 680

} 681

}; 682

Page 25: Pensar en C++ (Volumen 2)

25

683

int main() { 684

try { 685

UseResources ur(3); 686

} catch(int) { 687

cout << "inside handler" << endl; 688

} 689

} ///:~ 690

Listado 2.8. C01/Rawp.cpp 691

692

UseResources() 693 Cat() 694 Cat() 695 Cat() 696

allocating a Dog 697 inside handler 698

2.5.2. 699

//: C01:Wrapped.cpp 700

// Safe, atomic pointers. 701

#include <iostream> 702

#include <cstddef> 703

using namespace std; 704

705

// Simplified. Yours may have other arguments. 706

template<class T, int sz = 1> class PWrap { 707

T* ptr; 708

Page 26: Pensar en C++ (Volumen 2)

26

public: 709

class RangeError {}; // Exception class 710

PWrap() { 711

ptr = new T[sz]; 712

cout << "PWrap constructor" << endl; 713

} 714

~PWrap() { 715

delete[] ptr; 716

cout << "PWrap destructor" << endl; 717

} 718

T& operator[](int i) throw(RangeError) { 719

if(i >= 0 && i < sz) return ptr[i]; 720

throw RangeError(); 721

} 722

}; 723

724

class Cat { 725

public: 726

Cat() { cout << "Cat()" << endl; } 727

~Cat() { cout << "~Cat()" << endl; } 728

void g() {} 729

}; 730

731

class Dog { 732

public: 733

Page 27: Pensar en C++ (Volumen 2)

27

void* operator new[](size_t) { 734

cout << "Allocating a Dog" << endl; 735

throw 47; 736

} 737

void operator delete[](void* p) { 738

cout << "Deallocating a Dog" << endl; 739

::operator delete[](p); 740

} 741

}; 742

743

class UseResources { 744

PWrap<Cat, 3> cats; 745

PWrap<Dog> dog; 746

public: 747

UseResources() { cout << "UseResources()" << endl; } 748

~UseResources() { cout << "~UseResources()" << endl; } 749

void f() { cats[1].g(); } 750

}; 751

752

int main() { 753

try { 754

UseResources ur; 755

} catch(int) { 756

cout << "inside handler" << endl; 757

} catch(...) { 758

Page 28: Pensar en C++ (Volumen 2)

28

cout << "inside catch(...)" << endl; 759

} 760

} ///:~ 761

Listado 2.9. C01/Wrapped.cpp 762

763

Cat() 764 Cat() 765 Cat() 766

PWrap constructor 767 allocating a Dog 768

~Cat() 769 ~Cat() 770 ~Cat() 771

PWrap destructor 772 inside handler 773

2.5.3. auto_ptr 774

//: C01:Auto_ptr.cpp 775

// Illustrates the RAII nature of auto_ptr. 776

#include <memory> 777

#include <iostream> 778

#include <cstddef> 779

using namespace std; 780

781

class TraceHeap { 782

int i; 783

public: 784

static void* operator new(size_t siz) { 785

void* p = ::operator new(siz); 786

cout << "Allocating TraceHeap object on the heap " 787

Page 29: Pensar en C++ (Volumen 2)

29

<< "at address " << p << endl; 788

return p; 789

} 790

static void operator delete(void* p) { 791

cout << "Deleting TraceHeap object at address " 792

<< p << endl; 793

::operator delete(p); 794

} 795

TraceHeap(int i) : i(i) {} 796

int getVal() const { return i; } 797

}; 798

799

int main() { 800

auto_ptr<TraceHeap> pMyObject(new TraceHeap(5)); 801

cout << pMyObject->getVal() << endl; // Prints 5 802

} ///:~ 803

Listado 2.10. C01/Auto_ptr.cpp 804

805

2.5.4. Bloques try a nivel de función 806

//: C01:InitExcept.cpp {-bor} 807

// Handles exceptions from subobjects. 808

#include <iostream> 809

using namespace std; 810

Page 30: Pensar en C++ (Volumen 2)

30

811

class Base { 812

int i; 813

public: 814

class BaseExcept {}; 815

Base(int i) : i(i) { throw BaseExcept(); } 816

}; 817

818

class Derived : public Base { 819

public: 820

class DerivedExcept { 821

const char* msg; 822

public: 823

DerivedExcept(const char* msg) : msg(msg) {} 824

const char* what() const { return msg; } 825

}; 826

Derived(int j) try : Base(j) { 827

// Constructor body 828

cout << "This won't print" << endl; 829

} catch(BaseExcept&) { 830

throw DerivedExcept("Base subobject threw");; 831

} 832

}; 833

834

int main() { 835

Page 31: Pensar en C++ (Volumen 2)

31

try { 836

Derived d(3); 837

} catch(Derived::DerivedExcept& d) { 838

cout << d.what() << endl; // "Base subobject threw" 839

} 840

} ///:~ 841

Listado 2.11. C01/InitExcept.cpp 842

843

//: C01:FunctionTryBlock.cpp {-bor} 844

// Function-level try blocks. 845

// {RunByHand} (Don't run automatically by the makefile) 846

#include <iostream> 847

using namespace std; 848

849

int main() try { 850

throw "main"; 851

} catch(const char* msg) { 852

cout << msg << endl; 853

return 1; 854

} ///:~ 855

Listado 2.12. C01/FunctionTryBlock.cpp 856

857

2.6. Excepciones estándar 858

Page 32: Pensar en C++ (Volumen 2)

32

//: C01:StdExcept.cpp 859

// Derives an exception class from std::runtime_error. 860

#include <stdexcept> 861

#include <iostream> 862

using namespace std; 863

864

class MyError : public runtime_error { 865

public: 866

MyError(const string& msg = "") : runtime_error(msg) {} 867

}; 868

869

int main() { 870

try { 871

throw MyError("my message"); 872

} catch(MyError& x) { 873

cout << x.what() << endl; 874

} 875

} ///:~ 876

Listado 2.13. C01/StdExcept.cpp 877

878

2.7. Especificaciones de excepciones 879

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

void f(); 881

Page 33: Pensar en C++ (Volumen 2)

33

void f() throw(); 882

//: C01:Unexpected.cpp 883

// Exception specifications & unexpected(), 884

//{-msc} (Doesn't terminate properly) 885

#include <exception> 886

#include <iostream> 887

using namespace std; 888

889

class Up {}; 890

class Fit {}; 891

void g(); 892

893

void f(int i) throw(Up, Fit) { 894

switch(i) { 895

case 1: throw Up(); 896

case 2: throw Fit(); 897

} 898

g(); 899

} 900

901

// void g() {} // Version 1 902

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

904

void my_unexpected() { 905

Page 34: Pensar en C++ (Volumen 2)

34

cout << "unexpected exception thrown" << endl; 906

exit(0); 907

} 908

909

int main() { 910

set_unexpected(my_unexpected); // (Ignores return value) 911

for(int i = 1; i <=3; i++) 912

try { 913

f(i); 914

} catch(Up) { 915

cout << "Up caught" << endl; 916

} catch(Fit) { 917

cout << "Fit caught" << endl; 918

} 919

} ///:~ 920

Listado 2.14. C01/Unexpected.cpp 921

922

//: C01:BadException.cpp {-bor} 923

#include <exception> // For std::bad_exception 924

#include <iostream> 925

#include <cstdio> 926

using namespace std; 927

928

// Exception classes: 929

Page 35: Pensar en C++ (Volumen 2)

35

class A {}; 930

class B {}; 931

932

// terminate() handler 933

void my_thandler() { 934

cout << "terminate called" << endl; 935

exit(0); 936

} 937

938

// unexpected() handlers 939

void my_uhandler1() { throw A(); } 940

void my_uhandler2() { throw; } 941

942

// If we embed this throw statement in f or g, 943

// the compiler detects the violation and reports 944

// an error, so we put it in its own function. 945

void t() { throw B(); } 946

947

void f() throw(A) { t(); } 948

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

950

int main() { 951

set_terminate(my_thandler); 952

set_unexpected(my_uhandler1); 953

try { 954

Page 36: Pensar en C++ (Volumen 2)

36

f(); 955

} catch(A&) { 956

cout << "caught an A from f" << endl; 957

} 958

set_unexpected(my_uhandler2); 959

try { 960

g(); 961

} catch(bad_exception&) { 962

cout << "caught a bad_exception from g" << endl; 963

} 964

try { 965

f(); 966

} catch(...) { 967

cout << "This will never print" << endl; 968

} 969

} ///:~ 970

Listado 2.15. C01/BadException.cpp 971

972

2.7.1. ¿Mejores especificaciones de excepciones? 973

void f(); 974

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

2.7.2. Especificación de excepciones y herencia 976

Page 37: Pensar en C++ (Volumen 2)

37

//: C01:Covariance.cpp {-xo} 977

// Should cause compile error. {-mwcc}{-msc} 978

#include <iostream> 979

using namespace std; 980

981

class Base { 982

public: 983

class BaseException {}; 984

class DerivedException : public BaseException {}; 985

virtual void f() throw(DerivedException) { 986

throw DerivedException(); 987

} 988

virtual void g() throw(BaseException) { 989

throw BaseException(); 990

} 991

}; 992

993

class Derived : public Base { 994

public: 995

void f() throw(BaseException) { 996

throw BaseException(); 997

} 998

virtual void g() throw(DerivedException) { 999

throw DerivedException(); 1000

} 1001

Page 38: Pensar en C++ (Volumen 2)

38

}; ///:~ 1002

Listado 2.16. C01/Covariance.cpp 1003

1004

2.7.3. Cuándo no usar especificaciones de excepción 1005

T pop() throw(logic_error); 1006

2.8. Seguridad de la excepción 1007

void pop(); 1008

template<class T> T stack<T>::pop() { 1009

if(count == 0) 1010

throw logic_error("stack underflow"); 1011

else 1012

return data[--count]; 1013

} 1014

//: C01:SafeAssign.cpp 1015

// An Exception-safe operator=. 1016

#include <iostream> 1017

#include <new> // For std::bad_alloc 1018

#include <cstring> 1019

#include <cstddef> 1020

using namespace std; 1021

Page 39: Pensar en C++ (Volumen 2)

39

1022

// A class that has two pointer members using the heap 1023

class HasPointers { 1024

// A Handle class to hold the data 1025

struct MyData { 1026

const char* theString; 1027

const int* theInts; 1028

size_t numInts; 1029

MyData(const char* pString, const int* pInts, 1030

size_t nInts) 1031

: theString(pString), theInts(pInts), numInts(nInts) {} 1032

} *theData; // The handle 1033

// Clone and cleanup functions: 1034

static MyData* clone(const char* otherString, 1035

const int* otherInts, size_t nInts) { 1036

char* newChars = new char[strlen(otherString)+1]; 1037

int* newInts; 1038

try { 1039

newInts = new int[nInts]; 1040

} catch(bad_alloc&) { 1041

delete [] newChars; 1042

throw; 1043

} 1044

try { 1045

// This example uses built-in types, so it won't 1046

Page 40: Pensar en C++ (Volumen 2)

40

// throw, but for class types it could throw, so we 1047

// use a try block for illustration. (This is the 1048

// point of the example!) 1049

strcpy(newChars, otherString); 1050

for(size_t i = 0; i < nInts; ++i) 1051

newInts[i] = otherInts[i]; 1052

} catch(...) { 1053

delete [] newInts; 1054

delete [] newChars; 1055

throw; 1056

} 1057

return new MyData(newChars, newInts, nInts); 1058

} 1059

static MyData* clone(const MyData* otherData) { 1060

return clone(otherData->theString, otherData->theInts, 1061

otherData->numInts); 1062

} 1063

static void cleanup(const MyData* theData) { 1064

delete [] theData->theString; 1065

delete [] theData->theInts; 1066

delete theData; 1067

} 1068

public: 1069

HasPointers(const char* someString, const int* someInts, 1070

size_t numInts) { 1071

Page 41: Pensar en C++ (Volumen 2)

41

theData = clone(someString, someInts, numInts); 1072

} 1073

HasPointers(const HasPointers& source) { 1074

theData = clone(source.theData); 1075

} 1076

HasPointers& operator=(const HasPointers& rhs) { 1077

if(this != &rhs) { 1078

MyData* newData = clone(rhs.theData->theString, 1079

rhs.theData->theInts, rhs.theData->numInts); 1080

cleanup(theData); 1081

theData = newData; 1082

} 1083

return *this; 1084

} 1085

~HasPointers() { cleanup(theData); } 1086

friend ostream& 1087

operator<<(ostream& os, const HasPointers& obj) { 1088

os << obj.theData->theString << ": "; 1089

for(size_t i = 0; i < obj.theData->numInts; ++i) 1090

os << obj.theData->theInts[i] << ' '; 1091

return os; 1092

} 1093

}; 1094

1095

int main() { 1096

Page 42: Pensar en C++ (Volumen 2)

42

int someNums[] = { 1, 2, 3, 4 }; 1097

size_t someCount = sizeof someNums / sizeof someNums[0]; 1098

int someMoreNums[] = { 5, 6, 7 }; 1099

size_t someMoreCount = 1100

sizeof someMoreNums / sizeof someMoreNums[0]; 1101

HasPointers h1("Hello", someNums, someCount); 1102

HasPointers h2("Goodbye", someMoreNums, someMoreCount); 1103

cout << h1 << endl; // Hello: 1 2 3 4 1104

h1 = h2; 1105

cout << h1 << endl; // Goodbye: 5 6 7 1106

} ///:~ 1107

Listado 2.17. C01/SafeAssign.cpp 1108

1109

2.9. Programar con excepciones 1110

2.9.1. Cuándo evitar las excepciones 1111

2.9.2. Usos típicos de excepciones 1112

2.10. Sobrecarga 1113

//: C01:HasDestructor.cpp {O} 1114

class HasDestructor { 1115

public: 1116

~HasDestructor() {} 1117

}; 1118

Page 43: Pensar en C++ (Volumen 2)

43

1119

void g(); // For all we know, g may throw. 1120

1121

void f() { 1122

HasDestructor h; 1123

g(); 1124

} ///:~ 1125

Listado 2.18. C01/HasDestructor.cpp 1126

1127

2.11. Resumen 1128

2.12. Ejercicios 1129

3: Programación defensiva 1130

Tabla de contenidos 1131

3.1. Aserciones 1132

3.2. Un framework de pruebas unitarias sencillo 1133

3.3. Técnicas de depuración 1134

3.4. Resumen 1135

3.5. Ejercicios 1136

Escribir software puede ser un objetivo difícil para desarrolladores, pero 1137

unas pocas técnicas defensivas, aplicadas rutinariamente, pueden dirigir a un 1138

largo camino hacia la mejora de la calidad de su código. 1139

Aunque la complejidad de la producción típica de software garantiza que 1140

los probadores tendrán siempre trabajo, esperamos que anheles producir 1141

software sin defectos. Las técnicas de diseño orientada a objetos hacen 1142

mucho para limitar la dificultad de proyectos grandes, pero finalmente debe 1143

escribir bucles y funciones. Estos pequeños detalles de programación se 1144

convierten en los bloques de construcción de componentes mayores 1145

Page 44: Pensar en C++ (Volumen 2)

44

necesarios para sus diseños. Si sus blucles fallan por uno o sus funciones 1146

calculan los valores correctos sólo la mayoría de las veces, tiene problemas 1147

no importa como de elaborada sea su metodología general. En este capítulo, 1148

verá prácticas que ayudan a crear código robusto sin importar el tamaño de 1149

su proyecto. 1150

Su código es, entre otras cosas, una expresión de su intento de resolver 1151

un problema. Sería claro para el lector (incluyendo usted) exactamente lo 1152

que estaba pensando cuando diseño aquel bucle. En ciertos puntos de su 1153

programa, deberá crear atreverse con sentencias que considera alguna u 1154

otra condición. (Si no puede, no ha realmente solucionado todavía el 1155

problema.) Tales sentencias se llaman invariantes, puesto que deberían ser 1156

invariablemente verdad en el punto donde aparecen en el código; si no, o su 1157

diseño es defectuoso, o su código no refleja con precisión su diseño. 1158

Considere un programa que juega al juego de adivinanza mayor-menor. 1159

Una persona piensa un número entre el 1 y 100,y la otra persona adivina el 1160

número. (Permitirá al ordenador hacer la adivinanza.) La persona que piensa 1161

el número le dice al adivinador si su conjetura es mayor, menor o correcta. 1162

La mejor estrategia para el adivinador es la búsqueda binaria, que elige el 1163

punto medio del rango de los números donde el número buscado reside. La 1164

respuesta mayor-menor dice al adivinador que mitad de la lista ocupa el 1165

número, y el proceso se repite, reduciendo el tamaño del rango de búsqueda 1166

activo en cada iteración. ¿Entonces cómo escribe un bucle para realizar la 1167

repetición correctamente? No es suficiente simplemente decir 1168

bool adivinado = false; 1169

while(!adivinado) { ... } 1170

porque un usuario malintencionado podría responder engañosamente, y 1171

podría pasarse todo el día adivinando. ¿ Qué suposición, que sea sencilla, 1172

está haciendo cada vez que adivina? En otras palabras, ¿qué condición 1173

debería cumplir por diseño en cada iteración del bucle? 1174

La suposición sencilla es que el número secreto está dentro del actual 1175

rango activo de números sin adivinar: [1, 100]. Suponga que etiquetamos los 1176

puntos finales del rango con las variables bajo y alto. Cada vez que pasa por 1177

el bucle necesita asegurarse que si el número estaba en el rango [bajo, alto] 1178

Page 45: Pensar en C++ (Volumen 2)

45

al principio del bucle, calcule el nuevo rango de modo que todavía contenga 1179

el número al final de la iteración en curso. 1180

El objetivo es expresar el invariante del bucle en código de modo que una 1181

violación pueda ser detectada en tiempo de ejecución. Desafortunadamente, 1182

ya que el ordenador no conoce el número secreto, no puede expresar esta 1183

condición directamente en código, pero puede al menos hacer un comentario 1184

para este efecto: 1185

while(!adivinado) { // INVARIANTE: el número está en el rango [low, high] 1186

¿Qué ocurre cuando el usuario dice que una conjetura es demasiado alta o 1187

demasiado baja cuando no lo es? El engaño excluiría el número secreto del 1188

nuevo subrango. Porque una mentira siempre dirige a otra, finalmente su 1189

rango disminuirá a nada (puesto que se reduce a la mitad cada vez y el 1190

número secreto no está allí). Podemos expresar esta condición en el 1191

siguiente programa: 1192

//: C02:HiLo.cpp {RunByHand} 1193

// Plays the game of Hi-Lo to illustrate a loop invariant. 1194

#include <cstdlib> 1195

#include <iostream> 1196

#include <string> 1197

using namespace std; 1198

1199

int main() { 1200

cout << "Think of a number between 1 and 100" << endl 1201

<< "I will make a guess; " 1202

<< "tell me if I'm (H)igh or (L)ow" << endl; 1203

int low = 1, high = 100; 1204

bool guessed = false; 1205

while(!guessed) { 1206

Page 46: Pensar en C++ (Volumen 2)

46

// Invariant: the number is in the range [low, high] 1207

if(low > high) { // Invariant violation 1208

cout << "You cheated! I quit" << endl; 1209

return EXIT_FAILURE; 1210

} 1211

int guess = (low + high) / 2; 1212

cout << "My guess is " << guess << ". "; 1213

cout << "(H)igh, (L)ow, or (E)qual? "; 1214

string response; 1215

cin >> response; 1216

switch(toupper(response[0])) { 1217

case 'H': 1218

high = guess - 1; 1219

break; 1220

case 'L': 1221

low = guess + 1; 1222

break; 1223

case 'E': 1224

guessed = true; 1225

break; 1226

default: 1227

cout << "Invalid response" << endl; 1228

continue; 1229

} 1230

} 1231

Page 47: Pensar en C++ (Volumen 2)

47

cout << "I got it!" << endl; 1232

return EXIT_SUCCESS; 1233

} ///:~ 1234

Listado 3.1. C02/HiLo.cpp 1235

1236

La violación del invariante se detecta con la condición if(menor > mayor), 1237

porque si el usuario siempre dice la verdad, siempre encontraremos el 1238

número secreto antes que agotásemos los intentos. 1239

Usamos también una técnica del estándar C para informar sobre el estado 1240

de un programa al contexto llamante devolviendo diferentes valores desde 1241

main( ). Es portable para usar la sentencia return 0; para indicar éxito, pero 1242

no hay un valor portable para indicar fracaso. Por esta razón usamos la 1243

macro declarada para este propósito en <cstdlib>:EXIT_FAILURE. Por 1244

consistencia, cuando usamos EXIT_FAILURE también usamos 1245

EXIT_SUCCESS, a pesar de que éste es siempre definido como cero. 1246

3.1. Aserciones 1247

La condición en el programa mayor-menor depende de la entrada del 1248

usuario, por lo tanto no puede prevenir una violación del invariante. Sin 1249

embargo, los invariantes normalmente dependen solo del código que escribe, 1250

por eso comprobarán siempre si ha implementado su diseño correctamente. 1251

En este caso, es más claro hacer una aserción, que es un sentencia positiva 1252

que muestra sus decisiones de diseño. 1253

Suponga que está implementando un vector de enteros: un array 1254

expandible que crece a petición. La función que añade un elemento al vector 1255

debe primero verificar que hay un espacio vacío en el array subyacente que 1256

contiene los elementos; de lo contrario, necesita solicitar más espacio en la 1257

pila y copiar los elementos existentes al nuevo espacio antes de añadir el 1258

nuevo elemento (y borrar el viejo array). Tal función podría ser de la siguiente 1259

forma: 1260

Page 48: Pensar en C++ (Volumen 2)

48

void MyVector::push_back(int x) { 1261

if(nextSlot == capacity) 1262

grow(); 1263

assert(nextSlot < capacity); 1264

data[nextSlot++] = x; 1265

} 1266

En este ejemplo, la información es un array dinámico de ints con 1267

capacidad espacios y espacioSiguiente espacios en uso. El propósito de 1268

grow( ) es expandir el tamaño de la información para que el nuevo valor de 1269

capacidad sea estrictamente mayor que espacioSiguiente. El 1270

comportamiento correcto de MiVector depende de esta decisión de diseño, y 1271

nunca fallará si el resto del código secundario es correcto. Afirmamos la 1272

condición con la macro assert( ), que está definido en la cabecera <cassert>. 1273

La macro assert( ) de la biblioteca Estándar de C es breve, que resulta, 1274

portable. Si la condición en su parámetro no evalúa a cero, la ejecución 1275

continúa ininterrumpidamente; si no, un mensaje contiene el texto de la 1276

expresión culpable con su nombre de fichero fuente y el número de línea 1277

impreso en el canal de error estándar y el programa se suspende. ¿Es eso 1278

tan drástico? En la práctica, es mucho más drástico permitir que la ejecución 1279

continue cuando un supuesto de diseño básico ha fracasado. Su programa 1280

necesita ser arreglado. 1281

Si todo va bien, probará a conciencia su código con todas las aserciones 1282

intactas hasta el momento en que se haga uso del producto final. (Diremos 1283

más sobre pruebas más tarde.) Depende de la naturaleza de su aplicación, 1284

los ciclos de máquina necesarios para probar todas las aserciones en tiempo 1285

de ejecución podrían tener demasiado impacto en el rendimiento en 1286

producción. En ese caso, puede eliminar todas las aserciones del código 1287

automáticamente definiendo la macro NDEBUG y reconstruir la aplicación. 1288

Para ver como funciona esto, observe que una implementación típica de 1289

assert( ) se parece a esto: 1290

Page 49: Pensar en C++ (Volumen 2)

49

#ifdef NDEBUG 1291

#define assert(cond) ((void)0) 1292

#else 1293

void assertImpl(const char*, const char*, long); 1294

#define assert(cond) \ 1295

((cond) ? (void)0 : assertImpl(???)) 1296

#endif 1297

Cuando la macro NDEBUG está definida, el código se descompone a la 1298

expresión (void) 0, todo lo que queda en la cadena de compilación es una 1299

sentencia esencialmente vacía como un resultado de la semicolumna que 1300

añade a cada invocación de assert( ). Si NDEBUG no está definido, 1301

assert(cond) se expande a una sentencia condicional que, cuando cond es 1302

cero, llama a una función dependiente del compilador (que llamamos 1303

assertImpl( )) con argumento string representando el texto de cond, junto con 1304

el nombre de fichero y el número de línea donde aparece la aserción. 1305

(Usamos como un marcador de posición en el ejemplo, pero la cadena 1306

mencionada es de hecho computada allí, junto con el nombre del fichero y el 1307

número de línea donde la macro aparece en ese fichero. Como estos valores 1308

se obtienen es irrelevante para nuestra discusión.) Si quiere activar y 1309

desactivar aserciones en diferentes puntos de su programa, no debe solo 1310

#define o #undef NDEBUG, sino que debe también reincluir <cassert>. Las 1311

macros son evaluadas cuando el preprocesador los encuentra y así usa 1312

cualquier estado NDEBUG se aplica en el punto de inclusión. El camino más 1313

común define NDEBUG una vez para todo el programa es como una opción 1314

del compilador, o mediante la configuración del proyecto en su entorno visual 1315

o mediante la línea de comandos, como en: 1316

mycc NDEBUG myfile.cpp 1317

La mayoría de los compiladores usan la bandera para definir los nombres 1318

de las macros. (Substituya el nombre del ejecutable de su compiladores por 1319

mycc arriba.) La ventaja de este enfoque es que puede dejar sus aserciones 1320

Page 50: Pensar en C++ (Volumen 2)

50

en el código fuente como un inapreciable parte de documentación, y no hay 1321

aún castigo en tiempo de ejecución. Porque el código en una aserción 1322

desaparece cuando NDEBUG está definido, es importante que no haga 1323

trabajo en una aserción. Sólo las condiciones de prueba que no cambien el 1324

estado de su programa. 1325

Si usar NDEBUG para liberar código es una buena idea queda un tema de 1326

debate. Tony Hoare, una de los más influyentes expertos en informática de 1327

todos los tiempos,[15] ha sugerido que desactivando las comprobaciones en 1328

tiempo de ejecución como las aserciones es similar a un entusiasta de 1329

navegación que lleva un chaleco salvavidas mientras entrena en tierra y 1330

luego se deshace de él cuando va al mar.[16] Si una aserción falla en 1331

producción, tiene un problema mucho peor que la degradación en 1332

rendimiento, así que elija sabiamente. 1333

No todas las condiciones deberían ser cumplidas por aserciones. Los 1334

errores de usuario y los fallos de los recursos en tiempos de ejecución 1335

deberían ser señalados lanzando excepciones, como explicamos en detalle 1336

en el Capítulo 1. Es tentador usar aserciones para la mayoría de las 1337

condiciones de error mientras esbozamos código, con el propósito de 1338

remplazar muchos de ellos después con un manejador de excepciones 1339

robusto. Como cualquier otra tentación, úsese con moderación, pues podría 1340

olvidar hacer todos los cambios necesarios más tarde. Recuerde: las 1341

aserciones tienen la intención de verificar decisiones de diseño que fallarán 1342

sólo por lógica defectuosa del programador. Lo ideal es solucionar todas las 1343

violaciones de aserciones durante el desarrollo. No use aserciones para 1344

condiciones que no están totalmente en su control (por ejemplo, condiciones 1345

que dependen de la entrada del usuario). En particular, no querría usar 1346

aserciones para validar argumentos de función; lance un logic_error en su 1347

lugar. 1348

El uso de aserciones como una herramienta para asegurar la corrección de 1349

un programa fue formalizada por Bertran Meyer en su Diseño mediante 1350

metodología de contrato.[17] Cada función tiene un contrato implícito con los 1351

clientes que, dadas ciertas precondiciones, garantiza ciertas 1352

postcondiciones. En otras palabras, las precondiciones son los 1353

requerimientos para usar la función, como los argumentos que se facilitan 1354

Page 51: Pensar en C++ (Volumen 2)

51

dentro de ciertos rangos, y las postcondiciones son los resultados enviados 1355

por la función o por retorno por valor o por efecto colateral. 1356

Cuando los programas clientes fallan al darle un entrada válida, debe 1357

comentarles que han roto el contrato. Este no es el mejor momento para 1358

suspender el programa (aunque está justificado hacerlo desde que el 1359

contrato fue violado), pero una excepción es desde luego apropiada. Esto es 1360

porque la librería Estándar de C++ lanza excepciones derivadas de 1361

logic_error, como out_of_range.[18] Si hay funciones que sólo usted llama, 1362

no obstante, como funciones privadas en una clase de su propio diseño, la 1363

macro assert( ) es apropiada, puesto que tiene total control sobre la situación 1364

y desde luego quiere depurar su código antes de enviarlo. 1365

Una postcondición fallada indica un error de programa, y es apropiado usar 1366

aserciones para cualquier invariante en cualquier momento, incluyendo la 1367

postcondición de prueba al final de una función. Esto se aplica en particular a 1368

las funciones de una clase que mantienen el estado de un objeto. En el 1369

ejemplo MyVector previo, por ejemplo, un invariante razonable para todas las 1370

funciones sería: 1371

assert(0 <= siguienteEspacio && siguienteEspacio <= capacidad); 1372

o, si siguienteEspacio es un integer sin signo, sencillamente 1373

assert(siguienteEspacio <= capacidad); 1374

Tal tipo de invariante se llama invariante de clase y puede ser 1375

razonablemente forzada por una aserción. Las subclases juegan un papel de 1376

subcontratista para sus clases base porque deben mantener el contrato 1377

original entre la clase base y sus clientes. Por esta razón, las precondiciones 1378

en clases derivadas no deben imponer requerimientos adicionales más allá 1379

de aquellos del contrato base, y las postcondiciones deben cumplir al menos 1380

como mucho.[19] 1381

Validar resultados devueltos por el cliente, sin embargo, no es más o 1382

menos que probar, de manera que usar aserciones de postcondición en este 1383

caso sería duplicar trabajo. Sí, es buena documentación, pero más de un 1384

desarrollador has sido engañado usando incorrectamente las aserciones de 1385

post-condición como un substituto para pruebas de unidad. 1386

Page 52: Pensar en C++ (Volumen 2)

52

3.2. Un framework de pruebas unitarias sencillo 1387

Escribir software es todo sobre encontrar requerimientos.[20] Crear estos 1388

requerimientos es difícil, y pueden cambiar de un día a otro; podría descubrir 1389

en una reunión de proyecto semanal que lo que ha empleado la semana 1390

haciendo no es exactamente lo que los usuarios realmente quieren. 1391

Las personas no pueden articular requerimientos de software sin 1392

muestrear un sistema de trabajo en evolución. Es mucho mejor especificar 1393

un poco, diseñar un poco, codificar un poco y probar un poco. Entonces, 1394

después de evaluar el resultado, hacerlo todo de nuevo. La habilidad para 1395

desarrollar con una moda iterativa es uno de los mejores avances del 1396

enfoque orientado a objetos, pero requiere programadores ágiles que pueden 1397

hacer código fuerte. El cambio es duro. 1398

Otro ímpetu para el cambio viene de usted, el programador. El artífice que 1399

hay en usted quiere continuamente mejorar el diseño de su código. ¿Qué 1400

programador de mantenimiento no ha maldecido el envejecimiento, el 1401

producto de la compañía insignia como un mosaico de espaguetis 1402

inmodificable, enrevesado? La reluctancia de los supervisores en permitir 1403

que uno interfiera con un sistema que funciona le roba al código la flexibilidad 1404

que necesita para que perdure. Si no está roto, no arreglarlo finalmente le da 1405

el camino para, no podemos arreglarlo reescribámoslo. El cambio es 1406

necesario. 1407

Afortunadamente, nuestra industria está creciendo acostumbrada a la 1408

disciplina de refactoring, el arte de reestructura internamente código para 1409

mejorar su diseño, sin cambiar su comportamiento.[21] Tales mejoras 1410

incluyen extraer una nueva función de otra, o de forma inversa, combinar 1411

funciones, reemplazar una función con un objeto; parametrizar una función o 1412

clase; y reemplazar condicionales con polimorfismo. Refactorizar ayuda al 1413

código evolucionar. 1414

Si la fuerza para el cambio viene de los usuarios o programadores, los 1415

cambios hoy pueden destrozar lo trabajado ayer. Necesitamos un modo para 1416

construir código que resista el cambio y mejoras a lo largo del tiempo. 1417

La Programación Extrema (XP)[22] es sólo uno de las muchas prácticas 1418

que motivan la agilidad. En esta sección exploramos lo que pensamos es la 1419

Page 53: Pensar en C++ (Volumen 2)

53

clave para hacer un desarrollo flexible, incremental que tenga éxito: un 1420

framework de pruebas unitarias automatizada fácil de usar. (Note que los 1421

probadores, profesionales de software que prueban el código de otros para 1422

ganarse la vida, son todavía indispensables. Aquí, estamos simplemente 1423

describiendo un modo para ayudar a los desarrolladores a escribir mejor 1424

código.) 1425

Los desarrolladores escriben pruebas unitarias para conseguir confianza 1426

para decir las dos cosas más importantes que cualquier desarrollador puede 1427

decir: 1428

1. Entiendo los requerimientos. 1429

Mi código cumple esos requerimientos (hasta donde yo sé) 1430

No hay mejor modo para asegurar que sabe lo que el código que está por 1431

escribir debería hacer mejor que escribir primero pruebas unitarias. Este 1432

ejercicio sencillo ayuda a centrar la mente en las tareas siguientes y 1433

probablemente guiará a código que funcionalmente más rápido mejor que 1434

sólo saltar a codificar. O, expresarlo en términos XP: 1435

Probar + programar es más rápido que sólo programar. 1436

Escribir primero pruebas sólo le protegen contra condiciones límite que 1437

podrían destrozar su código, por lo tanto su código es más robusto. 1438

Cuando su código pasa todas sus pruebas, sabe que si el sistema no está 1439

funcionando, su código no es probablemente el problema. La frase todas mis 1440

pruebas funcionan es un fuerte razonamiento. 1441

3.2.1. Pruebas automatizadas 1442

Por lo tanto, ¿qué aspecto tiene una prueba unitaria? Demasiado a 1443

menudo los desarrolladores simplemente usan alguna entrada correcta para 1444

producir alguna salida esperada, que examinan visualmente. Existen dos 1445

peligros en este enfoque. Primero, los programas no siempre reciben sólo 1446

entradas correctas. Todos sabemos que deberíamos probar los límites de 1447

entrada de un programa, pero es duro pensar esto cuando está intentando 1448

simplemente hacer que las cosas funcionar. Si escribe primero la prueba 1449

para una función antes de comenzar a codificar, puede ponerse su traje de 1450

probador y preguntarse a si mismo, ¿qué haría posiblemente destrozar esto? 1451

Page 54: Pensar en C++ (Volumen 2)

54

Codificar una prueba que probará la función que escribirá no es erróneo, y 1452

luego ponerte el traje de desarrollador y hacerlo pasar. Escribirá mejor 1453

código que si no había escrito la prueba primero. 1454

El segundo peligro es que esperar una salida visualmente es tedioso y 1455

propenso a error. La mayoría de cualquier tipo de cosas que un humano 1456

puede hacer un ordenador puede hacerlas, pero sin el error humano. Es 1457

mejor formular pruebas como colecciones de expresiones boolean y tener un 1458

programa de prueba que informa de cualquier fallo. 1459

Por ejemplo, suponga que necesita construir una clase Fecha que tiene las 1460

siguientes propiedades: 1461

Una fecha puede estar inicializada con una cadena (AAAAMMDD), 3 1462

enteros (A, M, D), o nada (dando la fecha de hoy). 1463

Un objecto fecha puede producir su año, mes y día o una cadena de la 1464

forma AAAAMMDD. 1465

Todas las comparaciones relacionales están disponibles, además de 1466

calcular la duración entre dos fechas (en años, meses, y días). 1467

Las fechas para ser comparadas necesitan poder extenderse un número 1468

arbitrario de siglos(por ejemplo, 16002200). 1469

Su clase puede almacenar tres enteros que representan el año, mes y día. 1470

(Sólo asegúrese que el año es al menos de 16 bits de tamaño para satisfacer 1471

el último punto.) La interfaz de su clase Fecha se podría parecer a esto: 1472

//: C02:Date1.h 1473

// A first pass at Date.h. 1474

#ifndef DATE1_H 1475

#define DATE1_H 1476

#include <string> 1477

1478

class Date { 1479

public: 1480

Page 55: Pensar en C++ (Volumen 2)

55

// A struct to hold elapsed time: 1481

struct Duration { 1482

int years; 1483

int months; 1484

int days; 1485

Duration(int y, int m, int d) 1486

: years(y), months(m), days(d) {} 1487

}; 1488

Date(); 1489

Date(int year, int month, int day); 1490

Date(const std::string&); 1491

int getYear() const; 1492

int getMonth() const; 1493

int getDay() const; 1494

std::string toString() const; 1495

friend bool operator<(const Date&, const Date&); 1496

friend bool operator>(const Date&, const Date&); 1497

friend bool operator<=(const Date&, const Date&); 1498

friend bool operator>=(const Date&, const Date&); 1499

friend bool operator==(const Date&, const Date&); 1500

friend bool operator!=(const Date&, const Date&); 1501

friend Duration duration(const Date&, const Date&); 1502

}; 1503

#endif // DATE1_H ///:~ 1504

Page 56: Pensar en C++ (Volumen 2)

56

Listado 3.2. C02/Date1.h 1505

1506

Antes de que implemente esta clase, puede solidificar sus conocimientos 1507

de los requerimientos escribiendo el principio de un programa de prueba. 1508

Podría idear algo como lo siguiente: 1509

//: C02:SimpleDateTest.cpp 1510

//{L} Date 1511

#include <iostream> 1512

#include "Date.h" // From Appendix B 1513

using namespace std; 1514

1515

// Test machinery 1516

int nPass = 0, nFail = 0; 1517

void test(bool t) { if(t) nPass++; else nFail++; } 1518

1519

int main() { 1520

Date mybday(1951, 10, 1); 1521

test(mybday.getYear() == 1951); 1522

test(mybday.getMonth() == 10); 1523

test(mybday.getDay() == 1); 1524

cout << "Passed: " << nPass << ", Failed: " 1525

<< nFail << endl; 1526

} 1527

/* Expected output: 1528

Passed: 3, Failed: 0 1529

Page 57: Pensar en C++ (Volumen 2)

57

*/ ///:~ 1530

Listado 3.3. C02/SimpleDateTest.cpp 1531

1532

En este caso trivial, la función test( ) mantiene las variables globales 1533

nAprobar y nSuspender. La única revisión visual que hace es leer el 1534

resultado final. Si una prueba falla, un test( ) más sofisticado muestra un 1535

mensaje apropiado. El framework descrito más tarde en este capítulo tiene 1536

un función de prueba, entre otras cosas. 1537

Puede ahora implementar la clase Fecha para hacer pasar estas pruebas, 1538

y luego puede proceder iterativamente hasta que se satisfagan todos los 1539

requerimientos. Escribiendo primero pruebas, es más probable que piense 1540

en casos límite que podrían destrozar su próxima implementación, y es más 1541

probable que escriba el código correctamente la primera vez. Como ejercicio 1542

podría realizar la siguiente versión de una prueba para la clase Fecha: 1543

//: C02:SimpleDateTest2.cpp 1544

//{L} Date 1545

#include <iostream> 1546

#include "Date.h" 1547

using namespace std; 1548

1549

// Test machinery 1550

int nPass = 0, nFail = 0; 1551

void test(bool t) { if(t) ++nPass; else ++nFail; } 1552

1553

int main() { 1554

Date mybday(1951, 10, 1); 1555

Date today; 1556

Page 58: Pensar en C++ (Volumen 2)

58

Date myevebday("19510930"); 1557

1558

// Test the operators 1559

test(mybday < today); 1560

test(mybday <= today); 1561

test(mybday != today); 1562

test(mybday == mybday); 1563

test(mybday >= mybday); 1564

test(mybday <= mybday); 1565

test(myevebday < mybday); 1566

test(mybday > myevebday); 1567

test(mybday >= myevebday); 1568

test(mybday != myevebday); 1569

1570

// Test the functions 1571

test(mybday.getYear() == 1951); 1572

test(mybday.getMonth() == 10); 1573

test(mybday.getDay() == 1); 1574

test(myevebday.getYear() == 1951); 1575

test(myevebday.getMonth() == 9); 1576

test(myevebday.getDay() == 30); 1577

test(mybday.toString() == "19511001"); 1578

test(myevebday.toString() == "19510930"); 1579

1580

// Test duration 1581

Page 59: Pensar en C++ (Volumen 2)

59

Date d2(2003, 7, 4); 1582

Date::Duration dur = duration(mybday, d2); 1583

test(dur.years == 51); 1584

test(dur.months == 9); 1585

test(dur.days == 3); 1586

1587

// Report results: 1588

cout << "Passed: " << nPass << ", Failed: " 1589

<< nFail << endl; 1590

} ///:~ 1591

Listado 3.4. C02/SimpleDateTest2.cpp 1592

1593

Esta prueba puede ser desarrollada por completo. Por ejemplo, no hemos 1594

probado que duraciones grandes son manejadas correctamente. Pararemos 1595

aquí, pero coja la idea. La implementación entera para la case Fecha está 1596

disponible en los ficheros Date.h y Date.cpp en el apéndice.[23] 1597

3.2.2. El Framework TestSuite 1598

Algunas herramientas de pruebas unitarias automatizadas de C++ están 1599

disponibles en la World Wide Web para descargar, como CppUnit.[24] 1600

Nuestra intención aquí no es sólo presentar un mecanismo de prueba que 1601

sea fácil de usar, sino también fácil de entender internamente e incluso 1602

modificar si es necesario. Por lo tanto, en el espíritu de Hacer Lo Más Simple 1603

Que Podría Posiblemente Funcionar,[25] hemos desarrollado el Framework 1604

TestSuite, un espacio de nombres llamado TestSuite que contiene dos 1605

clases principales: Test y Suite. 1606

La clase Test es una clase base abstracta de la cual deriva un objeto test. 1607

Tiene constancia del número de éxitos y fracasos y muestra el texto de 1608

cualquier condición de prueba que falla. Simplemente para sobreescribir la 1609

Page 60: Pensar en C++ (Volumen 2)

60

función run( ), que debería llamar en turnos a la macro test_() para cada 1610

condición de prueba boolean que defina. 1611

Para definir una prueba para la clase Fecha usando el framework, puede 1612

heredar de Test como se muetra en el siguiente programa: 1613

//: C02:DateTest.h 1614

#ifndef DATETEST_H 1615

#define DATETEST_H 1616

#include "Date.h" 1617

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

1619

class DateTest : public TestSuite::Test { 1620

Date mybday; 1621

Date today; 1622

Date myevebday; 1623

public: 1624

DateTest(): mybday(1951, 10, 1), myevebday("19510930") {} 1625

void run() { 1626

testOps(); 1627

testFunctions(); 1628

testDuration(); 1629

} 1630

void testOps() { 1631

test_(mybday < today); 1632

test_(mybday <= today); 1633

test_(mybday != today); 1634

Page 61: Pensar en C++ (Volumen 2)

61

test_(mybday == mybday); 1635

test_(mybday >= mybday); 1636

test_(mybday <= mybday); 1637

test_(myevebday < mybday); 1638

test_(mybday > myevebday); 1639

test_(mybday >= myevebday); 1640

test_(mybday != myevebday); 1641

} 1642

void testFunctions() { 1643

test_(mybday.getYear() == 1951); 1644

test_(mybday.getMonth() == 10); 1645

test_(mybday.getDay() == 1); 1646

test_(myevebday.getYear() == 1951); 1647

test_(myevebday.getMonth() == 9); 1648

test_(myevebday.getDay() == 30); 1649

test_(mybday.toString() == "19511001"); 1650

test_(myevebday.toString() == "19510930"); 1651

} 1652

void testDuration() { 1653

Date d2(2003, 7, 4); 1654

Date::Duration dur = duration(mybday, d2); 1655

test_(dur.years == 51); 1656

test_(dur.months == 9); 1657

test_(dur.days == 3); 1658

} 1659

Page 62: Pensar en C++ (Volumen 2)

62

}; 1660

#endif // DATETEST_H ///:~ 1661

Listado 3.5. C02/DateTest.h 1662

1663

Ejecutar la prueba es una sencilla cuestión de instaciación de un objeto 1664

DateTest y llamar a su función run( ): 1665

//: C02:DateTest.cpp 1666

// Automated testing (with a framework). 1667

//{L} Date ../TestSuite/Test 1668

#include <iostream> 1669

#include "DateTest.h" 1670

using namespace std; 1671

1672

int main() { 1673

DateTest test; 1674

test.run(); 1675

return test.report(); 1676

} 1677

/* Output: 1678

Test "DateTest": 1679

Passed: 21, Failed: 0 1680

*/ ///:~ 1681

Listado 3.6. C02/DateTest.cpp 1682

1683

Page 63: Pensar en C++ (Volumen 2)

63

La función Test::report( ) muestra la salida previa y devuelve el número de 1684

fallos, de este modo es conveniente usarlo como valor de retorno desde el 1685

main( ). 1686

La clase Test usa RTTI[26] para obtener el nombre de su clase(por 1687

ejemplo, DateTest) para el informe. Hay también una función setStream() si 1688

quiere enviar los resultados de la prueba a un fichero en lugar de la salida 1689

estándar (por defecto). Verá la implementación de la clase Test más tarde en 1690

este capítulo. 1691

La macro test_( ) puede extraer el texto de la condición booleana que falla, 1692

junto con el nombre del fichero y número de línea.[27] Para ver lo que ocurre 1693

cuando un fallo aparece, puede insertar un error intencionado en el código, 1694

por ejemplo invirtiendo la condición en la primera llamda a test_( ) en 1695

DateTest::testOps( ) en el código de ejemplo previo. La salida indica 1696

exactamente que la prueba tenía un error y dónde ocurrió: 1697

DateTest fallo: (mybday > hoy) , DateTest.h (línea 31) Test "DateTest": 1698

Passados: 20 Fallados: 1 1699

Además de test_( ), el framework incluye las funciones succed_( ) y fail_( ), 1700

para casos donde una prueba Boolean no funcionará. Estas funciones se 1701

aplican cuando la clase que está probando podría lanzar excepciones. 1702

Durante la prueba, crear un conjunto de entrada que causará que la 1703

excepción aparezca. Si no, es un error y puede llamar a fail_( ) 1704

explicitamente para mostrar un mensaje y actualizar el contador de fallos. Si 1705

lanza la excecpión como se esperaba, llame a succeed_( ) para actualizar el 1706

contador de éxitos. 1707

Para ilustrar, suponga que modificamos la especificación de los dos 1708

constructor no por defecto de Date para lanzar una excepción DateError (un 1709

tipo anidado dentro de Date y derivado de std::logic_error) si los parámetros 1710

de entrada no representa un fecha válida: Date(const string& s) 1711

throw(DateError); Date(int year, int month, int day) throw(DateError); 1712

La función DateTest::run( ) puede ahora llamar a la siguiente función para 1713

probar el manejo de excepciones: 1714

void testExceptions() { 1715

Page 64: Pensar en C++ (Volumen 2)

64

try { 1716

Date d(0,0,0); // Invalid 1717

fail_("Invalid date undetected in Date int ctor"); 1718

} catch(Date::DateError&) { 1719

succeed_(); 1720

} 1721

try { 1722

Date d(""); // Invalid 1723

fail_("Invalid date undetected in Date string ctor"); 1724

} catch(Date::DateError&) { 1725

succeed_(); 1726

} 1727

} 1728

En ambos casos, si una excepción no se lanza, es un error. Fíjese que 1729

debe pasar manualmente un mensaje a fail_( ), pues no se está evaluando 1730

una expresión booleana. 1731

3.2.3. Suites de test 1732

Los proyectos reales contienen normalmente muchas clases, por lo tanto 1733

necesita un modo para agrupar pruebas para que pueda simplemente pulsar 1734

un solo botón para probar el proyecto entero.[28] La clase Suite recoge 1735

pruebas en una unidad funcional. Añada objetos Test a Suite con la función 1736

addTest( ), o puede incluir una suite existente entera con addSuite( ). Para 1737

ilustrar, el siguiente ejemplo reúna los programas del Capítulo 3 que usa la 1738

clase Test en una sola suite. Fíjese que este fichero aparecerá en el 1739

subdirectorio del Capítulo 3: 1740

Page 65: Pensar en C++ (Volumen 2)

65

//: C03:StringSuite.cpp 1741

//{L} ../TestSuite/Test ../TestSuite/Suite 1742

//{L} TrimTest 1743

// Illustrates a test suite for code from Chapter 3 1744

#include <iostream> 1745

#include "../TestSuite/Suite.h" 1746

#include "StringStorage.h" 1747

#include "Sieve.h" 1748

#include "Find.h" 1749

#include "Rparse.h" 1750

#include "TrimTest.h" 1751

#include "CompStr.h" 1752

using namespace std; 1753

using namespace TestSuite; 1754

1755

int main() { 1756

Suite suite("String Tests"); 1757

suite.addTest(new StringStorageTest); 1758

suite.addTest(new SieveTest); 1759

suite.addTest(new FindTest); 1760

suite.addTest(new RparseTest); 1761

suite.addTest(new TrimTest); 1762

suite.addTest(new CompStrTest); 1763

suite.run(); 1764

long nFail = suite.report(); 1765

Page 66: Pensar en C++ (Volumen 2)

66

suite.free(); 1766

return nFail; 1767

} 1768

/* Output: 1769

s1 = 62345 1770

s2 = 12345 1771

Suite "String Tests" 1772

==================== 1773

Test "StringStorageTest": 1774

Passed: 2 Failed: 0 1775

Test "SieveTest": 1776

Passed: 50 Failed: 0 1777

Test "FindTest": 1778

Passed: 9 Failed: 0 1779

Test "RparseTest": 1780

Passed: 8 Failed: 0 1781

Test "TrimTest": 1782

Passed: 11 Failed: 0 1783

Test "CompStrTest": 1784

Passed: 8 Failed: 0 1785

*/ ///:~ 1786

Listado 3.7. C03/StringSuite.cpp 1787

1788

5 de los tests de más arriba están completamente contenidos en los 1789

ficheros de cabecera. TrimTest no lo está, porque contiene datos estáticos 1790

que deben estar definidos en un fichero de implementación. Las dos 1791

Page 67: Pensar en C++ (Volumen 2)

67

primeras líneas de salida son trazos de la prueba StringStorage. Debe dar a 1792

la suite un nombre como argumento del constructor. La función Suite::run( ) 1793

llama a Test::run( ) po cada una de las pruebas que tiene. Más de lo mismo 1794

pasa con Suite::report( ), excepto que puede enviar los informes de pruebas 1795

individuales a cadenas destinaciones diferentes mejor que el informe de la 1796

suite. Si la prueba pasa a addSuite( ) ya tiene un puntero de cadena 1797

asignado, que lo guarda. En otro caso, obtiene su cadena del objeto Suite. 1798

(Como con Test, hay un segundo argumento opcional para el constructor 1799

suite que no se presenta a std::cout.) El destructor para Suite no borra 1800

automáticamente los punteros contenidos en Test porque no necesitan 1801

residir en la pila; este es el trabajo de Suite::free( ). 1802

3.2.4. El código del framework de prueba 1803

El código del framework de pruebas es un subdirectorio llamado TestSuite 1804

en la distribución de código disponible en www.MindView.net. Para usarlo, 1805

incluya la ruta de búsqueda para el subdirectorio TestSuite en la ruta de 1806

búsqueda de la biblioteca. Aquí está la cabecera para Test.h: 1807

//: TestSuite:Test.h 1808

#ifndef TEST_H 1809

#define TEST_H 1810

#include <string> 1811

#include <iostream> 1812

#include <cassert> 1813

using std::string; 1814

using std::ostream; 1815

using std::cout; 1816

1817

// fail_() has an underscore to prevent collision with 1818

// ios::fail(). For consistency, test_() and succeed_() 1819

Page 68: Pensar en C++ (Volumen 2)

68

// also have underscores. 1820

1821

#define test_(cond) \ 1822

do_test(cond, #cond, __FILE__, __LINE__) 1823

#define fail_(str) \ 1824

do_fail(str, __FILE__, __LINE__) 1825

1826

namespace TestSuite { 1827

1828

class Test { 1829

ostream* osptr; 1830

long nPass; 1831

long nFail; 1832

// Disallowed: 1833

Test(const Test&); 1834

Test& operator=(const Test&); 1835

protected: 1836

void do_test(bool cond, const string& lbl, 1837

const char* fname, long lineno); 1838

void do_fail(const string& lbl, 1839

const char* fname, long lineno); 1840

public: 1841

Test(ostream* osptr = &cout) { 1842

this->osptr = osptr; 1843

nPass = nFail = 0; 1844

Page 69: Pensar en C++ (Volumen 2)

69

} 1845

virtual ~Test() {} 1846

virtual void run() = 0; 1847

long getNumPassed() const { return nPass; } 1848

long getNumFailed() const { return nFail; } 1849

const ostream* getStream() const { return osptr; } 1850

void setStream(ostream* osptr) { this->osptr = osptr; } 1851

void succeed_() { ++nPass; } 1852

long report() const; 1853

virtual void reset() { nPass = nFail = 0; } 1854

}; 1855

1856

} // namespace TestSuite 1857

#endif // TEST_H ///:~ 1858

Listado 3.8. 1859

1860

Hay tres funciones virtuales en la clase Test: 1861

Un destructor virtual 1862

La función reset( ) 1863

La función virtual pura run( ) 1864

Como se explicó en el Volumen 1, es un error eliminar un objeto derivado 1865

de la pila a través de un puntero base a menos que la clase base tenga un 1866

destructor virtual. Cualquier clase propuesta para ser una clase base 1867

(normalmente evidenciadas por la presencia de al menos una de las otras 1868

funciones virtuales) tendría un destructor virtual. La implementación por 1869

defecto de Test::reset( ) pone los contadores de éxitos y fallos a cero. Podría 1870

querer sobreescribir esta función para poner el estado de los datos en su 1871

Page 70: Pensar en C++ (Volumen 2)

70

objeto de test derivado; sólo asegúrese de llamar a Test::rest( ) 1872

explícitamente en su sobreescritura de modo que los contadores se 1873

reajusten. La función Test::run( ) es virtual pura ya que es necesario para 1874

sobreescribirla en su clase derivada. 1875

Las macros test_( ) y fail_( ) pueden incluir la información disponible del 1876

nombre del fichero y el número de línea del preprocesador. Originalmente 1877

omitimos el guión bajo en los nombres, pero la macro fail colisiona con 1878

ios::fail( ), provocando errores de compilación. 1879

Aquí está la implementación del resto de las funciones Test: 1880

//: TestSuite:Test.cpp {O} 1881

#include "Test.h" 1882

#include <iostream> 1883

#include <typeinfo> 1884

using namespace std; 1885

using namespace TestSuite; 1886

1887

void Test::do_test(bool cond, const std::string& lbl, 1888

const char* fname, long lineno) { 1889

if(!cond) 1890

do_fail(lbl, fname, lineno); 1891

else 1892

succeed_(); 1893

} 1894

1895

void Test::do_fail(const std::string& lbl, 1896

const char* fname, long lineno) { 1897

++nFail; 1898

Page 71: Pensar en C++ (Volumen 2)

71

if(osptr) { 1899

*osptr << typeid(*this).name() 1900

<< "failure: (" << lbl << ") , " << fname 1901

<< " (line " << lineno << ")" << endl; 1902

} 1903

} 1904

1905

long Test::report() const { 1906

if(osptr) { 1907

*osptr << "Test \"" << typeid(*this).name() 1908

<< "\":\n\tPassed: " << nPass 1909

<< "\tFailed: " << nFail 1910

<< endl; 1911

} 1912

return nFail; 1913

} ///:~ 1914

Listado 3.9. 1915

1916

La clase Test lleva la cuenta del número de éxitos y fracasos además de la 1917

cadena donde quiere que Test::report( ) muestre los resultados. Las macros 1918

test_( ) y fail_() extraen la información del nombre del fichero actual y el 1919

número de línea del preprocesador y pasa el nombre del fichero a do_test( ) 1920

y el número de línea a do_fail( ), que hacen el mismo trabajo de mostrar un 1921

mensaje y actualizar el contador apropiado. No podemos pensar una buena 1922

razón para permitir copiar y asignar objetos de prueba, por lo que hemos 1923

rechazado estas operaciones para hacer sus prototipos privados y omitir el 1924

cuerpo de sus respectivas funciones. 1925

Page 72: Pensar en C++ (Volumen 2)

72

Aquí está el fichero de cabecera para Suite: 1926

//: TestSuite:Suite.h 1927

#ifndef SUITE_H 1928

#define SUITE_H 1929

#include <vector> 1930

#include <stdexcept> 1931

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

using std::vector; 1933

using std::logic_error; 1934

1935

namespace TestSuite { 1936

1937

class TestSuiteError : public logic_error { 1938

public: 1939

TestSuiteError(const string& s = "") 1940

: logic_error(s) {} 1941

}; 1942

1943

class Suite { 1944

string name; 1945

ostream* osptr; 1946

vector<Test*> tests; 1947

void reset(); 1948

// Disallowed ops: 1949

Suite(const Suite&); 1950

Page 73: Pensar en C++ (Volumen 2)

73

Suite& operator=(const Suite&); 1951

public: 1952

Suite(const string& name, ostream* osptr = &cout) 1953

: name(name) { this->osptr = osptr; } 1954

string getName() const { return name; } 1955

long getNumPassed() const; 1956

long getNumFailed() const; 1957

const ostream* getStream() const { return osptr; } 1958

void setStream(ostream* osptr) { this->osptr = osptr; } 1959

void addTest(Test* t) throw(TestSuiteError); 1960

void addSuite(const Suite&); 1961

void run(); // Calls Test::run() repeatedly 1962

long report() const; 1963

void free(); // Deletes tests 1964

}; 1965

1966

} // namespace TestSuite 1967

#endif // SUITE_H ///:~ 1968

Listado 3.10. 1969

1970

La clase Suite tiene punteros a sus objetos Test en un vector. Fíjese en la 1971

especificación de la excepción en la función addTest( ). Cuando añada una 1972

prueba a una suite, Suite::addTest( ) verifique que el puntero que pasa no 1973

sea null; si es null, se lanza una excepción TestSuiteError. Puesto que esto 1974

hace imposible añadir un puntero null a una suite, addSuite( ) afirma esta 1975

condición en cada prueba, como hacen las otras funciones que atraviesan el 1976

Page 74: Pensar en C++ (Volumen 2)

74

vector de pruebas (vea la siguiente implementación). Copiar y asignar están 1977

desestimados como están en la clase Test. 1978

//: TestSuite:Suite.cpp {O} 1979

#include "Suite.h" 1980

#include <iostream> 1981

#include <cassert> 1982

#include <cstddef> 1983

using namespace std; 1984

using namespace TestSuite; 1985

1986

void Suite::addTest(Test* t) throw(TestSuiteError) { 1987

// Verify test is valid and has a stream: 1988

if(t == 0) 1989

throw TestSuiteError("Null test in Suite::addTest"); 1990

else if(osptr && !t->getStream()) 1991

t->setStream(osptr); 1992

tests.push_back(t); 1993

t->reset(); 1994

} 1995

1996

void Suite::addSuite(const Suite& s) { 1997

for(size_t i = 0; i < s.tests.size(); ++i) { 1998

assert(tests[i]); 1999

addTest(s.tests[i]); 2000

} 2001

Page 75: Pensar en C++ (Volumen 2)

75

} 2002

2003

void Suite::free() { 2004

for(size_t i = 0; i < tests.size(); ++i) { 2005

delete tests[i]; 2006

tests[i] = 0; 2007

} 2008

} 2009

2010

void Suite::run() { 2011

reset(); 2012

for(size_t i = 0; i < tests.size(); ++i) { 2013

assert(tests[i]); 2014

tests[i]->run(); 2015

} 2016

} 2017

2018

long Suite::report() const { 2019

if(osptr) { 2020

long totFail = 0; 2021

*osptr << "Suite \"" << name 2022

<< "\"\n======="; 2023

size_t i; 2024

for(i = 0; i < name.size(); ++i) 2025

*osptr << '='; 2026

Page 76: Pensar en C++ (Volumen 2)

76

*osptr << "=" << endl; 2027

for(i = 0; i < tests.size(); ++i) { 2028

assert(tests[i]); 2029

totFail += tests[i]->report(); 2030

} 2031

*osptr << "======="; 2032

for(i = 0; i < name.size(); ++i) 2033

*osptr << '='; 2034

*osptr << "=" << endl; 2035

return totFail; 2036

} 2037

else 2038

return getNumFailed(); 2039

} 2040

2041

long Suite::getNumPassed() const { 2042

long totPass = 0; 2043

for(size_t i = 0; i < tests.size(); ++i) { 2044

assert(tests[i]); 2045

totPass += tests[i]->getNumPassed(); 2046

} 2047

return totPass; 2048

} 2049

2050

long Suite::getNumFailed() const { 2051

Page 77: Pensar en C++ (Volumen 2)

77

long totFail = 0; 2052

for(size_t i = 0; i < tests.size(); ++i) { 2053

assert(tests[i]); 2054

totFail += tests[i]->getNumFailed(); 2055

} 2056

return totFail; 2057

} 2058

2059

void Suite::reset() { 2060

for(size_t i = 0; i < tests.size(); ++i) { 2061

assert(tests[i]); 2062

tests[i]->reset(); 2063

} 2064

} ///:~ 2065

Listado 3.11. 2066

2067

Usaremos el framework TestSuite donde sea pertinente a lo largo del resto 2068

de este libro. 2069

3.3. Técnicas de depuración 2070

La mejor costumbre para eliminar fallos es usar aserciones como se 2071

explica al principio de este capítulo; haciendo esto le ayudará a encontrar 2072

errores lógicos antes de que causen problemas reales. Esta sección contiene 2073

otros consejos y técnicas que podrían ayudar durante la depuración. 2074

3.3.1. Macros de seguimiento 2075

Page 78: Pensar en C++ (Volumen 2)

78

Algunas veces es útil imprimir el código de cada sentencia cuando es 2076

ejecutada, o cout o trazar un fichero. Aquí esta una macro de preprocesaor 2077

para llevar a cabo esto: 2078

#define TRACE(ARG) cout << #ARG << endl; ARG 2079

Ahora puede ir a través y alrededor de las sentencias que traceé con esta 2080

macro. Sin embargo, esto puede introducir problemas. Por ejemplo, si coge 2081

la sentencia: 2082

for(int i = 0; i < 100; i++) cout << i << endl; 2083

y ponga ambas líneas dentro de la macro TRACE( ), obtiene esto: 2084

TRACE(for(int i = 0; i < 100; i++)) TRACE( cout << i << endl;) 2085

que se expande a esto: 2086

cout << "for(int i = 0; i < 100; i++)" << endl; for(int i = 0; i < 100; i++) cout << 2087

"cout << i << endl;" << endl; cout << i << endl; 2088

que no es exactamente lo que quiere. Por lo tanto, debe usar esta técnica 2089

cuidadosamente. 2090

Lo siguiente es una variación en la macro TRACE( ): 2091

#define D(a) cout << #a "=[" << a << "]" << endl; 2092

Si quiere mostrar una expresión, simplemente póngala dentro de una 2093

llamada a D( ). La expresión se muestra, seguida de su valor ( asumiendo 2094

que hay un operador sobrecargado << para el tipo de resultado). Por 2095

ejemplo, puede decir D(a + b). Puede usar esta macro en cualquier momento 2096

que quiera comprobar un valor intermedio. 2097

Estas dos macros representan las dos cosas fundamentales que hace con 2098

un depurador: trazar la ejecución de código y mostrar valores. Un buen 2099

depurador es una herramienta de productividad excelente, pero a veces los 2100

depuradores no están disponibles, o no es conveniente usarlos. Estas 2101

técnicas siempre funcionan, sin tener en cuenta la situación. 2102

3.3.2. Fichero de rastro 2103

ADVERTENCIA: Esta sección y la siguiente contienen código que está 2104

oficialmente sin aprobación por el Estándar C++. En particular, redefinimos 2105

Page 79: Pensar en C++ (Volumen 2)

79

cout y new mediante macros, que puede provocar resultados sorprendentes 2106

si no tiene cuidado. Nuestros ejemplos funcionan en todos los compiladores 2107

que usamos, comoquiera, y proporcionan información útil. Este es el único 2108

lugar en este libro donde nos desviaremos de la inviolabilidad de la práctica 2109

de codificar cumpliendo el estándar. ¡Úsalo bajo tu propio riesgo! Dese 2110

cuenta que para este trabajo, usar delcaraciones debe ser realizado, para 2111

que cout no esté prefijado por su nombre de espacio, p.e. std::cout no 2112

funcionará. 2113

El siguiente código crea fácilmente un fichero de seguimiento y envía todas 2114

las salidas que irían normalmente a cout a ese fichero. Todo lo que debe 2115

hacer es #define TRACEON e incluir el fichero de cabecera (por supuesto, es 2116

bastante fácil sólo escribir las dos líneas claves correctamente en su fichero): 2117

//: C03:Trace.h 2118

// Creating a trace file. 2119

#ifndef TRACE_H 2120

#define TRACE_H 2121

#include <fstream> 2122

2123

#ifdef TRACEON 2124

std::ofstream TRACEFILE__("TRACE.OUT"); 2125

#define cout TRACEFILE__ 2126

#endif 2127

2128

#endif // TRACE_H ///:~ 2129

Listado 3.12. C03/Trace.h 2130

2131

Aquí esta una prueba sencilla del fichero anterior: 2132

Page 80: Pensar en C++ (Volumen 2)

80

//: C03:Tracetst.cpp {-bor} 2133

#include <iostream> 2134

#include <fstream> 2135

#include "../require.h" 2136

using namespace std; 2137

2138

#define TRACEON 2139

#include "Trace.h" 2140

2141

int main() { 2142

ifstream f("Tracetst.cpp"); 2143

assure(f, "Tracetst.cpp"); 2144

cout << f.rdbuf(); // Dumps file contents to file 2145

} ///:~ 2146

Listado 3.13. C03/Tracetst.cpp 2147

2148

Porque cout ha sido textualmente convertido en algo más por Trace.h, 2149

todas las sentencias cout en su programa ahora envían información al fichero 2150

de seguimiento. Esto es una forma conveniente de capturar su salida en un 2151

fichero, en caso de que su sistema operativo no haga una fácil redirección de 2152

la salida. 2153

3.3.3. Encontrar agujeros en memoria 2154

Las siguientes técnicas sencillas de depuración están explicadas en el 2155

Volumen 1: 2156

1. Para comprobar los límites de un array, usa la plantilla Array en 2157

C16:Array3.cpp del Volumen 1 para todos los arrays. Puede desactivar la 2158

Page 81: Pensar en C++ (Volumen 2)

81

comprobación e incrementar la eficiencia cuando esté listo para enviar. 2159

(Aunque esto no trata con el caso de coger un puntero a un array.) 2160

2. Comprobar destructores no virtuales en clases base. Seguirle la pista a 2161

new/delete y malloc/free 2162

Los problemas comunes con la asignación de memoria incluyen llamadas 2163

por error a delete para memoria que no está libre, borrar el espacio libre más 2164

de una vez, y más a menudo, olvidando borrar un puntero. Esta sección 2165

discute un sistema que puede ayudarle a localizar estos tipos de problemas. 2166

Como cláusula adicional de exención de responsabilidad más allá de la 2167

sección precedente: por el modo que sobrecargamos new, la siguiente 2168

técnica puede no funcionar en todas las plataformas, y funcionará sólo para 2169

programas que no llaman explicitamente al operador de función new( ). 2170

Hemos sido bastante cuidadosos en este libro para presentar sólo código 2171

que se ajuste completamente al Estándar C++, pero en este ejemplo 2172

estamos haciendo una excepción por las siguientes razones: 2173

1. A pesar de que es técnicamente ilegal, funciona en muchos 2174

compiladores.[29] 2175

2. Ilustramos algunos pensamientos útiles en el trascurso del camino. 2176

Para usar el sistema de comprobación de memoria, simplemente incluya el 2177

fichero de cabecera MemCheck.h, conecte el fichero MemCheck.obj a su 2178

aplicación para interceptar todas las llamadas a new y delete, y llame a la 2179

macro MEM_ON( ) (se explica más tarde en esta sección) para iniciar el 2180

seguimiento de la memoria. Un seguimiento de todas las asignaciones y 2181

desasignaciones es impreso en la salida estándar (mediante stdout). Cuando 2182

use este sistema, todas las llamadas a new almacenan información sobre el 2183

fichero y la línea donde fueron llamados. Esto está dotado usando la sintaxis 2184

de colocación para el operador new.[30] Aunque normalmente use la sintaxis 2185

de colocación cuando necesite colocar objetos en un punto de memoria 2186

específico, puede también crear un operador new( ) con cualquier número de 2187

argumentos. Esto se usa en el siguiente ejemplo para almacenar los 2188

resultados de las macros __FILE__ y __LINE__ cuando se llama a new: 2189

//: C02:MemCheck.h 2190

Page 82: Pensar en C++ (Volumen 2)

82

#ifndef MEMCHECK_H 2191

#define MEMCHECK_H 2192

#include <cstddef> // For size_t 2193

2194

// Usurp the new operator (both scalar and array versions) 2195

void* operator new(std::size_t, const char*, long); 2196

void* operator new[](std::size_t, const char*, long); 2197

#define new new (__FILE__, __LINE__) 2198

2199

extern bool traceFlag; 2200

#define TRACE_ON() traceFlag = true 2201

#define TRACE_OFF() traceFlag = false 2202

2203

extern bool activeFlag; 2204

#define MEM_ON() activeFlag = true 2205

#define MEM_OFF() activeFlag = false 2206

2207

#endif // MEMCHECK_H ///:~ 2208

Listado 3.14. C02/MemCheck.h 2209

2210

Es importante incluir este fichero en cualquier fichero fuente en el que 2211

quiera seguir la actividad de la memoria libre, pero inclúyalo al final (después 2212

de sus otras directivas #include). La mayoría de las cabeceras en la 2213

biblioteca estándar son plantillas, y puesto que la mayoría de los 2214

compiladores usan el modelo de inclusión de compilación de plantilla 2215

(significa que todo el código fuente está en las cabeceras), la macro que 2216

reemplaza new en MemCheck.h usurpará todas las instancias del operador 2217

Page 83: Pensar en C++ (Volumen 2)

83

new en el código fuente de la biblioteca (y casi resultaría en errores de 2218

compilación). Además, está sólo interesado en seguir sus propios errores de 2219

memoria, no los de la biblioteca. 2220

En el siguiente fichero, que contiene la implementación del seguimiento de 2221

memoria, todo está hecho con C estándar I/O más que con iostreams C++. 2222

No debería influir, puesto que no estamos interfiriendo con el uso de iostream 2223

en la memoria libre, pero cuando lo intentamos, algunos compiladores se 2224

quejaron. Todos los compiladores estaban felices con la versión <cstdio>. 2225

//: C02:MemCheck.cpp {O} 2226

#include <cstdio> 2227

#include <cstdlib> 2228

#include <cassert> 2229

#include <cstddef> 2230

using namespace std; 2231

#undef new 2232

2233

// Global flags set by macros in MemCheck.h 2234

bool traceFlag = true; 2235

bool activeFlag = false; 2236

2237

namespace { 2238

2239

// Memory map entry type 2240

struct Info { 2241

void* ptr; 2242

const char* file; 2243

long line; 2244

Page 84: Pensar en C++ (Volumen 2)

84

}; 2245

2246

// Memory map data 2247

const size_t MAXPTRS = 10000u; 2248

Info memMap[MAXPTRS]; 2249

size_t nptrs = 0; 2250

2251

// Searches the map for an address 2252

int findPtr(void* p) { 2253

for(size_t i = 0; i < nptrs; ++i) 2254

if(memMap[i].ptr == p) 2255

return i; 2256

return -1; 2257

} 2258

2259

void delPtr(void* p) { 2260

int pos = findPtr(p); 2261

assert(pos >= 0); 2262

// Remove pointer from map 2263

for(size_t i = pos; i < nptrs-1; ++i) 2264

memMap[i] = memMap[i+1]; 2265

--nptrs; 2266

} 2267

2268

// Dummy type for static destructor 2269

Page 85: Pensar en C++ (Volumen 2)

85

struct Sentinel { 2270

~Sentinel() { 2271

if(nptrs > 0) { 2272

printf("Leaked memory at:\n"); 2273

for(size_t i = 0; i < nptrs; ++i) 2274

printf("\t%p (file: %s, line %ld)\n", 2275

memMap[i].ptr, memMap[i].file, memMap[i].line); 2276

} 2277

else 2278

printf("No user memory leaks!\n"); 2279

} 2280

}; 2281

2282

// Static dummy object 2283

Sentinel s; 2284

2285

} // End anonymous namespace 2286

2287

// Overload scalar new 2288

void* 2289

operator new(size_t siz, const char* file, long line) { 2290

void* p = malloc(siz); 2291

if(activeFlag) { 2292

if(nptrs == MAXPTRS) { 2293

printf("memory map too small (increase MAXPTRS)\n"); 2294

Page 86: Pensar en C++ (Volumen 2)

86

exit(1); 2295

} 2296

memMap[nptrs].ptr = p; 2297

memMap[nptrs].file = file; 2298

memMap[nptrs].line = line; 2299

++nptrs; 2300

} 2301

if(traceFlag) { 2302

printf("Allocated %u bytes at address %p ", siz, p); 2303

printf("(file: %s, line: %ld)\n", file, line); 2304

} 2305

return p; 2306

} 2307

2308

// Overload array new 2309

void* 2310

operator new[](size_t siz, const char* file, long line) { 2311

return operator new(siz, file, line); 2312

} 2313

2314

// Override scalar delete 2315

void operator delete(void* p) { 2316

if(findPtr(p) >= 0) { 2317

free(p); 2318

assert(nptrs > 0); 2319

Page 87: Pensar en C++ (Volumen 2)

87

delPtr(p); 2320

if(traceFlag) 2321

printf("Deleted memory at address %p\n", p); 2322

} 2323

else if(!p && activeFlag) 2324

printf("Attempt to delete unknown pointer: %p\n", p); 2325

} 2326

2327

// Override array delete 2328

void operator delete[](void* p) { 2329

operator delete(p); 2330

} ///:~ 2331

Listado 3.15. C02/MemCheck.cpp 2332

2333

Las banderas booleanas de traceFalg y activeFlag son globales, por lo que 2334

pueden ser modificados en su código por las macros TRACE_ON( ), 2335

TRACE_OFF( ), MEM_ON( ), y MEM_OFF( ). En general, encierre todo el 2336

código en su main( ) dentro una pareja MEM_ON( )-MEM_OFF( ) de modo 2337

que la memoria sea siempre trazada. Trazar, que repite la actividad de las 2338

funciones de sustitución por el operador new( ) y el operador delete( ), es por 2339

defecto, pero puede desactivarlo con TRACE_OFF( ). En cualquier caso, los 2340

resultados finales son siempre impresos (vea la prueba que se ejecuta más 2341

tarde en este capítulo). 2342

La facilidad MemCheck rastrea la memoria guardando todas las 2343

direcciones asignadas por el operador new( ) en un array de estructuras Info, 2344

que también tiene el nombre del fichero y el número de línea donde la 2345

llamada new se encuentra. Para prevenir la colisión con cualquier nombre 2346

que haya colocado en el espacio de nombres global, tanta información como 2347

sea posible se guarda dentro del espacio de nombre anónimo. La clase 2348

Page 88: Pensar en C++ (Volumen 2)

88

Sentinel existe únicamente para llamar a un destructor de objetos con 2349

estático cuando el programa termina. Este destructor inspecciona memMap 2350

para ver si algún puntero está esperando a ser borrado (indicando una 2351

perdida de memoria). 2352

Nuestro operador new( ) usa malloc( ) para conseguir memoria, y luego 2353

añade el puntero y su información de fichero asociado a memMap. La 2354

función de operador delete( ) deshace todo el trabajo llamando a free( ) y 2355

decrementando nptrs, pero primero se comprueba para ver si el puntero en 2356

cuestión está en el mapa en el primer lugar. Si no es así, o reintenta borrar 2357

una dirección que no está en el almacén libre, o re intenta borrar la que ya ha 2358

sido borrada y eliminada del mapa. La variable activeFlag es importante aquí 2359

porque no queremos procesar ninguna desasignación de alguna actividad del 2360

cierre del sistema. Llamando a MEM_OFF( ) al final de su código, activeFlag 2361

será puesta a falso, y posteriores llamadas para borrar serán ignoradas. 2362

(Está mal en un programa real, pero nuestra intención aquí es encontrar 2363

agujeros, no está depurando la biblioteca.) Por simplicidad, enviamos todo el 2364

trabajo por array new y delete a sus homólogos escalares. 2365

Lo siguiente es un test sencillo usando la facilidad MemCheck: 2366

//: C02:MemTest.cpp 2367

//{L} MemCheck 2368

// Test of MemCheck system. 2369

#include <iostream> 2370

#include <vector> 2371

#include <cstring> 2372

#include "MemCheck.h" // Must appear last! 2373

using namespace std; 2374

2375

class Foo { 2376

char* s; 2377

Page 89: Pensar en C++ (Volumen 2)

89

public: 2378

Foo(const char*s ) { 2379

this->s = new char[strlen(s) + 1]; 2380

strcpy(this->s, s); 2381

} 2382

~Foo() { delete [] s; } 2383

}; 2384

2385

int main() { 2386

MEM_ON(); 2387

cout << "hello" << endl; 2388

int* p = new int; 2389

delete p; 2390

int* q = new int[3]; 2391

delete [] q; 2392

int* r; 2393

delete r; 2394

vector<int> v; 2395

v.push_back(1); 2396

Foo s("goodbye"); 2397

MEM_OFF(); 2398

} ///:~ 2399

Listado 3.16. C02/MemTest.cpp 2400

2401

Page 90: Pensar en C++ (Volumen 2)

90

Este ejemplo verifica que puede usar MemCheck en presencia de streams, 2402

contenedores estándar, y clases que asignan memoria en constructores. Los 2403

punteros p y q son asignados y desasignados sin ningún problema, pero r no 2404

es un puntero de pila válido, así que la salida indica el error como un intento 2405

de borrar un puntero desconocido: 2406

hola Asignados 4 bytes en la dirección 0xa010778 (fichero: memtest.cpp, 2407

línea: 25) Deleted memory at address 0xa010778 Asignados 12 bytes en la 2408

dirección 0xa010778 (fichero: memtest.cpp, línea: 27) Memoria borrada en la 2409

dirección 0xa010778 Intento de borrar puntero desconocido: 0x1 Asignados 8 2410

bytes en la dirección 0xa0108c0 (fichero: memtest.cpp, línea: 14) Memoria 2411

borrada en la dirección 0xa0108c0 ¡No hay agujeros de memoria de usuario! 2412

A causa de la llamada a MEM_OFF( ), no se procesan posteriores 2413

llamadas al operador delete( ) por vector o ostream. Todavía podría 2414

conseguir algunas llamadas a delete realizadas dsede reasignaciones por los 2415

contenedores. 2416

Si llama a TRACE_OFF( ) al principio del programa, la salida es 2417

Hola Intento de borrar puntero desconocido: 0x1 ¡No hay agujeros de 2418

memoria de usuario! 2419

3.4. Resumen 2420

Muchos de los dolores de cabeza de la ingenería del software pueden ser 2421

evitados reflexionando sobre lo que está haciendo. Probablemente ha estado 2422

usando aserciones mentales cuando ha navegado por sus blucles y 2423

funciones, incluso si no ha usado rutinariamente la macro assert( ). Si usa 2424

assert( ), encontrará errores lógicos más pronto y acabará con código más 2425

legible también. Recuerde usar solo aserciones para invariantes, aunque, no 2426

para el manejo de error en tiempo de ejecución. 2427

Nada le dará más tranquilidad que código probado rigurosamente. Si ha 2428

sido un lío en el pasado, use un framework automatizado, como el que 2429

hemos presentado aquí, para integrar la rutina de pruebas en su trabajo 2430

diario. Usted (¡y sus usarios!) estarán contentos de que lo haga. 2431

Page 91: Pensar en C++ (Volumen 2)

91

3.5. Ejercicios 2432

Las soluciones para ejercicios seleccionados pueden encontrarse en el 2433

documento electrónico Pensar en C++ Volumen 2 Guía de Soluciones 2434

Comentadas disponible por una pequeña cuota en www.MindView.net. 2435

1. Escriba un programa de prueba usando el Framework TestSuite para la 2436

clase estándar vector que prueba rigurosamente prueba las siguientes 2437

funciones con un vector de enteros: push_back( ) (añade un elemento al final 2438

del vector) front( ) (devuelve el primer elemento en el vector), back( ) 2439

(devuelve el último elemento en el vector), pop_back( ) (elimina el último 2440

elemento sin devolverlo), at( ) (devuelve el elemento en una posición 2441

específica), y size( ) (devuelve el número de elementos). Asegúrese de 2442

verificar que vector::at( ) lanza una excepción std::out_of_range si el índice 2443

facilitado está fuera de rango. 2444

2. Supóngase que le piden desarrollar un clase llamada Rational que da 2445

soporte a números racionales (fracciones). La fracción en un objecto Rational 2446

debería siempre almacenarse en los términos más bajos, y un denominador 2447

de cero es un error. Aquí está una interfaz de ejemplo para esa clase 2448

Rational: 2449

//: C02:Rational.h {-xo} 2450

#ifndef RATIONAL_H 2451

#define RATIONAL_H 2452

#include <iosfwd> 2453

2454

class Rational { 2455

public: 2456

Rational(int numerator = 0, int denominator = 1); 2457

Rational operator-() const; 2458

friend Rational operator+(const Rational&, 2459

const Rational&); 2460

Page 92: Pensar en C++ (Volumen 2)

92

friend Rational operator-(const Rational&, 2461

const Rational&); 2462

friend Rational operator*(const Rational&, 2463

const Rational&); 2464

friend Rational operator/(const Rational&, 2465

const Rational&); 2466

friend std::ostream& 2467

operator<<(std::ostream&, const Rational&); 2468

friend std::istream& 2469

operator>>(std::istream&, Rational&); 2470

Rational& operator+=(const Rational&); 2471

Rational& operator-=(const Rational&); 2472

Rational& operator*=(const Rational&); 2473

Rational& operator/=(const Rational&); 2474

friend bool operator<(const Rational&, 2475

const Rational&); 2476

friend bool operator>(const Rational&, 2477

const Rational&); 2478

friend bool operator<=(const Rational&, 2479

const Rational&); 2480

friend bool operator>=(const Rational&, 2481

const Rational&); 2482

friend bool operator==(const Rational&, 2483

const Rational&); 2484

friend bool operator!=(const Rational&, 2485

Page 93: Pensar en C++ (Volumen 2)

93

const Rational&); 2486

}; 2487

#endif // RATIONAL_H ///:~ 2488

Listado 3.17. C02/Rational.h 2489

2490

Escriba una especificación completa para esta clase, incluyendo 2491

especificaciones de precondiciones, postcondiciones, y de excepción. 2492

3. Escriba un prueba usando el framework TestSuite que pruebe 2493

rigurosamente todas las especificaciones del ejercicio anterior, incluyendo 2494

probar las excepciones. 2495

4. Implemente la clase Rational de modo que pase todas las pruebas del 2496

ejercicio anterior. Use aserciones sólo para las invariantes. 2497

5. El fichero BuggedSearch.cpp de abajo contiene un función de búsqueda 2498

binaria que busca para el rango [pedir, final). Hay algunos errores en el 2499

algoritmo. Use las técnicas de seguimiento de este capítulo para depurar la 2500

función de búsqueda. 2501

Parte II. La librería Estándar de C++ 2502

El C++ Estándar no solo incorpora todas las librerías de Estándar C (con 2503

pequeños añadidos y cambios para permitir tipos seguros), también añade 2504

sus propias librerías. Estas librerías son mucho más potentes que las de 2505

C. La mejora al usarlas es análoga a la que se consigue al cambiar de C a 2506

C++. 2507

Esta sección del libro le da una introducción en profundidad a las partes 2508

clave de la librería Estándar de C++. 2509

La referencia más completa y también la más oscura para las librerías es 2510

el propio Estándar. The C++ Programming Language, Third Edition (Addison 2511

Wesley, 2000) de Bjarne Stroustrup sigue siendo una referencia fiable tanto 2512

para el lenguaje como para la librería. La referencia más aclamada en cuanto 2513

a la librería es The C++ Standard Library: A Tutorial and Reference, by 2514

Page 94: Pensar en C++ (Volumen 2)

94

Nicolai Josuttis (Addison Wesley, 1999). El objetivo de los capítulos de esta 2515

parte del libro es ofrecer un catálogo de descripciones y ejemplos para que 2516

disponga de un buen punto de partida para resolver cualquier problema que 2517

requiera el uso de las librerías Estándar. Sin embargo, algunas técnicas y 2518

temas se usan poco y no se tratan aquí. Si no puede encontrar algo en estos 2519

capítulos, mire en los dos libros que se citan anteriormente; esto libro no 2520

pretende reemplazarlos, más bien completarlos. En particular, esperamos 2521

que después de consular el material de los siguientes capítulos pueda 2522

comprender mejor esos libros. 2523

El lector notará que estos capítulos no contienen documentación 2524

exhaustiva describiendo cada función o clase del la Librería Estándar C++. 2525

Hemos dejado las descripciones completas a otros; en particular a 2526

Dinkumware C/C++ Library Reference de P.J. Plauger. Esta es una 2527

excelente documentación que puede puede ver con un navegador web cada 2528

vez que necesite buscar algo. Puede verla on-line o comprarla para verla en 2529

local. Contiene una referencia completa para las librerías de C y C++ (de 2530

modo que es buena para cualquier cuestión de programación en C/C++ 2531

Estándar). La documentación electrónica no sólo es efectiva porque pueda 2532

tenerla siempre a mano, sino porque también puede hacer búsquedas 2533

electrónicas. 2534

Cuando usted está programando activamente, estos recursos deberían 2535

satisfacer sus necesidades de referencias (y puede usarlas para buscar algo 2536

de este capítulo que no tenga claro). El Apéndice A incluye referencias 2537

adicionales. 2538

El primer capítulo de esta sección introduce la clase string del Estándar 2539

C++, que es una herramienta potente que simplifica la mayoría de las tareas 2540

de procesamiento de texto que podría tener que realizar. Casi cualquier 2541

cosas que tenga hecho para cadenas de caracteres en C puede hacerse con 2542

una llamada a un método de la clase string. 2543

El capítulo 4 cubre la librería iostreams, que contiene clases para procesar 2544

entrada y salida con ficheros, cadenas, y la consola del sistema. 2545

Aunque el Capítulo 5: «Las plantillas a fondo» no es explícitamente un 2546

capítulo de la librería, es una preparación necesaria para los dos siguientes 2547

capítulos. En el capítulo 6 examinaremos los algoritmos genéricos que ofrece 2548

Page 95: Pensar en C++ (Volumen 2)

95

la librería Estándar C++. Como están implementados con plantillas, esos 2549

algoritmos se pueden aplicar a cualquier secuencia de objetos. El Capítulo 7 2550

cubre los contenedores estándar y sus iteradores asociados Vemos los 2551

algoritmos primero porque se pueden utilizar usando únicamente arrays y el 2552

contenedor vector (que vimos en el Volumen 1). También es normal el uso 2553

de algoritmos estándar junto con contenedores, y es bueno que le resulten 2554

familiares antes de estudiar los contenedores. 2555

Tabla de contenidos 2556

4. Las cadenas a fondo 2557

4.1. ¿Qué es un string? 2558

4.2. Operaciones con cadenas 2559

4.3. Buscar en cadenas 2560

4.4. Una aplicación con cadenas 2561

4.5. Resumen 2562

4.6. Ejercicios 2563

5. Iostreams 2564

5.1. ¿Por que iostream? 2565

5.2. Iostreams al rescate 2566

5.3. Manejo errores de stream 2567

5.4. Iostreams de fichero 2568

5.5. Almacenamiento de iostream 2569

5.6. Buscar en iostreams 2570

5.7. Iostreams de string 2571

5.8. Formateo de stream de salida 2572

5.9. Manipuladores 2573

5.10. 2574

5.11. 2575

5.12. 2576

5.13. 2577

6. Las plantillas en profundidad 2578

6.1. 2579

6.2. 2580

6.3. 2581

6.4. 2582

6.5. 2583

6.6. 2584

6.7. 2585

6.8. 2586

7. Algoritmos genéricos 2587

7.1. Un primer vistazo 2588

7.2. Objetos-función 2589

7.3. Un catálogo de algoritmos STL 2590

7.4. Creando sus propios algoritmos tipo STL 2591

7.5. Resumen 2592

7.6. Ejercicios 2593

Page 96: Pensar en C++ (Volumen 2)

96

4: Las cadenas a fondo 2594

Tabla de contenidos 2595

4.1. ¿Qué es un string? 2596

4.2. Operaciones con cadenas 2597

4.3. Buscar en cadenas 2598

4.4. Una aplicación con cadenas 2599

4.5. Resumen 2600

4.6. Ejercicios 2601 El procesamiento de cadenas de caracteres en C es una de las mayores 2602

pérdidas de tiempo. Las cadenas de caracteres requieren que el 2603

programador tenga en cuenta las diferencias entre cadenas estáticas y las 2604

cadenas creadas en la pila y en el montón, además del hecho que a veces 2605

pasa como argumento un char* y a veces hay que copiar el arreglo entero. 2606

Precisamente porque la manipulación de cadenas es muy común, las 2607

cadenas de caracteres son una gran fuente de confusiones y errores. Es por 2608

ello que la creación de clases de cadenas sigue siendo desde hace años un 2609

ejercicio común para programadores novatos. La clase string de la 2610

biblioteca estándar de C++ resuelve el problema de la manipulación de 2611

caracteres de una vez por todas, gestionando la memoria incluso durante las 2612

asignaciones y las construcciones de copia. Simplemente no tiene que 2613

preocuparse por ello. 2614

Este capítulo[1] examina la clase string del Estándar C++; empieza con un 2615

vistazo a la composición de las string de C++ y como la versión de C++ 2616

difiere del tradicional arreglo de caracteres de C. Aprenderá sobre las 2617

operaciones y la manipulación usando objetos string, y verá como éstas se 2618

FIXME[acomodan a la variación] de conjuntos de caracteres y conversión de 2619

datos. 2620

Manipular texto es una de las aplicaciones más antiguas de la 2621

programación, por eso no resulta sorprendente que las string de C++ estén 2622

fuertemente inspiradas en las ideas y la terminología que ha usado 2623

continuamente en C y otros lenguajes. Conforme vaya aprendiendo sobre los 2624

string de C++, este hecho se debería ir viendo más claramente. Da igual el 2625

lenguaje de programación que escoja, hay tres cosas comunes que querrá 2626

hacer con las cadenas: 2627

Page 97: Pensar en C++ (Volumen 2)

97

Crear o modificar secuencias de caracteres almacenados en una 2628

cadena 2629

Detectar la presencia o ausencia de elementos dentro de la 2630

cadena 2631

Traducir entre diversos esquemas para representar cadenas de 2632

caracteres 2633

Verá como cada una de estas tareas se resuelve usando objetos string 2634

en C++. 2635

4.1. ¿Qué es un string? 2636

En C, una cadena es simplemente un arreglo de caracteres que siempre 2637

incluye un 0 binario (frecuentemente llamado terminador nulo) como 2638

elemento final del arreglo. Existen diferencias significativas entre los string 2639

de C++ y sus progenitoras en C. Primero, y más importante, los string de 2640

C++ esconden la implementación física de la secuencia de caracteres que 2641

contiene. No debe preocuparse de las dimensiones del arreglo o del 2642

terminador nulo. Un string también contiene cierta información para uso 2643

interno sobre el tamaño y la localización en memoria de los datos. 2644

Específicamente, un objeto string de C++ conoce su localización en 2645

memoria, su contenido, su longitud en caracteres, y la cantidad de caracteres 2646

que puede crecer antes de que el objeto string deba redimensionar su 2647

buffer interno de datos. Las string de C++, por tanto, reducen enormemente 2648

las probabilidades de cometer uno de los tres errores de programación en C 2649

más comunes y destructivos: sobrescribir los límites del arreglo, intentar 2650

acceder a un arreglo no inicializado o con valores de puntero incorrectos, y 2651

dejar punteros colgando después de que el arreglo deje de ocupar el espacio 2652

que estaba ocupando. 2653

La implementación exacta del esquema en memoria para una clase string 2654

no esta definida en el estándar C++. Esta arquitectura esta pensada para ser 2655

suficientemente flexible para permitir diferentes implementaciones de los 2656

fabricantes de compiladores, garantizando igualmente un comportamiento 2657

predecible por los usuarios. En particular, las condiciones exactas de cómo 2658

situar el almacenamiento para alojar los datos para un objeto string no 2659

están definidas. FIXME: Las reglas de alojamiento de un string fueron 2660

Page 98: Pensar en C++ (Volumen 2)

98

formuladas para permitir, pero no requerir, una implementación con 2661

referencias múltiples, pero dependiendo de la implementación usar 2662

referencias múltiples sin variar la semántica. Por decirlo de otra manera, en 2663

C, todos los arreglos de char ocupan una única región física de memoria. En 2664

C++, los objetos string individuales pueden o no ocupar regiones físicas 2665

únicas de memoria, pero si su conjunto de referencias evita almacenar 2666

copias duplicadas de datos, los objetos individuales deben parecer y actuar 2667

como si tuvieran sus propias regiones únicas de almacenamiento. 2668

//: C03:StringStorage.h 2669

#ifndef STRINGSTORAGE_H 2670

#define STRINGSTORAGE_H 2671

#include <iostream> 2672

#include <string> 2673

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

using std::cout; 2675

using std::endl; 2676

using std::string; 2677

2678

class StringStorageTest : public TestSuite::Test { 2679

public: 2680

void run() { 2681

string s1("12345"); 2682

// This may copy the first to the second or 2683

// use reference counting to simulate a copy: 2684

string s2 = s1; 2685

test_(s1 == s2); 2686

// Either way, this statement must ONLY modify s1: 2687

Page 99: Pensar en C++ (Volumen 2)

99

s1[0] = '6'; 2688

cout << "s1 = " << s1 << endl; // 62345 2689

cout << "s2 = " << s2 << endl; // 12345 2690

test_(s1 != s2); 2691

} 2692

}; 2693

#endif // STRINGSTORAGE_H ///:~ 2694

Listado 4.1. C03/StringStorage.h 2695

2696

Decimos que cuando una implementación solo hace una sola copia al 2697

modificar el string usa una estrategia de copiar al escribir. Esta 2698

aproximación ahorra tiempo y espacio cuando usamos string como 2699

parámetros por valor o en otras situaciones de solo lectura. 2700

El uso de referencias múltiples en la implementación de una librería 2701

debería ser transparente al usuario de la clase string. Desgraciadamente, 2702

esto no es siempre el caso. En programas multihilo, es prácticamente 2703

imposible usar implementaciones con múltiples referencias de forma 2704

segura[32].[2] 2705

4.2. Operaciones con cadenas 2706

Si ha programado en C, estará acostumbrado a la familia de funciones que 2707

leen, escriben, modifican y copian cadenas. Existen dos aspectos poco 2708

afortunados en la funciones de la librería estándar de C para manipular 2709

cadenas. Primero, hay dos familias pobremente organizadas: el grupo plano, 2710

y aquellos que requieren que se les suministre el número de caracteres para 2711

ser consideradas en la operación a mano. La lista de funciones en la librería 2712

de cadenas de C sorprende al usuario desprevenido con una larga lista de 2713

nombres crípticos y mayoritariamente impronunciables. Aunque el tipo y 2714

número de argumentos es algo consistente, para usarlas adecuadamente 2715

Page 100: Pensar en C++ (Volumen 2)

100

debe estar atento a los detalles de nombres de la función y a los parámetros 2716

que le pasas. 2717

La segunda trampa inherente a las herramientas para cadenas del 2718

estándar de C es que todas ellas explícitamente confían en la asunción de 2719

que cada cadena incluye un terminador nulo. Si por confusión o error el 2720

terminador nulo es omitido o sobrescrito, poco se puede hacer para impedir 2721

que las funciones de cadena de C manipulen la memoria más allá de los 2722

límites del espacio de alojamiento, a veces con resultados desastrosos. 2723

C++ aporta una vasta mejora en cuanto a conveniencia y seguridad de los 2724

objetos string. Para los propósitos de las actuales operaciones de 2725

manipulación, existe el mismo número de funciones que la librería de C, pero 2726

gracias a la sobrecarga, la funcionalidad es mucho mayor. Además, con una 2727

nomenclatura más sensata y un acertado uso de los argumentos por defecto, 2728

estas características se combinan para hacer de la clase string mucho más 2729

fácil de usar que la biblioteca de funciones de cadena de C. 2730

4.2.1. Añadiendo, insertando y concatenando cadenas 2731

Uno de los aspectos más valiosos y convenientes de los string en C++ es 2732

que crecen cuando lo necesitan, sin intervención por parte del programador. 2733

No solo hace el código de manejo del string sea inherentemente mas 2734

confiable, además elimina por completo las tediosas funciones "caseras" 2735

para controlar los limites del almacenamiento en donde nuestra cadena 2736

reside. Por ejemplo, si crea un objeto string e inicializa este string con 50 2737

copias de "X", y después copia en el 50 copias de "Zowie", el objeto, por sí 2738

mismo, readecua suficiente almacenamiento para acomodar el crecimiento 2739

de los datos. Quizás en ningún otro lugar es más apreciada esta propiedad 2740

que cuando las cadenas manipuladas por su código cambian de tamaño y no 2741

sabe cuan grande puede ser este cambio. La función miembro append() e 2742

insert() de string reubican de manera transparente el almacenamiento 2743

cuando un string crece: 2744

//: C03:StrSize.cpp 2745

#include <string> 2746

Page 101: Pensar en C++ (Volumen 2)

101

#include <iostream> 2747

using namespace std; 2748

2749

int main() { 2750

string bigNews("I saw Elvis in a UFO. "); 2751

cout << bigNews << endl; 2752

// How much data have we actually got? 2753

cout << "Size = " << bigNews.size() << endl; 2754

// How much can we store without reallocating? 2755

cout << "Capacity = " << bigNews.capacity() << endl; 2756

// Insert this string in bigNews immediately 2757

// before bigNews[1]: 2758

bigNews.insert(1, " thought I"); 2759

cout << bigNews << endl; 2760

cout << "Size = " << bigNews.size() << endl; 2761

cout << "Capacity = " << bigNews.capacity() << endl; 2762

// Make sure that there will be this much space 2763

bigNews.reserve(500); 2764

// Add this to the end of the string: 2765

bigNews.append("I've been working too hard."); 2766

cout << bigNews << endl; 2767

cout << "Size = " << bigNews.size() << endl; 2768

cout << "Capacity = " << bigNews.capacity() << endl; 2769

} ///:~ 2770

Page 102: Pensar en C++ (Volumen 2)

102

Listado 4.2. C03/StrSize.cpp 2771

2772

Aquí la salida desde un compilador cualquiera: 2773

I saw Elvis in a UFO. 2774 Size = 22 2775

Capacity = 31 2776 I thought I saw Elvis in a UFO. 2777

Size = 32 2778 Capacity = 47 2779

I thought I saw Elvis in a UFO. I've been 2780 working too hard. 2781

Size = 59 2782 Capacity = 511 2783

Este ejemplo demuestra que aunque puede ignorar con seguridad muchas 2784

de las responsabilidades de reserva y gestión de la memoria que tus string 2785

ocupan, C++ provee a los string con varias herramientas para monitorizar y 2786

gestionar su tamaño. Nótese la facilidad con la que hemos cambiado el 2787

tamaño de la memoria reservada para los string. La función size() retorna 2788

el numero de caracteres actualmente almacenados en el string y es idéntico 2789

a la función miembro lenght(). La función capacity() retorna el tamaño de 2790

la memoria subyacente actual, es decir, el número de caracteres que el 2791

string puede almacenar sin tener que reservar más memoria. La función 2792

reserve() es una optimización del mecanismo que indica su intención de 2793

especificar cierta cantidad de memoria para un futuro uso; capacity() 2794

siempre retorna un valor al menos tan largo como la ultima llamada a 2795

reserve(). La función resize() añade espacio si el nuevo tamaño es mayor 2796

que el tamaño actual del string; sino trunca el string. (Una sobrecarga de 2797

resize() puede especificar una adición diferente de caracteres). 2798

La manera exacta en que las funciones miembro de string reservan 2799

espacio para sus datos depende de la implementación de la librería. Cuando 2800

testeamos una implementación con el ejemplo anterior, parece que se hacia 2801

una reserva de una palabra de memoria (esto es, un entero) dejando un byte 2802

en blanco entre cada una de ellas. Los arquitectos de la clase string se 2803

esforzaron para poder mezclar el uso de las cadenas de caracteres de C y 2804

los objetos string, por lo que es probable por lo que se puede observar en 2805

StrSize.cpp, en esta implementación en particular, el byte esté añadido para 2806

acomodar fácilmente la inserción de un terminador nulo. 2807

Page 103: Pensar en C++ (Volumen 2)

103

4.2.2. Reemplazar caracteres en cadenas 2808

La función insert() es particularmente útil por que te evita el tener que 2809

estar seguro de que la inserción de caracteres en un string no sobrepasa el 2810

espacio reservado o sobrescribe los caracteres que inmediatamente 2811

siguientes al punto de inserción. El espacio crece y los caracteres existentes 2812

se mueven graciosamente para acomodar a los nuevos elementos. A veces, 2813

puede que no sea esto exactamente lo que quiere. Si quiere que el tamaño 2814

del string permanezca sin cambios, use la función replace() para 2815

sobrescribir los caracteres. Existe un número de versiones sobrecargadas de 2816

replace(), pero la más simple toma tres argumentos: un entero indicando 2817

donde empezar en el string, un entero indicando cuantos caracteres para 2818

eliminar del string original, y el string con el que reemplazaremos (que 2819

puede ser diferente en numero de caracteres que la cantidad eliminada). 2820

Aquí un ejemplo simple: 2821

//: C03:StringReplace.cpp 2822

// Simple find-and-replace in strings. 2823

#include <cassert> 2824

#include <string> 2825

using namespace std; 2826

2827

int main() { 2828

string s("A piece of text"); 2829

string tag("$tag$"); 2830

s.insert(8, tag + ' '); 2831

assert(s == "A piece $tag$ of text"); 2832

int start = s.find(tag); 2833

assert(start == 8); 2834

assert(tag.size() == 5); 2835

Page 104: Pensar en C++ (Volumen 2)

104

s.replace(start, tag.size(), "hello there"); 2836

assert(s == "A piece hello there of text"); 2837

} ///:~ 2838

Listado 4.3. C03/StringReplace.cpp 2839

2840

Tag es insertada en s (notese que la inserción ocurre antes de que el valor 2841

indicando el punto de inserción y de que el espacio extra haya sido añadido 2842

despues de Tag), y entonces es encontrada y reemplazada. 2843

Debería cerciorarse de que ha encontrado algo antes de realizar el 2844

replace(). En los ejemplos anteriores se reemplaza con un char*, pero 2845

existe una versión sobrecargada que reemplaza con un string. Aqui hay un 2846

ejempl más completo de demostración de replace(): 2847

//: C03:Replace.cpp 2848

#include <cassert> 2849

#include <cstddef> // For size_t 2850

#include <string> 2851

using namespace std; 2852

2853

void replaceChars(string& modifyMe, 2854

const string& findMe, const string& newChars) { 2855

// Look in modifyMe for the "find string" 2856

// starting at position 0: 2857

size_t i = modifyMe.find(findMe, 0); 2858

// Did we find the string to replace? 2859

if(i != string::npos) 2860

// Replace the find string with newChars: 2861

Page 105: Pensar en C++ (Volumen 2)

105

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

} 2863

2864

int main() { 2865

string bigNews = "I thought I saw Elvis in a UFO. " 2866

"I have been working too hard."; 2867

string replacement("wig"); 2868

string findMe("UFO"); 2869

// Find "UFO" in bigNews and overwrite it: 2870

replaceChars(bigNews, findMe, replacement); 2871

assert(bigNews == "I thought I saw Elvis in a " 2872

"wig. I have been working too hard."); 2873

} ///:~ 2874

Listado 4.4. C03/Replace.cpp 2875

2876

Si replace() no encuentra la cadena buscada, retorna un string::npos. El 2877

dato miembro npos es una constante estatica de la clase string que 2878

representa una posición de carácter que no existe[33]. [3] 2879

A diferencia de insert(), replace() no aumentará el espacio de 2880

alamcenamiento de string si copia nuevos caracteres en el medio de una 2881

serie de elementos de array existentes. Sin embargo, sí que cerecerá su 2882

espacio si es necesario, por ejemplo, cuando hace un "reemplazamiento" 2883

que pueda expandir el string más allá del final de la memoria reservada 2884

actual. Aquí un ejemplo: 2885

//: C03:ReplaceAndGrow.cpp 2886

#include <cassert> 2887

Page 106: Pensar en C++ (Volumen 2)

106

#include <string> 2888

using namespace std; 2889

2890

int main() { 2891

string bigNews("I have been working the grave."); 2892

string replacement("yard shift."); 2893

// The first argument says "replace chars 2894

// beyond the end of the existing string": 2895

bigNews.replace(bigNews.size() - 1, 2896

replacement.size(), replacement); 2897

assert(bigNews == "I have been working the " 2898

"graveyard shift."); 2899

} ///:~ 2900

Listado 4.5. C03/ReplaceAndGrow.cpp 2901

2902

La llamada a replace() empieza "reemplazando" más allá del final del 2903

array existente, que es equivalente a la operación append(). Nótese que en 2904

este ejemplo replace() expande el array coherentemente. 2905

Puede que haya estado buscando a través del capítulo; intentando hacer 2906

algo relativamente fácil como reemplazar todas las ocurrencias de un 2907

carácter con diferentes caracteres. Al buscar el material previo sobre 2908

reemplazar, puede que haya encontrado la respuesta, pero entonces ha 2909

empezaro viendo grupos de caracteres y contadores y otras cosas que 2910

parecen un poco demasiado complejas. ¿No tiene string una manera para 2911

reemplazar un carácter con otro simplemente? 2912

Puede escribir fácilmente cada funcin usando las funciones miembro 2913

find() y replace() como se muestra acontinuacion. 2914

Page 107: Pensar en C++ (Volumen 2)

107

//: C03:ReplaceAll.h 2915

#ifndef REPLACEALL_H 2916

#define REPLACEALL_H 2917

#include <string> 2918

2919

std::string& replaceAll(std::string& context, 2920

const std::string& from, const std::string& to); 2921

#endif // REPLACEALL_H ///:~ 2922

Listado 4.6. C03/ReplaceAll.h 2923

2924

//: C03:ReplaceAll.cpp {O} 2925

#include <cstddef> 2926

#include "ReplaceAll.h" 2927

using namespace std; 2928

2929

string& replaceAll(string& context, const string& from, 2930

const string& to) { 2931

size_t lookHere = 0; 2932

size_t foundHere; 2933

while((foundHere = context.find(from, lookHere)) 2934

!= string::npos) { 2935

context.replace(foundHere, from.size(), to); 2936

lookHere = foundHere + to.size(); 2937

} 2938

Page 108: Pensar en C++ (Volumen 2)

108

return context; 2939

} ///:~ 2940

Listado 4.7. C03/ReplaceAll.cpp 2941

2942

La versión de find() usada aquí toma como segundo argumento la 2943

posición donde empezar a buscar y retorna string::npos si no lo encuentra. 2944

Es importante avanzar en la posición contenida por la variable lookHere 2945

pasada como subcadena, en caso de que from es una subcadena de to. El 2946

siguiente programa comprueba la funcion replaceAll(): 2947

//: C03:ReplaceAllTest.cpp 2948

//{L} ReplaceAll 2949

#include <cassert> 2950

#include <iostream> 2951

#include <string> 2952

#include "ReplaceAll.h" 2953

using namespace std; 2954

2955

int main() { 2956

string text = "a man, a plan, a canal, Panama"; 2957

replaceAll(text, "an", "XXX"); 2958

assert(text == "a mXXX, a plXXX, a cXXXal, PXXXama"); 2959

} ///:~ 2960

Listado 4.8. C03/ReplaceAllTest.cpp 2961

2962

Page 109: Pensar en C++ (Volumen 2)

109

Como puede comprobar, la clase string por ella sola no resuelve todos los 2963

posibles problemas. Muchas soluciones se han dejado en los algoritmos de 2964

la librería estándar[4] por que la clase string puede parece justamente como 2965

una secuencia STL(gracias a los iteradores descritos antes). Todos los 2966

algoritmos genéricos funcionan en un "rango" de elementos dentro de un 2967

contenedor. Generalmente este rango es justamente desde el principio del 2968

contenedor hasta el final. Un objeto string se parece a un contenedor de 2969

caracteres: para obtener el principio de este rango use string::begin(), y 2970

para obtener el final del rango use string::end(). El siguiente 2971

ejemplomuestra el uso del algoritmo replace() para reemplazar todas las 2972

instancias de un determinado carácter "X" con "Y" 2973

//: C03:StringCharReplace.cpp 2974

#include <algorithm> 2975

#include <cassert> 2976

#include <string> 2977

using namespace std; 2978

2979

int main() { 2980

string s("aaaXaaaXXaaXXXaXXXXaaa"); 2981

replace(s.begin(), s.end(), 'X', 'Y'); 2982

assert(s == "aaaYaaaYYaaYYYaYYYYaaa"); 2983

} ///:~ 2984

Listado 4.9. C03/StringCharReplace.cpp 2985

2986

Nótese que esta función replace() no es llamada como función miembro 2987

de string. Además, a diferencia de la función string::replace(), que solo 2988

realiza un reemplazo, el algoritmo replace() reemplaza todas las instancias 2989

de un carácter con otro. 2990

Page 110: Pensar en C++ (Volumen 2)

110

El algoritmo replace() solo funciona con objetos individuales (en este 2991

caso, objetos char) y no reemplazará arreglos constantes o objetos string. 2992

Desde que un string se copmporta como una secuencia STL, un conjunto 2993

de algoritmos pueden serle aplicados, que resolverán otros problemas que 2994

las funciones miembro de string no resuelven. 2995

4.2.3. Concatenación usando operadores no-miembro 2996

sobrecargados 2997

Uno de los descubrimientos más deliciosos que esperan al programador 2998

de C que está aprendiendo sobre el manejo de cadenas en C++, es lo simple 2999

que es combinar y añadir string usando los operadores operator+ y 3000

operator+=. Estos operadores hacen combinaciones de cadenas 3001

sintacticamente parecidas a la suma de datos numéricos: 3002

//: C03:AddStrings.cpp 3003

#include <string> 3004

#include <cassert> 3005

using namespace std; 3006

3007

int main() { 3008

string s1("This "); 3009

string s2("That "); 3010

string s3("The other "); 3011

// operator+ concatenates strings 3012

s1 = s1 + s2; 3013

assert(s1 == "This That "); 3014

// Another way to concatenates strings 3015

s1 += s3; 3016

assert(s1 == "This That The other "); 3017

Page 111: Pensar en C++ (Volumen 2)

111

// You can index the string on the right 3018

s1 += s3 + s3[4] + "ooh lala"; 3019

assert(s1 == "This That The other The other oooh lala"); 3020

} ///:~ 3021

Listado 4.10. C03/AddStrings.cpp 3022

3023

Usar los operadores operator+ y operator+= es una manera flexible y 3024

conveniente de combinar los datos de las cadenas. En la parte derecha de la 3025

sentencia, puede usar casi cualquier tipo que evalúe a un grupo de uno o 3026

más caracteres. 3027

4.3. Buscar en cadenas 3028

La familia de funciones miembro de string find localiza un carácter o 3029

grupo de caracteres en una cadena dada. Aquí los miembros de la familia 3030

find() y su uso general: 3031

Función miembro de búsqueda en un string 3032

¿Qué/Cómo lo encuentra? 3033

find() 3034

Busca en un string un carácter determinado o un grupo de caracteres y 3035

retorna la posición de inicio de la primera ocurrencia o npos si ha sido 3036

encontrado. 3037

find_first_of() 3038

Busca en un string y retorna la posición de la primera ocurrencia de 3039

cualquier carácter en un grupo especifico. Si no encuentra ocurrencias, 3040

retorna npos. 3041

find_last_of() 3042

Busca en un string y retorna la posición de la última ocurrencia de 3043

cualquier carácter en un grupo específico. Si no encuentra ocurrencias, 3044

retorna npos. 3045

Page 112: Pensar en C++ (Volumen 2)

112

find_first_not_of( ) 3046

Busca en un string y retorna la posición de la primera ocurrencia que no 3047

pertenece a un grupo específico. Si no encontramos ningún elemento, 3048

retorna un npos 3049

find_last_not_of( ) 3050

Busca en un string y retorna la posición del elemento con el indice mayor 3051

que no pertenece a un grupo específico. Si no encontramos ningún 3052

elemento, retorna un npos 3053

rfind() 3054

Busca en un string, desde el final hasta el origen, un carácter o grupo de 3055

caracteres y retorna la posición inicial de la ocurrencia si se ha encontrado 3056

alguna. Si no encuentra ocurrencias, retorna npos. 3057

El uso más simple de find(), busca uno o más caracteres en un string. 3058

La versión sobrecargada de find() toma un parámetro que especifica el/los 3059

carácter(es) que buscar y opcionalmente un parámetro que dice donde 3060

empezar a buscar en el string la primera ocurrencia. (Por defecto la posición 3061

de incio es 0). Insertando la llamada a la función find() dentro de un bucle 3062

puede buscar fácilmente todas las ocurrencias de un carácter dado o un 3063

grupo de caracteres dentro de un string. 3064

El siguiente programa usa el método del Tamiz de Eratostenes para hallar 3065

los números primos menores de 50. Este método empieza con el número 2, 3066

marca todos los subsecuentes múltiplos de 2 ya que no son primos, y repite 3067

el proceso para el siguiente candidato a primo. El constructor de sieveTest 3068

inicializa sieveChars poniendo el tamaño inicial del arreglo de carácter y 3069

escribiendo el valor 'P' para cada miembro. 3070

//: C03:Sieve.h 3071

#ifndef SIEVE_H 3072

#define SIEVE_H 3073

#include <cmath> 3074

#include <cstddef> 3075

Page 113: Pensar en C++ (Volumen 2)

113

#include <string> 3076

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

using std::size_t; 3078

using std::sqrt; 3079

using std::string; 3080

3081

class SieveTest : public TestSuite::Test { 3082

string sieveChars; 3083

public: 3084

// Create a 50 char string and set each 3085

// element to 'P' for Prime: 3086

SieveTest() : sieveChars(50, 'P') {} 3087

void run() { 3088

findPrimes(); 3089

testPrimes(); 3090

} 3091

bool isPrime(int p) { 3092

if(p == 0 || p == 1) return false; 3093

int root = int(sqrt(double(p))); 3094

for(int i = 2; i <= root; ++i) 3095

if(p % i == 0) return false; 3096

return true; 3097

} 3098

void findPrimes() { 3099

// By definition neither 0 nor 1 is prime. 3100

Page 114: Pensar en C++ (Volumen 2)

114

// Change these elements to "N" for Not Prime: 3101

sieveChars.replace(0, 2, "NN"); 3102

// Walk through the array: 3103

size_t sieveSize = sieveChars.size(); 3104

int root = int(sqrt(double(sieveSize))); 3105

for(int i = 2; i <= root; ++i) 3106

// Find all the multiples: 3107

for(size_t factor = 2; factor * i < sieveSize; 3108

++factor) 3109

sieveChars[factor * i] = 'N'; 3110

} 3111

void testPrimes() { 3112

size_t i = sieveChars.find('P'); 3113

while(i != string::npos) { 3114

test_(isPrime(i++)); 3115

i = sieveChars.find('P', i); 3116

} 3117

i = sieveChars.find_first_not_of('P'); 3118

while(i != string::npos) { 3119

test_(!isPrime(i++)); 3120

i = sieveChars.find_first_not_of('P', i); 3121

} 3122

} 3123

}; 3124

#endif // SIEVE_H ///:~ 3125

Page 115: Pensar en C++ (Volumen 2)

115

Listado 4.11. C03/Sieve.h 3126

3127

//: C03:Sieve.cpp 3128

//{L} ../TestSuite/Test 3129

#include "Sieve.h" 3130

3131

int main() { 3132

SieveTest t; 3133

t.run(); 3134

return t.report(); 3135

} ///:~ 3136

Listado 4.12. C03/Sieve.cpp 3137

3138

La función find() puede recorrer el string, detectando múltiples 3139

ocurrencias de un carácter o un grupo de caracteres, y find_first_not_of() 3140

encuentra otros caracteres o subcadenas. 3141

No existen funciones en la clase string para cambiar entre 3142

mayúsculas/minúsculas en una cadena, pero puede crear esa función 3143

fácilmente usando la función de la libreria estándar de C toupper() y 3144

tolower(), que cambian los caracteres entre mayúsculas/minúsculas de uno 3145

en uno. El ejemplo siguiente ilustra una búsqueda sensible a 3146

mayúsculas/minúsculas. 3147

//: C03:Find.h 3148

#ifndef FIND_H 3149

#define FIND_H 3150

#include <cctype> 3151

Page 116: Pensar en C++ (Volumen 2)

116

#include <cstddef> 3152

#include <string> 3153

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

using std::size_t; 3155

using std::string; 3156

using std::tolower; 3157

using std::toupper; 3158

3159

// Make an uppercase copy of s 3160

inline string upperCase(const string& s) { 3161

string upper(s); 3162

for(size_t i = 0; i < s.length(); ++i) 3163

upper[i] = toupper(upper[i]); 3164

return upper; 3165

} 3166

3167

// Make a lowercase copy of s 3168

inline string lowerCase(const string& s) { 3169

string lower(s); 3170

for(size_t i = 0; i < s.length(); ++i) 3171

lower[i] = tolower(lower[i]); 3172

return lower; 3173

} 3174

3175

class FindTest : public TestSuite::Test { 3176

Page 117: Pensar en C++ (Volumen 2)

117

string chooseOne; 3177

public: 3178

FindTest() : chooseOne("Eenie, Meenie, Miney, Mo") {} 3179

void testUpper() { 3180

string upper = upperCase(chooseOne); 3181

const string LOWER = "abcdefghijklmnopqrstuvwxyz"; 3182

test_(upper.find_first_of(LOWER) == string::npos); 3183

} 3184

void testLower() { 3185

string lower = lowerCase(chooseOne); 3186

const string UPPER = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; 3187

test_(lower.find_first_of(UPPER) == string::npos); 3188

} 3189

void testSearch() { 3190

// Case sensitive search 3191

size_t i = chooseOne.find("een"); 3192

test_(i == 8); 3193

// Search lowercase: 3194

string test = lowerCase(chooseOne); 3195

i = test.find("een"); 3196

test_(i == 0); 3197

i = test.find("een", ++i); 3198

test_(i == 8); 3199

i = test.find("een", ++i); 3200

test_(i == string::npos); 3201

Page 118: Pensar en C++ (Volumen 2)

118

// Search uppercase: 3202

test = upperCase(chooseOne); 3203

i = test.find("EEN"); 3204

test_(i == 0); 3205

i = test.find("EEN", ++i); 3206

test_(i == 8); 3207

i = test.find("EEN", ++i); 3208

test_(i == string::npos); 3209

} 3210

void run() { 3211

testUpper(); 3212

testLower(); 3213

testSearch(); 3214

} 3215

}; 3216

#endif // FIND_H ///:~ 3217

Listado 4.13. C03/Find.h 3218

3219

//: C03:Find.cpp 3220

//{L} ../TestSuite/Test 3221

#include "Find.h" 3222

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

3224

int main() { 3225

Page 119: Pensar en C++ (Volumen 2)

119

FindTest t; 3226

t.run(); 3227

return t.report(); 3228

} ///:~ 3229

Listado 4.14. C03/Find.cpp 3230

3231

Tanto las funciones upperCase() como lowerCase() siguen la misma 3232

forma: hacen una copia de la cadena argumento y cambian entre 3233

mayúsculas/minúsculas. El programa Find.cpp no es la mejor solución para 3234

el problema para las mayúsculas/minúsculas, por lo que lo revisitaremos 3235

cuando examinemos la comparación entre cadenas. 3236

4.3.1. Busqueda inversa 3237

Si necesita buscar en una cadena desde el final hasta el principio (para 3238

encontrar datos en orden "último entra / primero sale"), puede usar la 3239

función miembro de string rfind(). 3240

//: C03:Rparse.h 3241

#ifndef RPARSE_H 3242

#define RPARSE_H 3243

#include <cstddef> 3244

#include <string> 3245

#include <vector> 3246

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

using std::size_t; 3248

using std::string; 3249

using std::vector; 3250

3251

Page 120: Pensar en C++ (Volumen 2)

120

class RparseTest : public TestSuite::Test { 3252

// To store the words: 3253

vector<string> strings; 3254

public: 3255

void parseForData() { 3256

// The ';' characters will be delimiters 3257

string s("now.;sense;make;to;going;is;This"); 3258

// The last element of the string: 3259

int last = s.size(); 3260

// The beginning of the current word: 3261

size_t current = s.rfind(';'); 3262

// Walk backward through the string: 3263

while(current != string::npos) { 3264

// Push each word into the vector. 3265

// Current is incremented before copying 3266

// to avoid copying the delimiter: 3267

++current; 3268

strings.push_back(s.substr(current, last - current)); 3269

// Back over the delimiter we just found, 3270

// and set last to the end of the next word: 3271

current -= 2; 3272

last = current + 1; 3273

// Find the next delimiter: 3274

current = s.rfind(';', current); 3275

} 3276

Page 121: Pensar en C++ (Volumen 2)

121

// Pick up the first word -- it's not 3277

// preceded by a delimiter: 3278

strings.push_back(s.substr(0, last)); 3279

} 3280

void testData() { 3281

// Test them in the new order: 3282

test_(strings[0] == "This"); 3283

test_(strings[1] == "is"); 3284

test_(strings[2] == "going"); 3285

test_(strings[3] == "to"); 3286

test_(strings[4] == "make"); 3287

test_(strings[5] == "sense"); 3288

test_(strings[6] == "now."); 3289

string sentence; 3290

for(size_t i = 0; i < strings.size() - 1; i++) 3291

sentence += strings[i] += " "; 3292

// Manually put last word in to avoid an extra space: 3293

sentence += strings[strings.size() - 1]; 3294

test_(sentence == "This is going to make sense now."); 3295

} 3296

void run() { 3297

parseForData(); 3298

testData(); 3299

} 3300

}; 3301

Page 122: Pensar en C++ (Volumen 2)

122

#endif // RPARSE_H ///:~ 3302

Listado 4.15. C03/Rparse.h 3303

3304

//: C03:Rparse.cpp 3305

//{L} ../TestSuite/Test 3306

#include "Rparse.h" 3307

3308

int main() { 3309

RparseTest t; 3310

t.run(); 3311

return t.report(); 3312

} ///:~ 3313

Listado 4.16. C03/Rparse.cpp 3314

3315

La función miembro de string rfind() vuelve por la cadena buscando 3316

elementos y reporta el indice del arreglo de las coincidencias de caracteres o 3317

string::npos si no tiene éxito. 3318

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

La función miembro find_first_of( ) y find_last_of( ) pueden ser 3320

convenientemente usadas para crear una pequeña utilidad the ayude a 3321

deshechar los espacion en blanco del final e inicio de la cadena. Nótese que 3322

no se toca el string originar sino que se devuelve una nuevo string: 3323

//: C03:Trim.h 3324

// General tool to strip spaces from both ends. 3325

Page 123: Pensar en C++ (Volumen 2)

123

#ifndef TRIM_H 3326

#define TRIM_H 3327

#include <string> 3328

#include <cstddef> 3329

3330

inline std::string trim(const std::string& s) { 3331

if(s.length() == 0) 3332

return s; 3333

std::size_t beg = s.find_first_not_of(" \a\b\f\n\r\t\v"); 3334

std::size_t end = s.find_last_not_of(" \a\b\f\n\r\t\v"); 3335

if(beg == std::string::npos) // No non-spaces 3336

return ""; 3337

return std::string(s, beg, end - beg + 1); 3338

} 3339

#endif // TRIM_H ///:~ 3340

Listado 4.17. C03/Trim.h 3341

3342

La primera prueba checkea si el string esta vacío; en ese caso, ya no se 3343

realizan más test, y se retorna una copia. Nótese que una vez los puntos del 3344

final son encontrados, el constructor de string construye un nuevo string 3345

desde el viejo, dándole el contador incial y la longitud. 3346

Las pruebas de una herramienta tan general den ser cuidadosas 3347

//: C03:TrimTest.h 3348

#ifndef TRIMTEST_H 3349

#define TRIMTEST_H 3350

Page 124: Pensar en C++ (Volumen 2)

124

#include "Trim.h" 3351

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

3353

class TrimTest : public TestSuite::Test { 3354

enum {NTESTS = 11}; 3355

static std::string s[NTESTS]; 3356

public: 3357

void testTrim() { 3358

test_(trim(s[0]) == "abcdefghijklmnop"); 3359

test_(trim(s[1]) == "abcdefghijklmnop"); 3360

test_(trim(s[2]) == "abcdefghijklmnop"); 3361

test_(trim(s[3]) == "a"); 3362

test_(trim(s[4]) == "ab"); 3363

test_(trim(s[5]) == "abc"); 3364

test_(trim(s[6]) == "a b c"); 3365

test_(trim(s[7]) == "a b c"); 3366

test_(trim(s[8]) == "a \t b \t c"); 3367

test_(trim(s[9]) == ""); 3368

test_(trim(s[10]) == ""); 3369

} 3370

void run() { 3371

testTrim(); 3372

} 3373

}; 3374

#endif // TRIMTEST_H ///:~ 3375

Page 125: Pensar en C++ (Volumen 2)

125

Listado 4.18. C03/TrimTest.h 3376

3377

//: C03:TrimTest.cpp {O} 3378

#include "TrimTest.h" 3379

3380

// Initialize static data 3381

std::string TrimTest::s[TrimTest::NTESTS] = { 3382

" \t abcdefghijklmnop \t ", 3383

"abcdefghijklmnop \t ", 3384

" \t abcdefghijklmnop", 3385

"a", "ab", "abc", "a b c", 3386

" \t a b c \t ", " \t a \t b \t c \t ", 3387

"\t \n \r \v \f", 3388

"" // Must also test the empty string 3389

}; ///:~ 3390

Listado 4.19. C03/TrimTest.cpp 3391

3392

//: C03:TrimTestMain.cpp 3393

//{L} ../TestSuite/Test TrimTest 3394

#include "TrimTest.h" 3395

3396

int main() { 3397

TrimTest t; 3398

t.run(); 3399

Page 126: Pensar en C++ (Volumen 2)

126

return t.report(); 3400

} ///:~ 3401

Listado 4.20. C03/TrimTestMain.cpp 3402

3403

En el arrglo de string, puede ver que los arreglos de carácter son 3404

automáticamente convertidos a objetos string. Este arreglo provee casos 3405

para checkear el borrado de espacios en blanco y tabuladores en los 3406

extremos, además de asegurar que los espacios y tabuladores no son 3407

borrados de la mitad de un string. 3408

4.3.3. Borrar caracteres de cadenas 3409

Borrar caracteres es fácil y eficiente con la función miembro erase(), que 3410

toma dos argumentos: donde empezar a borrar caracteres (que por defecto 3411

es 0), y cuantos caracteres borrar (que por defecto es string::npos). Si 3412

especifica más caracteres que los que quedan en el string, los caracteres 3413

restantes se borran igualmente (llamando erase() sin argumentos borra 3414

todos los caracteres del string). A veces es útil abrir un fichero HTML y 3415

borrar sus etiquetas y caracteres especiales de manera que tengamos algo 3416

aproximadamente igual al texto que obtendríamos en el navegador Web, sólo 3417

como un fichero de texto plano. El siguiente ejemplo usa erase() para hacer 3418

el trabajo: 3419

//: C03:HTMLStripper.cpp {RunByHand} 3420

//{L} ReplaceAll 3421

// Filter to remove html tags and markers. 3422

#include <cassert> 3423

#include <cmath> 3424

#include <cstddef> 3425

#include <fstream> 3426

Page 127: Pensar en C++ (Volumen 2)

127

#include <iostream> 3427

#include <string> 3428

#include "ReplaceAll.h" 3429

#include "../require.h" 3430

using namespace std; 3431

3432

string& stripHTMLTags(string& s) { 3433

static bool inTag = false; 3434

bool done = false; 3435

while(!done) { 3436

if(inTag) { 3437

// The previous line started an HTML tag 3438

// but didn't finish. Must search for '>'. 3439

size_t rightPos = s.find('>'); 3440

if(rightPos != string::npos) { 3441

inTag = false; 3442

s.erase(0, rightPos + 1); 3443

} 3444

else { 3445

done = true; 3446

s.erase(); 3447

} 3448

} 3449

else { 3450

// Look for start of tag: 3451

Page 128: Pensar en C++ (Volumen 2)

128

size_t leftPos = s.find('<'); 3452

if(leftPos != string::npos) { 3453

// See if tag close is in this line: 3454

size_t rightPos = s.find('>'); 3455

if(rightPos == string::npos) { 3456

inTag = done = true; 3457

s.erase(leftPos); 3458

} 3459

else 3460

s.erase(leftPos, rightPos - leftPos + 1); 3461

} 3462

else 3463

done = true; 3464

} 3465

} 3466

// Remove all special HTML characters 3467

replaceAll(s, "&lt;", "<"); 3468

replaceAll(s, "&gt;", ">"); 3469

replaceAll(s, "&amp;", "&"); 3470

replaceAll(s, "&nbsp;", " "); 3471

// Etc... 3472

return s; 3473

} 3474

3475

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

Page 129: Pensar en C++ (Volumen 2)

129

requireArgs(argc, 1, 3477

"usage: HTMLStripper InputFile"); 3478

ifstream in(argv[1]); 3479

assure(in, argv[1]); 3480

string s; 3481

while(getline(in, s)) 3482

if(!stripHTMLTags(s).empty()) 3483

cout << s << endl; 3484

} ///:~ 3485

Listado 4.21. C03/HTMLStripper.cpp 3486

3487

Este ejemplo borrará incluso las etiquetas HTML que se extienden a lo 3488

largo de varias líneas.[5] Esto se cumple gracias a la bandera estática inTag, 3489

que evalúa a cierto si el principio de una etiqueta es encontrada, pero la 3490

etiqueta de finalización correspondiente no es encontrada en la misma línea. 3491

Todas la formas de erase() aparecen en la función stripHTMLFlags().[6] La 3492

versión de getline() que usamos aquí es una función (global) declarada en 3493

la cabecera de string y es útil porque guarda una línea arbitrariamente larga 3494

en su argumento string. No necesita preocuparse de las dimensiones de un 3495

arreglo cuando trabaja con istream::getline(). Nótese que este programa 3496

usa la función replaceAll() vista antes en este capítulo. En el póximo 3497

capitulo, usaremos los flujos de cadena para crear una solución más 3498

elegante. 3499

4.3.4. Comparar cadenas 3500

Comparar cadenas es inherentemente diferente a comparar enteros. Los 3501

nombres tienen un significado universal y constante. Para evaluar la relación 3502

entre las magnitudes de dos cadenas, se necesita hacer una comparación 3503

léxica. Una comparación léxica significa que cuando se comprueba un 3504

Page 130: Pensar en C++ (Volumen 2)

130

carácter para saber si es "mayor que" o "menor que" otro carácter, está en 3505

realidad comparando la representación numérica de aquellos caracteres tal 3506

como están especificados en el orden del conjunto de caracteres que está 3507

siendo usado. La ordenación más habitual suele ser la secuencia ASCII, que 3508

asigna a los caracteres imprimibles para el lenguaje inglés números en un 3509

rango del 32 al 127 decimal. En la codificación ASCII, el primer "carácter" 3510

en la lista es el espacio, seguido de diversas marcas de puntuación común, y 3511

después las letras mayúsculas y minúsculas. Respecto al alfabeto, esto 3512

significa que las letras cercanas al principio tienen un valor ASCII menor a 3513

aquellos más cercanos al final. Con estos detalles en mente, se vuelve más 3514

fácil recordar que cuando una comparació léxica reporta que s1 es "mayor 3515

que" s2, simplemente significa que cuando fueron comparados, el primer 3516

carácter diferente en s1 estaba atrás en el alfabeto que el carácter en la 3517

misma posición en s2. 3518

C++ provee varias maneras de comparar cadenas, y cada una tiene 3519

ventajas. La más simple de usar son las funciones no-miembro 3520

sobrecargadas de operador: operator==, operator!= operator>, operator<, 3521

operator>= y operator<=. 3522

//: C03:CompStr.h 3523

#ifndef COMPSTR_H 3524

#define COMPSTR_H 3525

#include <string> 3526

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

using std::string; 3528

3529

class CompStrTest : public TestSuite::Test { 3530

public: 3531

void run() { 3532

// Strings to compare 3533

Page 131: Pensar en C++ (Volumen 2)

131

string s1("This"); 3534

string s2("That"); 3535

test_(s1 == s1); 3536

test_(s1 != s2); 3537

test_(s1 > s2); 3538

test_(s1 >= s2); 3539

test_(s1 >= s1); 3540

test_(s2 < s1); 3541

test_(s2 <= s1); 3542

test_(s1 <= s1); 3543

} 3544

}; 3545

#endif // COMPSTR_H ///:~ 3546

Listado 4.22. C03/CompStr.h 3547

3548

//: C03:CompStr.cpp 3549

//{L} ../TestSuite/Test 3550

#include "CompStr.h" 3551

3552

int main() { 3553

CompStrTest t; 3554

t.run(); 3555

return t.report(); 3556

Page 132: Pensar en C++ (Volumen 2)

132

} ///:~ 3557

Listado 4.23. C03/CompStr.cpp 3558

3559

Los operadores de comaración sobrecargados son útiles para comparar 3560

dos cadenas completas y elementos individuales de una cadena de 3561

caracteres. 3562

Nótese en el siguiente ejemplo la flexibilidad de los tipos de argumento 3563

ambos lados de los operadores de comparación. Por eficiencia, la clase 3564

string provee operadores sobrecargados para la comparación directa de 3565

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

que crear objetos string temporales. 3567

//: C03:Equivalence.cpp 3568

#include <iostream> 3569

#include <string> 3570

using namespace std; 3571

3572

int main() { 3573

string s2("That"), s1("This"); 3574

// The lvalue is a quoted literal 3575

// and the rvalue is a string: 3576

if("That" == s2) 3577

cout << "A match" << endl; 3578

// The left operand is a string and the right is 3579

// a pointer to a C-style null terminated string: 3580

if(s1 != s2.c_str()) 3581

cout << "No match" << endl; 3582

Page 133: Pensar en C++ (Volumen 2)

133

} ///:~ 3583

Listado 4.24. C03/Equivalence.cpp 3584

3585

La función c_str() retorna un const char* que apunta a una cadena estilo 3586

C terminada en nulo, equivalente en contenidos al objeto string. Esto se 3587

vuelve muy útil cuando se quiere pasar un strin a una función C, como atoi() 3588

o cualquiera de las funciones definidas en la cabecera cstring. Es un error 3589

usar el valor retornado por c_str() como un argumento constante en 3590

cualquier función. 3591

No encontrará el operador not (!) o los operadores de comparación 3592

lógicos (&& y ||) entre los operadore para string. (No encontrará ninguna 3593

versión sobrecargada de los operadores de bits de C: &, |, ^, o ~.) Los 3594

operadores de conversión no miembros sobrecargados para la clases string 3595

están limitados a un subconjunto que tiene una aplicación clara y no ambigua 3596

para caracteres individuales o grupos de caracteres. 3597

La función miembro compare() le ofrece un gran modo de comparación 3598

más sofisticado y preciso que el conjunto de operadores nomiembro. Provee 3599

versiones sobrecargadas para comparar: 3600

Dos string completos 3601

Parte de un string con un string completo 3602

Partes de dos string 3603

//: C03:Compare.cpp 3604

// Demonstrates compare() and swap(). 3605

#include <cassert> 3606

#include <string> 3607

using namespace std; 3608

3609

int main() { 3610

Page 134: Pensar en C++ (Volumen 2)

134

string first("This"); 3611

string second("That"); 3612

assert(first.compare(first) == 0); 3613

assert(second.compare(second) == 0); 3614

// Which is lexically greater? 3615

assert(first.compare(second) > 0); 3616

assert(second.compare(first) < 0); 3617

first.swap(second); 3618

assert(first.compare(second) < 0); 3619

assert(second.compare(first) > 0); 3620

} ///:~ 3621

Listado 4.25. C03/Compare.cpp 3622

3623

La función swap() en este ejemplo hace lo que su nombre implica: cambia 3624

el contenido del objeto por el del parámetro. Para comparar un subconjunto 3625

de caracteres en un o ambos string, añada argumentos que definen donde 3626

empezar y cuantos caracteres considerar. Por ejemplo, puede usar las 3627

siguientes versiones sobrecargadas de compare(): 3628

s1.compare(s1StartPos, s1NumberChars, s2, s2StartPos, 3629

s2NumberChars); 3630

Aqui un ejemplo: 3631

//: C03:Compare2.cpp 3632

// Illustrate overloaded compare(). 3633

#include <cassert> 3634

#include <string> 3635

using namespace std; 3636

Page 135: Pensar en C++ (Volumen 2)

135

3637

int main() { 3638

string first("This is a day that will live in infamy"); 3639

string second("I don't believe that this is what " 3640

"I signed up for"); 3641

// Compare "his is" in both strings: 3642

assert(first.compare(1, 7, second, 22, 7) == 0); 3643

// Compare "his is a" to "his is w": 3644

assert(first.compare(1, 9, second, 22, 9) < 0); 3645

} ///:~ 3646

Listado 4.26. C03/Compare2.cpp 3647

3648

Hasta ahora, en los ejemplos, hemos usado la sintaxis de indexación de 3649

arrays estilo C para referirnos a un carácter individual en un string. C++ 3650

provee de una alternativa a la notación s[n]: el miembro at(). Estos dos 3651

mecanismos de indexación producen los mismos resultados si todo va bien: 3652

//: C03:StringIndexing.cpp 3653

#include <cassert> 3654

#include <string> 3655

using namespace std; 3656

3657

int main() { 3658

string s("1234"); 3659

assert(s[1] == '2'); 3660

assert(s.at(1) == '2'); 3661

Page 136: Pensar en C++ (Volumen 2)

136

} ///:~ 3662

Listado 4.27. C03/StringIndexing.cpp 3663

3664

Sin embargo, existe una importante diferencia entre [ ] y at() . Cuando 3665

usted intenta referenciar el elemento de un arreglo que esta fuera de sus 3666

límites, at() tiene la delicadeza de lanzar una excepción, mientras que 3667

ordinariamente [ ] le dejará a su suerte. 3668

//: C03:BadStringIndexing.cpp 3669

#include <exception> 3670

#include <iostream> 3671

#include <string> 3672

using namespace std; 3673

3674

int main() { 3675

string s("1234"); 3676

// at() saves you by throwing an exception: 3677

try { 3678

s.at(5); 3679

} catch(exception& e) { 3680

cerr << e.what() << endl; 3681

} 3682

} ///:~ 3683

Listado 4.28. C03/BadStringIndexing.cpp 3684

3685

Page 137: Pensar en C++ (Volumen 2)

137

Los programadores responsables no usarán índices erráticos, pero puede 3686

que quiera beneficiarse de la comprobación automática de indices, 3687

usandoat() en el lugar de [ ] le da la oportunidad de recuperar 3688

diligentemente de las referencias a elementos de un arreglo que no existen. 3689

La ejecución de sobre uno de nuestros compiladores le da la siguiente salida: 3690

"invalid string position" 3691

La función miembro at() lanza un objeto de clase out_of_class, que 3692

deriva finalmente de std::exception. Capturando este objeto en un 3693

manejador de excepciones, puede tomar las medidas adecuadas como 3694

recalcular el índice incorrecto o hacer crecer el arreglo. Usar 3695

string::operator[]( ) no proporciona ningún tipo de protección y es tan 3696

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

4.3.5. Cadenas y rasgos de caracteres 3698

El programa Find.cpp anterior en este capítulo nos lleva a hacernos la 3699

pregunta obvia: ¿por que la comparación sensible a mayúsculas/minúsculas 3700

no es parte de la clase estándar string? La respuesta nos brinda un 3701

interesante transfondo sobre la verdadera naturaleza de los objetos string 3702

en C++. 3703

Considere qué significa para un carácter tener "mayúscula/minúscula". El 3704

Hebreo escrito, el Farsi, y el Kanji no usan el concepto de 3705

"mayúscula/minúscula", con lo que para esas lenguas esta idea carece de 3706

significado. Esto daria a entender que si existiera una manera de designar 3707

algunos lenguages como "todo mayúsculas" o "todo minúsculas", podriamos 3708

diseñar una solución generalizada. Sin embargo, algunos leguajes que 3709

emplean el concepto de "mayúscula/minúscula", tambien cambian el 3710

significado de caracteres particulares con acentos diacríticos, por ejemplo la 3711

cedilla del Español, el circumflexo en Francés y la diéresis en Alemán. Por 3712

esta razón, cualquier codificación sensible a mayúsculas que intenta ser 3713

comprensiva acaba siendo una pesadilla en su uso. 3714

Aunque tratamos habitualmente el string de C++ como una clase, esto no 3715

es del todo cierto. El tipo string es una especialización de algo más general, 3716

la plantilla basic_string< >. Observe como está declarada string en el 3717

fichero de cabecera de C++ estándar. 3718

Page 138: Pensar en C++ (Volumen 2)

138

typedef basic_string<char> string; 3719

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

basic_string< > 3721

template<class charT, class traits = char_traits<charT>, class 3722

allocator = allocator<charT> > class basic_string; 3723

En el Capítulo 5, examinamos las plantillas con gran detalle (mucho más 3724

que en el Capítulo 16 del volúmen 1). Por ahora nótese que el tipo string es 3725

creada cuando instanciamos la plantilla basic_string con char. Dentro de la 3726

declaración plantilla basic_string< > la línea: 3727

class traits = char_traits<charT<, 3728

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

basic_string< > es defineida por una clase basada en la plantilla 3730

char_traits< >. Así, la plantilla basic_string< > produce clases orientadas 3731

a string que manipulan otros tipos que char (caracteres anchos, por 3732

ejemplo). Para hacer esto, la plantilla char_traits< > controla el contenido y 3733

el comportamiento de la ordenación de una variedad de conjuntos de 3734

caracteres usando las funciones de comparación eq() (equal), ne() (not 3735

equal), y lt() (less than). Las funciones de comparación de basic_string< > 3736

confian en esto. 3737

Es por esto por lo que la clase string no incluye funciones miembro 3738

sensibles a mayúsculas/minúsculas: eso no esta en la descripción de su 3739

trabajo. Para cambiar la forma en que la clase string trata la comparación de 3740

caracteres, tiene que suministrar una plantilla char_traits< > diferente ya 3741

que define el comportamiento individual de las funciones miembro de 3742

comparación carácteres. 3743

Puede usar esta información para hacer un nuevo tipo de string que 3744

ignora las mayúsculas/minúsculas. Primero, definiremos una nueva plantilla 3745

no sensible a mayúsculas/minúsculas de char_traits< > que hereda de una 3746

plantilla existente. Luego, sobrescribiremos sólo los miembros que 3747

necesitamos cambiar para hacer la comparación carácter por carácter. 3748

(Además de los tres miembros de comparación léxica mencionados antes, 3749

daremos una nueva implementación para laspara las funciones de 3750

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

nueva clase basada en basic_string, pero usando nuestra plantilla 3752

Page 139: Pensar en C++ (Volumen 2)

139

insensible a mayúsculas/minúsculas, ichar_traits, como segundo 3753

argumento: 3754

//: C03:ichar_traits.h 3755

// Creating your own character traits. 3756

#ifndef ICHAR_TRAITS_H 3757

#define ICHAR_TRAITS_H 3758

#include <cassert> 3759

#include <cctype> 3760

#include <cmath> 3761

#include <cstddef> 3762

#include <ostream> 3763

#include <string> 3764

using std::allocator; 3765

using std::basic_string; 3766

using std::char_traits; 3767

using std::ostream; 3768

using std::size_t; 3769

using std::string; 3770

using std::toupper; 3771

using std::tolower; 3772

3773

struct ichar_traits : char_traits<char> { 3774

// We'll only change character-by- 3775

// character comparison functions 3776

static bool eq(char c1st, char c2nd) { 3777

Page 140: Pensar en C++ (Volumen 2)

140

return toupper(c1st) == toupper(c2nd); 3778

} 3779

static bool ne(char c1st, char c2nd) { 3780

return !eq(c1st, c2nd); 3781

} 3782

static bool lt(char c1st, char c2nd) { 3783

return toupper(c1st) < toupper(c2nd); 3784

} 3785

static int 3786

compare(const char* str1, const char* str2, size_t n) { 3787

for(size_t i = 0; i < n; ++i) { 3788

if(str1 == 0) 3789

return -1; 3790

else if(str2 == 0) 3791

return 1; 3792

else if(tolower(*str1) < tolower(*str2)) 3793

return -1; 3794

else if(tolower(*str1) > tolower(*str2)) 3795

return 1; 3796

assert(tolower(*str1) == tolower(*str2)); 3797

++str1; ++str2; // Compare the other chars 3798

} 3799

return 0; 3800

} 3801

static const char* 3802

Page 141: Pensar en C++ (Volumen 2)

141

find(const char* s1, size_t n, char c) { 3803

while(n-- > 0) 3804

if(toupper(*s1) == toupper(c)) 3805

return s1; 3806

else 3807

++s1; 3808

return 0; 3809

} 3810

}; 3811

3812

typedef basic_string<char, ichar_traits> istring; 3813

3814

inline ostream& operator<<(ostream& os, const istring& s) { 3815

return os << string(s.c_str(), s.length()); 3816

} 3817

#endif // ICHAR_TRAITS_H ///:~ 3818

Listado 4.29. C03/ichar_traits.h 3819

3820

Proporcionamos un typedef llamado istring ya que nuestra clase actuará 3821

como un string ordinario en todas sus formas, excepto que realizará todas 3822

las comparaciones sin respetar las mayúsculas/minúsculas. Por 3823

conveniencia, damos un operador sobrecargado operator<<() para que 3824

pueda imprimir los istring. Aque hay un ejemplo: 3825

//: C03:ICompare.cpp 3826

#include <cassert> 3827

Page 142: Pensar en C++ (Volumen 2)

142

#include <iostream> 3828

#include "ichar_traits.h" 3829

using namespace std; 3830

3831

int main() { 3832

// The same letters except for case: 3833

istring first = "tHis"; 3834

istring second = "ThIS"; 3835

cout << first << endl; 3836

cout << second << endl; 3837

assert(first.compare(second) == 0); 3838

assert(first.find('h') == 1); 3839

assert(first.find('I') == 2); 3840

assert(first.find('x') == string::npos); 3841

} ///:~ 3842

Listado 4.30. C03/ICompare.cpp 3843

3844

Este es solo un ejemplo de prueba. Para hacer istring completamente 3845

equivalente a un string, deberiamos haber creado las otras funciones 3846

necesarias para soportar el nuevo tipo istring. 3847

La cabecera <string> provee de un string ancho [8] gracias al siguiente 3848

typedef: 3849

typedef basic_string<wchar_t> wstring; 3850

El soporte para string ancho se revela tambien en los streams anchos 3851

(wostream en lugar de ostream, tambien definido en <iostream>) y en la 3852

especialización de wchar_t de los char_traits en la libreria estándar le da la 3853

posibilidad de hacer una version de carácter ancho de ichar_traits 3854

Page 143: Pensar en C++ (Volumen 2)

143

//: C03:iwchar_traits.h {-g++} 3855

// Creating your own wide-character traits. 3856

#ifndef IWCHAR_TRAITS_H 3857

#define IWCHAR_TRAITS_H 3858

#include <cassert> 3859

#include <cmath> 3860

#include <cstddef> 3861

#include <cwctype> 3862

#include <ostream> 3863

#include <string> 3864

3865

using std::allocator; 3866

using std::basic_string; 3867

using std::char_traits; 3868

using std::size_t; 3869

using std::towlower; 3870

using std::towupper; 3871

using std::wostream; 3872

using std::wstring; 3873

3874

struct iwchar_traits : char_traits<wchar_t> { 3875

// We'll only change character-by- 3876

// character comparison functions 3877

static bool eq(wchar_t c1st, wchar_t c2nd) { 3878

return towupper(c1st) == towupper(c2nd); 3879

Page 144: Pensar en C++ (Volumen 2)

144

} 3880

static bool ne(wchar_t c1st, wchar_t c2nd) { 3881

return towupper(c1st) != towupper(c2nd); 3882

} 3883

static bool lt(wchar_t c1st, wchar_t c2nd) { 3884

return towupper(c1st) < towupper(c2nd); 3885

} 3886

static int compare( 3887

const wchar_t* str1, const wchar_t* str2, size_t n) { 3888

for(size_t i = 0; i < n; i++) { 3889

if(str1 == 0) 3890

return -1; 3891

else if(str2 == 0) 3892

return 1; 3893

else if(towlower(*str1) < towlower(*str2)) 3894

return -1; 3895

else if(towlower(*str1) > towlower(*str2)) 3896

return 1; 3897

assert(towlower(*str1) == towlower(*str2)); 3898

++str1; ++str2; // Compare the other wchar_ts 3899

} 3900

return 0; 3901

} 3902

static const wchar_t* 3903

find(const wchar_t* s1, size_t n, wchar_t c) { 3904

Page 145: Pensar en C++ (Volumen 2)

145

while(n-- > 0) 3905

if(towupper(*s1) == towupper(c)) 3906

return s1; 3907

else 3908

++s1; 3909

return 0; 3910

} 3911

}; 3912

3913

typedef basic_string<wchar_t, iwchar_traits> iwstring; 3914

3915

inline wostream& operator<<(wostream& os, 3916

const iwstring& s) { 3917

return os << wstring(s.c_str(), s.length()); 3918

} 3919

#endif // IWCHAR_TRAITS_H ///:~ 3920

Listado 4.31. C03/iwchar_traits.h 3921

3922

Como puede ver, esto es principalmente un ejercicio de poner 'w' en el 3923

lugar adecuado del código fuente. El programa de prueba podria ser asi: 3924

//: C03:IWCompare.cpp {-g++} 3925

#include <cassert> 3926

#include <iostream> 3927

#include "iwchar_traits.h" 3928

Page 146: Pensar en C++ (Volumen 2)

146

using namespace std; 3929

3930

int main() { 3931

// The same letters except for case: 3932

iwstring wfirst = L"tHis"; 3933

iwstring wsecond = L"ThIS"; 3934

wcout << wfirst << endl; 3935

wcout << wsecond << endl; 3936

assert(wfirst.compare(wsecond) == 0); 3937

assert(wfirst.find('h') == 1); 3938

assert(wfirst.find('I') == 2); 3939

assert(wfirst.find('x') == wstring::npos); 3940

} ///:~ 3941

Listado 4.32. C03/IWCompare.cpp 3942

3943

Desgraciadamente, todavia algunos compiladores siguen sin ofrecer un 3944

soporte robusto para caracteres anchos. 3945

4.4. Una aplicación con cadenas 3946

Si ha observado atentamente los códigos de ejemplo de este libro, habrá 3947

observado que ciertos elementos en los comentarios envuelven el código. 3948

Son usados por un programa en Python que escribió Bruce para extraer el 3949

código en ficheros y configurar makefiles para construir el código. Por 3950

ejemplo, una doble barra segida de dos puntos en el comienzo de una línea 3951

denota la primera línea de un fichero de código . El resto de la línea contiene 3952

información describiendo el nombre del fichero y su locaización y cuando 3953

deberia ser solo compilado en vez constituir un fichero ejecutable. Por 3954

ejemplo, la primera línea del programa anterior contiene la cadena 3955

Page 147: Pensar en C++ (Volumen 2)

147

C03:IWCompare.cpp, indicando que el fichero IWCompare.cpp deberia ser 3956

extraido en el directorio C03. 3957

La última línea del fichero fuente contiene una triple barra seguida de dos 3958

puntos y un signo "~". Es la primera línea tiene una exclamación 3959

inmediatamente después de los dos puntos, la primera y la última línea del 3960

código fuente no son para ser extraídas en un fichero (solo es para ficheros 3961

solo de datos). (Si se está preguntando por que evitamos mostrar estos 3962

elementos, es por que no queremos romper el extractor de código cuando lo 3963

aplicamos al texto del libro!). 3964

El programa en Python de Bruce hace muchas más cosas que 3965

simplemente extraer el código. Si el elemento "{O}" sigue al nombre del 3966

fichero, su entrada en el makefile solo será configurada para compilar y no 3967

para enlazarla en un ejecutable. (El Test Framework en el Capítulo 2 está 3968

contruida de esta manera). Para enlazar un fichero con otro fuente de 3969

ejemplo, el fichero fuente del ejecutable objetivo contendrá una directiva 3970

"{L}", como aquí: 3971

//{L} ../TestSuite/Test 3972

Esta sección le presentará un programa para extraer todo el código para 3973

que pueda compilarlo e inspeccionarlo manualmente. Puede usar este 3974

programa para extraer todo el codigo de este libro salvando el fichero como 3975

un fichero de texto[9] (llamémosle TICV2.txt)y ejecutando algo como la 3976

siguiente línea de comandos: C:> extractCode TICV2.txt /TheCode 3977

Este comando lee el fichero de texto TICV2.txt y escribe todos los archivos 3978

de código fuente en subdirectorios bajo el definido /TheCode. El arbol de 3979

directorios se mostrará como sigue: 3980

TheCode/ C0B/ C01/ C02/ C03/ C04/ C05/ C06/ C07/ C08/ C09/ C10/ 3981

C11/ TestSuite/ 3982

Los ficheros de código fuente que contienen los ejemplos de cada capítulo 3983

estarán en el correspondiente directorio. 3984

Aquí está el programa: 3985

//: C03:ExtractCode.cpp {-edg} {RunByHand} 3986

// Extracts code from text. 3987

Page 148: Pensar en C++ (Volumen 2)

148

#include <cassert> 3988

#include <cstddef> 3989

#include <cstdio> 3990

#include <cstdlib> 3991

#include <fstream> 3992

#include <iostream> 3993

#include <string> 3994

using namespace std; 3995

3996

// Legacy non-standard C header for mkdir() 3997

#if defined(__GNUC__) || defined(__MWERKS__) 3998

#include <sys/stat.h> 3999

#elif defined(__BORLANDC__) || defined(_MSC_VER) \ 4000

|| defined(__DMC__) 4001

#include <direct.h> 4002

#else 4003

#error Compiler not supported 4004

#endif 4005

4006

// Check to see if directory exists 4007

// by attempting to open a new file 4008

// for output within it. 4009

bool exists(string fname) { 4010

size_t len = fname.length(); 4011

if(fname[len-1] != '/' && fname[len-1] != '\\') 4012

Page 149: Pensar en C++ (Volumen 2)

149

fname.append("/"); 4013

fname.append("000.tmp"); 4014

ofstream outf(fname.c_str()); 4015

bool existFlag = outf; 4016

if(outf) { 4017

outf.close(); 4018

remove(fname.c_str()); 4019

} 4020

return existFlag; 4021

} 4022

4023

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

// See if input file name provided 4025

if(argc == 1) { 4026

cerr << "usage: extractCode file [dir]" << endl; 4027

exit(EXIT_FAILURE); 4028

} 4029

// See if input file exists 4030

ifstream inf(argv[1]); 4031

if(!inf) { 4032

cerr << "error opening file: " << argv[1] << endl; 4033

exit(EXIT_FAILURE); 4034

} 4035

// Check for optional output directory 4036

string root("./"); // current is default 4037

Page 150: Pensar en C++ (Volumen 2)

150

if(argc == 3) { 4038

// See if output directory exists 4039

root = argv[2]; 4040

if(!exists(root)) { 4041

cerr << "no such directory: " << root << endl; 4042

exit(EXIT_FAILURE); 4043

} 4044

size_t rootLen = root.length(); 4045

if(root[rootLen-1] != '/' && root[rootLen-1] != '\\') 4046

root.append("/"); 4047

} 4048

// Read input file line by line 4049

// checking for code delimiters 4050

string line; 4051

bool inCode = false; 4052

bool printDelims = true; 4053

ofstream outf; 4054

while(getline(inf, line)) { 4055

size_t findDelim = line.find("//" "/:~"); 4056

if(findDelim != string::npos) { 4057

// Output last line and close file 4058

if(!inCode) { 4059

cerr << "Lines out of order" << endl; 4060

exit(EXIT_FAILURE); 4061

} 4062

Page 151: Pensar en C++ (Volumen 2)

151

assert(outf); 4063

if(printDelims) 4064

outf << line << endl; 4065

outf.close(); 4066

inCode = false; 4067

printDelims = true; 4068

} else { 4069

findDelim = line.find("//" ":"); 4070

if(findDelim == 0) { 4071

// Check for '!' directive 4072

if(line[3] == '!') { 4073

printDelims = false; 4074

++findDelim; // To skip '!' for next search 4075

} 4076

// Extract subdirectory name, if any 4077

size_t startOfSubdir = 4078

line.find_first_not_of(" \t", findDelim+3); 4079

findDelim = line.find(':', startOfSubdir); 4080

if(findDelim == string::npos) { 4081

cerr << "missing filename information\n" << endl; 4082

exit(EXIT_FAILURE); 4083

} 4084

string subdir; 4085

if(findDelim > startOfSubdir) 4086

subdir = line.substr(startOfSubdir, 4087

Page 152: Pensar en C++ (Volumen 2)

152

findDelim - startOfSubdir); 4088

// Extract file name (better be one!) 4089

size_t startOfFile = findDelim + 1; 4090

size_t endOfFile = 4091

line.find_first_of(" \t", startOfFile); 4092

if(endOfFile == startOfFile) { 4093

cerr << "missing filename" << endl; 4094

exit(EXIT_FAILURE); 4095

} 4096

// We have all the pieces; build fullPath name 4097

string fullPath(root); 4098

if(subdir.length() > 0) 4099

fullPath.append(subdir).append("/"); 4100

assert(fullPath[fullPath.length()-1] == '/'); 4101

if(!exists(fullPath)) 4102

#if defined(__GNUC__) || defined(__MWERKS__) 4103

mkdir(fullPath.c_str(), 0); // Create subdir 4104

#else 4105

mkdir(fullPath.c_str()); // Create subdir 4106

#endif 4107

fullPath.append(line.substr(startOfFile, 4108

endOfFile - startOfFile)); 4109

outf.open(fullPath.c_str()); 4110

if(!outf) { 4111

cerr << "error opening " << fullPath 4112

Page 153: Pensar en C++ (Volumen 2)

153

<< " for output" << endl; 4113

exit(EXIT_FAILURE); 4114

} 4115

inCode = true; 4116

cout << "Processing " << fullPath << endl; 4117

if(printDelims) 4118

outf << line << endl; 4119

} 4120

else if(inCode) { 4121

assert(outf); 4122

outf << line << endl; // Output middle code line 4123

} 4124

} 4125

} 4126

exit(EXIT_SUCCESS); 4127

} ///:~ 4128

Listado 4.33. C03/ExtractCode.cpp 4129

4130

Primero observará algunas directivas de compilación condicionales. La 4131

función mkdir(), que crea un directorio en el sistema de ficheros, se define 4132

por el estándar POSIX[10] en la cabecera (<direct.h>). La respectiva signatura 4133

de mkdir() también difiere: POSIX especifica dos argumentos, las viejas 4134

versiones sólo uno. Por esta razón, existe más de una directiva de 4135

compilación condicional después en el programa para elegir la llamada 4136

correcta a mkdir(). Normalmente no usamos compilaciones condicionales en 4137

los ejemplos de este libro, pero en este programa en particular es demasiado 4138

Page 154: Pensar en C++ (Volumen 2)

154

útil para no poner un poco de trabajo extra dentro, ya que puede usarse para 4139

extraer todo el código con él. 4140

La función exists() en ExtractCode.cpp prueba que un directorio existe 4141

abriendo un fiechero temporal en él. Si la obertura falla, el directorio no 4142

existe. Borre el fichero enviando su nombre como unchar* a std::remove(). 4143

El programa principal valida los argumentos de la línea de comandos y 4144

después lee el fichero de entrada línea por línea, mirando por los 4145

delimitadores especiales de código fuente. La bandera booleana inCode 4146

indica que el programa esta en el medio de un fichero fuente, así que las 4147

lineas deben ser extraídas. La bandera printDelims será verdadero si el 4148

elemento de obertura no está seguido de un signo de exclamanción; si no la 4149

primera y la última línea no son escritas. Es importante comprobar el último 4150

delimitador primero, por que el elemnto inicial es un subconjuntom y 4151

buscando por el elemento inicial debería retornar cierto en ambos casos. Si 4152

encontramos el elemento final, verificamos que estamos en el medio del 4153

procesamiento de un fichero fuente; sino, algo va mal con la manera en que 4154

los delimitadores han sido colocados en el fichero de texto. Si inCode es 4155

verdadero, todo está bien, y escribiremos (opcionalmente) la última linea y 4156

cerraremos el fichero. Cuando el elemento de obertura se encuentra, 4157

procesamos el directorio y el nombre del fichero y abrimos el fichero. Las 4158

siguientes funciones relacionadas con string fueron usadas en este ejemplo: 4159

length( ), append( ), getline( ), find( ) (dos versiones), 4160

find_first_not_of( ), substr( ),find_first_of( ), c_str( ), y, por 4161

supuesto, operator<<( ) 4162

4.5. Resumen 4163

Los objetos string proporcionan a los desarrolladores un gran número de 4164

ventajas sobre sus contrapartidas en C. La mayoria de veces, la clase string 4165

hacen a las cadenas con punteros a caracteres innecesarios. Esto elimina 4166

por completo una clase de defectos de software que radican en el uso de 4167

punteros no inicializados o con valores incorrectos. 4168

FIXME: Los string de C++, de manera transparente y dinámica, hacen 4169

crecer el espacio de alamcenamiento para acomodar los cambios de tamaño 4170

Page 155: Pensar en C++ (Volumen 2)

155

de los datos de la cadena. Cuando los datos en n string crece por encima 4171

de los límites de la memoria asignada inicialmente para ello, el objeto string 4172

hará las llamadas para la gestión de la memoria para obtener el espacio y 4173

retornar el espacio al montón. La gestión consistente de la memoria 4174

previente lagunas de memoria y tiene el potencial de ser mucho más 4175

eficiente que un "hágalo usted mismo". 4176

Las funciones de la clase string proporcionan un sencillo y comprensivo 4177

conjunto de herramientas para crear, modificar y buscar en cadenas. Las 4178

comparaciones entre string siempre son sensibles a 4179

mayúsculas/minúsculas, pero usted puede solucionar el problema copiando 4180

los datos a una cadena estilo C acabada en nulo y usando funciones no 4181

sensibles a mayúsculas/minúsculas, convirtiendo temporalmente los datos 4182

contenidos a mayúsculas o minúsculas, o creando una clase string sensible 4183

que sobreescribe los rasgos de carácter usados para crear un objeto 4184

basic_string 4185

4.6. Ejercicios 4186

Las soluciones a los ejercicios se pueden encontrar en el documento 4187

electrónico titulado «The Thinking in C++ Annotated Solution Guide», 4188

disponible por poco dinero en http://www.bruceeckel.com/. 4189

1. Escriba y pruebe una función que invierta el orden de los 4190

caracteres en una cadena. 4191

2. 2. Un palindromo es una palabra o grupo de palabras que tanto 4192

hacia delante hacia atrás se leen igual. Por ejemplo "madam" o 4193

"wow". Escriba un programa que tome un string como argumento 4194

desde la línea de comandos y, usando la función del ejercicio anterior, 4195

escriba si el string es un palíndromo o no. 4196

3. 3. Haga que el programa del Ejercicio 2 retorne verdadero 4197

incluso si las letras simetricas difieren en mayúsculas/minúsculas. Por 4198

ejemplo, "Civic" debería retornar verdadero aunque la primera letra 4199

sea mayúscula. 4200

Page 156: Pensar en C++ (Volumen 2)

156

4. 4. Cambie el programa del Ejercicio 3 para ignorar la puntuación 4201

y los espacios también. Por ejemplo "Able was I, ere I saw Elba." 4202

debería retornar verdadero. 4203

5. 5. Usando las siguientes declaraciones de string y solo char (no 4204

literales de cadena o números mágicos): 4205

string one("I walked down the canyon with the moving 4206

mountain bikers."); string two("The bikers passed by me too 4207

close for comfort."); string three("I went hiking instead."); 4208

produzca la siguiente frase: 4209

I moved down the canyon with the mountain bikers. The mountain 4210

bikers passed by me too close for comfort. So I went hiking instead. 4211

6. 6. Escriba un programa llamado "reemplazo" que tome tres 4212

argumentos de la línea de comandos representando un fichero de 4213

texto de entrada, una frase para reemplazar (llámela from), y una 4214

cadena de reemplazo (llámela to). El programa debería escribir un 4215

nuevo fichero en la salida estandar con todas las ocurrencias de from 4216

reemplazadas por to. 4217

7. 7. Repetir el ejercicio anterior pero reemplazando todas las 4218

instancias pero ignorando las mayúsculas/minúsculas. 4219

8. 8. Haga su programa a partir del Ejercicio 3 tomando un nombre 4220

de fichero de la linea de comandos, y despues mostrando todas las 4221

palabras que son palíndromos (ignorando las mayúsculas/minúsculas) 4222

en el fichero. No intente buscar palabras para palíndromos que son 4223

mas largos que una palabra (a diferencia del ejercicio 4). 4224

9. 9. Modifique HTMLStripper.cpp para que cuando encuentre una 4225

etiqueta, muestre el nombre de la etiqueta, entonces muestre el 4226

contenido del fichero entre la etiqueta y la etiqueta de finalización de 4227

fichero. Asuma que no existen etiquetas anidadas, y que todas las 4228

etiquetas tienen etiquetas de finalizacion (denotadas con 4229

</TAGNAME>). 4230

10. 10. Escriba un programa que tome tres argumentos de la línea 4231

de comandos (un nombre de fichero y dos cadenas) y muestre en la 4232

consola todas la líneas en el fichero que tengan las dos cadenas en la 4233

Page 157: Pensar en C++ (Volumen 2)

157

línea, alguna cadena, solo una cadena o ninguna de ellas, basándose 4234

en la entreada de un usuario al principio del programa (el usuario 4235

elegirá que modo de búsqueda usar). Para todo excepto para la 4236

opción "ninguna cadena", destaque la cadena(s) de entrada colocando 4237

un asterisco (*) al principio y al final de cada cadena que coincida 4238

cuando sea mostrada. 4239

11. 11. Escriba un programa que tome dos argumentos de la linea 4240

de comandos (un nombre de fichero y una cadena) y cuente el 4241

numero de veces que la cadena esta en el fichero, incluso si es una 4242

subcadena (pero ignorando los solapamientos). Por ejemplo, una 4243

cadena de entrada de "ba" debería coincidir dos veces en la palabra 4244

"basquetball", pero la cadena de entrada "ana" solo deberia coincidir 4245

una vez en "banana". Muestre por la consola el número de veces que 4246

la cadena coincide en el fichero, igual que la longitud media de las 4247

palabras donde la cadena coincide. (Si la cadena coincide más de una 4248

vez en una palabra, cuente solamente la palabra una vez en el cálculo 4249

de la media). 4250

12. 12. Escriba un programa que tome un nombre de fichero de la 4251

línea de comandos y perfile el uso del carácter, incluyendo la 4252

puntuación y los espacios (todos los valores de caracteres desde el 4253

0x21 [33] hasta el 0x7E [126], además del carácter de espacio). Esto 4254

es, cuente el numero de ocurrencias para cada carácter en el fichero, 4255

después muestre los resultados ordenados secuencialmente (espacio, 4256

despues !, ", #, etc.) o por frecuencia descendente o ascendente 4257

basado en una entrada de usuario al principio del programa. Para el 4258

espacio, muestre la palabra "espacio" en vez del carácter ' '. Una 4259

ejecución de ejemplo debe mostrarse como esto: 4260

Formato secuencial, ascendente o descendente (S/A/D): D t: 526 r: 4261

490 etc. 4262

13. 13. Usando find() y rfind(), escriba un programa que tome dos 4263

argumentos de lánea de comandos (un nombre de fichero y una 4264

cadena) y muestre la primera y la última palapra (y sus indices) que no 4265

coinciden con la cadena, asi como los indice de la primera y la última 4266

Page 158: Pensar en C++ (Volumen 2)

158

instancia de la cadena. Muestre "No Encontrado" si alguna de las 4267

busquedas fallan. 4268

14. 14. Usando la familia de fuciones find_first_of (pero no 4269

exclusivamente), escriba un programa que borrará todos los 4270

caracteres no alfanuméricos excepto los espacios y los puntos de un 4271

fichero. Despues convierta a mayúsculas la primera letra que siga a 4272

un punto. 4273

15. 15. Otra vez, usando la familia de funciones find_first_of, 4274

escriba un programa que acepte un nombre de fichero como 4275

argumentod elinea de comandos y después formatee todos los 4276

números en un fichero de moneda. Ignore los puntos decimales 4277

después del primero despues de un carácter no mumérico, e 4278

redondee al 4279

16. 16. Escriba un programa que acepte dos argumentos por línea 4280

de comandos (un nombre de fichero y un numero) y mezcle cada 4281

paralabra en el fichero cambiando aleatoriamente dos de sus letras el 4282

número de veces especificado en el segundo parametro. (Esto es, si 4283

le pasamos 0 a su programa desde la línea de comandos, las palabras 4284

no serán mezcladas; si le pasamos un 1, un par de letras 4285

aleatoriamente elegidas deben ser cambiadas, para una entrada de 2, 4286

dos parejas aleatorias deben ser intercambiadas, etc.). 4287

17. 17. Escriba un programa que acepte un nombre de fichero desde 4288

la línea de comandos y muestre el numero de frases (definido como el 4289

numero de puntos en el fichero), el número medio de caracteres por 4290

frase, y el número total de caracteres en el fichero. 4291

4292

4293

[1] Algunos materiales de este capítulo fueron creados originalmente por 4294

Nancy Nicolaisen 4295

[2] Es dificil hacer implementaciones con multiples referencias para trabajar de 4296

manera segura en multihilo. (Ver [More Exceptional C++, pp.104-14]). Ver 4297

Capitulo 10 para más información sobre multiples hilos 4298

[3] Es una abrviación de "no position", y su valor más alto puede ser 4299

representado por el ubicador de string size_type (std::size_t por defecto). 4300

Page 159: Pensar en C++ (Volumen 2)

159

[4] Descrito en profundidad en el Capítulo 6. 4301

[5] Para mantener la exposición simple, esta version no maneja etiquetas 4302

anidadas, como los comentarios. 4303

[6] Es tentador usar aquí las matemáticas para evitar algunas llamadas a 4304

erase(), pero como en algunos casos uno de los operandos es string::npos 4305

(el entero sin signo más grande posible), ocurre un desbordamiento del entero 4306

y se cuelga el algoritmo. 4307

[7] Por las razones de seguridad mencionadas, el C++ Standards Committee 4308

está considerando una propuesta de redefinición del string::operator[] para 4309

comportarse de manera idéntica al string::at() para C++0x. 4310

[8] (N.del T.) Se refiere a string amplio puesto que esta formado por 4311

caracteres anchos wchar_t que deben soportar la codificación mas grande que 4312

soporte el compilador. Casi siempre esta codificación es Unicode, por lo que 4313

casi siempre el ancho de wchar_t es 2 bytes 4314

[9] Esté alerta porque algunas versiones de Microsoft Word que substituyen 4315

erroneamente los caracteres con comilla simple con un carácter ASCII cuando 4316

salva el documento como texto, causan un error de compilación. No tenemos 4317

idea de porqué pasa esto. Simplemente reemplace el carácter manualmente 4318

con un apóstrofe. 4319

[10] POSIX, un estándar IEEE, es un "Portable Operating System Interface" 4320

(Interficie de Sistema Operativo Portable) y es una generalización de muchas 4321

de las llamadas a sistema de bajo nivel encontradas en los sistemas UNIX. 4322

5: Iostreams 4323

Tabla de contenidos 4324

5.1. ¿Por que iostream? 4325

5.2. Iostreams al rescate 4326

5.3. Manejo errores de stream 4327

5.4. Iostreams de fichero 4328

5.5. Almacenamiento de iostream 4329

5.6. Buscar en iostreams 4330

5.7. Iostreams de string 4331

5.8. Formateo de stream de salida 4332

5.9. Manipuladores 4333

5.10. 4334

5.11. 4335

5.12. 4336

5.13. 4337 Puedes hacer mucho más con el problema general de E/S que 4338

simplemente coger el E/S estándar y convertirlo en una clase. 4339

¿No seria genial si pudiera hacer que todos los 'receptáculos' -E/S 4340

estándar, ficheros, e incluso boques de memoria- parecieran iguales de 4341

Page 160: Pensar en C++ (Volumen 2)

160

manera que solo tuviera que recordar una interficie? Esta es la idea que hay 4342

detrás de los iostreams. Son mucho más sencillos, seguros, y a veces 4343

incluso más eficientes que el conjunto de funciones de la libreria estándar de 4344

C stdio. 4345

Las clases de iostream son generalmente la primera parte de la libreria de 4346

C++ que los nuevos programadores de C++ parender a usar. En este 4347

capítulo se discute sobre las mejoras que representan los iostream sobre las 4348

funciones de stdio de C y explora el comprotamiento de los ficheros y 4349

streams de strings además de los streams de consola. 4350

5.1. ¿Por que iostream? 4351

Se debe estar preguntando que hay de malo en la buena y vieja librería de 4352

C. ¿Por que no 'incrustar' la libreria de C en una clase y ya está? A veces 4353

esta solución es totalmente válida. Por ejemplo, suponga que quiere estar 4354

seguro que un fichero representado por un puntero de stdio FILE siempre es 4355

abierto de forma segura y cerrado correctamente sin tener que confiar en que 4356

el usuario se acuerde de llamar a la función close(). El siguiente programa 4357

es este intento: 4358

//: C04:FileClass.h 4359

// stdio files wrapped. 4360

#ifndef FILECLASS_H 4361

#define FILECLASS_H 4362

#include <cstdio> 4363

#include <stdexcept> 4364

4365

class FileClass { 4366

std::FILE* f; 4367

public: 4368

struct FileClassError : std::runtime_error { 4369

Page 161: Pensar en C++ (Volumen 2)

161

FileClassError(const char* msg) 4370

: std::runtime_error(msg) {} 4371

}; 4372

FileClass(const char* fname, const char* mode = "r"); 4373

~FileClass(); 4374

std::FILE* fp(); 4375

}; 4376

#endif // FILECLASS_H ///:~ 4377

Listado 5.1. C04/FileClass.h 4378

4379

Cuando trabaja con ficheros E/S en C, usted trabaja con punteros 4380

desnudos a una struct de FILE, pero esta clase envuelve los punteros y 4381

garantiza que es correctamente inicializada y destruida usando el constructor 4382

y el destructor. El segundo parámetro del constructor es el modo del fichero, 4383

que por defecto es 'r' para 'leer' 4384

Para pedir el valor del puntero para usarlo en las funciones de fichero de 4385

E/S, use la función de acceso fp(). Aquí están las definiciones de las 4386

funciones miembro: 4387

//: C04:FileClass.cpp {O} 4388

// FileClass Implementation. 4389

#include "FileClass.h" 4390

#include <cstdlib> 4391

#include <cstdio> 4392

using namespace std; 4393

4394

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

Page 162: Pensar en C++ (Volumen 2)

162

if((f = fopen(fname, mode)) == 0) 4396

throw FileClassError("Error opening file"); 4397

} 4398

4399

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

4401

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

Listado 5.2. C04/FileClass.cpp 4403

4404

El constructor llama a fopen(), tal como se haría normalmente, pero 4405

además se asegura que el resultado no es cero, que indica un error al abrir el 4406

fichero. Si el fichero no se abre correctamente, se lanza una excepción. 4407

El destructor cierra el fichero, y la función de acceso fp() retorna f. Este 4408

es un ejemplo de uso de FileClass: 4409

//: C04:FileClassTest.cpp 4410

//{L} FileClass 4411

#include <cstdlib> 4412

#include <iostream> 4413

#include "FileClass.h" 4414

using namespace std; 4415

4416

int main() { 4417

try { 4418

FileClass f("FileClassTest.cpp"); 4419

const int BSIZE = 100; 4420

Page 163: Pensar en C++ (Volumen 2)

163

char buf[BSIZE]; 4421

while(fgets(buf, BSIZE, f.fp())) 4422

fputs(buf, stdout); 4423

} catch(FileClass::FileClassError& e) { 4424

cout << e.what() << endl; 4425

return EXIT_FAILURE; 4426

} 4427

return EXIT_SUCCESS; 4428

} // File automatically closed by destructor 4429

///:~ 4430

Listado 5.3. C04/FileClassTest.cpp 4431

4432

Se crea el objeto FileClass y se usa en llamadas a funciones E/S de 4433

fichero normal de C, llamando a fp(). Cuando haya acabado con ella, 4434

simplemente olvídese; el fichero será cerrado por el destructor al final del 4435

ámbito de la variable. 4436

Incluso teniendo en cuenta que FILE es un puntero privado, no es 4437

particularmente seguro porque fp() lo recupera. Ya que el único efecto que 4438

parece estar garantizado es la inicialización y la liberación, ¿por que no 4439

hacerlo público o usar una struct en su lugar? Nótese que mientras se 4440

puede obtener una copia de f usando fp(), no se puede asignar a f -que 4441

está completamente bajo el control de la clase. Después de capturar el 4442

puntero retornado por fp(), el programador cliente todavía puede asignar a 4443

la estructura elementos o incluso cerrarlo, con lo que la seguridad esta en la 4444

garantía de un puntero a FILE válido mas que en el correcto contenido de la 4445

estructura. 4446

Si quiere completa seguridad, tiene que evitar que el usuario acceda 4447

directamente al puntero FILE. Cada una de las versiones de las funciones 4448

normales de E/S a ficheros deben ser mostradas como miembros de clase 4449

Page 164: Pensar en C++ (Volumen 2)

164

para que todo lo que se pueda hacer desde el acercamiento de C esté 4450

disponible en la clase de C++. 4451

//: C04:Fullwrap.h 4452

// Completely hidden file IO. 4453

#ifndef FULLWRAP_H 4454

#define FULLWRAP_H 4455

#include <cstddef> 4456

#include <cstdio> 4457

#undef getc 4458

#undef putc 4459

#undef ungetc 4460

using std::size_t; 4461

using std::fpos_t; 4462

4463

class File { 4464

std::FILE* f; 4465

std::FILE* F(); // Produces checked pointer to f 4466

public: 4467

File(); // Create object but don't open file 4468

File(const char* path, const char* mode = "r"); 4469

~File(); 4470

int open(const char* path, const char* mode = "r"); 4471

int reopen(const char* path, const char* mode); 4472

int getc(); 4473

int ungetc(int c); 4474

Page 165: Pensar en C++ (Volumen 2)

165

int putc(int c); 4475

int puts(const char* s); 4476

char* gets(char* s, int n); 4477

int printf(const char* format, ...); 4478

size_t read(void* ptr, size_t size, size_t n); 4479

size_t write(const void* ptr, size_t size, size_t n); 4480

int eof(); 4481

int close(); 4482

int flush(); 4483

int seek(long offset, int whence); 4484

int getpos(fpos_t* pos); 4485

int setpos(const fpos_t* pos); 4486

long tell(); 4487

void rewind(); 4488

void setbuf(char* buf); 4489

int setvbuf(char* buf, int type, size_t sz); 4490

int error(); 4491

void clearErr(); 4492

}; 4493

#endif // FULLWRAP_H ///:~ 4494

Listado 5.4. C04/Fullwrap.h 4495

4496

Esta clase contiene casi todas las funciones de E/S de fichero de 4497

<cstdio>. (vfprintf() no esta; se implementa en la función miembro 4498

printf() ) 4499

Page 166: Pensar en C++ (Volumen 2)

166

El fichero tiene el mismo constructor que en el ejemplo anterior, y también 4500

tiene un constructor por defecto. El constructor por defecto es importante si 4501

se crea un array de objetos File o se usa un objeto File como miembro de 4502

otra clase donde la inicialización no se realiza en el contructor, sino cierto 4503

tiempo después de que el objeto envolvente se cree. 4504

El constructor por defecto pone a cero el puntero a FILE privado f. Pero 4505

ahora , antes de cualquier referencia a f, el valor debe ser comprobado para 4506

asegurarse que no es cero. Esto se consigue con F(), que es privado porque 4507

está pensado para ser usado solamente por otras funciones miembro. (No 4508

queremos dar acceso directo a usuarios a la estructura de FILE subyacente 4509

en esta clase). 4510

Este acercamiento no es terrible en ningún sentido. Es bastante funcional, 4511

y se puede imaginar haciendo clases similares para la E/S estándar 4512

(consola) y para los formateos en el core (leer/escribir un trozo de la memoria 4513

en vez de un fichero o la consola). 4514

Este bloque de código es el interprete en tiempo de ejecución usado para 4515

las listas variables de argumentos. Este es el código que analiza el formato 4516

de su cadena en tiempo de ejecución y recoge e interpreta argumentos 4517

desde una lista variable de argumentos. Es un problema por cuatro razones: 4518

1. Incluso si solo se usa una fracción de la funcionalidad del 4519

interprete, se carga todo en el ejecutable. Luego si quiere usar un 4520

printf("%c", 'x'); , usted tendrá todo el paquete, incluido las partes 4521

que imprimen números en coma flotante y cadenas. No hay una 4522

opción estándar para reducir el la cantidad de espacio usado por el 4523

programa. 4524

2. Como la interpretación pasa en tiempo de ejecución, no se 4525

puede evitar un empeoramiento del rendimiento. Esto es frustrante por 4526

que toda la información está allí, en el formato de la cadena, en 4527

tiempo de compilación, pero no se evalua hasta la ejecución. Por otro 4528

lado, si se pudieran analizar los argumentos en el formateo de la 4529

cadena durante la compilación, se podrían hacer llamadas directas a 4530

funciones que tuvieran el potencial de ser mucho más rápidas que un 4531

interprete en tiempo de ejecución (aunque la familia de funciones de 4532

printf() acostumbran a estar bastante bien optimizadas). 4533

Page 167: Pensar en C++ (Volumen 2)

167

3. Como el formateo de la cadena no se evalua hasta la ejecución, 4534

no se hace una comprobación de errores al compilar. Probalblemente 4535

está familiarizado con este problema si ha intentado buscar errores 4536

que provienen del uso de un número o tipo de argumentos incorrecto 4537

en una sentencia printf(). C++ ayuda mucho a encontrar 4538

rápidamente errores durante la compilación y hacerle la vida más fácil. 4539

Parece una tonteria desechar la seguridad en los tipos de datos para 4540

la libreria de E/S, especialmente cuando usamos intensivamente las 4541

E/S. 4542

4. Para C++, el más crucial de los problemas es que la familia de 4543

funciones de printf() no es particularmente extensible. Esta realmente 4544

diseñada para manejar solo los tipos básicos de datos en C (char, int, 4545

float, double, wchar_t, char*, wchar_t*, y void*) y sus variaciones. 4546

Debe estar pensando que cada vez que añade una nueva clase, 4547

puede añadir funciones sobrecargadas printf() y scanf() (y sus 4548

variaciones para ficheros y strings), pero recuerde: las funciones 4549

sobrecargadas deben tener diferentes tipos de listas de argumentos, y 4550

la familia de funciones de printf() esconde esa información en la 4551

cadena formateada y su lista variable de argumentos. Para un 4552

lenguage como C++, cuya virtud es que se pueden añadir fácilmente 4553

nuevos tipos de datos, esta es una restricción inaceptable. 4554

5.2. Iostreams al rescate 4555

Estos problemas dejan claro que la E/S es una de las principales 4556

prioridades para la librería de clases estándar de C++. Como 'hello, worlod' 4557

es el primer programa que cualquiera escribe en un nuevo lenguaje, y porque 4558

la E/S es parte de virtualmente cualquier programa, la librería de E/S en C++ 4559

debe ser particularmente fácil de usar. Tembién tiene el reto mucho mayor de 4560

acomodar cualquier nueva clase. Por tanto, estas restricciones requieren que 4561

esta librería de clases fundamentales tengan un diseño realmente inspirado. 4562

Además de ganar en abstracción y claridad en su trabajo con las E/S y el 4563

formateo, en este capítulo verá lo potente que puede llegar a ser esta librería 4564

de C++. 4565

Page 168: Pensar en C++ (Volumen 2)

168

5.2.1. Insertadores y extractores 4566

Un stream es un objeto que transporta y formatea carácteres de un ancho 4567

fijo. Puede tener un stream de entrada (por medio de los descendientes de la 4568

clase istream), o un stream de salida (con objetos derivados de ostream), o 4569

un stream que hace las dos cosas simultáneamente (con objetos derivados 4570

de iostream). La librería iostream provee tipos diferentes de estas clases: 4571

ifstream, ofstream y fstream para ficheros, y istringstream, 4572

ostringstream, y stringstream para comunicarese con la clase string del 4573

estándar C++. Todas estas clases stream tiene prácticamente la misma 4574

interfaz, por lo que usted puede usar streams de manera uniforme, aunque 4575

esté trabajando con un fichero, la E/S estándar, una región de la memoria, o 4576

un objeto string. La única interfaz que aprenderá también funciona para 4577

extensiones añadidas para soportar nuevas clases. Algunas funciones 4578

implementan sus comandos de formateo, y algunas funciones leen y 4579

escriben caracteres sin formatear. 4580

Las clases stream mencionadas antes son actualmente especializaciones 4581

de plantillas, muchas como la clase estándar string son especializaciones 4582

de la plantilla basic_string. Las clases básicas en la jerarquia de herencias 4583

son mostradas en la siguiente figura: [11] 4584

La clase ios_base declara todo aquello que es común a todos los stream, 4585

independientemente del tipo de carácteres que maneja el stream. Estas 4586

declaraciones son principalmente constantes y funciones para manejarlas, 4587

algunas de ella las verá a durante este capítulo. El resto de clases son 4588

plantillas que tienen un tipo de caracter subyacente como parámetro. La 4589

clase istream, por ejemplo, está definida a continuación: 4590

typedef basic_istream<char< istream; 4591

Todas las clases mencionadas antes estan definidas de manera similar. 4592

También hay definiciones de tipo para todas las clases de stream usando 4593

wchar_t (la anchura de este tipo de carácteres se discute en el Capítulo 3) en 4594

lugar de char. Miraremos esto al final de este capítulo. La plantilla basic_ios 4595

define funciones comunes para la entrada y la salida, pero depende del tipo 4596

Page 169: Pensar en C++ (Volumen 2)

169

de carácter subyacente (no vamos a usarlo mucho). La plantilla 4597

basic_istream define funciones genéricas para la entrada y basic_ostream 4598

hace lo mismo para la salida. Las clases para ficheros y streams de strings 4599

introducidas después añaden funcionalidad para sus tipos especificos de 4600

stream. 4601

En la librería de iostream, se han sobrecargado dos operadores para 4602

simplificar el uso de iostreams. El operador << se denomina frecuentemente 4603

instertador para iostreams, y el operador >> se denomina frecuentemente 4604

extractor. 4605

Los extractores analizan la información esperada por su objeto destino de 4606

acuerdo con su tipo. Para ver un ejemplo de esto, puede usar el objeto cin, 4607

que es el equivalente de iostream de stdin en C, esto es, entrada estándar 4608

redireccionable. Este objeto viene predefinido cuando usted incluye la 4609

cabecera <iostream>. 4610

int i; 4611

cin >> i; 4612

4613

float f; 4614

cin >> f; 4615

4616

char c; 4617

cin >> c; 4618

4619

char buf[100]; 4620

cin >> buf; 4621

Existe un operador sobrecargado >> para cada tipo fundamental de dato. 4622

Usted también puede sobrecargar los suyos, como verá más adelante. 4623

Page 170: Pensar en C++ (Volumen 2)

170

Para recuperar el contenido de las variables, puede usar el objeto cout 4624

(correspondiente con la salida estándar; también existe un objeto cerr 4625

correspondiente con la salida de error estándar) con el insertador <<: 4626

cout << "i = "; 4627

cout << i; 4628

cout << "\n"; 4629

cout << "f = "; 4630

cout << f; 4631

cout << "\n"; 4632

cout << "c = "; 4633

cout << c; 4634

cout << "\n"; 4635

cout << "buf = "; 4636

cout << buf; 4637

cout << "\n"; 4638

Esto es tedioso y no parece ser un gran avance sobre printf(), aparte de 4639

la mejora en la comprobación de tipos. Afortunadamente, los insertadores y 4640

extractores sobrecargados están diseñados para ser encadenados dentro de 4641

expresiones más complejas que son mucho más fáciles de escribir (y leer): 4642

cout << "i = " << i << endl; 4643

cout << "f = " << f << endl; 4644

cout << "c = " << c << endl; 4645

cout << "buf = " << buf << endl; 4646

Page 171: Pensar en C++ (Volumen 2)

171

Definir insertadores y extractores para sus propias clases es simplemente 4647

una cuestion de sobrecargar los operadores asociados para hacer el trabajo 4648

correcto, de la siguente manera: 4649

Hacer del primer parámetro una referencia no constante al stream (istream 4650

para la entrada, ostream para la salida). 4651

Realizar la operación de insertar/extraer datos hacia/desde el stream 4652

(procesando los componentes del objeto). 4653

Retornar una referencia al stream 4654

El stream no debe ser constante porque el procesado de los datos del 4655

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

encadenado de operaciones en una sentencia individual, como se mostró 4657

antes. 4658

Como ejemplo, considere como representar la salida de un objeto Date en 4659

formato MM-DD-AAAA . El siguiente insertador hace este trabajo: 4660

ostream& operator<<(ostream& os, const Date& d) { 4661

char fillc = os.fill('0'); 4662

os << setw(2) << d.getMonth() << '-' 4663

<< setw(2) << d.getDay() << '-' 4664

<< setw(4) << setfill(fillc) << d.getYear(); 4665

return os; 4666

} 4667

Esta función no puede ser miembro de la clase Date por que el operando 4668

de la izquierda << debe ser el stream de salida. La función miembro fill() 4669

de ostream cambia el carácter de relleno usado cuando la anchura del campo 4670

de salida, determinada por el manipulador setw(), es mayor que el 4671

necesitado por los datos. Usamos un caracter '0' ya que los meses anteriores 4672

a Octubre mostrarán un cero en primer lugar, como '09' para Septiembre. La 4673

funcion fill() también retorna el caracter de relleno anterior (que por 4674

Page 172: Pensar en C++ (Volumen 2)

172

defecto es un espacio en blanco) para que podamos recuperarlo después 4675

con el manipulador setfill(). Discutiremos los manipuladores en 4676

profundidad más adelante en este capítulo. 4677

Los extractores requieren algo más cuidado porque las cosas pueden ir 4678

mal con los datos de entrada. La manera de avisar sobre errores en el 4679

stream es activar el bit de error del stream, como se muestra a continuación: 4680

istream& operator>>(istream& is, Date& d) { 4681

is >> d.month; 4682

char dash; 4683

is >> dash; 4684

if(dash != '-') 4685

is.setstate(ios::failbit); 4686

is >> d.day; 4687

is >> dash; 4688

if(dash != '-') 4689

is.setstate(ios::failbit); 4690

is >> d.year; 4691

return is; 4692

} 4693

Cuando se activa el bit de error en un stream, todas las operaciones 4694

posteriores serán ignoradas hasta que el stream sea devuelto a un estado 4695

correcto (explicado brevemente). Esto es porque el código de arriba continua 4696

extrayendo incluso is ios::failbit está activado. Esta implementación es 4697

poco estricta ya que permite espacios en blanco entre los numeros y guiones 4698

en la cadena de la fecha (por que el operador >> ignora los espacios en 4699

blanco por defecto cuado lee tipos fundamentales). La cadena de fecha a 4700

continuación es válida para este extractor: 4701

Page 173: Pensar en C++ (Volumen 2)

173

"08-10-2003" 4702

"8-10-2003" 4703

"08 - 10 - 2003" 4704

Pero estas no: 4705

"A-10-2003" // No alpha characters allowed 4706

"08%10/2003" // Only dashes allowed as a delimiter 4707

Discutiremos los estados de los stream en mayor profundidad en la 4708

sección 'Manejar errores de stream' después en este capítulo. 4709

5.2.2. Uso común 4710

Como se ilustraba en el extractor de Date, debe estar alerta por las 4711

entradas erróneas. Si la entrada produce un valor inesperado, el proceso se 4712

tuerce y es difícil de recuperar. Además, por defecto, la entrada formateada 4713

está delimitada por espacios en blanco. Considere que ocurre cuando 4714

recogemos los fragmentos de código anteriores en un solo programa: 4715

//: V2C04:Iosexamp.cpp {RunByHand} 4716

y le proporcionamos la siguiente entrada: 4717

12 1.4 c this is a test 4718

esperamos la misma salida que si le hubieramos proporcionado esto: 4719

12 4720

1.4 4721

Page 174: Pensar en C++ (Volumen 2)

174

c 4722

this is a test 4723

pero la salida es algo inesperado 4724

i = 12 4725

f = 1.4 4726

c = c 4727

buf = this 0xc 4728

Nótese que buf solo tiene la primera palabra porque la rutina de entrada 4729

busca un espacio que delimite la entrada, que es el que se encuentra 4730

después de 'tihs.' Además, si la entrada continua de datos es mayor que el 4731

espacio reservado por buf, sobrepasamos los limites del buffer. 4732

En la práctica, usualmente deseará obtener la entrada desde programas 4733

interactivos, una linea cada vez como secuencia de carácteres, leerla, y 4734

después hacer las conversiones necesarias hasta que estén seguras en un 4735

buffer. De esta manera no deberá preocuparse por la rutina de entrada 4736

fallando por datos inesperados. 4737

Otra consideración es todo el concepto de interfaz de línea de comandos. 4738

Esto tenia sentido en el pasado cuando la consola era la única interfaz con la 4739

máquina, pero el mundo está cambiando rápidamente hacia otro donde la 4740

interfaz gráfica de usuario (GUI) domina. ¿Cual es el sentido de la E/S por 4741

consola en este mundo? Esto le da mucho más sentido a ignorar cin en 4742

general, salvo para ejemplos simples y tests, y hacer los siguientes 4743

acercamientos: 4744

1. Si su programa requiere entrada, ¿leer esta entrada desde un 4745

fichero? Pronto verá que es remarcablemente fácil usar ficheros con 4746

iostream. Iostream para ficheros todavia funciona perfectamente con 4747

una GUI. 4748

Page 175: Pensar en C++ (Volumen 2)

175

2. Leer la entrada sin intentar convertirla, como hemos sugerido. 4749

Cuando la entrada es algun sitio donde no podemos arriesgarnos 4750

durante la conversión, podemos escanearla de manera segura. 4751

3. La salida es diferente. Si está usando una interfaz gráfica, cout 4752

no necesariamente funciona, y usted debe mandarlo a un fichero (que 4753

es indéntico a mandarlo a un cout) o usar los componentes del GUI 4754

para mostrar los datos. En cualquier otra situacion, a menudo tiene 4755

sentido mandarlo a cout. En ambos casos, la funciones de formateo 4756

de la salida de iostream son muy útiles. 4757

Otra práctica común ahorra tiempo en compilaciones largas. Consideres, 4758

por ejemplo, cómo quiere declarar los operadores del stream Date 4759

introducidos antes en el capítulo en un fichero de cabecera. Usted solo 4760

necesita incluir los prototipos para las funciones, luego no es necesario 4761

incluir la cabecera entera de <iostream> en Date.h. La práctica estándar es 4762

declarar solo las clases, algo como esto: 4763

class ostream; 4764

Esta es una vieja tecnica para separar la interfaz de la implementación y a 4765

menudo la llaman declaración avanzada( y ostream en este punto debe ser 4766

considerada un tipo incompleto, ya que la definición de la clase no ha sido 4767

vista todavia por el compilador). 4768

Esto con funcionará asi, igualmente, por dos razones: 4769

Las clases stream estan definidas en el espacio de nombres std. 4770

Son plantillas. 4771

La declaración correcta debería ser: 4772

namespace std { 4773

template<class charT, class traits = char_traits<charT> > 4774

class basic_ostream; 4775

typedef basic_ostream<char> ostream; 4776

Page 176: Pensar en C++ (Volumen 2)

176

} 4777

(Como puede ver, como las clase string, las clases stream usan las clases 4778

de rasgos de caracter mencionadas en el Capítulo 3). Como puede ser 4779

terriblemente tedioso darle un tipo a todas las clases stream a las que quiere 4780

referenciar, el estándar provee una cabecera que lo hace por usted: 4781

// Date.h 4782

#include <iosfwd> 4783

4784

class Date { 4785

friend std::ostream& operator<<(std::ostream&, 4786

const Date&); 4787

friend std::istream& operator>>(std::istream&, Date&); 4788

// Etc. 4789

5.2.3. Entrada orientada a líneas 4790

Para recoger la entrada de línea en línea, tiene tres opciones: 4791

La función miembre get() 4792

La función miembro getline() 4793

La función global getline() definida en la cabecera <string> 4794

Las primeras dos funciones toman tres parámentros: 4795

Un puntero a un buffer de carácters donde se guarda el resultado. 4796

El tamaño de este buffer (para no sobrepasarlo). 4797

El carácter de finalización, para conocer cuando parar de leer la entrada. 4798

Page 177: Pensar en C++ (Volumen 2)

177

El carácter de finalización tiene un valor por defecto de '\n', que es el que 4799

usted usará usualmente. Ambas funciones almacenan un cero en el buffer 4800

resultante cuando encuentran el caracter de terminación en la entrada. 4801

Entonces, ¿cual es la diferencia? Sutil pero importante: get() se detiene 4802

cuando vee el delimitador en el stream de entrada, pero no lo extrae de 4803

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

delimitador, retornará inmediatamente sin ninguna entrada contenida. 4805

(Presumiblemente, en su lugar usará un delimitador diferente en la siguiente 4806

sentencia get() o una función de entrada diferente.) La función getline(), 4807

por el contrario, extrae el delimitador del stream de entrada, pero tampoco lo 4808

almacena en el buffer resultante. 4809

La función getline() definida en <string> es conveniente. No es una 4810

función miembro, sino una función aislada declarada en el espacio de 4811

nombres std. Sólo toma dos parámetros que no son por defecto, el stream 4812

de entrada y el objeto string para rellenar. Como su propio nombre dice, lee 4813

carácteres hasta que encuentra la primera aparición del delimitador ('\n' por 4814

defecto) y consume y descarta el delimitador. La ventaja de esta función es 4815

que lo lee dentro del objeto string, así que no se tiene que preocuparse del 4816

tamaño del buffer. 4817

Generalmente, cuando esta procesando un fichero de texto en el que 4818

usted quiere leer de línea en línea, usted querra usar una de las funciones 4819

getline(). Versiones sobrecargadas de get() 4820

Versiones sobrecargadas de get() 4821

La función get() también viene en tres versiones sobrecargadas: una sin 4822

argumentos que retorna el siguiente carácter usando un valor de retorno int; 4823

una que recoge un carácter dentro de su argumento char usando una 4824

referencia; y una que almacena directamente dentro del buffer subyacente de 4825

otro objeto iostream. Este último se explora después en el capítulo. 4826

Leyendo bytes sin formato 4827

Si usted sabe exactamente con que esta tratando y quiere mover los bytes 4828

directamente dentro de una variable, un array, o una estructura de memoria, 4829

puede usar la función de E/S sin formatear read(). El primer argumento para 4830

Page 178: Pensar en C++ (Volumen 2)

178

esta función es un puntero a la destinación en memoria, y el segundo es el 4831

número de bytes para leer. Es especialmente útil su usted ha almacenado 4832

previamente la información a un fichero, por ejemplo, en formato binario 4833

usando la función miembro complementaria write() para el stream de salida 4834

(usando el mismo compilador, por supuesto). Verá ejemplos de todas estas 4835

funciones más adelante. 4836

5.3. Manejo errores de stream 4837

El extractor de Date mostrado antes activa el bit de error de un stream bajo 4838

ciertas condiciones. ¿Como sabe un usuario que este error ha ocurrido? 4839

Puede detectar errores del stream llamando a ciertas funciones miembro del 4840

stream para ver si tenemos un estado de error, o si a usted no le preocupa 4841

qué tipo de error ha pasado, puede evaluar el stream en un contexto 4842

Booleano. Ambas técnicas derivan del estado del bit de error de un stream. 4843

5.3.1. Estados del stream 4844

La clase ios_base, desde la que ios deriva,[12]define cuatro banderas que 4845

puede usar para comprobar el estado de un stream: 4846

Bandera 4847

Significado 4848

badbit 4849

Algún error fatal (quizás físico) ha ocurrido. El stream debe considerarse 4850

no usable. 4851

eofbit 4852

Ha ocurrido un final de entrada (ya sea por haber encontrado un final físico 4853

de un stream de fichero o por que el usuario ha terminado el stream de 4854

consola, (usando un Ctrl-Z o Ctrl-D). 4855

failbit 4856

Una operación de E/S ha fallado, casi seguro que por datos inválidos (p.e. 4857

encontrar letras cuando se intentaba leer un número). El stream todavía se 4858

puede usar. El failbit también se activa cuando ocurre un final de entrada. 4859

goodbit 4860

Page 179: Pensar en C++ (Volumen 2)

179

Todo va bien; no hay errores. La final de la entrada todavía no ha ocurrido. 4861

Puede comprobar si alguna de estas condiciones ha ocurrido llamando a la 4862

función miembro correspondiente que retorna un valor Booleano indicando 4863

cual de estas ha sido activada. La función miembro de stream good() retorna 4864

cierto si ninguno de los otros tres bits se han activado. La función eof() 4865

retorna cierto si eofbit está activado, que ocurre con un intento de leer de un 4866

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

entrada ocurre en C++ cuando tratamos de leer pasado el final del medio 4868

físico, failbit también se activa para indicar que los datos esperados no 4869

han sido correctamente leídos. La función fail() retorna cierto si failbit o 4870

badbit están activados, y bad() retorna cierto solo si badbit está activado. 4871

Una vez alguno de los bit de error de un stream se activa, permanece 4872

activo, cosa que no siempre es lo que se quiere. Cuando leemos un fichero, 4873

usted puede querer colocarse en una posición anterior en el fichero antes de 4874

su final. Simplemenete moviendo el puntero del fichero no se desactiva el 4875

eofbit o el failbit; debe hacerlo usted mismo con la función clear(), 4876

haciendo algo así: 4877

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

Después de llamar a clear(), good() retornará cierto si es llamada 4879

inmediatamente. Como vió en el extractor de Date antes, la función 4880

setstate() activa los bits que usted le pasa.¿Eso significa que setstate no 4881

afecta a los otros bits? Si ya esta activo, permanece activo. Si usted quiere 4882

activar ciertos bits pero en el mismo momento, desactivar el resto, usted 4883

puede llamar una versión sobrecargada de clear(), pasandole una 4884

expresion binaria representando los bits que quiere que se activen, así: 4885

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

La mayoría del tiempo usted no estará interesado en comprobar los bits de 4887

estado del stream individualmente. Generalmente usted simplemente quiere 4888

conocer si todo va bien. Ese es el caso cuando quiere leer un fichero del 4889

Page 180: Pensar en C++ (Volumen 2)

180

principio al final; usted quiere saber simplemente cuando la entrada de datos 4890

se ha agotado. Puede usar una conversion de la función definida para void* 4891

que es automáticamente llamada cuando un stream esta en una expresión 4892

booleana. Leer un stream hasta el final de la entrada usando este idioma se 4893

parece a lo siguiente: 4894

int i; 4895

while(myStream >> i) 4896

cout << i << endl; 4897

Recuerde que operator>>() retorna su argumento stream, así que la 4898

sentencia while anterior comprueba el stream como una expresión booleana. 4899

Este ejemplo particular asume que el stream de entrada myStream contiene 4900

enteros separados por un espacio en blanco. La función ios_base::operator 4901

void*() simplemente llama a good() en su stream y retorna el resultado.[13] 4902

Como la mayoría de operaciones de stream retornan su stream, usar ese 4903

idioma es conveniente. 4904

5.3.2. Streams y excepciones 4905

Los iostream han existido como parte de C++ mucho antes que hubieran 4906

excepciones, luego comprobar el estado de un stream manualmente era la 4907

manera en que se hacia. Para mantener la compatibilidad, este es todavía el 4908

status quo, pero los modernos iostream pueden lanzar excepciones en su 4909

lugar. La función miembro de stream exceptions() toma un parámetro 4910

representando los bits de estado para los que usted quiere lanzar la 4911

excepcion. Siempre que el stream encuentra este estado,este lanza una 4912

excepcion de tipo std::ios_base::failure, que hereda de std::exception. 4913

Aunque usted puede disparar una excepción para alguno de los cuatro 4914

estados de un stream, no es necesariamente una buena idea activar las 4915

excepciones para cada uno de ellos. Tal como explica el Capítulo uno, se 4916

usan las excepciones para condiciones verdaderamente excepcionales, 4917

¡pero el final de un fichero no solo no es excepcional! ¡Es lo que se espera! 4918

Page 181: Pensar en C++ (Volumen 2)

181

Por esta razón, solo debe querer activar las excepciones para errores 4919

representados por badbit, que deberia ser como esto: 4920

myStream.exceptions(ios::badbit); 4921

Usted activa las excepciones stream por stream, ya que exceptions() es 4922

una función miembro para los streams. La función exceptions() retorna una 4923

máscara de bits [14] (de tipo iostate, que es un tipo dependiente del 4924

compilador convertible a int) indicando que estados de stream causarán 4925

excepciones. Si estos estados ya han sido activados, la excepción será 4926

lanzada inmediatamente. Por supuesto, si usa excepciones en conexiones a 4927

streams, debería estar preparado paracapturarlas, lo que quiere decir que 4928

necesita envolver todos los stream bon bloques try que tengan un manejador 4929

ios::failure. Muchos programadores encuentran tedioso y simplemente 4930

comprueban manualmente donde esperan encontrar errores (ya que, por 4931

ejemplo, no esperan encontrar bad() al retornar true la mayoria de veces). 4932

Esto es otra razón que tienen los streams para que el lanzamiento de 4933

excepciones sea opcional y no por defecto. en cualquier caso, usted peude 4934

elegir como quiere manejar los errores de stream. Por las mismas razones 4935

que recomendamos el uso de excepciones para el manejo de rrores en otros 4936

contextos, lo hacemos aqui. 4937

5.4. Iostreams de fichero 4938

Manipular ficheros con iostream es mucho más fácil y seguro que usar 4939

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

hace el trabajo. No necesita cerrar el fichero explícitamente (aunque puede, 4941

usando la función miembro close()) porque el destructor lo cerrará cuando el 4942

objeto salga del ámbito. Para crear un fichero que por defecto sea de 4943

entrada, cree un objeto ifstream . Para crear un fichero que por defecto es 4944

de salida, cree un objeto ofstream. Un fstream puede hacer ambas cosas. 4945

Las clases de stream de fichero encajan dentro de las clases iostream 4946

como se muestra en la siguiente figura: 4947

Page 182: Pensar en C++ (Volumen 2)

182

Como antes, las clases que usted usa en realidad son especializaciones 4948

de plantillas definidas por definiciones de tipo. Por ejemplo, ifstream, que 4949

procesa ficheros de char, es definida como: 4950

typedef basic_ifstream<char> ifstream; 4951

5.4.1. Un ejemplo de procesado de fichero. 4952

Aqui tiene un ejemplo que muestra algunas de las características 4953

discutidas antes. Nótese que la inclusión de <fstream> para delarar las 4954

clases de fichero de E/S. Aunque en muchas plataformas esto también 4955

incluye <iostream> automáticamente, los compiladores no están obligados a 4956

hacer esto. Si usted quiere compatibilidad, incluya siempre ambas 4957

cabeceras. 4958

//: C04:Strfile.cpp 4959

// Stream I/O with files; 4960

// The difference between get() & getline(). 4961

#include <fstream> 4962

#include <iostream> 4963

#include "../require.h" 4964

using namespace std; 4965

4966

int main() { 4967

const int SZ = 100; // Buffer size; 4968

char buf[SZ]; 4969

{ 4970

ifstream in("Strfile.cpp"); // Read 4971

assure(in, "Strfile.cpp"); // Verify open 4972

Page 183: Pensar en C++ (Volumen 2)

183

ofstream out("Strfile.out"); // Write 4973

assure(out, "Strfile.out"); 4974

int i = 1; // Line counter 4975

4976

// A less-convenient approach for line input: 4977

while(in.get(buf, SZ)) { // Leaves \n in input 4978

in.get(); // Throw away next character (\n) 4979

cout << buf << endl; // Must add \n 4980

// File output just like standard I/O: 4981

out << i++ << ": " << buf << endl; 4982

} 4983

} // Destructors close in & out 4984

4985

ifstream in("Strfile.out"); 4986

assure(in, "Strfile.out"); 4987

// More convenient line input: 4988

while(in.getline(buf, SZ)) { // Removes \n 4989

char* cp = buf; 4990

while(*cp != ':') 4991

++cp; 4992

cp += 2; // Past ": " 4993

cout << cp << endl; // Must still add \n 4994

} 4995

} ///:~ 4996

Page 184: Pensar en C++ (Volumen 2)

184

Listado 5.5. C04/Strfile.cpp 4997

4998

La creación tanto del ifstream como del ofstream están seguidas de un 4999

assure() para garantizar que el fichero ha sido abierto exitosamente. El 5000

objeto resultante, usado en una situación donde el compilador espera un 5001

resultado booleano, produce un valor que indica éxito o fracaso. 5002

El primer while demuestra el uso de dos formas de la función get(). La 5003

primera toma los carácteres dentro de un buffer y pone un delimitador cero 5004

en el buffer cuando bien SZ-1 carácteres han sido leidos o bien el tercer 5005

argumento (que por defecto es '\n') es encontrado. La función get() deja el 5006

carácter delimitador en el stream de entrada, así que este delimitador debe 5007

ser eliminado via in.get() usando la forma de get() sin argumentos. Puede 5008

usar tambien la función miembro ignore(), que tiene dos parámetros por 5009

defecto. El primer argumento es el número de carácteres para descartar y 5010

por defecto es uno. El segundo argumento es el carácter en el que ignore() 5011

se detiene (después de extraerlo) y por defecto es EOF. 5012

A continuación, se muestran dos sentencias de salida similares: una hacia 5013

cout y la otra al fichero de salida. Nótese la conveniencia aquí - no necesita 5014

preocuparse del tipo de objeto porque las sentencias de formateo trabajan 5015

igual con todos los objetos ostream. El primero hace eco de la linea en la 5016

salida estándar, y el segundo escribe la línea hacia el fichero de salida e 5017

incluye el número de línea. 5018

Para demostrar getline(), abra el fichero recién creado y quite los 5019

números de linea. Para asegurarse que el fichero se cierra correctamente 5020

antes de abrirlo para la lectura, usted tiene dos opciones. Puede envolver la 5021

primera parte del programa con llaves para forzar que el objeto out salga del 5022

ámbito, llamando así al destructor y cerrando el fichero, que es lo que se 5023

hace aquí. Tambien puede l lamar a close() para ambos ficheros; si hace 5024

esto, puede despues rehusar el objeto de entrada llamando a la función 5025

miembro open(). 5026

El segundo while muestra como getline() borra el caracter terminador 5027

(su tercer argumento, que por defecto es '\n') del stream de entrada cuando 5028

este es encontrado. Aunque getline(), como get(), pone un cero en el 5029

buffer, este todavía no inserta el carácter de terminación. 5030

Page 185: Pensar en C++ (Volumen 2)

185

Este ejemplo, así como la mayoría de ejemplos en este capítulo, asume 5031

que cada llamada a alguna sobrecarga de getline() encontrará un carácter 5032

de nueva línea. Si este no es el caso, la estado eofbit del stream será 5033

activado y la llamada a getline() retornará falso, causando que el programa 5034

pierda la última línea de la entrada. 5035

5.4.2. Modos de apertura 5036

Puede controlar la manera en que un fichero es abierto sobreescribiendo 5037

los argumentos por defecto del constructor. La siguiente tabla muestra las 5038

banderas que controlan el modo de un fichero: 5039

Bandera 5040

Función 5041

ios::in 5042

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

ofstream para prevenir que un fichero existente sea truncado. 5044

ios::out 5045

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

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

ios::app 5048

Abre un fichero de salida para solo añadir . 5049

ios::ate 5050

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

ios::trunc 5052

Trunca el fichero antiguo si este ya existe. 5053

ios::binary 5054

Abre un fichero en modo binario. Por defecto es en modo texto. 5055

Puede combinar estas banderas usando la operación or para bits 5056

El flag binario, aun siendo portable, solo tiene efecto en algunos sistemas 5057

no UNIX, como sistemas operativos derivados de MS-DOS, que tiene 5058

convenciones especiales para el almacenamiento de delimitadores de final 5059

de línea. Por ejemplo, en sistemas MS-DOS en modo texto (el cual es por 5060

Page 186: Pensar en C++ (Volumen 2)

186

defecto), cada vez que usted inserta un nuevo carácter de nueva línea ('\n'), 5061

el sistema de ficheros en realidad inserta dos carácteres, un par retorno de 5062

carro/fin de línea (CRLF), que es el par de carácteres ASCII 0x0D y 0x0A. En 5063

sentido opuesto, cuando usted lee este fichero de vuelta a memoria en modo 5064

texto, cada ocurrencia de este par de bytes causa que un '\n' sea enviado al 5065

programa en su lugar. Si quiere sobrepasar este procesado especial, puede 5066

abrir el fichero en modo binario. El modo binario no tiene nada que ver ya 5067

que usted puede escribir bytes sin formato en un fichero - siempre puede 5068

(llamando a write()). Usted debería, por tanto, abrir un fichero en modo 5069

binario cuando vaya a usar read() o write(), porque estas funciones toman 5070

un contador de bytes como parámetro. Tener carácteres extra '\r' estropeará 5071

su contador de bytes en estas instancias. Usted también puede abrir un 5072

fichero en formato binario si va a usar comandos de posicionamiento en el 5073

stream que se discuten más adelante. 5074

Usted puede abrir un fichero tanto para entrada como salida declarando un 5075

objeto fstream. cuando declara un objeto fstream, debe usar suficientes 5076

banderas de modos de apertura mencionados antes para dejar que el 5077

sistema de ficheros sepa si quiere leer, escribir, o ambos. Para cambiar de 5078

salida a entrada, necesita o bien limpiar el stream o bien cambiar la posición 5079

en el fichero. Para cambiar de entrada a salida, cambie la posicion en el 5080

fichero. Para crear un fichero usando un objeto fstream, use la bandera de 5081

modo de apertura ios::trunc en la llamada al constructor para usar entrada 5082

y salida. 5083

5.5. Almacenamiento de iostream 5084

Las buenas prácticas de diseño dictan que, cuando cree una nueva clase, 5085

debe esforzarse en ocultar los detalles de la implementación subyacente 5086

tanto como sea posible al usuario de la clase. Usted le muestra solo aquello 5087

que necesita conocer y el resto se hace privado para evitar confusiones. 5088

Cuando usamos insertadores y extractores, normalmente usted no conoce o 5089

tiene cuidado con los bytes que se consumen o se producen, ya que usted 5090

está tratando con E/S estándar, ficheros, memoria, o alguna nueva clase o 5091

dispositivo creado. 5092

Page 187: Pensar en C++ (Volumen 2)

187

Llega un momento, no obstante, en el que es importante comunicar con la 5093

parte del iostream que produce o consume bytes. Para proveer esta parte 5094

con una interfaz común y esconder todavía su implementación subyacente, 5095

la librería estándar la abstrae dentro de su clase, llamada streambuf. Cada 5096

objeto iostream contiene un puntero a alguna clase de streambuf. (El tipo 5097

depende de que se esté tratando con E/S estándar, ficheros, memoria, etc.). 5098

Puede acceder al streambuf directamente; por ejemplo, puede mover bytes 5099

sin formatear dentro y fuera del streambuf sin formatearlos a través de la 5100

encapsulación del iostream. Esto es posible llamando a las funciones 5101

miembro del objeto streambuf. 5102

Actualmente, la cosa más importante que debe conocer es que cada 5103

objeto iostream contiene un puntero a un objeto streambuf, y el objeto 5104

streambuf tiene algunas funciones miembro que puede llamar si es 5105

necesario. Para ficheros y streams de string, hay tipos especializados de 5106

buffers de stream, como ilustra la figura siguiente: 5107

Para permitirle el acceso al streambuf, cada objeto iostream tiene una 5108

función miembro llamada rdbuf() que retorna el puntero a un objeto 5109

streambuf. De esta manera usted puede llamar cualquier función miembro 5110

del streambuf subyacente. No obstante, una de las cosas más interesantes 5111

que usted puede hacer con el puntero al streambuf es conectarlo con otro 5112

objeto iostream usando el operador <<. Esto inserta todos los carácteres del 5113

objeto dentro del que está al lado izquierdo del <<. Si quiere mover todos los 5114

carácteres de un iostream a otro, no necesita ponerse con el tedioso (y 5115

potencialmente inclinado a errores de código) proceso de leer de carácter por 5116

carácter o línea por línea. Este es un acercamiento mucho más elegante. 5117

Aqui está un programa muy simple que abre un fichero y manda el 5118

contenido a la salida estándar (similar al ejemplo previo): 5119

//: C04:Stype.cpp 5120

// Type a file to standard output. 5121

#include <fstream> 5122

#include <iostream> 5123

Page 188: Pensar en C++ (Volumen 2)

188

#include "../require.h" 5124

using namespace std; 5125

5126

int main() { 5127

ifstream in("Stype.cpp"); 5128

assure(in, "Stype.cpp"); 5129

cout << in.rdbuf(); // Outputs entire file 5130

} ///:~ 5131

Listado 5.6. C04/Stype.cpp 5132

5133

Un ifstream se crea usando el fichero de código fuente para este programa 5134

como argumento. La función assure() reporta un fallo si el fichero no puede 5135

ser abierto. Todo el trabajo pasa realmente en la sentencia 5136

cout << in.rdbuf(); 5137

que manda todo el contenido del fichero a cout. No solo es un código más 5138

sucinto, a menudo es más eficiente que mover los byte de uno en uno. 5139

Una forma de get() escribe directamente dentro del streambuf de otro 5140

objeto. El primer argumento es una referencia al streambuf de destino, y el 5141

segundo es el carácter de terminación ('\n' por defecto), que detiene la 5142

función get(). Así que existe todavía otra manera de imprimir el resultado de 5143

un fichero en la salida estándar: 5144

//: C04:Sbufget.cpp 5145

// Copies a file to standard output. 5146

#include <fstream> 5147

#include <iostream> 5148

Page 189: Pensar en C++ (Volumen 2)

189

#include "../require.h" 5149

using namespace std; 5150

5151

int main() { 5152

ifstream in("Sbufget.cpp"); 5153

assure(in); 5154

streambuf& sb = *cout.rdbuf(); 5155

while(!in.get(sb).eof()) { 5156

if(in.fail()) // Found blank line 5157

in.clear(); 5158

cout << char(in.get()); // Process '\n' 5159

} 5160

} ///:~ 5161

Listado 5.7. C04/Sbufget.cpp 5162

5163

La función rdbuf() retorna un puntero, que tiene que ser desreferenciado 5164

para satisfacer las necesidades de la función para ver el objeto. Los buffers 5165

de stream no estan pensados para ser copiados (no tienen contructor de 5166

copia), por lo que definimos sb como una referencia al buffer de stream de 5167

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

de entrada tenga una línea en blanco (este la tiene). Cuando esta particular 5169

versión sobrecargada de get() vee dos carácteres de nueva línea en una fila 5170

(una evidencia de una línea en blanco), activa el bit de error del stream de 5171

entrada, asi que se debe llamar a clear() para resetearlo y que así el stream 5172

pueda continuar siendo leído. La segunda llamada a get() extrae y hace eco 5173

de cualquier delimitador de nueva línea. (Recuerde, la función get() no 5174

extrae este delimitador como sí lo hace getline()). 5175

Page 190: Pensar en C++ (Volumen 2)

190

Probablemente no necesitará usar una técnica como esta a menudo, pero 5176

es bueno saber que existe.[15] 5177

5.6. Buscar en iostreams 5178

Cada tipo de iostream tiene el concepto de donde está el 'siguiente' 5179

carácter que proviene de (si es un istream) o que va hacia (si es un 5180

ostream). En algunas situaciones, puede querer mover la posición en este 5181

stream. Puede hacer esto usando dos modelos: uno usa una localización 5182

absoluta en el stream llamada streampos; el segundo trabaja como las 5183

funciones fseek() de la librería estándar de C para un fichero y se mueve un 5184

número dado de bytes desde el principio, final o la posición actual en el 5185

fichero. 5186

El acercamiento de streampos requiere que primero llame una función 5187

'tell':( tellp() para un ostream o tellg() para un istream. (La 'p' se refiere a 5188

'put pointer' y la 'g' se refiere a 'get pointer'). Esta función retorna un 5189

streampos que puede usar después en llamadas a seekp() para un ostream o 5190

seekg() para un ostream cuando usted quiere retornar a la posición en el 5191

stream. 5192

La segunda aproximación es una búsqueda relativa y usa versiones 5193

sobrecargadas de seekp() y seekg(). El primer argumento es el número de 5194

carácteres a mover: puede ser positivo o negativo. El segundo argumento es 5195

la dirección desde donde buscar: 5196

ios::beg 5197

Desde el principio del stream 5198

ios::cur 5199

Posición actual del stream 5200

ios::end 5201

Desde el principio del stream 5202

Aquí un ejemplo que muestra el movimiento por un fichero, pero recuerde, 5203

no esta limitado a buscar en ficheros como lo está con stdio de C. Con C++, 5204

puede buscar en cualquier tipo de iostream (aunque los objetos stream 5205

estándar, como cin y cout, lo impiden explícitamente): 5206

Page 191: Pensar en C++ (Volumen 2)

191

//: C04:Seeking.cpp 5207

// Seeking in iostreams. 5208

#include <cassert> 5209

#include <cstddef> 5210

#include <cstring> 5211

#include <fstream> 5212

#include "../require.h" 5213

using namespace std; 5214

5215

int main() { 5216

const int STR_NUM = 5, STR_LEN = 30; 5217

char origData[STR_NUM][STR_LEN] = { 5218

"Hickory dickory dus. . .", 5219

"Are you tired of C++?", 5220

"Well, if you have,", 5221

"That's just too bad,", 5222

"There's plenty more for us!" 5223

}; 5224

char readData[STR_NUM][STR_LEN] = {{ 0 }}; 5225

ofstream out("Poem.bin", ios::out | ios::binary); 5226

assure(out, "Poem.bin"); 5227

for(int i = 0; i < STR_NUM; i++) 5228

out.write(origData[i], STR_LEN); 5229

out.close(); 5230

ifstream in("Poem.bin", ios::in | ios::binary); 5231

Page 192: Pensar en C++ (Volumen 2)

192

assure(in, "Poem.bin"); 5232

in.read(readData[0], STR_LEN); 5233

assert(strcmp(readData[0], "Hickory dickory dus. . .") 5234

== 0); 5235

// Seek -STR_LEN bytes from the end of file 5236

in.seekg(-STR_LEN, ios::end); 5237

in.read(readData[1], STR_LEN); 5238

assert(strcmp(readData[1], "There's plenty more for us!") 5239

== 0); 5240

// Absolute seek (like using operator[] with a file) 5241

in.seekg(3 * STR_LEN); 5242

in.read(readData[2], STR_LEN); 5243

assert(strcmp(readData[2], "That's just too bad,") == 0); 5244

// Seek backwards from current position 5245

in.seekg(-STR_LEN * 2, ios::cur); 5246

in.read(readData[3], STR_LEN); 5247

assert(strcmp(readData[3], "Well, if you have,") == 0); 5248

// Seek from the begining of the file 5249

in.seekg(1 * STR_LEN, ios::beg); 5250

in.read(readData[4], STR_LEN); 5251

assert(strcmp(readData[4], "Are you tired of C++?") 5252

== 0); 5253

} ///:~ 5254

Listado 5.8. C04/Seeking.cpp 5255

Page 193: Pensar en C++ (Volumen 2)

193

5256

Este programa escribe un poema a un fichero usando un stream de salida 5257

binaria. Como reabrimos como un ifstream, usamos seekg() para posicionar 5258

el 'get pointer'. Como puede ver, puede buscar desde el principio o el final del 5259

archivo o desde la posición actual del archivo. Obviamente, debe proveer un 5260

número positivo para mover desde el principio del archivo y un número 5261

negativo para mover hacia atrás. 5262

Ahora que ya conoce el streambuf y como buscar, ya puede entender un 5263

método alternativo (aparte de usar un objeto fstream) para crear un objeto 5264

stream que podrá leer y escribir en un archivo. El siguiente código crea un 5265

ifstream con banderas que dicen que es un fichero de entrada y de salida. 5266

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

con el buffer subyacente del stream: 5268

ifstream in("filename", ios::in | ios::out); 5269

ostream out(in.rdbuf()); 5270

Debe estar preguntándose que ocurre cuando usted lee en uno de estos 5271

objetos. Aqui tiene un ejemplo: 5272

//: C04:Iofile.cpp 5273

// Reading & writing one file. 5274

#include <fstream> 5275

#include <iostream> 5276

#include "../require.h" 5277

using namespace std; 5278

5279

int main() { 5280

ifstream in("Iofile.cpp"); 5281

assure(in, "Iofile.cpp"); 5282

Page 194: Pensar en C++ (Volumen 2)

194

ofstream out("Iofile.out"); 5283

assure(out, "Iofile.out"); 5284

out << in.rdbuf(); // Copy file 5285

in.close(); 5286

out.close(); 5287

// Open for reading and writing: 5288

ifstream in2("Iofile.out", ios::in | ios::out); 5289

assure(in2, "Iofile.out"); 5290

ostream out2(in2.rdbuf()); 5291

cout << in2.rdbuf(); // Print whole file 5292

out2 << "Where does this end up?"; 5293

out2.seekp(0, ios::beg); 5294

out2 << "And what about this?"; 5295

in2.seekg(0, ios::beg); 5296

cout << in2.rdbuf(); 5297

} ///:~ 5298

Listado 5.9. C04/Iofile.cpp 5299

5300

Las primeras cinco líneas copian el código fuente de este programa en un 5301

fichero llamado iofile.out y después cierra los ficheros. Esto le da un texto 5302

seguro con el que practicar. Entonces, la técnica antes mencionada se usa 5303

para crear dos objetos que leen y escriben en el mismo fichero. En cout << 5304

in2.rebuf(), puede ver como puntero 'get' es inicializado al principio del 5305

fichero. El puntero 'put', en cambio, se coloca en el final del fichero para que 5306

'Where does this end up' aparezca añadido al fichero. No obstante, si el 5307

puntero 'put' es movido al principio con un seekp(), todo el texto insertado 5308

sobreescribe el existente. Ambas escrituras pueden verse cuando el puntero 5309

Page 195: Pensar en C++ (Volumen 2)

195

'get' se mueve otra vez al principio con seekg(), y el fichero se muestra. El 5310

fichero es automáticamente guardado cuando out2 sale del ámbito y su 5311

destructor es invocado. 5312

5.7. Iostreams de string 5313

Un stream de cadena funciona directamente en memoria en vez de con 5314

ficheros o la salida estándar. Usa las mismas funciones de lectura y formateo 5315

que usó con cin y cout para manipular bits en memoria. En ordenadores 5316

antiguos, la memoria se refería al núcleo, con lo que este tipo de 5317

funcionalidad se llama a menudo formateo en el núcleo. 5318

Los nombres de clases para streams de cadena son una copia de los 5319

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

carácteres de él, puede crear un istringstream. Si quiere poner carácteres 5321

en un stream de cadena, puede crear un ostringstream. Todas las 5322

declaraciones para streams de cadena están en la cabecera estándar 5323

<sstream>. Como es habitual, hay plantillas de clases dentro de la jerarquia 5324

de los iostreams, como se muestra en la siguiente figura: 5325

5.7.1. Streams de cadena de entrada 5326

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

istringstream inicializado con el string. El siguiente programa muestra 5328

como usar un objeto istringstream: 5329

//: C04:Istring.cpp 5330

// Input string streams. 5331

#include <cassert> 5332

#include <cmath> // For fabs() 5333

#include <iostream> 5334

#include <limits> // For epsilon() 5335

#include <sstream> 5336

Page 196: Pensar en C++ (Volumen 2)

196

#include <string> 5337

using namespace std; 5338

5339

int main() { 5340

istringstream s("47 1.414 This is a test"); 5341

int i; 5342

double f; 5343

s >> i >> f; // Whitespace-delimited input 5344

assert(i == 47); 5345

double relerr = (fabs(f) - 1.414) / 1.414; 5346

assert(relerr <= numeric_limits<double>::epsilon()); 5347

string buf2; 5348

s >> buf2; 5349

assert(buf2 == "This"); 5350

cout << s.rdbuf(); // " is a test" 5351

} ///:~ 5352

Listado 5.10. C04/Istring.cpp 5353

5354

Puede ver que es un acercamiento más flexible y general para transformar 5355

cadenas de carácteres para valores con tipo que la librería de funciones del 5356

estándar de C, como atof() o atoi(), aunque esta última puede ser más 5357

eficaz para las conversiones individuales. 5358

En la expresión s >> i >> f, el primer número se extrae en i, y en el 5359

segundo en f. Este no es 'el primer conjunto de carácteres delimitado por 5360

espacios en blanco' por que depende del tipo de datos que está siendo 5361

extraído. Por ejemplo, si la cadena fuera '1.414 47 This is a test', entonces i 5362

tomaría el valor 1 porque la rutina de entrada se pararía en el punto decimal. 5363

Page 197: Pensar en C++ (Volumen 2)

197

Entonces f tomaría 0.414. Esto puede ser muy útil i si quiere partir un 5364

número de coma flotante entre la parte entera y la decimal. De otra manera 5365

parecería un error. El segundo assert() calcula el error relativo entre lo que 5366

leemos y lo que esperamos; siempre es mejor hacer esto que comparar la 5367

igualdad de números de coma flotante. La constante devuelta por epsilon(), 5368

definida en <limits>, representa la epsilon de la máquina para números de 5369

doble precisión, el cual es la mejor tolerancia que se puede esperar para 5370

satisfacer las comparaciones de double.[16]. 5371

Como debe haber supuesto, buf2 no toma el resto del string, 5372

simplemente la siguiente palabra delimitada por espacios en blanco. En 5373

general, el mejor usar el extractor en iostreams cuando usted conoce 5374

exactamente la secuencia de datos en el stream de entrada y los convierte a 5375

algún otro tipo que un string de carácteres. No obstante, si quiere extraer el 5376

resto del string de una sola vez y enviarlo a otro iostream, puede usar 5377

rdbuf() como se muestra. 5378

Para probar el extractor de Date al principio de este capítulo, hemos usado 5379

un stream de cadena de entrada con el siguiente programa de prueba: 5380

//: C04:DateIOTest.cpp 5381

//{L} ../C02/Date 5382

#include <iostream> 5383

#include <sstream> 5384

#include "../C02/Date.h" 5385

using namespace std; 5386

5387

void testDate(const string& s) { 5388

istringstream os(s); 5389

Date d; 5390

os >> d; 5391

if(os) 5392

Page 198: Pensar en C++ (Volumen 2)

198

cout << d << endl; 5393

else 5394

cout << "input error with \"" << s << "\"" << endl; 5395

} 5396

5397

int main() { 5398

testDate("08-10-2003"); 5399

testDate("8-10-2003"); 5400

testDate("08 - 10 - 2003"); 5401

testDate("A-10-2003"); 5402

testDate("08%10/2003"); 5403

} ///:~ 5404

Listado 5.11. C04/DateIOTest.cpp 5405

5406

Cada literal de cadena en main() se pasa por referencia a testDate(), que 5407

a su vez lo envuelve en un istringstream con lo que podemos probar el 5408

extractor de stream que escribimos para los objetos Date. La función 5409

testDate() también empieza por probar el insertador, operator<<(). 5410

5.7.2. Streams de cadena de salida 5411

Para crear un stream de cadena de salida, simplemente cree un objeto 5412

ostringstream, que maneja un buffer de carácteres dinamicamente 5413

dimensionado para guardar cualquier cosas que usted inserte. Para tomar el 5414

resultado formateado como un objeto de string, llame a la función miembro 5415

setr(). Aqui tiene un ejemplo: 5416

//: C04:Ostring.cpp {RunByHand} 5417

Page 199: Pensar en C++ (Volumen 2)

199

// Illustrates ostringstream. 5418

#include <iostream> 5419

#include <sstream> 5420

#include <string> 5421

using namespace std; 5422

5423

int main() { 5424

cout << "type an int, a float and a string: "; 5425

int i; 5426

float f; 5427

cin >> i >> f; 5428

cin >> ws; // Throw away white space 5429

string stuff; 5430

getline(cin, stuff); // Get rest of the line 5431

ostringstream os; 5432

os << "integer = " << i << endl; 5433

os << "float = " << f << endl; 5434

os << "string = " << stuff << endl; 5435

string result = os.str(); 5436

cout << result << endl; 5437

} ///:~ 5438

Listado 5.12. C04/Ostring.cpp 5439

5440

Esto es similar al ejemplo Istring.cpp anterior que pedía un int y un float. 5441

A continueación una simple ejecución (la entrada por teclado está escrita en 5442

negrita). 5443

Page 200: Pensar en C++ (Volumen 2)

200

type an int, a float and a string: FIXME:10 20.5 the end 5444 integer = 10 5445 float = 20.5 5446

string = the end 5447

Puede ver que, como otros stream de salida, puede usar las herramientas 5448

ordinarias de formateo, como el operador << y endl, para enviar bytes hacia 5449

el ostringstream. La función str() devuelve un nuevo objeto string cada 5450

vez que usted la llama con lo que el stringbuf contenido permanece 5451

inalterado. 5452

En el capítulo previo, presentamos un programa, HTMLStripper.cpp, que 5453

borraba todas las etiqietas HTML y los códigos especiales de un fichero de 5454

texto. Como prometíamos, aqui está una versión más elegante usando 5455

streams de cadena. 5456

//: C04:HTMLStripper2.cpp {RunByHand} 5457

//{L} ../C03/ReplaceAll 5458

// Filter to remove html tags and markers. 5459

#include <cstddef> 5460

#include <cstdlib> 5461

#include <fstream> 5462

#include <iostream> 5463

#include <sstream> 5464

#include <stdexcept> 5465

#include <string> 5466

#include "../C03/ReplaceAll.h" 5467

#include "../require.h" 5468

using namespace std; 5469

5470

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

Page 201: Pensar en C++ (Volumen 2)

201

size_t leftPos; 5472

while((leftPos = s.find('<')) != string::npos) { 5473

size_t rightPos = s.find('>', leftPos+1); 5474

if(rightPos == string::npos) { 5475

ostringstream msg; 5476

msg << "Incomplete HTML tag starting in position " 5477

<< leftPos; 5478

throw runtime_error(msg.str()); 5479

} 5480

s.erase(leftPos, rightPos - leftPos + 1); 5481

} 5482

// Remove all special HTML characters 5483

replaceAll(s, "&lt;", "<"); 5484

replaceAll(s, "&gt;", ">"); 5485

replaceAll(s, "&amp;", "&"); 5486

replaceAll(s, "&nbsp;", " "); 5487

// Etc... 5488

return s; 5489

} 5490

5491

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

requireArgs(argc, 1, 5493

"usage: HTMLStripper2 InputFile"); 5494

ifstream in(argv[1]); 5495

assure(in, argv[1]); 5496

Page 202: Pensar en C++ (Volumen 2)

202

// Read entire file into string; then strip 5497

ostringstream ss; 5498

ss << in.rdbuf(); 5499

try { 5500

string s = ss.str(); 5501

cout << stripHTMLTags(s) << endl; 5502

return EXIT_SUCCESS; 5503

} catch(runtime_error& x) { 5504

cout << x.what() << endl; 5505

return EXIT_FAILURE; 5506

} 5507

} ///:~ 5508

Listado 5.13. C04/HTMLStripper2.cpp 5509

5510

En este programa leemos el fichero entero dentro de un string insertando 5511

una llamada rdbuf() del stream de fichero al ostringstream. Ahora es fácil 5512

buscar parejas de delimitadores HTML y borrarlas sin tener que 5513

preocuparnos de límites de líneas como teniamos con la versión previa en el 5514

Capítulo 3. 5515

El siguiente ejemplo muestra como usar un stream de cadena bidireccional 5516

(esto es, lectura/escritura): 5517

//: C04:StringSeeking.cpp {-bor}{-dmc} 5518

// Reads and writes a string stream. 5519

#include <cassert> 5520

#include <sstream> 5521

#include <string> 5522

Page 203: Pensar en C++ (Volumen 2)

203

using namespace std; 5523

5524

int main() { 5525

string text = "We will hook no fish"; 5526

stringstream ss(text); 5527

ss.seekp(0, ios::end); 5528

ss << " before its time."; 5529

assert(ss.str() == 5530

"We will hook no fish before its time."); 5531

// Change "hook" to "ship" 5532

ss.seekg(8, ios::beg); 5533

string word; 5534

ss >> word; 5535

assert(word == "hook"); 5536

ss.seekp(8, ios::beg); 5537

ss << "ship"; 5538

// Change "fish" to "code" 5539

ss.seekg(16, ios::beg); 5540

ss >> word; 5541

assert(word == "fish"); 5542

ss.seekp(16, ios::beg); 5543

ss << "code"; 5544

assert(ss.str() == 5545

"We will ship no code before its time."); 5546

ss.str("A horse of a different color."); 5547

Page 204: Pensar en C++ (Volumen 2)

204

assert(ss.str() == "A horse of a different color."); 5548

} ///:~ 5549

Listado 5.14. C04/StringSeeking.cpp 5550

5551

Como siempre para mover el puntero de inserción, usted llama a seekp(), 5552

y para reposicionar el fichero de lectura, usted llama a seekg(). Incluso 5553

aunque no lo hemos mostrado con este ejemplo, los stream de cadeana son 5554

un poco más permisivos que los stream de fichero ya que podemos cambiar 5555

de lectura a escritura y viceversa en cualquier momento. No necesita 5556

reposicionar el puntero de lectura o de escritura o vaciar el stream. Este 5557

progrma también ilustra la sobrecarga de str() que reemplaza el stringbuf 5558

contenido en el stream con una nueva cadena. 5559

5.8. Formateo de stream de salida 5560

El objetivo del diseño de los iostream es permitir que usted pueda mover 5561

y/o formatear carácteres fácilmente. Ciertamente no podria ser de mucha 5562

utilidad si no se pudiera hacer la mayoria de los formateos provistos por la 5563

familia de funciones de printf() en C. Es esta sección, usted aprenderá 5564

todo sobre las funciones de formateo de salida que estan disponibles para 5565

iostream, con lo que puede formatear los bytes de la manera que usted 5566

quiera. 5567

Las funciones de formateo en iostream pueden ser algo cunfusas al 5568

principio porque a menudo existe más de una manera de controlar el 5569

formateo: a través de funciones miembro y manipuladores. Para confundir 5570

más las cosas, una función miembro genérica pone banderas de estado para 5571

controlar el formateo, como la justificación a la derecha o izquierda, el uso de 5572

letras mayúsculas para la notación hexadecimal, para siempre usar un punto 5573

decimal para valores de coma flotante, y cosas así. En el otro lado, funciones 5574

miembro separadas activan y leen valores para el caracter de relleno, la 5575

anchura del campo, y la precisión. 5576

Page 205: Pensar en C++ (Volumen 2)

205

En un intento de clarificar todo esto, primero examinaremos el formateo 5577

interno de los datos de un iostream, y las funciones miembro que pueden 5578

modificar estos datos. (Todo puede ser controlado por funciones miembro si 5579

se desea). Cubriremos los manipuladores aparte. 5580

5.8.1. Banderas de formateo 5581

La clase ios contiene los miembros de datos para guardar toda la 5582

información de formateo perteneciente a un stream. Algunos de estos datos 5583

tiene un rango de valores de datos y son guardados en variables: la precisión 5584

de la coma flotante, la anchura del campo de salida, y el carácter usado para 5585

rellenar la salida (normalmente un espacio). El resto del formateo es 5586

determinado por banderas, que generalmente están combinadas para 5587

ahorrar espacio y se llaman colectivamente banderas de formateo. Puede 5588

recuperar los valores de las banderas de formateo con la función miembro 5589

ios::flag(), que no toma argumentos y retorna un objeto de tipo fmtflags 5590

(usualmente un sinónimo de long) que contiene las banderas de formateo 5591

actuales. El resto de funciones hacen cambios en las banderas de formateo y 5592

retornan el valor previo de las banderas de formateo. 5593

fmtflags ios::flags(fmtflags newflags); 5594

fmtflags ios::setf(fmtflags ored_flag); 5595

fmtflags ios::unsetf(fmtflags clear_flag); 5596

fmtflags ios::setf(fmtflags bits, fmtflags field); 5597

La primera función fuerza que todas las banderas cambien, que a veces es 5598

lo que usted quiere. Más a menudo, usted cambia una bandera cada vez 5599

usando las otras tres funciones. 5600

El uso de setf() puede parecer algo confusa. Para conocer qué versión 5601

sobrecargada usar, debe conocer el tipo de la bandera que está cambiando. 5602

Existen dos tipos de banderas: las que simplemente estan activadas o no, y 5603

aquellas que trabajan en grupo con otras banderas. La banderas que estan 5604

encendidas/apagadas son las más simples de entender por que usted las 5605

Page 206: Pensar en C++ (Volumen 2)

206

enciende con setf(fmtflags) y las apaga con unsetf(fmtflags). Estas 5606

banderas se muestran en la siguiente tabla: 5607

bandera activa/inactiva 5608

Efecto 5609

ios::skipws 5610

Se salta los espacios en blanco. ( Para la entrada esto es por defecto). 5611

ios::showbase 5612

Indica la base numérica (que puede ser, por ejemplo, decimal, octal o 5613

hexadecimal) cuando imprimimos el valor entero. Los stream de entrada 5614

tambié reconocen el prefijo de base cuando showbase está activo. 5615

ios::showpoint 5616

Muestra el punto decimal insertando ceros para valores de coma flotante. 5617

ios::uppercase 5618

Muestra A-F mayúsculas para valores hexadecimales y E para científicos. 5619

ios::showpos 5620

Muestra el signo de sumar (+) para los valores positivos 5621

ios::unitbuf 5622

'Unit buffering.' El stream es borrado después de cada inseción. 5623

Por ejemplo, para mostrar el signo de sumar para cout, puede usar 5624

cout.setf(ios::showpos). Para dejar de mostrar el signo de sumar, escriba 5625

cout.unsetf(ios::showpos). 5626

La bandera de unitbuf controla el almacenamiento unitario, que significa 5627

que cada inserción es lanzada a su stream de salida inmediatamente. Esto 5628

es útil para hacer recuento de errores, ya que en caso de fallo del programa, 5629

sus datos son todavía escritos al fichero de log. El siguiente programa ilustra 5630

el almacenamiento unitario. 5631

//: C04:Unitbuf.cpp {RunByHand} 5632

#include <cstdlib> // For abort() 5633

#include <fstream> 5634

Page 207: Pensar en C++ (Volumen 2)

207

using namespace std; 5635

5636

int main() { 5637

ofstream out("log.txt"); 5638

out.setf(ios::unitbuf); 5639

out << "one" << endl; 5640

out << "two" << endl; 5641

abort(); 5642

} ///:~ 5643

Listado 5.15. C04/Unitbuf.cpp 5644

5645

Es necesario activar el almacenamiento unitario antes de que cualquier 5646

inserción sea hecha en el stream. Cuando hemos descomentado la llamada 5647

a setf(), un compilador en particular ha escrito solo la letra 'o' en el fichero 5648

log.txt. Con el almacenamiento unitario, ningun dato se perdió. 5649

El stream de salida estándar cerr tiene el almacenamiento unitario 5650

activado por defecto. Hay un coste para el almacenamiento unitario, asi que 5651

si un stream de salida se usa intensivamente, no active el almacenamiento 5652

unitario a menos que la eficiencia no sea una consideración. 5653

5.8.2. Campos de formateo 5654

El segundo tipo de banderas de formateo trabajan en grupo. Solo una de 5655

estas banderas pueden ser activadas cada vez, como los botones de una 5656

vieja radio de coche - usted apretaba una y el resto saltaban. 5657

Desafortunadamente esto no pasa automáticamente, y usted tiene que poner 5658

atención a que bandera está activando para no llamar accidentalmente a la 5659

función setf() incorrecta. Por ejemplo, hay una bandera para cada una de 5660

las bases numéricas: hexadecimal, decimal y octal. A estas banderas se 5661

refiere en conjunto ios::basefield. Si la bandera ios::dec está activa y 5662

Page 208: Pensar en C++ (Volumen 2)

208

usted llama setf(ios::hex), usted activará la bandera de ios::hex, pero no 5663

desactivará la bandera de ios::dec, resultando en un comportamiento 5664

indeterminado. En vez de esto,llame a la segunda forma de la función setf() 5665

como esta: setf(ios::hex, ios::basefield) . Esta función primero limpia 5666

todos los bits de ios::basefield y luego activa ios::hex. Así, esta forma de 5667

setf() asegura que las otras banderas en el grupo 'saltan' cuando usted 5668

activa una. El manipulador ios::hex lo hace todo por usted, 5669

automáticamente, asi que no tiene que preocuparse con los detalles de la 5670

implementación interna de esta clase o tener cuidado de que esto es una 5671

seria de banderas binarias. Más adelante verá que hay manipuladores para 5672

proveer de la funcionalidad equivalente en todas las parts donde usted fuera 5673

a usar setf(). 5674

Aquí están los grupos de banderas y sus efectos: 5675

ios::basefield 5676

Efecto 5677

ios::dec 5678

Formatea valores enteros en base 10 (decimal)(Formateo por defecto - 5679

ningún prefijo es visible). 5680

ios::hex 5681

Formatea valores enteros en base 16 (hexadecimal). 5682

ios::oct 5683

Formatea valores enteros en base 8 (octal). 5684

ios::floatfield 5685

Efecto 5686

ios::scientific 5687

Muestra números en coma flotante en formato científico. El campo 5688

precisión indica el numero de dígitos después del punto decimal. 5689

ios::fixed 5690

Muestra números en coma flotante en formato fijado. El campo precisión 5691

indica en número de dígitos después del punto decimal. 5692

'automatic' (Ninguno de los bits está activado). 5693

Page 209: Pensar en C++ (Volumen 2)

209

El campo precisión indica el número total de dígitos significativos. 5694

ios::adjustfield 5695

Efecto 5696

ios::left 5697

Valores con alineación izquierda; se llena hasta la derecha con el carácter 5698

de relleno. 5699

ios::right 5700

Valores con alineación derecha; se llena hasta la izquierda con el carácter 5701

de relleno. Esta es la alineación por defecto. 5702

ios::internal 5703

Añade carácteres de relleno despues de algún signo inicial o indicador de 5704

base, pero antes del valor. (En otras palabras, el signo, si está presente, se 5705

justifica a la izquierda mientras el número se justifica a la derecha). 5706

5.8.3. Anchura, relleno y precisión 5707

La variables internas que controlan la anchura del campo de salida, el 5708

carácter usado para rellenar el campo de salida, y la precisión para escribir 5709

números de coma flotante son escritos y leídos por funciones miembro del 5710

mismo nombre. 5711

Función 5712

Efecto 5713

int ios::width( ) 5714

Retorna la anchura actual. Por defecto es 0. Se usa para la inserción y la 5715

extracción. 5716

int ios::width(int n) 5717

Pone la anchura, retorna la anchura previa. 5718

int ios::fill( ) 5719

Retorna el carácter de relleno actual. Por defecto es el espacio. 5720

int ios::fill(int n) 5721

Poner el carácter de relleno, retorna el carácter de relleno anterior. 5722

int ios::precision( ) 5723

Page 210: Pensar en C++ (Volumen 2)

210

Retorna la precisión actual de la coma flotante. Por defecto es 6. 5724

int ios::precision(int n) 5725

Pone la precisión de la coma flotante, retorna la precisión anteriot. Vea la 5726

tabla ios::floatfield para el significado de 'precisión'. 5727

El relleno y la precisión son bastante claras, pero la anchura requiera 5728

alguna explicación. Cuando la anchura es 0, insertar un valor produce el 5729

mínimo número de carácteres necesario para representar este valor. Una 5730

anchura positiva significa que insertar un valor producirá al menos tantos 5731

carácteres como la anchura; si el valor tiene menos carácteres que la 5732

anchura, el carácter de relleno llena el campo. No obstante, el valor nunca 5733

será truncado, con lo que si usted intenta escribir 123 con una anchura de 5734

dos, seguirá obteniendo 123. El campo anchura especifica un minimo 5735

número de carácteres; no hay forma de especificar un número mínimo. 5736

La anchura también es diferente por que vuelve a ser cero por cada 5737

insertador o extractor que puede ser influenciado por este valor. Realmente 5738

no es una variable de estado, sino más bien un argumento implícito para los 5739

extractores y insertadores. Si quiere una anchura constante, llame a width() 5740

despues de cada inserción o extracción. 5741

5.8.4. Un ejemplo exhaustivo 5742

Para estar seguros de que usted conoce como llamar a todas las funciones 5743

discutidas previamente, aquí tiene un ejemplo que las llama a todas: 5744

//: C04:Format.cpp 5745

// Formatting Functions. 5746

#include <fstream> 5747

#include <iostream> 5748

#include "../require.h" 5749

using namespace std; 5750

#define D(A) T << #A << endl; A 5751

Page 211: Pensar en C++ (Volumen 2)

211

5752

int main() { 5753

ofstream T("format.out"); 5754

assure(T); 5755

D(int i = 47;) 5756

D(float f = 2300114.414159;) 5757

const char* s = "Is there any more?"; 5758

5759

D(T.setf(ios::unitbuf);) 5760

D(T.setf(ios::showbase);) 5761

D(T.setf(ios::uppercase | ios::showpos);) 5762

D(T << i << endl;) // Default is dec 5763

D(T.setf(ios::hex, ios::basefield);) 5764

D(T << i << endl;) 5765

D(T.setf(ios::oct, ios::basefield);) 5766

D(T << i << endl;) 5767

D(T.unsetf(ios::showbase);) 5768

D(T.setf(ios::dec, ios::basefield);) 5769

D(T.setf(ios::left, ios::adjustfield);) 5770

D(T.fill('0');) 5771

D(T << "fill char: " << T.fill() << endl;) 5772

D(T.width(10);) 5773

T << i << endl; 5774

D(T.setf(ios::right, ios::adjustfield);) 5775

D(T.width(10);) 5776

Page 212: Pensar en C++ (Volumen 2)

212

T << i << endl; 5777

D(T.setf(ios::internal, ios::adjustfield);) 5778

D(T.width(10);) 5779

T << i << endl; 5780

D(T << i << endl;) // Without width(10) 5781

5782

D(T.unsetf(ios::showpos);) 5783

D(T.setf(ios::showpoint);) 5784

D(T << "prec = " << T.precision() << endl;) 5785

D(T.setf(ios::scientific, ios::floatfield);) 5786

D(T << endl << f << endl;) 5787

D(T.unsetf(ios::uppercase);) 5788

D(T << endl << f << endl;) 5789

D(T.setf(ios::fixed, ios::floatfield);) 5790

D(T << f << endl;) 5791

D(T.precision(20);) 5792

D(T << "prec = " << T.precision() << endl;) 5793

D(T << endl << f << endl;) 5794

D(T.setf(ios::scientific, ios::floatfield);) 5795

D(T << endl << f << endl;) 5796

D(T.setf(ios::fixed, ios::floatfield);) 5797

D(T << f << endl;) 5798

5799

D(T.width(10);) 5800

T << s << endl; 5801

Page 213: Pensar en C++ (Volumen 2)

213

D(T.width(40);) 5802

T << s << endl; 5803

D(T.setf(ios::left, ios::adjustfield);) 5804

D(T.width(40);) 5805

T << s << endl; 5806

} ///:~ 5807

Listado 5.16. C04/Format.cpp 5808

5809

Este ejemplo usa un truco para crear un fichero de traza para que pueda 5810

monitorizar lo que está pasando. La macro D(a) usa el preprocesador 5811

'convirtiendo a string' para convertir a en una cadena para mostrar. Entonces 5812

se reitera a con lo que la sentencia se ejecuta. La macro envia toda la 5813

información a un fichero llamado T, que es el fichero de traza. La salida es: 5814

int i = 47; 5815 float f = 2300114.414159; 5816

T.setf(ios::unitbuf); 5817 T.setf(ios::showbase); 5818

T.setf(ios::uppercase | ios::showpos); 5819 T << i << endl; 5820

+47 5821 T.setf(ios::hex, ios::basefield); 5822

T << i << endl; 5823 0X2F 5824

T.setf(ios::oct, ios::basefield); 5825 T << i << endl; 5826

057 5827 T.unsetf(ios::showbase); 5828

T.setf(ios::dec, ios::basefield); 5829 T.setf(ios::left, ios::adjustfield); 5830

T.fill('0'); 5831 T << "fill char: " << T.fill() << endl; 5832

fill char: 0 5833 T.width(10); 5834 +470000000 5835

T.setf(ios::right, ios::adjustfield); 5836 T.width(10); 5837 0000000+47 5838

T.setf(ios::internal, ios::adjustfield); 5839 T.width(10); 5840 +000000047 5841

T << i << endl; 5842 +47 5843

T.unsetf(ios::showpos); 5844

Page 214: Pensar en C++ (Volumen 2)

214

T.setf(ios::showpoint); 5845 T << "prec = " << T.precision() << endl; 5846

prec = 6 5847 T.setf(ios::scientific, ios::floatfield); 5848

T << endl << f << endl; 5849 5850

2.300114E+06 5851 T.unsetf(ios::uppercase); 5852 T << endl << f << endl; 5853

5854 2.300114e+06 5855

T.setf(ios::fixed, ios::floatfield); 5856 T << f << endl; 5857 2300114.500000 5858

T.precision(20); 5859 T << "prec = " << T.precision() << endl; 5860

prec = 20 5861 T << endl << f << endl; 5862

5863 2300114.50000000000000000000 5864

T.setf(ios::scientific, ios::floatfield); 5865 T << endl << f << endl; 5866

5867 2.30011450000000000000e+06 5868

T.setf(ios::fixed, ios::floatfield); 5869 T << f << endl; 5870

2300114.50000000000000000000 5871 T.width(10); 5872

Is there any more? 5873 T.width(40); 5874

0000000000000000000000Is there any more? 5875 T.setf(ios::left, ios::adjustfield); 5876

T.width(40); 5877 Is there any more?0000000000000000000000 5878

Estudiar esta salida debería clarificar su entendimiento del formateo de las 5879

funciones miembro de iostream . 5880

5.9. Manipuladores 5881

Como puede ver en el programa previo, llamar a funciones miembro para 5882

operaciones de formateo de stream puede ser un poco tedioso. Para hacer 5883

las cosas mas fáciles de leer y escribir, existe un conjunto de manipuladores 5884

para duplicar las acciones previstas por las funciones miembro. Las 5885

manipuladores son convenientes por que usted puede insertarlos para que 5886

actuen dentro de una expresion contenedora; no necesita crear una 5887

sentencia de llamada a función separada. 5888

Los manipuladores cambian el estado de un stream en vez de (o además 5889

de) procesar los datos. Cuando insertamos un endl en una expresión de 5890

Page 215: Pensar en C++ (Volumen 2)

215

salida, por ejemplo, no solo inserta un carácter de nueva linea, sino que 5891

ademas termina el stream (esto es, saca todos los carácteres pendientes que 5892

han sido almacenadas en el buffer interno del stream pero todavia no en la 5893

salida). Puede terminar el stream simplemente asi: 5894

cout << flush; 5895

Lo que causa una llamada a la función miembro flush(), como esta: 5896

cout.flush(); 5897

como efecto lateral (nada es insertado dentro de stream). Adicionalmente 5898

los manipuladores básicos cambirán la base del número a oct (octal), dec 5899

(decimal) o hex (hexadecimal). 5900

cout << hex << "0x" << i << endl; 5901

En este caso, la salida numérica continuará en modo hexadecimal hasta 5902

que usted lo cambie insertando o dec o oct en el stream de salida. 5903

También existe un manipulador para la extracción que se 'come' los 5904

espacios en blanco: 5905

cin >> ws; 5906

Los manipuladores sin argumentos son provistos en <iostream>. Esto 5907

incluye dec, oct,y hex, que hacen las mismas acciones que, respectivamente, 5908

setf(ios::dec, ios::basefield), setf(ios::oct, ios::basefield), y 5909

setf(ios::hex, ios::basefield), aunque más sucintamente. La cabecera 5910

<iostream> también incluye ws, endl, y flush y el conjunto adicional mostrado 5911

aquí: 5912

Manipulador 5913

Page 216: Pensar en C++ (Volumen 2)

216

Efecto 5914

showbase noshowbase 5915

Indica la base numérica (dec, oct, o hex) cuando imprimimos un entero. 5916

showpos noshowpos 5917

Muestra el signo más (+) para valores positivos. 5918

uppercase nouppercase 5919

Muestra mayúsculas A-F para valores hexadecimales, y muestra E para 5920

valores científicos. 5921

showpoint noshowpoint 5922

Muestra punto decimal y ceros arrastrados para valores de coma flotante. 5923

skipws noskipws 5924

Escapa los espacios en blanco en la entrada. 5925

left right internal 5926

Alineación izquierda, relleno a la derecha. Alineación derecha, relleno a la 5927

izquierda. Rellenar entre el signo o el indicador de base y el valor. 5928

scientific fixed 5929

Indica la preferencia al mostrar la salida para coma flotante (notación 5930

científica versus coma flotante decimal). 5931

5.9.1. Manipuladores con argumentos 5932

Existen seis manipuladores estándar, como setw(), que toman 5933

argumentos. Están definidos en el fichero de cabecera <iomanip>, y están 5934

enumerados en la siguiente tabla: 5935

Manipulador 5936

Efecto 5937

setiosflags(fmtflags n) 5938

Equivalente a una llamada a setf(n). La activación continua hasta el 5939

siguiente cambio, como ios::setf(). 5940

resetiosflags(fmtflags n) 5941

Page 217: Pensar en C++ (Volumen 2)

217

Limpia solo las banderas de formato especificadas por n. La activación 5942

permanece hasta el siguiente cambio, como ios::unsetf(). 5943

setbase(base n) 5944

Cambia la base a n, donde n es 10, 8 o 16. (Caulquier otra opción resulta 5945

en 0). Si n es cero, la salida es base 10, pero la entrada usa convenciones 5946

de C: 10 es 10, 010 es 8, y 0xf es 15. Puede usar también dec, oct y hex 5947

para la salida. 5948

setfill(char n) 5949

Cambia el carácter de relleno a n, como ios::fill(). 5950

setprecision(int n) 5951

Cambia la precision a n, como ios::precision(). 5952

setw(int n) 5953

Cambia la anchura del campo a n, como en ios::width() 5954

Si está usando mucho el formateo, usted puede ver como usar los 5955

manipuladores en vez de llamar a funciones miembro de stream puede 5956

limpiar su código. Como ejemplo, aquí tiene un programa de la sección 5957

previa reescrito para usar los manipuladores. (La macro D() ha sido borrada 5958

para hacerlo más fácil de leer). 5959

//: C04:Manips.cpp 5960

// Format.cpp using manipulators. 5961

#include <fstream> 5962

#include <iomanip> 5963

#include <iostream> 5964

using namespace std; 5965

5966

int main() { 5967

ofstream trc("trace.out"); 5968

int i = 47; 5969

Page 218: Pensar en C++ (Volumen 2)

218

float f = 2300114.414159; 5970

char* s = "Is there any more?"; 5971

5972

trc << setiosflags(ios::unitbuf 5973

| ios::showbase | ios::uppercase 5974

| ios::showpos); 5975

trc << i << endl; 5976

trc << hex << i << endl 5977

<< oct << i << endl; 5978

trc.setf(ios::left, ios::adjustfield); 5979

trc << resetiosflags(ios::showbase) 5980

<< dec << setfill('0'); 5981

trc << "fill char: " << trc.fill() << endl; 5982

trc << setw(10) << i << endl; 5983

trc.setf(ios::right, ios::adjustfield); 5984

trc << setw(10) << i << endl; 5985

trc.setf(ios::internal, ios::adjustfield); 5986

trc << setw(10) << i << endl; 5987

trc << i << endl; // Without setw(10) 5988

5989

trc << resetiosflags(ios::showpos) 5990

<< setiosflags(ios::showpoint) 5991

<< "prec = " << trc.precision() << endl; 5992

trc.setf(ios::scientific, ios::floatfield); 5993

trc << f << resetiosflags(ios::uppercase) << endl; 5994

Page 219: Pensar en C++ (Volumen 2)

219

trc.setf(ios::fixed, ios::floatfield); 5995

trc << f << endl; 5996

trc << f << endl; 5997

trc << setprecision(20); 5998

trc << "prec = " << trc.precision() << endl; 5999

trc << f << endl; 6000

trc.setf(ios::scientific, ios::floatfield); 6001

trc << f << endl; 6002

trc.setf(ios::fixed, ios::floatfield); 6003

trc << f << endl; 6004

trc << f << endl; 6005

6006

trc << setw(10) << s << endl; 6007

trc << setw(40) << s << endl; 6008

trc.setf(ios::left, ios::adjustfield); 6009

trc << setw(40) << s << endl; 6010

} ///:~ 6011

Listado 5.17. C04/Manips.cpp 6012

6013

Puede ver que un montón de sentencias múltiples han sido condensadas 6014

dentro de una sola inserción encadenada. Nótese que la llamada a 6015

setiosflags() en que se pasa el OR binario de las banderas. Esto se podría 6016

haber hecho también con setf() y unsetf() como en el ejemplo previo. 6017

//: C04:InputWidth.cpp 6018

// Shows limitations of setw with input. 6019

Page 220: Pensar en C++ (Volumen 2)

220

#include <cassert> 6020

#include <cmath> 6021

#include <iomanip> 6022

#include <limits> 6023

#include <sstream> 6024

#include <string> 6025

using namespace std; 6026

6027

int main() { 6028

istringstream is("one 2.34 five"); 6029

string temp; 6030

is >> setw(2) >> temp; 6031

assert(temp == "on"); 6032

is >> setw(2) >> temp; 6033

assert(temp == "e"); 6034

double x; 6035

is >> setw(2) >> x; 6036

double relerr = fabs(x - 2.34) / x; 6037

assert(relerr <= numeric_limits<double>::epsilon()); 6038

} ///:~ 6039

Listado 5.18. C04/InputWidth.cpp 6040

6041

5.9.2. 6042

Page 221: Pensar en C++ (Volumen 2)

221

ostream& endl(ostream&); 6043

cout << "howdy" << endl; 6044

ostream& ostream::operator<<(ostream& (*pf)(ostream&)) { 6045

return pf(*this); 6046

} 6047

//: C04:nl.cpp 6048

// Creating a manipulator. 6049

#include <iostream> 6050

using namespace std; 6051

6052

ostream& nl(ostream& os) { 6053

return os << '\n'; 6054

} 6055

6056

int main() { 6057

cout << "newlines" << nl << "between" << nl 6058

<< "each" << nl << "word" << nl; 6059

} ///:~ 6060

Listado 5.19. C04/nl.cpp 6061

6062

cout.operator<<(nl) è nl(cout) 6063

os << '\n'; 6064

Page 222: Pensar en C++ (Volumen 2)

222

5.9.3. 6065

//: C04:Effector.cpp 6066

// Jerry Schwarz's "effectors." 6067

#include <cassert> 6068

#include <limits> // For max() 6069

#include <sstream> 6070

#include <string> 6071

using namespace std; 6072

6073

// Put out a prefix of a string: 6074

class Fixw { 6075

string str; 6076

public: 6077

Fixw(const string& s, int width) : str(s, 0, width) {} 6078

friend ostream& operator<<(ostream& os, const Fixw& fw) { 6079

return os << fw.str; 6080

} 6081

}; 6082

6083

// Print a number in binary: 6084

typedef unsigned long ulong; 6085

6086

class Bin { 6087

ulong n; 6088

public: 6089

Page 223: Pensar en C++ (Volumen 2)

223

Bin(ulong nn) { n = nn; } 6090

friend ostream& operator<<(ostream& os, const Bin& b) { 6091

const ulong ULMAX = numeric_limits<ulong>::max(); 6092

ulong bit = ~(ULMAX >> 1); // Top bit set 6093

while(bit) { 6094

os << (b.n & bit ? '1' : '0'); 6095

bit >>= 1; 6096

} 6097

return os; 6098

} 6099

}; 6100

6101

int main() { 6102

string words = "Things that make us happy, make us wise"; 6103

for(int i = words.size(); --i >= 0;) { 6104

ostringstream s; 6105

s << Fixw(words, i); 6106

assert(s.str() == words.substr(0, i)); 6107

} 6108

ostringstream xs, ys; 6109

xs << Bin(0xCAFEBABEUL); 6110

assert(xs.str() == 6111

"1100""1010""1111""1110""1011""1010""1011""1110"); 6112

ys << Bin(0x76543210UL); 6113

assert(ys.str() == 6114

Page 224: Pensar en C++ (Volumen 2)

224

"0111""0110""0101""0100""0011""0010""0001""0000"); 6115

} ///:~ 6116

Listado 5.20. C04/Effector.cpp 6117

6118

5.10. 6119

5.10.1. 6120

//: C04:Cppcheck.cpp 6121

// Configures .h & .cpp files to conform to style 6122

// standard. Tests existing files for conformance. 6123

#include <fstream> 6124

#include <sstream> 6125

#include <string> 6126

#include <cstddef> 6127

#include "../require.h" 6128

using namespace std; 6129

6130

bool startsWith(const string& base, const string& key) { 6131

return base.compare(0, key.size(), key) == 0; 6132

} 6133

6134

void cppCheck(string fileName) { 6135

enum bufs { BASE, HEADER, IMPLEMENT, HLINE1, GUARD1, 6136

GUARD2, GUARD3, CPPLINE1, INCLUDE, BUFNUM }; 6137

Page 225: Pensar en C++ (Volumen 2)

225

string part[BUFNUM]; 6138

part[BASE] = fileName; 6139

// Find any '.' in the string: 6140

size_t loc = part[BASE].find('.'); 6141

if(loc != string::npos) 6142

part[BASE].erase(loc); // Strip extension 6143

// Force to upper case: 6144

for(size_t i = 0; i < part[BASE].size(); i++) 6145

part[BASE][i] = toupper(part[BASE][i]); 6146

// Create file names and internal lines: 6147

part[HEADER] = part[BASE] + ".h"; 6148

part[IMPLEMENT] = part[BASE] + ".cpp"; 6149

part[HLINE1] = "//" ": " + part[HEADER]; 6150

part[GUARD1] = "#ifndef " + part[BASE] + "_H"; 6151

part[GUARD2] = "#define " + part[BASE] + "_H"; 6152

part[GUARD3] = "#endif // " + part[BASE] +"_H"; 6153

part[CPPLINE1] = string("//") + ": " + part[IMPLEMENT]; 6154

part[INCLUDE] = "#include \"" + part[HEADER] + "\""; 6155

// First, try to open existing files: 6156

ifstream existh(part[HEADER].c_str()), 6157

existcpp(part[IMPLEMENT].c_str()); 6158

if(!existh) { // Doesn't exist; create it 6159

ofstream newheader(part[HEADER].c_str()); 6160

assure(newheader, part[HEADER].c_str()); 6161

newheader << part[HLINE1] << endl 6162

Page 226: Pensar en C++ (Volumen 2)

226

<< part[GUARD1] << endl 6163

<< part[GUARD2] << endl << endl 6164

<< part[GUARD3] << endl; 6165

} else { // Already exists; verify it 6166

stringstream hfile; // Write & read 6167

ostringstream newheader; // Write 6168

hfile << existh.rdbuf(); 6169

// Check that first three lines conform: 6170

bool changed = false; 6171

string s; 6172

hfile.seekg(0); 6173

getline(hfile, s); 6174

bool lineUsed = false; 6175

// The call to good() is for Microsoft (later too): 6176

for(int line = HLINE1; hfile.good() && line <= GUARD2; 6177

++line) { 6178

if(startsWith(s, part[line])) { 6179

newheader << s << endl; 6180

lineUsed = true; 6181

if(getline(hfile, s)) 6182

lineUsed = false; 6183

} else { 6184

newheader << part[line] << endl; 6185

changed = true; 6186

lineUsed = false; 6187

Page 227: Pensar en C++ (Volumen 2)

227

} 6188

} 6189

// Copy rest of file 6190

if(!lineUsed) 6191

newheader << s << endl; 6192

newheader << hfile.rdbuf(); 6193

// Check for GUARD3 6194

string head = hfile.str(); 6195

if(head.find(part[GUARD3]) == string::npos) { 6196

newheader << part[GUARD3] << endl; 6197

changed = true; 6198

} 6199

// If there were changes, overwrite file: 6200

if(changed) { 6201

existh.close(); 6202

ofstream newH(part[HEADER].c_str()); 6203

assure(newH, part[HEADER].c_str()); 6204

newH << "//@//\n" // Change marker 6205

<< newheader.str(); 6206

} 6207

} 6208

if(!existcpp) { // Create cpp file 6209

ofstream newcpp(part[IMPLEMENT].c_str()); 6210

assure(newcpp, part[IMPLEMENT].c_str()); 6211

newcpp << part[CPPLINE1] << endl 6212

Page 228: Pensar en C++ (Volumen 2)

228

<< part[INCLUDE] << endl; 6213

} else { // Already exists; verify it 6214

stringstream cppfile; 6215

ostringstream newcpp; 6216

cppfile << existcpp.rdbuf(); 6217

// Check that first two lines conform: 6218

bool changed = false; 6219

string s; 6220

cppfile.seekg(0); 6221

getline(cppfile, s); 6222

bool lineUsed = false; 6223

for(int line = CPPLINE1; 6224

cppfile.good() && line <= INCLUDE; ++line) { 6225

if(startsWith(s, part[line])) { 6226

newcpp << s << endl; 6227

lineUsed = true; 6228

if(getline(cppfile, s)) 6229

lineUsed = false; 6230

} else { 6231

newcpp << part[line] << endl; 6232

changed = true; 6233

lineUsed = false; 6234

} 6235

} 6236

// Copy rest of file 6237

Page 229: Pensar en C++ (Volumen 2)

229

if(!lineUsed) 6238

newcpp << s << endl; 6239

newcpp << cppfile.rdbuf(); 6240

// If there were changes, overwrite file: 6241

if(changed) { 6242

existcpp.close(); 6243

ofstream newCPP(part[IMPLEMENT].c_str()); 6244

assure(newCPP, part[IMPLEMENT].c_str()); 6245

newCPP << "//@//\n" // Change marker 6246

<< newcpp.str(); 6247

} 6248

} 6249

} 6250

6251

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

if(argc > 1) 6253

cppCheck(argv[1]); 6254

else 6255

cppCheck("cppCheckTest.h"); 6256

} ///:~ 6257

Listado 5.21. C04/Cppcheck.cpp 6258

6259

// CPPCHECKTEST.h 6260

#ifndef CPPCHECKTEST_H 6261

Page 230: Pensar en C++ (Volumen 2)

230

#define CPPCHECKTEST_H 6262

#endif // CPPCHECKTEST_H 6263

// PPCHECKTEST.cpp 6264

#include "CPPCHECKTEST.h" 6265

5.10.2. 6266

//: C04:Showerr.cpp {RunByHand} 6267

// Un-comment error generators. 6268

#include <cstddef> 6269

#include <cstdlib> 6270

#include <cstdio> 6271

#include <fstream> 6272

#include <iostream> 6273

#include <sstream> 6274

#include <string> 6275

#include "../require.h" 6276

using namespace std; 6277

6278

const string USAGE = 6279

"usage: showerr filename chapnum\n" 6280

"where filename is a C++ source file\n" 6281

"and chapnum is the chapter name it's in.\n" 6282

"Finds lines commented with //! and removes\n" 6283

"the comment, appending //(#) where # is unique\n" 6284

Page 231: Pensar en C++ (Volumen 2)

231

"across all files, so you can determine\n" 6285

"if your compiler finds the error.\n" 6286

"showerr /r\n" 6287

"resets the unique counter."; 6288

6289

class Showerr { 6290

const int CHAP; 6291

const string MARKER, FNAME; 6292

// File containing error number counter: 6293

const string ERRNUM; 6294

// File containing error lines: 6295

const string ERRFILE; 6296

stringstream edited; // Edited file 6297

int counter; 6298

public: 6299

Showerr(const string& f, const string& en, 6300

const string& ef, int c) 6301

: CHAP(c), MARKER("//!"), FNAME(f), ERRNUM(en), 6302

ERRFILE(ef), counter(0) {} 6303

void replaceErrors() { 6304

ifstream infile(FNAME.c_str()); 6305

assure(infile, FNAME.c_str()); 6306

ifstream count(ERRNUM.c_str()); 6307

if(count) count >> counter; 6308

int linecount = 1; 6309

Page 232: Pensar en C++ (Volumen 2)

232

string buf; 6310

ofstream errlines(ERRFILE.c_str(), ios::app); 6311

assure(errlines, ERRFILE.c_str()); 6312

while(getline(infile, buf)) { 6313

// Find marker at start of line: 6314

size_t pos = buf.find(MARKER); 6315

if(pos != string::npos) { 6316

// Erase marker: 6317

buf.erase(pos, MARKER.size() + 1); 6318

// Append counter & error info: 6319

ostringstream out; 6320

out << buf << " // (" << ++counter << ") " 6321

<< "Chapter " << CHAP 6322

<< " File: " << FNAME 6323

<< " Line " << linecount << endl; 6324

edited << out.str(); 6325

errlines << out.str(); // Append error file 6326

} 6327

else 6328

edited << buf << "\n"; // Just copy 6329

++linecount; 6330

} 6331

} 6332

void saveFiles() { 6333

ofstream outfile(FNAME.c_str()); // Overwrites 6334

Page 233: Pensar en C++ (Volumen 2)

233

assure(outfile, FNAME.c_str()); 6335

outfile << edited.rdbuf(); 6336

ofstream count(ERRNUM.c_str()); // Overwrites 6337

assure(count, ERRNUM.c_str()); 6338

count << counter; // Save new counter 6339

} 6340

}; 6341

6342

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

const string ERRCOUNT("../errnum.txt"), 6344

ERRFILE("../errlines.txt"); 6345

requireMinArgs(argc, 1, USAGE.c_str()); 6346

if(argv[1][0] == '/' || argv[1][0] == '-') { 6347

// Allow for other switches: 6348

switch(argv[1][1]) { 6349

case 'r': case 'R': 6350

cout << "reset counter" << endl; 6351

remove(ERRCOUNT.c_str()); // Delete files 6352

remove(ERRFILE.c_str()); 6353

return EXIT_SUCCESS; 6354

default: 6355

cerr << USAGE << endl; 6356

return EXIT_FAILURE; 6357

} 6358

} 6359

Page 234: Pensar en C++ (Volumen 2)

234

if(argc == 3) { 6360

Showerr s(argv[1], ERRCOUNT, ERRFILE, atoi(argv[2])); 6361

s.replaceErrors(); 6362

s.saveFiles(); 6363

} 6364

} ///:~ 6365

Listado 5.22. C04/Showerr.cpp 6366

6367

5.10.3. 6368

//: C04:DataLogger.h 6369

// Datalogger record layout. 6370

#ifndef DATALOG_H 6371

#define DATALOG_H 6372

#include <ctime> 6373

#include <iosfwd> 6374

#include <string> 6375

using std::ostream; 6376

6377

struct Coord { 6378

int deg, min, sec; 6379

Coord(int d = 0, int m = 0, int s = 0) 6380

: deg(d), min(m), sec(s) {} 6381

std::string toString() const; 6382

Page 235: Pensar en C++ (Volumen 2)

235

}; 6383

6384

ostream& operator<<(ostream&, const Coord&); 6385

6386

class DataPoint { 6387

std::time_t timestamp; // Time & day 6388

Coord latitude, longitude; 6389

double depth, temperature; 6390

public: 6391

DataPoint(std::time_t ts, const Coord& lat, 6392

const Coord& lon, double dep, double temp) 6393

: timestamp(ts), latitude(lat), longitude(lon), 6394

depth(dep), temperature(temp) {} 6395

DataPoint() : timestamp(0), depth(0), temperature(0) {} 6396

friend ostream& operator<<(ostream&, const DataPoint&); 6397

}; 6398

#endif // DATALOG_H ///:~ 6399

Listado 5.23. C04/DataLogger.h 6400

6401

//: C04:DataLogger.cpp {O} 6402

// Datapoint implementations. 6403

#include "DataLogger.h" 6404

#include <iomanip> 6405

#include <iostream> 6406

Page 236: Pensar en C++ (Volumen 2)

236

#include <sstream> 6407

#include <string> 6408

using namespace std; 6409

6410

ostream& operator<<(ostream& os, const Coord& c) { 6411

return os << c.deg << '*' << c.min << '\'' 6412

<< c.sec << '"'; 6413

} 6414

6415

string Coord::toString() const { 6416

ostringstream os; 6417

os << *this; 6418

return os.str(); 6419

} 6420

6421

ostream& operator<<(ostream& os, const DataPoint& d) { 6422

os.setf(ios::fixed, ios::floatfield); 6423

char fillc = os.fill('0'); // Pad on left with '0' 6424

tm* tdata = localtime(&d.timestamp); 6425

os << setw(2) << tdata->tm_mon + 1 << '\\' 6426

<< setw(2) << tdata->tm_mday << '\\' 6427

<< setw(2) << tdata->tm_year+1900 << ' ' 6428

<< setw(2) << tdata->tm_hour << ':' 6429

<< setw(2) << tdata->tm_min << ':' 6430

<< setw(2) << tdata->tm_sec; 6431

Page 237: Pensar en C++ (Volumen 2)

237

os.fill(' '); // Pad on left with ' ' 6432

streamsize prec = os.precision(4); 6433

os << " Lat:" << setw(9) << d.latitude.toString() 6434

<< ", Long:" << setw(9) << d.longitude.toString() 6435

<< ", depth:" << setw(9) << d.depth 6436

<< ", temp:" << setw(9) << d.temperature; 6437

os.fill(fillc); 6438

os.precision(prec); 6439

return os; 6440

} ///:~ 6441

Listado 5.24. C04/DataLogger.cpp 6442

6443

//: C04:Datagen.cpp 6444

// Test data generator. 6445

//{L} DataLogger 6446

#include <cstdlib> 6447

#include <ctime> 6448

#include <cstring> 6449

#include <fstream> 6450

#include "DataLogger.h" 6451

#include "../require.h" 6452

using namespace std; 6453

6454

int main() { 6455

Page 238: Pensar en C++ (Volumen 2)

238

time_t timer; 6456

srand(time(&timer)); // Seed the random number generator 6457

ofstream data("data.txt"); 6458

assure(data, "data.txt"); 6459

ofstream bindata("data.bin", ios::binary); 6460

assure(bindata, "data.bin"); 6461

for(int i = 0; i < 100; i++, timer += 55) { 6462

// Zero to 199 meters: 6463

double newdepth = rand() % 200; 6464

double fraction = rand() % 100 + 1; 6465

newdepth += 1.0 / fraction; 6466

double newtemp = 150 + rand() % 200; // Kelvin 6467

fraction = rand() % 100 + 1; 6468

newtemp += 1.0 / fraction; 6469

const DataPoint d(timer, Coord(45,20,31), 6470

Coord(22,34,18), newdepth, 6471

newtemp); 6472

data << d << endl; 6473

bindata.write(reinterpret_cast<const char*>(&d), 6474

sizeof(d)); 6475

} 6476

} ///:~ 6477

Listado 5.25. C04/Datagen.cpp 6478

6479

Page 239: Pensar en C++ (Volumen 2)

239

//: C04:Datascan.cpp 6480

//{L} DataLogger 6481

#include <fstream> 6482

#include <iostream> 6483

#include "DataLogger.h" 6484

#include "../require.h" 6485

using namespace std; 6486

6487

int main() { 6488

ifstream bindata("data.bin", ios::binary); 6489

assure(bindata, "data.bin"); 6490

DataPoint d; 6491

while(bindata.read(reinterpret_cast<char*>(&d), 6492

sizeof d)) 6493

cout << d << endl; 6494

} ///:~ 6495

Listado 5.26. C04/Datascan.cpp 6496

6497

5.11. 6498

5.11.1. 6499

5.11.2. 6500

//: C04:Locale.cpp {-g++}{-bor}{-edg} {RunByHand} 6501

// Illustrates effects of locales. 6502

Page 240: Pensar en C++ (Volumen 2)

240

#include <iostream> 6503

#include <locale> 6504

using namespace std; 6505

6506

int main() { 6507

locale def; 6508

cout << def.name() << endl; 6509

locale current = cout.getloc(); 6510

cout << current.name() << endl; 6511

float val = 1234.56; 6512

cout << val << endl; 6513

// Change to French/France 6514

cout.imbue(locale("french")); 6515

current = cout.getloc(); 6516

cout << current.name() << endl; 6517

cout << val << endl; 6518

6519

cout << "Enter the literal 7890,12: "; 6520

cin.imbue(cout.getloc()); 6521

cin >> val; 6522

cout << val << endl; 6523

cout.imbue(def); 6524

cout << val << endl; 6525

} ///:~ 6526

Page 241: Pensar en C++ (Volumen 2)

241

Listado 5.27. C04/Locale.cpp 6527

6528

//: C04:Facets.cpp {-bor}{-g++}{-mwcc}{-edg} 6529

#include <iostream> 6530

#include <locale> 6531

#include <string> 6532

using namespace std; 6533

6534

int main() { 6535

// Change to French/France 6536

locale loc("french"); 6537

cout.imbue(loc); 6538

string currency = 6539

use_facet<moneypunct<char> >(loc).curr_symbol(); 6540

char point = 6541

use_facet<moneypunct<char> >(loc).decimal_point(); 6542

cout << "I made " << currency << 12.34 << " today!" 6543

<< endl; 6544

} ///:~ 6545

Listado 5.28. C04/Facets.cpp 6546

6547

5.12. 6548

5.13. 6549

Page 242: Pensar en C++ (Volumen 2)

242

//: C04:Exercise14.cpp 6550

#include <fstream> 6551

#include <iostream> 6552

#include <sstream> 6553

#include "../require.h" 6554

using namespace std; 6555

6556

#define d(a) cout << #a " ==\t" << a << endl; 6557

6558

void tellPointers(fstream& s) { 6559

d(s.tellp()); 6560

d(s.tellg()); 6561

cout << endl; 6562

} 6563

void tellPointers(stringstream& s) { 6564

d(s.tellp()); 6565

d(s.tellg()); 6566

cout << endl; 6567

} 6568

int main() { 6569

fstream in("Exercise14.cpp"); 6570

assure(in, "Exercise14.cpp"); 6571

in.seekg(10); 6572

tellPointers(in); 6573

in.seekp(20); 6574

Page 243: Pensar en C++ (Volumen 2)

243

tellPointers(in); 6575

stringstream memStream("Here is a sentence."); 6576

memStream.seekg(10); 6577

tellPointers(memStream); 6578

memStream.seekp(5); 6579

tellPointers(memStream); 6580

} ///:~ 6581

Listado 5.29. C04/Exercise14.cpp 6582

6583

//: C04:Exercise15.txt&#13; 6584

Australia&#13; 6585

5E56,7667230284,Langler,Tyson,31.2147,0.00042117361&#13; 6586

2B97,7586701,Oneill,Zeke,553.429,0.0074673053156065&#13; 6587

4D75,7907252710,Nickerson,Kelly,761.612,0.010276276&#13; 6588

9F2,6882945012,Hartenbach,Neil,47.9637,0.0006471644&#13; 6589

Austria&#13; 6590

480F,7187262472,Oneill,Dee,264.012,0.00356226040013&#13; 6591

1B65,4754732628,Haney,Kim,7.33843,0.000099015948475&#13; 6592

DA1,1954960784,Pascente,Lester,56.5452,0.0007629529&#13; 6593

3F18,1839715659,Elsea,Chelsy,801.901,0.010819887645&#13; 6594

Belgium&#13; 6595

BDF,5993489554,Oneill,Meredith,283.404,0.0038239127&#13; 6596

5AC6,6612945602,Parisienne,Biff,557.74,0.0075254727&#13; 6597

6AD,6477082,Pennington,Lizanne,31.0807,0.0004193544&#13; 6598

Page 244: Pensar en C++ (Volumen 2)

244

4D0E,7861652688,Sisca,Francis,704.751,0.00950906238&#13; 6599

Bahamas&#13; 6600

37D8,6837424208,Parisienne,Samson,396.104,0.0053445&#13; 6601

5E98,6384069,Willis,Pam,90.4257,0.00122009564059246&#13; 6602

1462,1288616408,Stover,Hazal,583.939,0.007878970561&#13; 6603

5FF3,8028775718,Stromstedt,Bunk,39.8712,0.000537974&#13; 6604

1095,3737212,Stover,Denny,3.05387,0.000041205248883&#13; 6605

7428,2019381883,Parisienne,Shane,363.272,0.00490155&#13; 6606

///:~&#13; 6607

Listado 5.30. C04/Exercise15.txt 6608

6609

6610

6611

[11] Explicadas en profundidad en el capítulo 5. 6612

[12] Por esa razón usted puede escribir ios::failbit en lugar de 6613

ios_base::failbit para ahorrar pulsaciones. 6614

[13] Es común el uso de operator void*() en vez de operator bool() porque 6615

las conversiones implícitas de booleano a entero pueden causar sorpresas; 6616

pueden emplazarle incorrectamente un stream en un contexto donde una 6617

conversion a integer puede ser aplicada. La función operator void*() solo 6618

será llamada implícitamente en el cuerpo de una expresión booleana. 6619

[14] un tipo integral usado para alojar bits aislados. 6620

[15] Un tratado mucho más en profundidad de buffers de stream y streams en 6621

general puede ser encontrado en[ Langer & Kreft's, Standar C++ iostreams and 6622

Locales, Addison-Wesley, 1999.] 6623

[16] Para más información sobre la epsilon de la máquina y el cómputo de 6624

punto flotante en general, vea el artículo de Chuck, "The Standard C Library, 6625

Part 3", C/C++ Users Journal, Marzo 1995, disponible en 6626

www.freshsources.com/1995006a.htm 6627

Page 245: Pensar en C++ (Volumen 2)

245

6: Las plantillas en profundidad 6628

Tabla de contenidos 6629

6.1. 6630

6.2. 6631

6.3. 6632

6.4. 6633

6.5. 6634

6.6. 6635

6.7. 6636

6.8. 6637

6.1. 6638

template<class T> class Stack { 6639

T* data; 6640

size_t count; 6641

public: 6642

void push(const T& t); 6643

// Etc. 6644

}; 6645

Stack<int> myStack; // A Stack of ints 6646

6.1.1. 6647

template<class T, size_t N> class Stack { 6648

T data[N]; // Fixed capacity is N 6649

size_t count; 6650

public: 6651

void push(const T& t); 6652

// Etc. 6653

Page 246: Pensar en C++ (Volumen 2)

246

}; 6654

Stack<int, 100> myFixedStack; 6655

//: C05:Urand.h {-bor} 6656

// Unique randomizer. 6657

#ifndef URAND_H 6658

#define URAND_H 6659

#include <bitset> 6660

#include <cstddef> 6661

#include <cstdlib> 6662

#include <ctime> 6663

using std::size_t; 6664

using std::bitset; 6665

6666

template<size_t UpperBound> class Urand { 6667

bitset<UpperBound> used; 6668

public: 6669

Urand() { srand(time(0)); } // Randomize 6670

size_t operator()(); // The "generator" function 6671

}; 6672

6673

template<size_t UpperBound> 6674

inline size_t Urand<UpperBound>::operator()() { 6675

if(used.count() == UpperBound) 6676

used.reset(); // Start over (clear bitset) 6677

Page 247: Pensar en C++ (Volumen 2)

247

size_t newval; 6678

while(used[newval = rand() % UpperBound]) 6679

; // Until unique value is found 6680

used[newval] = true; 6681

return newval; 6682

} 6683

#endif // URAND_H ///:~ 6684

Listado 6.1. C05/Urand.h 6685

6686

//: C05:UrandTest.cpp {-bor} 6687

#include <iostream> 6688

#include "Urand.h" 6689

using namespace std; 6690

6691

int main() { 6692

Urand<10> u; 6693

for(int i = 0; i < 20; ++i) 6694

cout << u() << ' '; 6695

} ///:~ 6696

Listado 6.2. C05/UrandTest.cpp 6697

6698

6.1.2. 6699

Page 248: Pensar en C++ (Volumen 2)

248

template<class T, size_t N = 100> class Stack { 6700

T data[N]; // Fixed capacity is N 6701

size_t count; 6702

public: 6703

void push(const T& t); 6704

// Etc. 6705

}; 6706

template<class T = int, size_t N = 100> // Both defaulted 6707

class Stack { 6708

T data[N]; // Fixed capacity is N 6709

size_t count; 6710

public: 6711

void push(const T& t); 6712

// Etc. 6713

}; 6714

6715

Stack<> myStack; // Same as Stack<int, 100> 6716

template<class T, class Allocator = allocator<T> > 6717

class vector; 6718

//: C05:FuncDef.cpp 6719

#include <iostream> 6720

using namespace std; 6721

6722

template<class T> T sum(T* b, T* e, T init = T()) { 6723

Page 249: Pensar en C++ (Volumen 2)

249

while(b != e) 6724

init += *b++; 6725

return init; 6726

} 6727

6728

int main() { 6729

int a[] = { 1, 2, 3 }; 6730

cout << sum(a, a + sizeof a / sizeof a[0]) << endl; // 6 6731

} ///:~ 6732

Listado 6.3. C05/FuncDef.cpp 6733

6734

6.1.3. 6735

//: C05:TempTemp.cpp 6736

// Illustrates a template template parameter. 6737

#include <cstddef> 6738

#include <iostream> 6739

using namespace std; 6740

6741

template<class T> 6742

class Array { // A simple, expandable sequence 6743

enum { INIT = 10 }; 6744

T* data; 6745

size_t capacity; 6746

Page 250: Pensar en C++ (Volumen 2)

250

size_t count; 6747

public: 6748

Array() { 6749

count = 0; 6750

data = new T[capacity = INIT]; 6751

} 6752

~Array() { delete [] data; } 6753

void push_back(const T& t) { 6754

if(count == capacity) { 6755

// Grow underlying array 6756

size_t newCap = 2 * capacity; 6757

T* newData = new T[newCap]; 6758

for(size_t i = 0; i < count; ++i) 6759

newData[i] = data[i]; 6760

delete [] data; 6761

data = newData; 6762

capacity = newCap; 6763

} 6764

data[count++] = t; 6765

} 6766

void pop_back() { 6767

if(count > 0) 6768

--count; 6769

} 6770

T* begin() { return data; } 6771

Page 251: Pensar en C++ (Volumen 2)

251

T* end() { return data + count; } 6772

}; 6773

6774

template<class T, template<class> class Seq> 6775

class Container { 6776

Seq<T> seq; 6777

public: 6778

void append(const T& t) { seq.push_back(t); } 6779

T* begin() { return seq.begin(); } 6780

T* end() { return seq.end(); } 6781

}; 6782

6783

int main() { 6784

Container<int, Array> container; 6785

container.append(1); 6786

container.append(2); 6787

int* p = container.begin(); 6788

while(p != container.end()) 6789

cout << *p++ << endl; 6790

} ///:~ 6791

Listado 6.4. C05/TempTemp.cpp 6792

6793

Seq<T> seq; 6794

template<class T, template<class> class Seq> 6795

Page 252: Pensar en C++ (Volumen 2)

252

template<class T, template<class U> class Seq> 6796

T operator++(int); 6797

//: C05:TempTemp2.cpp 6798

// A multi-variate template template parameter. 6799

#include <cstddef> 6800

#include <iostream> 6801

using namespace std; 6802

6803

template<class T, size_t N> class Array { 6804

T data[N]; 6805

size_t count; 6806

public: 6807

Array() { count = 0; } 6808

void push_back(const T& t) { 6809

if(count < N) 6810

data[count++] = t; 6811

} 6812

void pop_back() { 6813

if(count > 0) 6814

--count; 6815

} 6816

T* begin() { return data; } 6817

T* end() { return data + count; } 6818

}; 6819

Page 253: Pensar en C++ (Volumen 2)

253

6820

template<class T,size_t N,template<class,size_t> class Seq> 6821

class Container { 6822

Seq<T,N> seq; 6823

public: 6824

void append(const T& t) { seq.push_back(t); } 6825

T* begin() { return seq.begin(); } 6826

T* end() { return seq.end(); } 6827

}; 6828

6829

int main() { 6830

const size_t N = 10; 6831

Container<int, N, Array> container; 6832

container.append(1); 6833

container.append(2); 6834

int* p = container.begin(); 6835

while(p != container.end()) 6836

cout << *p++ << endl; 6837

} ///:~ 6838

Listado 6.5. C05/TempTemp2.cpp 6839

6840

//: C05:TempTemp3.cpp {-bor}{-msc} 6841

// Template template parameters and default arguments. 6842

#include <cstddef> 6843

Page 254: Pensar en C++ (Volumen 2)

254

#include <iostream> 6844

using namespace std; 6845

6846

template<class T, size_t N = 10> // A default argument 6847

class Array { 6848

T data[N]; 6849

size_t count; 6850

public: 6851

Array() { count = 0; } 6852

void push_back(const T& t) { 6853

if(count < N) 6854

data[count++] = t; 6855

} 6856

void pop_back() { 6857

if(count > 0) 6858

--count; 6859

} 6860

T* begin() { return data; } 6861

T* end() { return data + count; } 6862

}; 6863

6864

template<class T, template<class, size_t = 10> class Seq> 6865

class Container { 6866

Seq<T> seq; // Default used 6867

public: 6868

Page 255: Pensar en C++ (Volumen 2)

255

void append(const T& t) { seq.push_back(t); } 6869

T* begin() { return seq.begin(); } 6870

T* end() { return seq.end(); } 6871

}; 6872

6873

int main() { 6874

Container<int, Array> container; 6875

container.append(1); 6876

container.append(2); 6877

int* p = container.begin(); 6878

while(p != container.end()) 6879

cout << *p++ << endl; 6880

} ///:~ 6881

Listado 6.6. C05/TempTemp3.cpp 6882

6883

template<class T, template<class, size_t = 10> class Seq> 6884

//: C05:TempTemp4.cpp {-bor}{-msc} 6885

// Passes standard sequences as template arguments. 6886

#include <iostream> 6887

#include <list> 6888

#include <memory> // Declares allocator<T> 6889

#include <vector> 6890

using namespace std; 6891

Page 256: Pensar en C++ (Volumen 2)

256

6892

template<class T, template<class U, class = allocator<U> > 6893

class Seq> 6894

class Container { 6895

Seq<T> seq; // Default of allocator<T> applied implicitly 6896

public: 6897

void push_back(const T& t) { seq.push_back(t); } 6898

typename Seq<T>::iterator begin() { return seq.begin(); } 6899

typename Seq<T>::iterator end() { return seq.end(); } 6900

}; 6901

6902

int main() { 6903

// Use a vector 6904

Container<int, vector> vContainer; 6905

vContainer.push_back(1); 6906

vContainer.push_back(2); 6907

for(vector<int>::iterator p = vContainer.begin(); 6908

p != vContainer.end(); ++p) { 6909

cout << *p << endl; 6910

} 6911

// Use a list 6912

Container<int, list> lContainer; 6913

lContainer.push_back(3); 6914

lContainer.push_back(4); 6915

for(list<int>::iterator p2 = lContainer.begin(); 6916

Page 257: Pensar en C++ (Volumen 2)

257

p2 != lContainer.end(); ++p2) { 6917

cout << *p2 << endl; 6918

} 6919

} ///:~ 6920

Listado 6.7. C05/TempTemp4.cpp 6921

6922

6.1.4. 6923

//: C05:TypenamedID.cpp {-bor} 6924

// Uses 'typename' as a prefix for nested types. 6925

6926

template<class T> class X { 6927

// Without typename, you should get an error: 6928

typename T::id i; 6929

public: 6930

void f() { i.g(); } 6931

}; 6932

6933

class Y { 6934

public: 6935

class id { 6936

public: 6937

void g() {} 6938

}; 6939

Page 258: Pensar en C++ (Volumen 2)

258

}; 6940

6941

int main() { 6942

X<Y> xy; 6943

xy.f(); 6944

} ///:~ 6945

Listado 6.8. C05/TypenamedID.cpp 6946

6947

//: C05:PrintSeq.cpp {-msc}{-mwcc} 6948

// A print function for Standard C++ sequences. 6949

#include <iostream> 6950

#include <list> 6951

#include <memory> 6952

#include <vector> 6953

using namespace std; 6954

6955

template<class T, template<class U, class = allocator<U> > 6956

class Seq> 6957

void printSeq(Seq<T>& seq) { 6958

for(typename Seq<T>::iterator b = seq.begin(); 6959

b != seq.end();) 6960

cout << *b++ << endl; 6961

} 6962

6963

Page 259: Pensar en C++ (Volumen 2)

259

int main() { 6964

// Process a vector 6965

vector<int> v; 6966

v.push_back(1); 6967

v.push_back(2); 6968

printSeq(v); 6969

// Process a list 6970

list<int> lst; 6971

lst.push_back(3); 6972

lst.push_back(4); 6973

printSeq(lst); 6974

} ///:~ 6975

Listado 6.9. C05/PrintSeq.cpp 6976

6977

typename Seq<T>::iterator It; 6978

typedef typename Seq<It>::iterator It; 6979

//: C05:UsingTypename.cpp 6980

// Using 'typename' in the template argument list. 6981

6982

template<typename T> class X {}; 6983

6984

int main() { 6985

Page 260: Pensar en C++ (Volumen 2)

260

X<int> x; 6986

} ///:~ 6987

Listado 6.10. C05/UsingTypename.cpp 6988

6989

6.1.5. 6990

//: C05:DotTemplate.cpp 6991

// Illustrate the .template construct. 6992

#include <bitset> 6993

#include <cstddef> 6994

#include <iostream> 6995

#include <string> 6996

using namespace std; 6997

6998

template<class charT, size_t N> 6999

basic_string<charT> bitsetToString(const bitset<N>& bs) { 7000

return bs. template to_string<charT, char_traits<charT>, 7001

allocator<charT> >(); 7002

} 7003

7004

int main() { 7005

bitset<10> bs; 7006

bs.set(1); 7007

bs.set(5); 7008

Page 261: Pensar en C++ (Volumen 2)

261

cout << bs << endl; // 0000100010 7009

string s = bitsetToString<char>(bs); 7010

cout << s << endl; // 0000100010 7011

} ///:~ 7012

Listado 6.11. C05/DotTemplate.cpp 7013

7014

template<class charT, class traits, class Allocator> 7015

basic_string<charT, traits, Allocator> to_string() const; 7016

wstring s = bitsetToString<wchar_t>(bs); 7017

6.1.6. 7018

template<typename T> class complex { 7019

public: 7020

template<class X> complex(const complex<X>&); 7021

complex<float> z(1, 2); 7022

complex<double> w(z); 7023

template<typename T> 7024

template<typename X> 7025

complex<T>::complex(const complex<X>& c) {/* Body here' */} 7026

int data[5] = { 1, 2, 3, 4, 5 }; 7027

vector<int> v1(data, data+5); 7028

vector<double> v2(v1.begin(), v1.end()); 7029

template<class InputIterator> 7030

Page 262: Pensar en C++ (Volumen 2)

262

vector(InputIterator first, InputIterator last, 7031

const Allocator& = Allocator()); 7032

//: C05:MemberClass.cpp 7033

// A member class template. 7034

#include <iostream> 7035

#include <typeinfo> 7036

using namespace std; 7037

7038

template<class T> class Outer { 7039

public: 7040

template<class R> class Inner { 7041

public: 7042

void f(); 7043

}; 7044

}; 7045

7046

template<class T> template<class R> 7047

void Outer<T>::Inner<R>::f() { 7048

cout << "Outer == " << typeid(T).name() << endl; 7049

cout << "Inner == " << typeid(R).name() << endl; 7050

cout << "Full Inner == " << typeid(*this).name() << endl; 7051

} 7052

7053

int main() { 7054

Page 263: Pensar en C++ (Volumen 2)

263

Outer<int>::Inner<bool> inner; 7055

inner.f(); 7056

} ///:~ 7057

Listado 6.12. C05/MemberClass.cpp 7058

7059

Outer == int 7060 Inner == bool 7061

Full Inner == Outer<int>::Inner<bool> 7062

6.2. 7063

template<typename T> const T& min(const T& a, const T& b) { 7064

return (a < b) ? a : b; 7065

} 7066

int z = min<int>(i, j); 7067

6.2.1. 7068

int z = min(i, j); 7069

int z = min(x, j); // x is a double 7070

int z = min<double>(x, j); 7071

template<typename T, typename U> 7072

const T& min(const T& a, const U& b) { 7073

return (a < b) ? a : b; 7074

} 7075

//: C05:StringConv.h 7076

Page 264: Pensar en C++ (Volumen 2)

264

// Function templates to convert to and from strings. 7077

#ifndef STRINGCONV_H 7078

#define STRINGCONV_H 7079

#include <string> 7080

#include <sstream> 7081

7082

template<typename T> T fromString(const std::string& s) { 7083

std::istringstream is(s); 7084

T t; 7085

is >> t; 7086

return t; 7087

} 7088

7089

template<typename T> std::string toString(const T& t) { 7090

std::ostringstream s; 7091

s << t; 7092

return s.str(); 7093

} 7094

#endif // STRINGCONV_H ///:~ 7095

Listado 6.13. C05/StringConv.h 7096

7097

//: C05:StringConvTest.cpp 7098

#include <complex> 7099

#include <iostream> 7100

Page 265: Pensar en C++ (Volumen 2)

265

#include "StringConv.h" 7101

using namespace std; 7102

7103

int main() { 7104

int i = 1234; 7105

cout << "i == \"" << toString(i) << "\"" << endl; 7106

float x = 567.89; 7107

cout << "x == \"" << toString(x) << "\"" << endl; 7108

complex<float> c(1.0, 2.0); 7109

cout << "c == \"" << toString(c) << "\"" << endl; 7110

cout << endl; 7111

7112

i = fromString<int>(string("1234")); 7113

cout << "i == " << i << endl; 7114

x = fromString<float>(string("567.89")); 7115

cout << "x == " << x << endl; 7116

c = fromString<complex<float> >(string("(1.0,2.0)")); 7117

cout << "c == " << c << endl; 7118

} ///:~ 7119

Listado 6.14. C05/StringConvTest.cpp 7120

7121

i == "1234" 7122 x == "567.89" 7123 c == "(1,2)" 7124

7125 i == 1234 7126

x == 567.89 7127 c == (1,2) 7128

Page 266: Pensar en C++ (Volumen 2)

266

//: C05:ImplicitCast.cpp 7129

7130

template<typename R, typename P> 7131

R implicit_cast(const P& p) { 7132

return p; 7133

} 7134

7135

int main() { 7136

int i = 1; 7137

float x = implicit_cast<float>(i); 7138

int j = implicit_cast<int>(x); 7139

//! char* p = implicit_cast<char*>(i); 7140

} ///:~ 7141

Listado 6.15. C05/ImplicitCast.cpp 7142

7143

//: C05:ArraySize.cpp 7144

#include <cstddef> 7145

using std::size_t; 7146

7147

template<size_t R, size_t C, typename T> 7148

void init1(T a[R][C]) { 7149

for(size_t i = 0; i < R; ++i) 7150

for(size_t j = 0; j < C; ++j) 7151

a[i][j] = T(); 7152

Page 267: Pensar en C++ (Volumen 2)

267

} 7153

7154

template<size_t R, size_t C, class T> 7155

void init2(T (&a)[R][C]) { // Reference parameter 7156

for(size_t i = 0; i < R; ++i) 7157

for(size_t j = 0; j < C; ++j) 7158

a[i][j] = T(); 7159

} 7160

7161

int main() { 7162

int a[10][20]; 7163

init1<10,20>(a); // Must specify 7164

init2(a); // Sizes deduced 7165

} ///:~ 7166

Listado 6.16. C05/ArraySize.cpp 7167

7168

6.2.2. 7169

//: C05:MinTest.cpp 7170

#include <cstring> 7171

#include <iostream> 7172

using std::strcmp; 7173

using std::cout; 7174

using std::endl; 7175

Page 268: Pensar en C++ (Volumen 2)

268

7176

template<typename T> const T& min(const T& a, const T& b) { 7177

return (a < b) ? a : b; 7178

} 7179

7180

const char* min(const char* a, const char* b) { 7181

return (strcmp(a, b) < 0) ? a : b; 7182

} 7183

7184

double min(double x, double y) { 7185

return (x < y) ? x : y; 7186

} 7187

7188

int main() { 7189

const char *s2 = "say \"Ni-!\"", *s1 = "knights who"; 7190

cout << min(1, 2) << endl; // 1: 1 (template) 7191

cout << min(1.0, 2.0) << endl; // 2: 1 (double) 7192

cout << min(1, 2.0) << endl; // 3: 1 (double) 7193

cout << min(s1, s2) << endl; // 4: knights who (const 7194

// char*) 7195

cout << min<>(s1, s2) << endl; // 5: say "Ni-!" 7196

// (template) 7197

} ///:~ 7198

Listado 6.17. C05/MinTest.cpp 7199

Page 269: Pensar en C++ (Volumen 2)

269

7200

template<typename T> 7201

const T& min(const T& a, const T& b, const T& c); 7202

6.2.3. 7203

//: C05:TemplateFunctionAddress.cpp {-mwcc} 7204

// Taking the address of a function generated 7205

// from a template. 7206

7207

template<typename T> void f(T*) {} 7208

7209

void h(void (*pf)(int*)) {} 7210

7211

template<typename T> void g(void (*pf)(T*)) {} 7212

7213

int main() { 7214

h(&f<int>); // Full type specification 7215

h(&f); // Type deduction 7216

g<int>(&f<int>); // Full type specification 7217

g(&f<int>); // Type deduction 7218

g<int>(&f); // Partial (but sufficient) specification 7219

} ///:~ 7220

Listado 6.18. C05/TemplateFunctionAddress.cpp 7221

7222

Page 270: Pensar en C++ (Volumen 2)

270

// The variable s is a std::string 7223

transform(s.begin(), s.end(), s.begin(), tolower); 7224

//: C05:FailedTransform.cpp {-xo} 7225

#include <algorithm> 7226

#include <cctype> 7227

#include <iostream> 7228

#include <string> 7229

using namespace std; 7230

7231

int main() { 7232

string s("LOWER"); 7233

transform(s.begin(), s.end(), s.begin(), tolower); 7234

cout << s << endl; 7235

} ///:~ 7236

Listado 6.19. C05/FailedTransform.cpp 7237

7238

template<class charT> charT toupper(charT c, 7239

const locale& loc); 7240

template<class charT> charT tolower(charT c, 7241

const locale& loc); 7242

transform(s.begin(),s.end(),s.begin() 7243

static_cast<int(*)(int)>(tolower)); 7244

Page 271: Pensar en C++ (Volumen 2)

271

//: C05:StrTolower.cpp {O} {-mwcc} 7245

#include <algorithm> 7246

#include <cctype> 7247

#include <string> 7248

using namespace std; 7249

7250

string strTolower(string s) { 7251

transform(s.begin(), s.end(), s.begin(), tolower); 7252

return s; 7253

} ///:~ 7254

Listado 6.20. C05/StrTolower.cpp 7255

7256

//: C05:Tolower.cpp {-mwcc} 7257

//{L} StrTolower 7258

#include <algorithm> 7259

#include <cctype> 7260

#include <iostream> 7261

#include <string> 7262

using namespace std; 7263

string strTolower(string); 7264

7265

int main() { 7266

string s("LOWER"); 7267

cout << strTolower(s) << endl; 7268

Page 272: Pensar en C++ (Volumen 2)

272

} ///:~ 7269

Listado 6.21. C05/Tolower.cpp 7270

7271

//: C05:ToLower2.cpp {-mwcc} 7272

#include <algorithm> 7273

#include <cctype> 7274

#include <iostream> 7275

#include <string> 7276

using namespace std; 7277

7278

template<class charT> charT strTolower(charT c) { 7279

return tolower(c); // One-arg version called 7280

} 7281

7282

int main() { 7283

string s("LOWER"); 7284

transform(s.begin(),s.end(),s.begin(),&strTolower<char>); 7285

cout << s << endl; 7286

} ///:~ 7287

Listado 6.22. C05/ToLower2.cpp 7288

7289

6.2.4. 7290

Page 273: Pensar en C++ (Volumen 2)

273

//: C05:ApplySequence.h 7291

// Apply a function to an STL sequence container. 7292

7293

// const, 0 arguments, any type of return value: 7294

template<class Seq, class T, class R> 7295

void apply(Seq& sq, R (T::*f)() const) { 7296

typename Seq::iterator it = sq.begin(); 7297

while(it != sq.end()) 7298

((*it++)->*f)(); 7299

} 7300

7301

// const, 1 argument, any type of return value: 7302

template<class Seq, class T, class R, class A> 7303

void apply(Seq& sq, R(T::*f)(A) const, A a) { 7304

typename Seq::iterator it = sq.begin(); 7305

while(it != sq.end()) 7306

((*it++)->*f)(a); 7307

} 7308

7309

// const, 2 arguments, any type of return value: 7310

template<class Seq, class T, class R, 7311

class A1, class A2> 7312

void apply(Seq& sq, R(T::*f)(A1, A2) const, 7313

A1 a1, A2 a2) { 7314

typename Seq::iterator it = sq.begin(); 7315

Page 274: Pensar en C++ (Volumen 2)

274

while(it != sq.end()) 7316

((*it++)->*f)(a1, a2); 7317

} 7318

7319

// Non-const, 0 arguments, any type of return value: 7320

template<class Seq, class T, class R> 7321

void apply(Seq& sq, R (T::*f)()) { 7322

typename Seq::iterator it = sq.begin(); 7323

while(it != sq.end()) 7324

((*it++)->*f)(); 7325

} 7326

7327

// Non-const, 1 argument, any type of return value: 7328

template<class Seq, class T, class R, class A> 7329

void apply(Seq& sq, R(T::*f)(A), A a) { 7330

typename Seq::iterator it = sq.begin(); 7331

while(it != sq.end()) 7332

((*it++)->*f)(a); 7333

} 7334

7335

// Non-const, 2 arguments, any type of return value: 7336

template<class Seq, class T, class R, 7337

class A1, class A2> 7338

void apply(Seq& sq, R(T::*f)(A1, A2), 7339

A1 a1, A2 a2) { 7340

Page 275: Pensar en C++ (Volumen 2)

275

typename Seq::iterator it = sq.begin(); 7341

while(it != sq.end()) 7342

((*it++)->*f)(a1, a2); 7343

} 7344

// Etc., to handle maximum likely arguments ///:~ 7345

Listado 6.23. C05/ApplySequence.h 7346

7347

//: C05:Gromit.h 7348

// The techno-dog. Has member functions 7349

// with various numbers of arguments. 7350

#include <iostream> 7351

7352

class Gromit { 7353

int arf; 7354

int totalBarks; 7355

public: 7356

Gromit(int arf = 1) : arf(arf + 1), totalBarks(0) {} 7357

void speak(int) { 7358

for(int i = 0; i < arf; i++) { 7359

std::cout << "arf! "; 7360

++totalBarks; 7361

} 7362

std::cout << std::endl; 7363

} 7364

Page 276: Pensar en C++ (Volumen 2)

276

char eat(float) const { 7365

std::cout << "chomp!" << std::endl; 7366

return 'z'; 7367

} 7368

int sleep(char, double) const { 7369

std::cout << "zzz..." << std::endl; 7370

return 0; 7371

} 7372

void sit() const { 7373

std::cout << "Sitting..." << std::endl; 7374

} 7375

}; ///:~ 7376

Listado 6.24. C05/Gromit.h 7377

7378

//: C05:ApplyGromit.cpp 7379

// Test ApplySequence.h. 7380

#include <cstddef> 7381

#include <iostream> 7382

#include <vector> 7383

#include "ApplySequence.h" 7384

#include "Gromit.h" 7385

#include "../purge.h" 7386

using namespace std; 7387

7388

Page 277: Pensar en C++ (Volumen 2)

277

int main() { 7389

vector<Gromit*> dogs; 7390

for(size_t i = 0; i < 5; i++) 7391

dogs.push_back(new Gromit(i)); 7392

apply(dogs, &Gromit::speak, 1); 7393

apply(dogs, &Gromit::eat, 2.0f); 7394

apply(dogs, &Gromit::sleep, 'z', 3.0); 7395

apply(dogs, &Gromit::sit); 7396

purge(dogs); 7397

} ///:~ 7398

Listado 6.25. C05/ApplyGromit.cpp 7399

7400

6.2.5. 7401

template<class T> void f(T); 7402

template<class T> void f(T*); 7403

template<class T> void f(const T*); 7404

//: C05:PartialOrder.cpp 7405

// Reveals ordering of function templates. 7406

#include <iostream> 7407

using namespace std; 7408

7409

template<class T> void f(T) { 7410

Page 278: Pensar en C++ (Volumen 2)

278

cout << "T" << endl; 7411

} 7412

7413

template<class T> void f(T*) { 7414

cout << "T*" << endl; 7415

} 7416

7417

template<class T> void f(const T*) { 7418

cout << "const T*" << endl; 7419

} 7420

7421

int main() { 7422

f(0); // T 7423

int i = 0; 7424

f(&i); // T* 7425

const int j = 0; 7426

f(&j); // const T* 7427

} ///:~ 7428

Listado 6.26. C05/PartialOrder.cpp 7429

7430

6.3. 7431

6.3.1. 7432

const char* min(const char* a, const char* b) { 7433

Page 279: Pensar en C++ (Volumen 2)

279

return (strcmp(a, b) < 0) ? a : b; 7434

} 7435

//: C05:MinTest2.cpp 7436

#include <cstring> 7437

#include <iostream> 7438

using std::strcmp; 7439

using std::cout; 7440

using std::endl; 7441

7442

template<class T> const T& min(const T& a, const T& b) { 7443

return (a < b) ? a : b; 7444

} 7445

7446

// An explicit specialization of the min template 7447

template<> 7448

const char* const& min<const char*>(const char* const& a, 7449

const char* const& b) { 7450

return (strcmp(a, b) < 0) ? a : b; 7451

} 7452

7453

int main() { 7454

const char *s2 = "say \"Ni-!\"", *s1 = "knights who"; 7455

cout << min(s1, s2) << endl; 7456

cout << min<>(s1, s2) << endl; 7457

Page 280: Pensar en C++ (Volumen 2)

280

} ///:~ 7458

Listado 6.27. C05/MinTest2.cpp 7459

7460

template<class T, class Allocator = allocator<T> > 7461

class vector {...}; 7462

template<> class vector<bool, allocator<bool> > {...}; 7463

6.3.2. 7464

template<class Allocator> class vector<bool, Allocator>; 7465

//: C05:PartialOrder2.cpp 7466

// Reveals partial ordering of class templates. 7467

#include <iostream> 7468

using namespace std; 7469

7470

template<class T, class U> class C { 7471

public: 7472

void f() { cout << "Primary Template\n"; } 7473

}; 7474

7475

template<class U> class C<int, U> { 7476

public: 7477

void f() { cout << "T == int\n"; } 7478

Page 281: Pensar en C++ (Volumen 2)

281

}; 7479

7480

template<class T> class C<T, double> { 7481

public: 7482

void f() { cout << "U == double\n"; } 7483

}; 7484

7485

template<class T, class U> class C<T*, U> { 7486

public: 7487

void f() { cout << "T* used\n"; } 7488

}; 7489

7490

template<class T, class U> class C<T, U*> { 7491

public: 7492

void f() { cout << "U* used\n"; } 7493

}; 7494

7495

template<class T, class U> class C<T*, U*> { 7496

public: 7497

void f() { cout << "T* and U* used\n"; } 7498

}; 7499

7500

template<class T> class C<T, T> { 7501

public: 7502

void f() { cout << "T == U\n"; } 7503

Page 282: Pensar en C++ (Volumen 2)

282

}; 7504

7505

int main() { 7506

C<float, int>().f(); // 1: Primary template 7507

C<int, float>().f(); // 2: T == int 7508

C<float, double>().f(); // 3: U == double 7509

C<float, float>().f(); // 4: T == U 7510

C<float*, float>().f(); // 5: T* used [T is float] 7511

C<float, float*>().f(); // 6: U* used [U is float] 7512

C<float*, int*>().f(); // 7: T* and U* used [float,int] 7513

// The following are ambiguous: 7514

// 8: C<int, int>().f(); 7515

// 9: C<double, double>().f(); 7516

// 10: C<float*, float*>().f(); 7517

// 11: C<int, int*>().f(); 7518

// 12: C<int*, int*>().f(); 7519

} ///:~ 7520

Listado 6.28. C05/PartialOrder2.cpp 7521

7522

6.3.3. 7523

//: C05:Sortable.h 7524

// Template specialization. 7525

#ifndef SORTABLE_H 7526

Page 283: Pensar en C++ (Volumen 2)

283

#define SORTABLE_H 7527

#include <cstring> 7528

#include <cstddef> 7529

#include <string> 7530

#include <vector> 7531

using std::size_t; 7532

7533

template<class T> 7534

class Sortable : public std::vector<T> { 7535

public: 7536

void sort(); 7537

}; 7538

7539

template<class T> 7540

void Sortable<T>::sort() { // A simple sort 7541

for(size_t i = this->size(); i > 0; --i) 7542

for(size_t j = 1; j < i; ++j) 7543

if(this->at(j-1) > this->at(j)) { 7544

T t = this->at(j-1); 7545

this->at(j-1) = this->at(j); 7546

this->at(j) = t; 7547

} 7548

} 7549

7550

// Partial specialization for pointers: 7551

Page 284: Pensar en C++ (Volumen 2)

284

template<class T> 7552

class Sortable<T*> : public std::vector<T*> { 7553

public: 7554

void sort(); 7555

}; 7556

7557

template<class T> 7558

void Sortable<T*>::sort() { 7559

for(size_t i = this->size(); i > 0; --i) 7560

for(size_t j = 1; j < i; ++j) 7561

if(*this->at(j-1) > *this->at(j)) { 7562

T* t = this->at(j-1); 7563

this->at(j-1) = this->at(j); 7564

this->at(j) = t; 7565

} 7566

} 7567

7568

// Full specialization for char* 7569

// (Made inline here for convenience -- normally you would 7570

// place the function body in a separate file and only 7571

// leave the declaration here). 7572

template<> inline void Sortable<char*>::sort() { 7573

for(size_t i = this->size(); i > 0; --i) 7574

for(size_t j = 1; j < i; ++j) 7575

if(std::strcmp(this->at(j-1), this->at(j)) > 0) { 7576

Page 285: Pensar en C++ (Volumen 2)

285

char* t = this->at(j-1); 7577

this->at(j-1) = this->at(j); 7578

this->at(j) = t; 7579

} 7580

} 7581

#endif // SORTABLE_H ///:~ 7582

Listado 6.29. C05/Sortable.h 7583

7584

//: C05:Sortable.cpp 7585

//{-bor} (Because of bitset in Urand.h) 7586

// Testing template specialization. 7587

#include <cstddef> 7588

#include <iostream> 7589

#include "Sortable.h" 7590

#include "Urand.h" 7591

using namespace std; 7592

7593

#define asz(a) (sizeof a / sizeof a[0]) 7594

7595

char* words[] = { "is", "running", "big", "dog", "a", }; 7596

char* words2[] = { "this", "that", "theother", }; 7597

7598

int main() { 7599

Sortable<int> is; 7600

Page 286: Pensar en C++ (Volumen 2)

286

Urand<47> rnd; 7601

for(size_t i = 0; i < 15; ++i) 7602

is.push_back(rnd()); 7603

for(size_t i = 0; i < is.size(); ++i) 7604

cout << is[i] << ' '; 7605

cout << endl; 7606

is.sort(); 7607

for(size_t i = 0; i < is.size(); ++i) 7608

cout << is[i] << ' '; 7609

cout << endl; 7610

7611

// Uses the template partial specialization: 7612

Sortable<string*> ss; 7613

for(size_t i = 0; i < asz(words); ++i) 7614

ss.push_back(new string(words[i])); 7615

for(size_t i = 0; i < ss.size(); ++i) 7616

cout << *ss[i] << ' '; 7617

cout << endl; 7618

ss.sort(); 7619

for(size_t i = 0; i < ss.size(); ++i) { 7620

cout << *ss[i] << ' '; 7621

delete ss[i]; 7622

} 7623

cout << endl; 7624

7625

Page 287: Pensar en C++ (Volumen 2)

287

// Uses the full char* specialization: 7626

Sortable<char*> scp; 7627

for(size_t i = 0; i < asz(words2); ++i) 7628

scp.push_back(words2[i]); 7629

for(size_t i = 0; i < scp.size(); ++i) 7630

cout << scp[i] << ' '; 7631

cout << endl; 7632

scp.sort(); 7633

for(size_t i = 0; i < scp.size(); ++i) 7634

cout << scp[i] << ' '; 7635

cout << endl; 7636

} ///:~ 7637

Listado 6.30. C05/Sortable.cpp 7638

7639

6.3.4. 7640

//: C05:DelayedInstantiation.cpp 7641

// Member functions of class templates are not 7642

// instantiated until they're needed. 7643

7644

class X { 7645

public: 7646

void f() {} 7647

}; 7648

Page 288: Pensar en C++ (Volumen 2)

288

7649

class Y { 7650

public: 7651

void g() {} 7652

}; 7653

7654

template<typename T> class Z { 7655

T t; 7656

public: 7657

void a() { t.f(); } 7658

void b() { t.g(); } 7659

}; 7660

7661

int main() { 7662

Z<X> zx; 7663

zx.a(); // Doesn't create Z<X>::b() 7664

Z<Y> zy; 7665

zy.b(); // Doesn't create Z<Y>::a() 7666

} ///:~ 7667

Listado 6.31. C05/DelayedInstantiation.cpp 7668

7669

//: C05:Nobloat.h 7670

// Shares code for storing pointers in a Stack. 7671

#ifndef NOBLOAT_H 7672

Page 289: Pensar en C++ (Volumen 2)

289

#define NOBLOAT_H 7673

#include <cassert> 7674

#include <cstddef> 7675

#include <cstring> 7676

7677

// The primary template 7678

template<class T> class Stack { 7679

T* data; 7680

std::size_t count; 7681

std::size_t capacity; 7682

enum { INIT = 5 }; 7683

public: 7684

Stack() { 7685

count = 0; 7686

capacity = INIT; 7687

data = new T[INIT]; 7688

} 7689

void push(const T& t) { 7690

if(count == capacity) { 7691

// Grow array store 7692

std::size_t newCapacity = 2 * capacity; 7693

T* newData = new T[newCapacity]; 7694

for(size_t i = 0; i < count; ++i) 7695

newData[i] = data[i]; 7696

delete [] data; 7697

Page 290: Pensar en C++ (Volumen 2)

290

data = newData; 7698

capacity = newCapacity; 7699

} 7700

assert(count < capacity); 7701

data[count++] = t; 7702

} 7703

void pop() { 7704

assert(count > 0); 7705

--count; 7706

} 7707

T top() const { 7708

assert(count > 0); 7709

return data[count-1]; 7710

} 7711

std::size_t size() const { return count; } 7712

}; 7713

7714

// Full specialization for void* 7715

template<> class Stack<void *> { 7716

void** data; 7717

std::size_t count; 7718

std::size_t capacity; 7719

enum { INIT = 5 }; 7720

public: 7721

Stack() { 7722

Page 291: Pensar en C++ (Volumen 2)

291

count = 0; 7723

capacity = INIT; 7724

data = new void*[INIT]; 7725

} 7726

void push(void* const & t) { 7727

if(count == capacity) { 7728

std::size_t newCapacity = 2*capacity; 7729

void** newData = new void*[newCapacity]; 7730

std::memcpy(newData, data, count*sizeof(void*)); 7731

delete [] data; 7732

data = newData; 7733

capacity = newCapacity; 7734

} 7735

assert(count < capacity); 7736

data[count++] = t; 7737

} 7738

void pop() { 7739

assert(count > 0); 7740

--count; 7741

} 7742

void* top() const { 7743

assert(count > 0); 7744

return data[count-1]; 7745

} 7746

std::size_t size() const { return count; } 7747

Page 292: Pensar en C++ (Volumen 2)

292

}; 7748

7749

// Partial specialization for other pointer types 7750

template<class T> class Stack<T*> : private Stack<void *> { 7751

typedef Stack<void *> Base; 7752

public: 7753

void push(T* const & t) { Base::push(t); } 7754

void pop() {Base::pop();} 7755

T* top() const { return static_cast<T*>(Base::top()); } 7756

std::size_t size() { return Base::size(); } 7757

}; 7758

#endif // NOBLOAT_H ///:~ 7759

Listado 6.32. C05/Nobloat.h 7760

7761

//: C05:NobloatTest.cpp 7762

#include <iostream> 7763

#include <string> 7764

#include "Nobloat.h" 7765

using namespace std; 7766

7767

template<class StackType> 7768

void emptyTheStack(StackType& stk) { 7769

while(stk.size() > 0) { 7770

cout << stk.top() << endl; 7771

Page 293: Pensar en C++ (Volumen 2)

293

stk.pop(); 7772

} 7773

} 7774

7775

// An overload for emptyTheStack (not a specialization!) 7776

template<class T> 7777

void emptyTheStack(Stack<T*>& stk) { 7778

while(stk.size() > 0) { 7779

cout << *stk.top() << endl; 7780

stk.pop(); 7781

} 7782

} 7783

7784

int main() { 7785

Stack<int> s1; 7786

s1.push(1); 7787

s1.push(2); 7788

emptyTheStack(s1); 7789

Stack<int *> s2; 7790

int i = 3; 7791

int j = 4; 7792

s2.push(&i); 7793

s2.push(&j); 7794

emptyTheStack(s2); 7795

} ///:~ 7796

Page 294: Pensar en C++ (Volumen 2)

294

Listado 6.33. C05/NobloatTest.cpp 7797

7798

6.4. 7799

6.4.1. 7800

MyClass::f(); 7801

x.f(); 7802

p->f(); 7803

#include <iostream> 7804

#include <string> 7805

// ... 7806

std::string s("hello"); 7807

std::cout << s << std::endl; 7808

std::operator<<(std::operator<<(std::cout,s),std::endl); 7809

operator<<(std::cout, s); 7810

(f)(x, y); // ADL suppressed 7811

//: C05:Lookup.cpp 7812

// Only produces correct behavior with EDG, 7813

// and Metrowerks using a special option. 7814

#include <iostream> 7815

using std::cout; 7816

using std::endl; 7817

7818

void f(double) { cout << "f(double)" << endl; } 7819

Page 295: Pensar en C++ (Volumen 2)

295

7820

template<class T> class X { 7821

public: 7822

void g() { f(1); } 7823

}; 7824

7825

void f(int) { cout << "f(int)" << endl; } 7826

7827

int main() { 7828

X<int>().g(); 7829

} ///:~ 7830

Listado 6.34. C05/Lookup.cpp 7831

7832

f(double) 7833

//: C05:Lookup2.cpp {-bor}{-g++}{-dmc} 7834

// Microsoft: use option -Za (ANSI mode) 7835

#include <algorithm> 7836

#include <iostream> 7837

#include <typeinfo> 7838

using std::cout; 7839

using std::endl; 7840

7841

void g() { cout << "global g()" << endl; } 7842

Page 296: Pensar en C++ (Volumen 2)

296

7843

template<class T> class Y { 7844

public: 7845

void g() { 7846

cout << "Y<" << typeid(T).name() << ">::g()" << endl; 7847

} 7848

void h() { 7849

cout << "Y<" << typeid(T).name() << ">::h()" << endl; 7850

} 7851

typedef int E; 7852

}; 7853

7854

typedef double E; 7855

7856

template<class T> void swap(T& t1, T& t2) { 7857

cout << "global swap" << endl; 7858

T temp = t1; 7859

t1 = t2; 7860

t2 = temp; 7861

} 7862

7863

template<class T> class X : public Y<T> { 7864

public: 7865

E f() { 7866

g(); 7867

Page 297: Pensar en C++ (Volumen 2)

297

this->h(); 7868

T t1 = T(), t2 = T(1); 7869

cout << t1 << endl; 7870

swap(t1, t2); 7871

std::swap(t1, t2); 7872

cout << typeid(E).name() << endl; 7873

return E(t2); 7874

} 7875

}; 7876

7877

int main() { 7878

X<int> x; 7879

cout << x.f() << endl; 7880

} ///:~ 7881

Listado 6.35. C05/Lookup2.cpp 7882

7883

global g() 7884 Y<int>::h() 7885

0 7886 global swap 7887

double 7888 1 7889

6.4.2. 7890

//: C05:FriendScope.cpp 7891

#include <iostream> 7892

using namespace std; 7893

Page 298: Pensar en C++ (Volumen 2)

298

7894

class Friendly { 7895

int i; 7896

public: 7897

Friendly(int theInt) { i = theInt; } 7898

friend void f(const Friendly&); // Needs global def. 7899

void g() { f(*this); } 7900

}; 7901

7902

void h() { 7903

f(Friendly(1)); // Uses ADL 7904

} 7905

7906

void f(const Friendly& fo) { // Definition of friend 7907

cout << fo.i << endl; 7908

} 7909

7910

int main() { 7911

h(); // Prints 1 7912

Friendly(2).g(); // Prints 2 7913

} ///:~ 7914

Listado 6.36. C05/FriendScope.cpp 7915

7916

//: C05:FriendScope2.cpp 7917

Page 299: Pensar en C++ (Volumen 2)

299

#include <iostream> 7918

using namespace std; 7919

7920

// Necessary forward declarations: 7921

template<class T> class Friendly; 7922

template<class T> void f(const Friendly<T>&); 7923

7924

template<class T> class Friendly { 7925

T t; 7926

public: 7927

Friendly(const T& theT) : t(theT) {} 7928

friend void f<>(const Friendly<T>&); 7929

void g() { f(*this); } 7930

}; 7931

7932

void h() { 7933

f(Friendly<int>(1)); 7934

} 7935

7936

template<class T> void f(const Friendly<T>& fo) { 7937

cout << fo.t << endl; 7938

} 7939

7940

int main() { 7941

h(); 7942

Page 300: Pensar en C++ (Volumen 2)

300

Friendly<int>(2).g(); 7943

} ///:~ 7944

Listado 6.37. C05/FriendScope2.cpp 7945

7946

//: C05:FriendScope3.cpp {-bor} 7947

// Microsoft: use the -Za (ANSI-compliant) option 7948

#include <iostream> 7949

using namespace std; 7950

7951

template<class T> class Friendly { 7952

T t; 7953

public: 7954

Friendly(const T& theT) : t(theT) {} 7955

friend void f(const Friendly<T>& fo) { 7956

cout << fo.t << endl; 7957

} 7958

void g() { f(*this); } 7959

}; 7960

7961

void h() { 7962

f(Friendly<int>(1)); 7963

} 7964

7965

int main() { 7966

Page 301: Pensar en C++ (Volumen 2)

301

h(); 7967

Friendly<int>(2).g(); 7968

} ///:~ 7969

Listado 6.38. C05/FriendScope3.cpp 7970

7971

template<class T> class Box { 7972

T t; 7973

public: 7974

Box(const T& theT) : t(theT) {} 7975

}; 7976

//: C05:Box1.cpp 7977

// Defines template operators. 7978

#include <iostream> 7979

using namespace std; 7980

7981

// Forward declarations 7982

template<class T> class Box; 7983

7984

template<class T> 7985

Box<T> operator+(const Box<T>&, const Box<T>&); 7986

7987

template<class T> 7988

ostream& operator<<(ostream&, const Box<T>&); 7989

Page 302: Pensar en C++ (Volumen 2)

302

7990

template<class T> class Box { 7991

T t; 7992

public: 7993

Box(const T& theT) : t(theT) {} 7994

friend Box operator+<>(const Box<T>&, const Box<T>&); 7995

friend ostream& operator<< <>(ostream&, const Box<T>&); 7996

}; 7997

7998

template<class T> 7999

Box<T> operator+(const Box<T>& b1, const Box<T>& b2) { 8000

return Box<T>(b1.t + b2.t); 8001

} 8002

8003

template<class T> 8004

ostream& operator<<(ostream& os, const Box<T>& b) { 8005

return os << '[' << b.t << ']'; 8006

} 8007

8008

int main() { 8009

Box<int> b1(1), b2(2); 8010

cout << b1 + b2 << endl; // [3] 8011

// cout << b1 + 2 << endl; // No implicit conversions! 8012

} ///:~ 8013

Page 303: Pensar en C++ (Volumen 2)

303

Listado 6.39. C05/Box1.cpp 8014

8015

//: C05:Box2.cpp 8016

// Defines non-template operators. 8017

#include <iostream> 8018

using namespace std; 8019

8020

template<class T> class Box { 8021

T t; 8022

public: 8023

Box(const T& theT) : t(theT) {} 8024

friend Box<T> operator+(const Box<T>& b1, 8025

const Box<T>& b2) { 8026

return Box<T>(b1.t + b2.t); 8027

} 8028

friend ostream& 8029

operator<<(ostream& os, const Box<T>& b) { 8030

return os << '[' << b.t << ']'; 8031

} 8032

}; 8033

8034

int main() { 8035

Box<int> b1(1), b2(2); 8036

cout << b1 + b2 << endl; // [3] 8037

cout << b1 + 2 << endl; // [3] 8038

Page 304: Pensar en C++ (Volumen 2)

304

} ///:~ 8039

Listado 6.40. C05/Box2.cpp 8040

8041

// Inside Friendly: 8042

friend void f<>(const Friendly<double>&); 8043

// Inside Friendly: 8044

friend void g(int); // g(int) befriends all Friendlys 8045

template<class T> class Friendly { 8046

template<class U> friend void f<>(const Friendly<U>&); 8047

6.5. 8048

6.5.1. 8049

template<class T> class numeric_limits { 8050

public: 8051

static const bool is_specialized = false; 8052

static T min() throw(); 8053

static T max() throw(); 8054

static const int digits = 0; 8055

static const int digits10 = 0; 8056

static const bool is_signed = false; 8057

static const bool is_integer = false; 8058

static const bool is_exact = false; 8059

static const int radix = 0; 8060

Page 305: Pensar en C++ (Volumen 2)

305

static T epsilon() throw(); 8061

static T round_error() throw(); 8062

static const int min_exponent = 0; 8063

static const int min_exponent10 = 0; 8064

static const int max_exponent = 0; 8065

static const int max_exponent10 = 0; 8066

static const bool has_infinity = false; 8067

static const bool has_quiet_NaN = false; 8068

static const bool has_signaling_NaN = false; 8069

static const float_denorm_style has_denorm = 8070

denorm_absent; 8071

static const bool has_denorm_loss = false; 8072

static T infinity() throw(); 8073

static T quiet_NaN() throw(); 8074

static T signaling_NaN() throw(); 8075

static T denorm_min() throw(); 8076

static const bool is_iec559 = false; 8077

static const bool is_bounded = false; 8078

static const bool is_modulo = false; 8079

static const bool traps = false; 8080

static const bool tinyness_before = false; 8081

static const float_round_style round_style = 8082

round_toward_zero; 8083

}; 8084

template<class charT, 8085

Page 306: Pensar en C++ (Volumen 2)

306

class traits = char_traits<charT>, 8086

class allocator = allocator<charT> > 8087

class basic_string; 8088

template<> struct char_traits<char> { 8089

typedef char char_type; 8090

typedef int int_type; 8091

typedef streamoff off_type; 8092

typedef streampos pos_type; 8093

typedef mbstate_t state_type; 8094

static void assign(char_type& c1, const char_type& c2); 8095

static bool eq(const char_type& c1, const char_type& c2); 8096

static bool lt(const char_type& c1, const char_type& c2); 8097

static int compare(const char_type* s1, 8098

const char_type* s2, size_t n); 8099

static size_t length(const char_type* s); 8100

static const char_type* find(const char_type* s, 8101

size_t n, 8102

const char_type& a); 8103

static char_type* move(char_type* s1, 8104

const char_type* s2, size_t n); 8105

static char_type* copy(char_type* s1, 8106

const char_type* s2, size_t n); 8107

static char_type* assign(char_type* s, size_t n, 8108

char_type a); 8109

static int_type not_eof(const int_type& c); 8110

Page 307: Pensar en C++ (Volumen 2)

307

static char_type to_char_type(const int_type& c); 8111

static int_type to_int_type(const char_type& c); 8112

static bool eq_int_type(const int_type& c1, 8113

const int_type& c2); 8114

static int_type eof(); 8115

}; 8116

std::string s; 8117

std::basic_string<char, std::char_traits<char>, 8118

std::allocator<char> > s; 8119

//: C05:BearCorner.h 8120

#ifndef BEARCORNER_H 8121

#define BEARCORNER_H 8122

#include <iostream> 8123

using std::ostream; 8124

8125

// Item classes (traits of guests): 8126

class Milk { 8127

public: 8128

friend ostream& operator<<(ostream& os, const Milk&) { 8129

return os << "Milk"; 8130

} 8131

}; 8132

8133

class CondensedMilk { 8134

Page 308: Pensar en C++ (Volumen 2)

308

public: 8135

friend ostream& 8136

operator<<(ostream& os, const CondensedMilk &) { 8137

return os << "Condensed Milk"; 8138

} 8139

}; 8140

8141

class Honey { 8142

public: 8143

friend ostream& operator<<(ostream& os, const Honey&) { 8144

return os << "Honey"; 8145

} 8146

}; 8147

8148

class Cookies { 8149

public: 8150

friend ostream& operator<<(ostream& os, const Cookies&) { 8151

return os << "Cookies"; 8152

} 8153

}; 8154

8155

// Guest classes: 8156

class Bear { 8157

public: 8158

friend ostream& operator<<(ostream& os, const Bear&) { 8159

Page 309: Pensar en C++ (Volumen 2)

309

return os << "Theodore"; 8160

} 8161

}; 8162

8163

class Boy { 8164

public: 8165

friend ostream& operator<<(ostream& os, const Boy&) { 8166

return os << "Patrick"; 8167

} 8168

}; 8169

8170

// Primary traits template (empty-could hold common types) 8171

template<class Guest> class GuestTraits; 8172

8173

// Traits specializations for Guest types 8174

template<> class GuestTraits<Bear> { 8175

public: 8176

typedef CondensedMilk beverage_type; 8177

typedef Honey snack_type; 8178

}; 8179

8180

template<> class GuestTraits<Boy> { 8181

public: 8182

typedef Milk beverage_type; 8183

typedef Cookies snack_type; 8184

Page 310: Pensar en C++ (Volumen 2)

310

}; 8185

#endif // BEARCORNER_H ///:~ 8186

Listado 6.41. C05/BearCorner.h 8187

8188

6.5.2. 8189

template<> struct char_traits<wchar_t> { 8190

typedef wchar_t char_type; 8191

typedef wint_t int_type; 8192

typedef streamoff off_type; 8193

typedef wstreampos pos_type; 8194

typedef mbstate_t state_type; 8195

static void assign(char_type& c1, const char_type& c2); 8196

static bool eq(const char_type& c1, const char_type& c2); 8197

static bool lt(const char_type& c1, const char_type& c2); 8198

static int compare(const char_type* s1, 8199

const char_type* s2, size_t n); 8200

static size_t length(const char_type* s); 8201

static const char_type* find(const char_type* s, 8202

size_t n, 8203

const char_type& a); 8204

static char_type* move(char_type* s1, 8205

const char_type* s2, size_t n); 8206

static char_type* copy(char_type* s1, 8207

Page 311: Pensar en C++ (Volumen 2)

311

const char_type* s2, size_t n); 8208

static char_type* assign(char_type* s, size_t n, 8209

char_type a); 8210

static int_type not_eof(const int_type& c); 8211

static char_type to_char_type(const int_type& c); 8212

static int_type to_int_type(const char_type& c); 8213

static bool eq_int_type(const int_type& c1, 8214

const int_type& c2); 8215

static int_type eof(); 8216

}; 8217

//: C05:BearCorner2.cpp 8218

// Illustrates policy classes. 8219

#include <iostream> 8220

#include "BearCorner.h" 8221

using namespace std; 8222

8223

// Policy classes (require a static doAction() function): 8224

class Feed { 8225

public: 8226

static const char* doAction() { return "Feeding"; } 8227

}; 8228

8229

class Stuff { 8230

public: 8231

Page 312: Pensar en C++ (Volumen 2)

312

static const char* doAction() { return "Stuffing"; } 8232

}; 8233

8234

// The Guest template (uses a policy and a traits class) 8235

template<class Guest, class Action, 8236

class traits = GuestTraits<Guest> > 8237

class BearCorner { 8238

Guest theGuest; 8239

typedef typename traits::beverage_type beverage_type; 8240

typedef typename traits::snack_type snack_type; 8241

beverage_type bev; 8242

snack_type snack; 8243

public: 8244

BearCorner(const Guest& g) : theGuest(g) {} 8245

void entertain() { 8246

cout << Action::doAction() << " " << theGuest 8247

<< " with " << bev 8248

<< " and " << snack << endl; 8249

} 8250

}; 8251

8252

int main() { 8253

Boy cr; 8254

BearCorner<Boy, Feed> pc1(cr); 8255

pc1.entertain(); 8256

Page 313: Pensar en C++ (Volumen 2)

313

Bear pb; 8257

BearCorner<Bear, Stuff> pc2(pb); 8258

pc2.entertain(); 8259

} ///:~ 8260

Listado 6.42. C05/BearCorner2.cpp 8261

8262

6.5.3. 8263

//: C05:CountedClass.cpp 8264

// Object counting via static members. 8265

#include <iostream> 8266

using namespace std; 8267

8268

class CountedClass { 8269

static int count; 8270

public: 8271

CountedClass() { ++count; } 8272

CountedClass(const CountedClass&) { ++count; } 8273

~CountedClass() { --count; } 8274

static int getCount() { return count; } 8275

}; 8276

8277

int CountedClass::count = 0; 8278

8279

Page 314: Pensar en C++ (Volumen 2)

314

int main() { 8280

CountedClass a; 8281

cout << CountedClass::getCount() << endl; // 1 8282

CountedClass b; 8283

cout << CountedClass::getCount() << endl; // 2 8284

{ // An arbitrary scope: 8285

CountedClass c(b); 8286

cout << CountedClass::getCount() << endl; // 3 8287

a = c; 8288

cout << CountedClass::getCount() << endl; // 3 8289

} 8290

cout << CountedClass::getCount() << endl; // 2 8291

} ///:~ 8292

Listado 6.43. C05/CountedClass.cpp 8293

8294

//: C05:CountedClass2.cpp 8295

// Erroneous attempt to count objects. 8296

#include <iostream> 8297

using namespace std; 8298

8299

class Counted { 8300

static int count; 8301

public: 8302

Counted() { ++count; } 8303

Page 315: Pensar en C++ (Volumen 2)

315

Counted(const Counted&) { ++count; } 8304

~Counted() { --count; } 8305

static int getCount() { return count; } 8306

}; 8307

8308

int Counted::count = 0; 8309

8310

class CountedClass : public Counted {}; 8311

class CountedClass2 : public Counted {}; 8312

8313

int main() { 8314

CountedClass a; 8315

cout << CountedClass::getCount() << endl; // 1 8316

CountedClass b; 8317

cout << CountedClass::getCount() << endl; // 2 8318

CountedClass2 c; 8319

cout << CountedClass2::getCount() << endl; // 3 (Error) 8320

} ///:~ 8321

Listado 6.44. C05/CountedClass2.cpp 8322

8323

//: C05:CountedClass3.cpp 8324

#include <iostream> 8325

using namespace std; 8326

8327

Page 316: Pensar en C++ (Volumen 2)

316

template<class T> class Counted { 8328

static int count; 8329

public: 8330

Counted() { ++count; } 8331

Counted(const Counted<T>&) { ++count; } 8332

~Counted() { --count; } 8333

static int getCount() { return count; } 8334

}; 8335

8336

template<class T> int Counted<T>::count = 0; 8337

8338

// Curious class definitions 8339

class CountedClass : public Counted<CountedClass> {}; 8340

class CountedClass2 : public Counted<CountedClass2> {}; 8341

8342

int main() { 8343

CountedClass a; 8344

cout << CountedClass::getCount() << endl; // 1 8345

CountedClass b; 8346

cout << CountedClass::getCount() << endl; // 2 8347

CountedClass2 c; 8348

cout << CountedClass2::getCount() << endl; // 1 (!) 8349

} ///:~ 8350

Listado 6.45. C05/CountedClass3.cpp 8351

Page 317: Pensar en C++ (Volumen 2)

317

8352

6.6. 8353

//: C05:Factorial.cpp 8354

// Compile-time computation using templates. 8355

#include <iostream> 8356

using namespace std; 8357

8358

template<int n> struct Factorial { 8359

enum { val = Factorial<n-1>::val * n }; 8360

}; 8361

8362

template<> struct Factorial<0> { 8363

enum { val = 1 }; 8364

}; 8365

8366

int main() { 8367

cout << Factorial<12>::val << endl; // 479001600 8368

} ///:~ 8369

Listado 6.46. C05/Factorial.cpp 8370

8371

double nums[Factorial<5>::val]; 8372

assert(sizeof nums == sizeof(double)*120); 8373

Page 318: Pensar en C++ (Volumen 2)

318

6.6.1. 8374

//: C05:Fibonacci.cpp 8375

#include <iostream> 8376

using namespace std; 8377

8378

template<int n> struct Fib { 8379

enum { val = Fib<n-1>::val + Fib<n-2>::val }; 8380

}; 8381

8382

template<> struct Fib<1> { enum { val = 1 }; }; 8383

8384

template<> struct Fib<0> { enum { val = 0 }; }; 8385

8386

int main() { 8387

cout << Fib<5>::val << endl; // 6 8388

cout << Fib<20>::val << endl; // 6765 8389

} ///:~ 8390

Listado 6.47. C05/Fibonacci.cpp 8391

8392

int val = 1; 8393

while(p--) 8394

val *= n; --> 8395

int power(int n, int p) { 8396

return (p == 0) ? 1 : n*power(n, p - 1); 8397

Page 319: Pensar en C++ (Volumen 2)

319

} 8398

//: C05:Power.cpp 8399

#include <iostream> 8400

using namespace std; 8401

8402

template<int N, int P> struct Power { 8403

enum { val = N * Power<N, P-1>::val }; 8404

}; 8405

8406

template<int N> struct Power<N, 0> { 8407

enum { val = 1 }; 8408

}; 8409

8410

int main() { 8411

cout << Power<2, 5>::val << endl; // 32 8412

} ///:~ 8413

Listado 6.48. C05/Power.cpp 8414

8415

//: C05:Accumulate.cpp 8416

// Passes a "function" as a parameter at compile time. 8417

#include <iostream> 8418

using namespace std; 8419

8420

Page 320: Pensar en C++ (Volumen 2)

320

// Accumulates the results of F(0)..F(n) 8421

template<int n, template<int> class F> struct Accumulate { 8422

enum { val = Accumulate<n-1, F>::val + F<n>::val }; 8423

}; 8424

8425

// The stopping criterion (returns the value F(0)) 8426

template<template<int> class F> struct Accumulate<0, F> { 8427

enum { val = F<0>::val }; 8428

}; 8429

8430

// Various "functions": 8431

template<int n> struct Identity { 8432

enum { val = n }; 8433

}; 8434

8435

template<int n> struct Square { 8436

enum { val = n*n }; 8437

}; 8438

8439

template<int n> struct Cube { 8440

enum { val = n*n*n }; 8441

}; 8442

8443

int main() { 8444

cout << Accumulate<4, Identity>::val << endl; // 10 8445

Page 321: Pensar en C++ (Volumen 2)

321

cout << Accumulate<4, Square>::val << endl; // 30 8446

cout << Accumulate<4, Cube>::val << endl; // 100 8447

} ///:~ 8448

Listado 6.49. C05/Accumulate.cpp 8449

8450

void mult(int a[ROWS][COLS], int x[COLS], int y[COLS]) { 8451

for(int i = 0; i < ROWS; ++i) { 8452

y[i] = 0; 8453

for(int j = 0; j < COLS; ++j) 8454

y[i] += a[i][j]*x[j]; 8455

} 8456

} 8457

void mult(int a[ROWS][COLS], int x[COLS], int y[COLS]) { 8458

for(int i = 0; i < ROWS; ++i) { 8459

y[i] = 0; 8460

for(int j = 0; j < COLS; j += 2) 8461

y[i] += a[i][j]*x[j] + a[i][j+1]*x[j+1]; 8462

} 8463

} 8464

//: C05:Unroll.cpp 8465

// Unrolls an implicit loop via inlining. 8466

#include <iostream> 8467

using namespace std; 8468

Page 322: Pensar en C++ (Volumen 2)

322

8469

template<int n> inline int power(int m) { 8470

return power<n-1>(m) * m; 8471

} 8472

8473

template<> inline int power<1>(int m) { 8474

return m; 8475

} 8476

8477

template<> inline int power<0>(int m) { 8478

return 1; 8479

} 8480

8481

int main() { 8482

int m = 4; 8483

cout << power<3>(m) << endl; 8484

} ///:~ 8485

Listado 6.50. C05/Unroll.cpp 8486

8487

//: C05:Max.cpp 8488

#include <iostream> 8489

using namespace std; 8490

8491

template<int n1, int n2> struct Max { 8492

Page 323: Pensar en C++ (Volumen 2)

323

enum { val = n1 > n2 ? n1 : n2 }; 8493

}; 8494

8495

int main() { 8496

cout << Max<10, 20>::val << endl; // 20 8497

} ///:~ 8498

Listado 6.51. C05/Max.cpp 8499

8500

//: C05:Conditionals.cpp 8501

// Uses compile-time conditions to choose code. 8502

#include <iostream> 8503

using namespace std; 8504

8505

template<bool cond> struct Select {}; 8506

8507

template<> class Select<true> { 8508

static void statement1() { 8509

cout << "This is statement1 executing\n"; 8510

} 8511

public: 8512

static void f() { statement1(); } 8513

}; 8514

8515

template<> class Select<false> { 8516

Page 324: Pensar en C++ (Volumen 2)

324

static void statement2() { 8517

cout << "This is statement2 executing\n"; 8518

} 8519

public: 8520

static void f() { statement2(); } 8521

}; 8522

8523

template<bool cond> void execute() { 8524

Select<cond>::f(); 8525

} 8526

8527

int main() { 8528

execute<sizeof(int) == 4>(); 8529

} ///:~ 8530

Listado 6.52. C05/Conditionals.cpp 8531

8532

if(cond) 8533

statement1(); 8534

else 8535

statement2(); 8536

//: C05:StaticAssert1.cpp {-xo} 8537

// A simple, compile-time assertion facility 8538

Page 325: Pensar en C++ (Volumen 2)

325

8539

#define STATIC_ASSERT(x) \ 8540

do { typedef int a[(x) ? 1 : -1]; } while(0) 8541

8542

int main() { 8543

STATIC_ASSERT(sizeof(int) <= sizeof(long)); // Passes 8544

STATIC_ASSERT(sizeof(double) <= sizeof(int)); // Fails 8545

} ///:~ 8546

Listado 6.53. C05/StaticAssert1.cpp 8547

8548

//: C05:StaticAssert2.cpp {-g++} 8549

#include <iostream> 8550

using namespace std; 8551

8552

// A template and a specialization 8553

template<bool> struct StaticCheck { 8554

StaticCheck(...); 8555

}; 8556

8557

template<> struct StaticCheck<false> {}; 8558

8559

// The macro (generates a local class) 8560

#define STATIC_CHECK(expr, msg) { \ 8561

class Error_##msg {}; \ 8562

Page 326: Pensar en C++ (Volumen 2)

326

sizeof((StaticCheck<expr>(Error_##msg()))); \ 8563

} 8564

8565

// Detects narrowing conversions 8566

template<class To, class From> To safe_cast(From from) { 8567

STATIC_CHECK(sizeof(From) <= sizeof(To), 8568

NarrowingConversion); 8569

return reinterpret_cast<To>(from); 8570

} 8571

8572

int main() { 8573

void* p = 0; 8574

int i = safe_cast<int>(p); 8575

cout << "int cast okay" << endl; 8576

//! char c = safe_cast<char>(p); 8577

} ///:~ 8578

Listado 6.54. C05/StaticAssert2.cpp 8579

8580

int i = safe_cast<int>(p); 8581

{ \ 8582

class Error_NarrowingConversion {}; \ 8583

sizeof(StaticCheck<sizeof(void*) <= sizeof(int)> \ 8584

(Error_NarrowingConversion())); \ 8585

} 8586

Page 327: Pensar en C++ (Volumen 2)

327

char c = safe_cast<char>(p); 8587

{ \ 8588

class Error_NarrowingConversion {}; \ 8589

sizeof(StaticCheck<sizeof(void*) <= sizeof(char)> \ 8590

(Error_NarrowingConversion())); \ 8591

} 8592

sizeof(StaticCheck<false>(Error_NarrowingConversion())); 8593

Cannot cast from 'Error_NarrowingConversion' to 'StaticCheck<0>' in 8594 function 8595

char safe_cast<char,void *>(void *) 8596

6.6.2. 8597

D = A + B + C; 8598

//: C05:MyVector.cpp 8599

// Optimizes away temporaries via templates. 8600

#include <cstddef> 8601

#include <cstdlib> 8602

#include <ctime> 8603

#include <iostream> 8604

using namespace std; 8605

8606

// A proxy class for sums of vectors 8607

template<class, size_t> class MyVectorSum; 8608

8609

Page 328: Pensar en C++ (Volumen 2)

328

template<class T, size_t N> class MyVector { 8610

T data[N]; 8611

public: 8612

MyVector<T,N>& operator=(const MyVector<T,N>& right) { 8613

for(size_t i = 0; i < N; ++i) 8614

data[i] = right.data[i]; 8615

return *this; 8616

} 8617

MyVector<T,N>& operator=(const MyVectorSum<T,N>& right); 8618

const T& operator[](size_t i) const { return data[i]; } 8619

T& operator[](size_t i) { return data[i]; } 8620

}; 8621

8622

// Proxy class hold references; uses lazy addition 8623

template<class T, size_t N> class MyVectorSum { 8624

const MyVector<T,N>& left; 8625

const MyVector<T,N>& right; 8626

public: 8627

MyVectorSum(const MyVector<T,N>& lhs, 8628

const MyVector<T,N>& rhs) 8629

: left(lhs), right(rhs) {} 8630

T operator[](size_t i) const { 8631

return left[i] + right[i]; 8632

} 8633

}; 8634

Page 329: Pensar en C++ (Volumen 2)

329

8635

// Operator to support v3 = v1 + v2 8636

template<class T, size_t N> MyVector<T,N>& 8637

MyVector<T,N>::operator=(const MyVectorSum<T,N>& right) { 8638

for(size_t i = 0; i < N; ++i) 8639

data[i] = right[i]; 8640

return *this; 8641

} 8642

8643

// operator+ just stores references 8644

template<class T, size_t N> inline MyVectorSum<T,N> 8645

operator+(const MyVector<T,N>& left, 8646

const MyVector<T,N>& right) { 8647

return MyVectorSum<T,N>(left, right); 8648

} 8649

8650

// Convenience functions for the test program below 8651

template<class T, size_t N> void init(MyVector<T,N>& v) { 8652

for(size_t i = 0; i < N; ++i) 8653

v[i] = rand() % 100; 8654

} 8655

8656

template<class T, size_t N> void print(MyVector<T,N>& v) { 8657

for(size_t i = 0; i < N; ++i) 8658

cout << v[i] << ' '; 8659

Page 330: Pensar en C++ (Volumen 2)

330

cout << endl; 8660

} 8661

8662

int main() { 8663

srand(time(0)); 8664

MyVector<int, 5> v1; 8665

init(v1); 8666

print(v1); 8667

MyVector<int, 5> v2; 8668

init(v2); 8669

print(v2); 8670

MyVector<int, 5> v3; 8671

v3 = v1 + v2; 8672

print(v3); 8673

MyVector<int, 5> v4; 8674

// Not yet supported: 8675

//! v4 = v1 + v2 + v3; 8676

} ///:~ 8677

Listado 6.55. C05/MyVector.cpp 8678

8679

v1 = v2 + v3; // Add two vectors 8680

v3.operator=<int,5>(MyVectorSum<int,5>(v2, v3)); 8681

v4 = v1 + v2 + v3; 8682

Page 331: Pensar en C++ (Volumen 2)

331

(v1 + v2) + v3; 8683

//: C05:MyVector2.cpp 8684

// Handles sums of any length with expression templates. 8685

#include <cstddef> 8686

#include <cstdlib> 8687

#include <ctime> 8688

#include <iostream> 8689

using namespace std; 8690

8691

// A proxy class for sums of vectors 8692

template<class, size_t, class, class> class MyVectorSum; 8693

8694

template<class T, size_t N> class MyVector { 8695

T data[N]; 8696

public: 8697

MyVector<T,N>& operator=(const MyVector<T,N>& right) { 8698

for(size_t i = 0; i < N; ++i) 8699

data[i] = right.data[i]; 8700

return *this; 8701

} 8702

template<class Left, class Right> MyVector<T,N>& 8703

operator=(const MyVectorSum<T,N,Left,Right>& right); 8704

const T& operator[](size_t i) const { 8705

return data[i]; 8706

Page 332: Pensar en C++ (Volumen 2)

332

} 8707

T& operator[](size_t i) { 8708

return data[i]; 8709

} 8710

}; 8711

8712

// Allows mixing MyVector and MyVectorSum 8713

template<class T, size_t N, class Left, class Right> 8714

class MyVectorSum { 8715

const Left& left; 8716

const Right& right; 8717

public: 8718

MyVectorSum(const Left& lhs, const Right& rhs) 8719

: left(lhs), right(rhs) {} 8720

T operator[](size_t i) const { 8721

return left[i] + right[i]; 8722

} 8723

}; 8724

8725

template<class T, size_t N> 8726

template<class Left, class Right> 8727

MyVector<T,N>& 8728

MyVector<T,N>:: 8729

operator=(const MyVectorSum<T,N,Left,Right>& right) { 8730

for(size_t i = 0; i < N; ++i) 8731

Page 333: Pensar en C++ (Volumen 2)

333

data[i] = right[i]; 8732

return *this; 8733

} 8734

// operator+ just stores references 8735

template<class T, size_t N> 8736

inline MyVectorSum<T,N,MyVector<T,N>,MyVector<T,N> > 8737

operator+(const MyVector<T,N>& left, 8738

const MyVector<T,N>& right) { 8739

return MyVectorSum<T,N,MyVector<T,N>,MyVector<T,N> > 8740

(left,right); 8741

} 8742

8743

template<class T, size_t N, class Left, class Right> 8744

inline MyVectorSum<T, N, MyVectorSum<T,N,Left,Right>, 8745

MyVector<T,N> > 8746

operator+(const MyVectorSum<T,N,Left,Right>& left, 8747

const MyVector<T,N>& right) { 8748

return MyVectorSum<T,N,MyVectorSum<T,N,Left,Right>, 8749

MyVector<T,N> > 8750

(left, right); 8751

} 8752

// Convenience functions for the test program below 8753

template<class T, size_t N> void init(MyVector<T,N>& v) { 8754

for(size_t i = 0; i < N; ++i) 8755

v[i] = rand() % 100; 8756

Page 334: Pensar en C++ (Volumen 2)

334

} 8757

8758

template<class T, size_t N> void print(MyVector<T,N>& v) { 8759

for(size_t i = 0; i < N; ++i) 8760

cout << v[i] << ' '; 8761

cout << endl; 8762

} 8763

8764

int main() { 8765

srand(time(0)); 8766

MyVector<int, 5> v1; 8767

init(v1); 8768

print(v1); 8769

MyVector<int, 5> v2; 8770

init(v2); 8771

print(v2); 8772

MyVector<int, 5> v3; 8773

v3 = v1 + v2; 8774

print(v3); 8775

// Now supported: 8776

MyVector<int, 5> v4; 8777

v4 = v1 + v2 + v3; 8778

print(v4); 8779

MyVector<int, 5> v5; 8780

v5 = v1 + v2 + v3 + v4; 8781

Page 335: Pensar en C++ (Volumen 2)

335

print(v5); 8782

} ///:~ 8783

Listado 6.56. C05/MyVector2.cpp 8784

8785

v4 = v1 + v2 + v3; 8786

v4.operator+(MVS(MVS(v1, v2), v3)); 8787

6.7. 8788

6.7.1. 8789

6.7.2. 8790

//: C05:OurMin.h 8791

#ifndef OURMIN_H 8792

#define OURMIN_H 8793

// The declaration of min() 8794

template<typename T> const T& min(const T&, const T&); 8795

#endif // OURMIN_H ///:~ 8796

Listado 6.57. C05/OurMin.h 8797

8798

//: C05:MinInstances.cpp {O} 8799

#include "OurMin.cpp" 8800

8801

Page 336: Pensar en C++ (Volumen 2)

336

// Explicit Instantiations for int and double 8802

template const int& min<int>(const int&, const int&); 8803

template const double& min<double>(const double&, 8804

const double&); 8805

///:~ 8806

Listado 6.58. C05/MinInstances.cpp 8807

8808

//: C05:OurMin.cpp {O} 8809

#ifndef OURMIN_CPP 8810

#define OURMIN_CPP 8811

#include "OurMin.h" 8812

8813

template<typename T> const T& min(const T& a, const T& b) { 8814

return (a < b) ? a : b; 8815

} 8816

#endif // OURMIN_CPP ///:~ 8817

Listado 6.59. C05/OurMin.cpp 8818

8819

1 8820 3.1 8821

6.7.3. 8822

//: C05:OurMin2.h 8823

// Declares min as an exported template 8824

Page 337: Pensar en C++ (Volumen 2)

337

// (Only works with EDG-based compilers) 8825

#ifndef OURMIN2_H 8826

#define OURMIN2_H 8827

export template<typename T> const T& min(const T&, 8828

const T&); 8829

#endif // OURMIN2_H ///:~ 8830

Listado 6.60. C05/OurMin2.h 8831

8832

// C05:OurMin2.cpp 8833

// The definition of the exported min template 8834

// (Only works with EDG-based compilers) 8835

#include "OurMin2.h" 8836

export 8837

template<typename T> const T& min(const T& a, const T& b) { 8838

return (a < b) ? a : b; 8839

} ///:~ 8840

Listado 6.61. C05/OurMin2.cpp 8841

8842

6.7.4. 8843

6.8. 8844

//: C05:Exercise4.cpp {-xo} 8845

Page 338: Pensar en C++ (Volumen 2)

338

class Noncomparable {}; 8846

8847

struct HardLogic { 8848

Noncomparable nc1, nc2; 8849

void compare() { 8850

return nc1 == nc2; // Compiler error 8851

} 8852

}; 8853

8854

template<class T> struct SoftLogic { 8855

Noncomparable nc1, nc2; 8856

void noOp() {} 8857

void compare() { 8858

nc1 == nc2; 8859

} 8860

}; 8861

8862

int main() { 8863

SoftLogic<Noncomparable> l; 8864

l.noOp(); 8865

} ///:~ 8866

Listado 6.62. C05/Exercise4.cpp 8867

8868

//: C05:Exercise7.cpp {-xo} 8869

Page 339: Pensar en C++ (Volumen 2)

339

class Buddy {}; 8870

8871

template<class T> class My { 8872

int i; 8873

public: 8874

void play(My<Buddy>& s) { 8875

s.i = 3; 8876

} 8877

}; 8878

8879

int main() { 8880

My<int> h; 8881

My<Buddy> me, bud; 8882

h.play(bud); 8883

me.play(bud); 8884

} ///:~ 8885

Listado 6.63. C05/Exercise7.cpp 8886

8887

//: C05:Exercise8.cpp {-xo} 8888

template<class T> double pythag(T a, T b, T c) { 8889

return (-b + sqrt(double(b*b - 4*a*c))) / 2*a; 8890

} 8891

8892

int main() { 8893

Page 340: Pensar en C++ (Volumen 2)

340

pythag(1, 2, 3); 8894

pythag(1.0, 2.0, 3.0); 8895

pythag(1, 2.0, 3.0); 8896

pythag<double>(1, 2.0, 3.0); 8897

} ///:~ 8898

Listado 6.64. C05/Exercise8.cpp 8899

8900

7: Algoritmos genéricos 8901

Tabla de contenidos 8902

7.1. Un primer vistazo 8903

7.2. Objetos-función 8904

7.3. Un catálogo de algoritmos STL 8905

7.4. Creando sus propios algoritmos tipo STL 8906

7.5. Resumen 8907

7.6. Ejercicios 8908

Los algoritmos son la base de la computación. Ser capaz de escribir un 8909

algoritmo que funcione con cualquier tipo de se secuencia hace que sus 8910

programas sean simples y seguros. La habilidad para adaptar algoritmos en 8911

tiempo de ejecución a revolucionado el desarrollo de software. 8912

El subconjunto de la Librería Estándar de C++ conocido como Standard 8913

Template Library (STL)[17] fue diseñado entorno a algoritmos genéricos —8914

código que procesa secuencias de cualquier tipo de valores de un modo 8915

seguro. El objetivo era usar algoritmos predefinidos para casi cualquier tarea, 8916

en lugar de codificar a mano cada vez que se necesitara procesar una 8917

colección de datos. Sin embargo, ese potencial requiere cierto aprendizaje. 8918

Para cuando llegue al final de este capítulo, debería ser capaz de decidir por 8919

sí mismo si los algoritmos le resultan útiles o demasiado confusos de 8920

recordar. Si es como la mayoría de la gente, se resistirá al principio pero 8921

entonces tenderá a usarlos más y más con el tiempo. 8922

7.1. Un primer vistazo 8923

Page 341: Pensar en C++ (Volumen 2)

341

Entre otras cosas, los algoritmos genéricos de la librería estándar 8924

proporcionan un vocabulario con el que desribir soluciones. Una vez que los 8925

algoritmos le sean familiares, tendrá un nuevo conjunto de palabras con el 8926

que discutir que está haciendo, y esas palabras son de un nivel mayor que 8927

las que tenía antes. No necesitará decir «Este bucle recorre y asigna de aquí 8928

a ahí... oh, ya veo, ¡está copiando!» En su lugar dirá simplemente copy(). 8929

Esto es lo que hemos estado haciendo desde el principio de la programación 8930

de computadores —creando abstracciones de alto nivel para expresar lo que 8931

está haciendo y perder menos tiempo diciendo cómo hacerlo. El «cómo» se 8932

ha resuelto una vez y para todo y está oculto en el código del algoritmo, listo 8933

para ser reutilizado cuando se necesite. 8934

Vea aquí un ejemplo de cómo utilizar el algoritmo copy: 8935

//: C06:CopyInts.cpp 8936

// Copies ints without an explicit loop. 8937

#include <algorithm> 8938

#include <cassert> 8939

#include <cstddef> // For size_t 8940

using namespace std; 8941

8942

int main() { 8943

int a[] = { 10, 20, 30 }; 8944

const size_t SIZE = sizeof a / sizeof a[0]; 8945

int b[SIZE]; 8946

copy(a, a + SIZE, b); 8947

for(size_t i = 0; i < SIZE; ++i) 8948

assert(a[i] == b[i]); 8949

} ///:~ 8950

Page 342: Pensar en C++ (Volumen 2)

342

Listado 7.1. C06/CopyInts.cpp 8951

8952

Los dos primeros parámetros de copy representan el rango de la secuencia 8953

de entrada —en este caso del array a. Los rangos se especifican con un par 8954

de punteros. El primero apunta al primer elemento de la secuencia, y el 8955

segungo apunta una posición después del final del array (justo después del 8956

último elemento). Esto puede parecer extraño al principio, pero es una 8957

antigua expresión idiomática de C que resulta bastante práctica. Por ejemplo, 8958

la diferencia entre esos dos punteros devuelve el número de elementos de la 8959

secuencia. Más importante, en la implementación de copy(), el segundo 8960

puntero puede actual como un centinela para para la iteración a través de la 8961

secuencia. El tercer argumento hace referencia al comienzo de la secuencia 8962

de salida, que es el array b en el ejemplo. Se asume que el array b tiene 8963

suficiente espacio para recibir los elementos copiados. 8964

El algotirmo copy() no parece muy excitante if solo puediera procesar 8965

enteros. Puede copiar cualquier tipo de secuencia. El siguiente ejemplo copia 8966

objetos string. 8967

//: C06:CopyStrings.cpp 8968

// Copies strings. 8969

#include <algorithm> 8970

#include <cassert> 8971

#include <cstddef> 8972

#include <string> 8973

using namespace std; 8974

8975

int main() { 8976

string a[] = {"read", "my", "lips"}; 8977

const size_t SIZE = sizeof a / sizeof a[0]; 8978

string b[SIZE]; 8979

Page 343: Pensar en C++ (Volumen 2)

343

copy(a, a + SIZE, b); 8980

assert(equal(a, a + SIZE, b)); 8981

} ///:~ 8982

Listado 7.2. C06/CopyStrings.cpp 8983

8984

Este ejmeplo presenta otro algoritmo, equal(), que devuelve cierto solo si 8985

cada elemento de la primera secuencia es igual (usando su operator==()) a 8986

su elemento correspondiente en la segunda secuencia. Este ejemplo recorre 8987

cada secuencia 2 veces, una para copiar, y otra para comparar, sin ningún 8988

bucle explícito. 8989

Los algoritmos genéricos consiguen esta flexibilidad porque son funciones 8990

parametrizadas (plantillas). Si piensa en la implementación de copy() verá 8991

que es algo como lo siguiente, que es «casi» correcto: 8992

template<typename T> 8993

void copy(T* begin, T* end, T* dest) { 8994

while (begin != end) 8995

*dest++ = *begin++; 8996

} 8997

Decimos «casi» porque copy() puede procesar secuencias delimitadas por 8998

cualquier cosa que actúe como un puntero, tal como un iterador. De ese 8999

modo, copy() se puede utilizar para duplicar un vector, como en el siguiente 9000

ejemplo. 9001

//: C06:CopyVector.cpp 9002

// Copies the contents of a vector. 9003

#include <algorithm> 9004

Page 344: Pensar en C++ (Volumen 2)

344

#include <cassert> 9005

#include <cstddef> 9006

#include <vector> 9007

using namespace std; 9008

9009

int main() { 9010

int a[] = { 10, 20, 30 }; 9011

const size_t SIZE = sizeof a / sizeof a[0]; 9012

vector<int> v1(a, a + SIZE); 9013

vector<int> v2(SIZE); 9014

copy(v1.begin(), v1.end(), v2.begin()); 9015

assert(equal(v1.begin(), v1.end(), v2.begin())); 9016

} ///:~ 9017

Listado 7.3. C06/CopyVector.cpp 9018

9019

El primer vector, v1, es inicializado a partir de una secuencia de enteros en 9020

el array a. La definición del vector v2 usa un contructor diferente de vector 9021

que reserva sitio para SIZE elementos, inicializados a cero (el valor por 9022

defecto para enteros). 9023

Igual que con el ejemplo anterior con el array, es importante que v2 tenga 9024

suficiente espacio para recibir una copia de los contenidos de v1. Por 9025

conveniencia, una función de librería especial, back_inserter(), retorna un 9026

tipo especial de iterador que inserta elementos en lugar de sobre-escribirlos, 9027

de modo que la memoria del contenedor se expande conforme se necesita. 9028

El siguiente ejemplo usa back_inserter(), y por eso no hay que establecer 9029

el tamaño del vector de salida, v2, antes de tiempo. 9030

//: C06:InsertVector.cpp 9031

Page 345: Pensar en C++ (Volumen 2)

345

// Appends the contents of a vector to another. 9032

#include <algorithm> 9033

#include <cassert> 9034

#include <cstddef> 9035

#include <iterator> 9036

#include <vector> 9037

using namespace std; 9038

9039

int main() { 9040

int a[] = { 10, 20, 30 }; 9041

const size_t SIZE = sizeof a / sizeof a[0]; 9042

vector<int> v1(a, a + SIZE); 9043

vector<int> v2; // v2 is empty here 9044

copy(v1.begin(), v1.end(), back_inserter(v2)); 9045

assert(equal(v1.begin(), v1.end(), v2.begin())); 9046

} ///:~ 9047

Listado 7.4. C06/InsertVector.cpp 9048

9049

La función back_inserter() está definida en el fichero de cabecera 9050

<iterator>. Explicaremos los iteradores de inserción en profundidad en el 9051

próximo capítulo. 9052

Dado que los iteradores son idénticos a punteros en todos los sentidos 9053

importantes, puede escribir los algoritmos de la librería estándar de modo 9054

que los argumentos puedan ser tanto punteros como iteradores. Por esta 9055

razón, la implementación de copy() se parece más al siguiente código: 9056

template<typename Iterator> 9057

Page 346: Pensar en C++ (Volumen 2)

346

void copy(Iterator begin, Iterator end, Iterator dest) { 9058

while (begin != end) 9059

*begin++ = *dest++; 9060

} 9061

Para cualquier tipo de argumento que use en la llamada, copy() asume 9062

que implementa adecuadamente la indirección y los operadores de 9063

incremento. Si no lo hace, obtendrás un error de compilación. 9064

7.1.1. Predicados 9065

A veces, podría querer copiar solo un subconjunto bien definido de una 9066

secuencia a otra; solo aquellos elementos que satisfagan una condición 9067

particular. Para conseguir esta flexibilidad, muchos algoritmos tienen una 9068

forma alternativa de llamada que permite proporcionar un predicado, que es 9069

simplemente una función que retorna un valor booleano basado en algún 9070

criterio. Suponga por ejemplo, que solo quiere extraer de una secuencia de 9071

enteros, aquellos que son menores o iguales de 15. Una versión de copy() 9072

llamada remove_copy_if() puede hacer el trabajo, tal que así: 9073

//: C06:CopyInts2.cpp 9074

// Ignores ints that satisfy a predicate. 9075

#include <algorithm> 9076

#include <cstddef> 9077

#include <iostream> 9078

using namespace std; 9079

9080

// You supply this predicate 9081

bool gt15(int x) { return 15 < x; } 9082

9083

Page 347: Pensar en C++ (Volumen 2)

347

int main() { 9084

int a[] = { 10, 20, 30 }; 9085

const size_t SIZE = sizeof a / sizeof a[0]; 9086

int b[SIZE]; 9087

int* endb = remove_copy_if(a, a+SIZE, b, gt15); 9088

int* beginb = b; 9089

while(beginb != endb) 9090

cout << *beginb++ << endl; // Prints 10 only 9091

} ///:~ 9092

Listado 7.5. C06/CopyInts2.cpp 9093

9094

La función remove_copy_if() acepta los rangos definidos por punteros 9095

habituales, seguidos de un predicado de su elección. El predicado debe ser 9096

un puntero a función[FIXME] que toma un argumento simple del mismo tipo 9097

que los elementos de la secuencia, y que debe retornar un booleano. Aquí, la 9098

función gt15 returna verdadero si su argumento es mayor que 15. El 9099

algoritmo remove_copy_if() aplica gt15() a cada elemento en la secuencia 9100

de entrada e ignora aquellos elementos para los cuales el predicado 9101

devuelve verdad cuando escribe la secuencia de salida. 9102

El siguiente programa ilustra otra variación más del algoritmo de copia. 9103

//: C06:CopyStrings2.cpp 9104

// Replaces strings that satisfy a predicate. 9105

#include <algorithm> 9106

#include <cstddef> 9107

#include <iostream> 9108

#include <string> 9109

Page 348: Pensar en C++ (Volumen 2)

348

using namespace std; 9110

9111

// The predicate 9112

bool contains_e(const string& s) { 9113

return s.find('e') != string::npos; 9114

} 9115

9116

int main() { 9117

string a[] = {"read", "my", "lips"}; 9118

const size_t SIZE = sizeof a / sizeof a[0]; 9119

string b[SIZE]; 9120

string* endb = replace_copy_if(a, a + SIZE, b, 9121

contains_e, string("kiss")); 9122

string* beginb = b; 9123

while(beginb != endb) 9124

cout << *beginb++ << endl; 9125

} ///:~ 9126

Listado 7.6. C06/CopyStrings2.cpp 9127

9128

En lugar de simplemente ignorar elementos que no satisfagan el 9129

predicado, replace_copy_if() substituye un valor fijo para esos elementos 9130

cuando escribe la secuencia de salida. La salida es: 9131

kiss 9132 my 9133

lips 9134

Page 349: Pensar en C++ (Volumen 2)

349

como la ocurrencia original de «read», la única cadena de entrada que 9135

contiene la letra «e», es reemplazada por la palabra «kiss», como se 9136

especificó en el último argumento en la llamada a replace_copy_if(). 9137

El algoritmo replace_if() cambia la secuencia original in situ, en lugar de 9138

escribir en una secuencia de salida separada, tal como muestra el siguiente 9139

programa: 9140

//: C06:ReplaceStrings.cpp 9141

// Replaces strings in-place. 9142

#include <algorithm> 9143

#include <cstddef> 9144

#include <iostream> 9145

#include <string> 9146

using namespace std; 9147

9148

bool contains_e(const string& s) { 9149

return s.find('e') != string::npos; 9150

} 9151

9152

int main() { 9153

string a[] = {"read", "my", "lips"}; 9154

const size_t SIZE = sizeof a / sizeof a[0]; 9155

replace_if(a, a + SIZE, contains_e, string("kiss")); 9156

string* p = a; 9157

while(p != a + SIZE) 9158

cout << *p++ << endl; 9159

Page 350: Pensar en C++ (Volumen 2)

350

} ///:~ 9160

Listado 7.7. C06/ReplaceStrings.cpp 9161

9162

7.1.2. Iteradores de flujo 9163

Como cualquier otra buena librería, la Librería Estándar de C++ intenta 9164

proporcionar modos convenientes de automatizar tareas comunes. 9165

Mencionamos al principio de este capítulo puede usar algoritmos genéricos 9166

en lugar de bucles. Hasta el momento, sin embargo, nuestros ejemplos 9167

siguen usando un bucle explícito para imprimir su salida. Dado que imprimir 9168

la salida es una de las tareas más comunes, es de esperar que haya una 9169

forma de automatizar eso también. 9170

Ahí es donde los iteradores de flujo entran en juego. Un iterador de flujo 9171

usa un flujo como secuencia de entrada o salida. Para eliminar el bucle de 9172

salida en el programa CopyInts2.cpp, puede hacer algo como lo siguiente: 9173

//: C06:CopyInts3.cpp 9174

// Uses an output stream iterator. 9175

#include <algorithm> 9176

#include <cstddef> 9177

#include <iostream> 9178

#include <iterator> 9179

using namespace std; 9180

9181

bool gt15(int x) { return 15 < x; } 9182

9183

int main() { 9184

Page 351: Pensar en C++ (Volumen 2)

351

int a[] = { 10, 20, 30 }; 9185

const size_t SIZE = sizeof a / sizeof a[0]; 9186

remove_copy_if(a, a + SIZE, 9187

ostream_iterator<int>(cout, "\n"), gt15); 9188

} ///:~ 9189

Listado 7.8. C06/CopyInts3.cpp 9190

9191

En este ejemplo, reemplazaremos la secuencia de salida b en el tercer 9192

argumento de remove_copy_if() con un iterador de flujo de salida, que es 9193

una instancia de la clase ostream_iterator declarada en el fichero 9194

<iterator>. Los iteradores de flujo de salida sobrecargan sus operadores de 9195

copia-asignación para escribir a sus flujos. Esta instancia en particular de 9196

ostream_iterator está vinculada al flujo de salida cout. Cada vez que 9197

remove_copy_if() asigna un entero de la secuencia a a cout a través de este 9198

iterador, el iterador escribe el entero a cout y automáticamente escribe 9199

también una instancia de la cada de separador indicada en su segundo 9200

argumento, que en este caso contiene el carácter de nueva linea. 9201

Es igual de fácil escribir en un fichero proporcionando un flujo de salida 9202

asociado a un fichero en lugar de cout. 9203

//: C06:CopyIntsToFile.cpp 9204

// Uses an output file stream iterator. 9205

#include <algorithm> 9206

#include <cstddef> 9207

#include <fstream> 9208

#include <iterator> 9209

using namespace std; 9210

9211

Page 352: Pensar en C++ (Volumen 2)

352

bool gt15(int x) { return 15 < x; } 9212

9213

int main() { 9214

int a[] = { 10, 20, 30 }; 9215

const size_t SIZE = sizeof a / sizeof a[0]; 9216

ofstream outf("ints.out"); 9217

remove_copy_if(a, a + SIZE, 9218

ostream_iterator<int>(outf, "\n"), gt15); 9219

} ///:~ 9220

Listado 7.9. C06/CopyIntsToFile.cpp 9221

9222

Un iterador de flujo de entrada permite a un algoritmo leer su secuencia de 9223

entrada desde un flujo de entrada. Esto se consigue haciendo que tanto el 9224

constructor como operator++() lean el siguiente elemento del flujo 9225

subyacente y sobrecargando operator*() para conseguir el valor leído 9226

previamente. Dado que los algoritmos requieren dos punteros para delimitar 9227

la secuencia de entrada, puede construir un istream_iterator de dos 9228

formas, como puede ver en el siguiente programa. 9229

//: C06:CopyIntsFromFile.cpp 9230

// Uses an input stream iterator. 9231

#include <algorithm> 9232

#include <fstream> 9233

#include <iostream> 9234

#include <iterator> 9235

#include "../require.h" 9236

using namespace std; 9237

Page 353: Pensar en C++ (Volumen 2)

353

9238

bool gt15(int x) { return 15 < x; } 9239

9240

int main() { 9241

ofstream ints("someInts.dat"); 9242

ints << "1 3 47 5 84 9"; 9243

ints.close(); 9244

ifstream inf("someInts.dat"); 9245

assure(inf, "someInts.dat"); 9246

remove_copy_if(istream_iterator<int>(inf), 9247

istream_iterator<int>(), 9248

ostream_iterator<int>(cout, "\n"), gt15); 9249

} ///:~ 9250

Listado 7.10. C06/CopyIntsFromFile.cpp 9251

9252

El primer argumento de replace_copy_if() en este programa asocia un 9253

objeto istream_iterator al fichero de entrada que contiene enteros. El 9254

segundo argumento usa el constructor por defecto de la clase 9255

istream_iterator. Esta llamada construye un valor especial de 9256

istream_iterator que indica el fin de fichero, de modo que cuando el primer 9257

iterador encuentra el final del fichero físico, se compara con el valor de 9258

istream_iterator<int>(), permitiendo al algoritmo terminar correctamente. 9259

Fíjese que este ejemplo evita usar un array explícito. 9260

7.1.3. Complejidad algorítmica 9261

Usar una librería es una cuestión de confianza. Debe confiar en que los 9262

desarrolladores no solo proporcionan la funcionalidad correcta, sino también 9263

esperar que las funciones se ejecutan tan eficientemente como sea posible. 9264

Page 354: Pensar en C++ (Volumen 2)

354

Es mejor escribir sus propios bucles que usar algoritmos que degradan el 9265

rendimiento. 9266

Para garantizar la calidad de las implementaciones de la librería, la 9267

estándar de C++ no solo especifica lo que debería hacer un algoritmo, 9268

también cómo de rápido debería hacerlo y a veces cuánto espacio debería 9269

usar. Cualquier algoritmo que no cumpla con los requisitos de rendimiento no 9270

es conforma al estándar. La medida de la eficiencia operacional de un 9271

algoritmo se llama complejidad. 9272

Cuando es posible, el estándar especifica el número exacto de 9273

operaciones que un algoritmo debería usar. El algoritmo count_if(), por 9274

ejemplo, retorna el número de elementos de una secuencia que cumplan el 9275

predicado especificado. La siguiente llamada a count_if(), si se aplica a una 9276

secuencia de enteros similar a los ejemplos anteriores de este capítulo, 9277

devuelve el número de elementos mayores que 15: 9278

size_t n = count_if(a, a + SIZE, gt15); 9279

Dado que count_if() debe comprobar cada elemento exactamente una 9280

vez, se especificó hacer un número de comprobaciones que sea 9281

exactamente igual que el número de elementos en la secuencia. El algoritmo 9282

copy() tiene la misma especificación. 9283

Otros algoritmos pueden estar especificados para realizar cierto número 9284

máximo de operaciones. El algoritmo find() busca a través de una 9285

secuencia hasta encontrar un elemento igual a su tercer argumento. 9286

int* p = find(a, a + SIZE, 20); 9287

Para tan pronto como encuentre el elemento y devuelve un puntero a la 9288

primera ocurrencia. Si no encuentra ninguno, retorna un puntero a una 9289

posición pasado el final de la secuencia (a+SIZE en este ejemplo). De modo 9290

que find() realiza como máximo tantas comparaciones como elementos 9291

tenga la secuencia. 9292

Page 355: Pensar en C++ (Volumen 2)

355

A veces el número de operaciones que realiza un algoritmo no se puede 9293

medir con tanta precisión. En esos casos, el estándar especifica la 9294

complejidad asintótica del algoritmo, que es una medida de cómo se 9295

comportará el algoritmo con secuencias largas comparadas con formulas 9296

bien conocidas. Un buen ejemplo es el algoritmo sort(), del que el estándar 9297

dice que requiere «aproximadamente n log n comparaciones de media» (n es 9298

el número de elementos de la secuencia). [FIXME]. Esta medida de 9299

complejidad da una idea del coste de un algoritmo y al menos le da una base 9300

fiable para comparar algoritmos. Como verá en el siguiente capítulo, el 9301

método find() para el contendor set tiene complejidad logarítmica, que 9302

implica que el coste de una búsqueda de un elemento en un set será, para 9303

conjuntos grandes, proporcional al logaritmo del número de elementos. Eso 9304

es mucho menor que el número de elementos para un n grande, de modo 9305

que siempre es mejor buscar en un set utilizando el método en lugar del 9306

algoritmo genérico. 9307

7.2. Objetos-función 9308

//: C06:GreaterThanN.cpp 9309

#include <iostream> 9310

using namespace std; 9311

9312

class gt_n { 9313

int value; 9314

public: 9315

gt_n(int val) : value(val) {} 9316

bool operator()(int n) { return n > value; } 9317

}; 9318

9319

int main() { 9320

Page 356: Pensar en C++ (Volumen 2)

356

gt_n f(4); 9321

cout << f(3) << endl; // Prints 0 (for false) 9322

cout << f(5) << endl; // Prints 1 (for true) 9323

} ///:~ 9324

Listado 7.11. C06/GreaterThanN.cpp 9325

9326

7.2.1. Clasificación de objetos-función 9327

7.2.2. Creación automática de objetos-función 9328

//: C06:CopyInts4.cpp 9329

// Uses a standard function object and adaptor. 9330

#include <algorithm> 9331

#include <cstddef> 9332

#include <functional> 9333

#include <iostream> 9334

#include <iterator> 9335

using namespace std; 9336

9337

int main() { 9338

int a[] = { 10, 20, 30 }; 9339

const size_t SIZE = sizeof a / sizeof a[0]; 9340

remove_copy_if(a, a + SIZE, 9341

ostream_iterator<int>(cout, "\n"), 9342

bind2nd(greater<int>(), 15)); 9343

Page 357: Pensar en C++ (Volumen 2)

357

} ///:~ 9344

Listado 7.12. C06/CopyInts4.cpp 9345

9346

//: C06:CountNotEqual.cpp 9347

// Count elements not equal to 20. 9348

#include <algorithm> 9349

#include <cstddef> 9350

#include <functional> 9351

#include <iostream> 9352

using namespace std; 9353

9354

int main() { 9355

int a[] = { 10, 20, 30 }; 9356

const size_t SIZE = sizeof a / sizeof a[0]; 9357

cout << count_if(a, a + SIZE, 9358

not1(bind1st(equal_to<int>(), 20)));// 2 9359

} ///:~ 9360

Listado 7.13. C06/CountNotEqual.cpp 9361

9362

7.2.3. Objetos-función adaptables 9363

7.2.4. Más ejemplos de objetos-función 9364

//: C06:Generators.h 9365

Page 358: Pensar en C++ (Volumen 2)

358

// Different ways to fill sequences. 9366

#ifndef GENERATORS_H 9367

#define GENERATORS_H 9368

#include <cstring> 9369

#include <set> 9370

#include <cstdlib> 9371

9372

// A generator that can skip over numbers: 9373

class SkipGen { 9374

int i; 9375

int skp; 9376

public: 9377

SkipGen(int start = 0, int skip = 1) 9378

: i(start), skp(skip) {} 9379

int operator()() { 9380

int r = i; 9381

i += skp; 9382

return r; 9383

} 9384

}; 9385

9386

// Generate unique random numbers from 0 to mod: 9387

class URandGen { 9388

std::set<int> used; 9389

int limit; 9390

Page 359: Pensar en C++ (Volumen 2)

359

public: 9391

URandGen(int lim) : limit(lim) {} 9392

int operator()() { 9393

while(true) { 9394

int i = int(std::rand()) % limit; 9395

if(used.find(i) == used.end()) { 9396

used.insert(i); 9397

return i; 9398

} 9399

} 9400

} 9401

}; 9402

9403

// Produces random characters: 9404

class CharGen { 9405

static const char* source; 9406

static const int len; 9407

public: 9408

char operator()() { 9409

return source[std::rand() % len]; 9410

} 9411

}; 9412

#endif // GENERATORS_H ///:~ 9413

Listado 7.14. C06/Generators.h 9414

Page 360: Pensar en C++ (Volumen 2)

360

9415

//: C06:Generators.cpp {O} 9416

#include "Generators.h" 9417

const char* CharGen::source = "ABCDEFGHIJK" 9418

"LMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; 9419

const int CharGen::len = std::strlen(source); 9420

///:~ 9421

Listado 7.15. C06/Generators.cpp 9422

9423

//: C06:FunctionObjects.cpp {-bor} 9424

// Illustrates selected predefined function object 9425

// templates from the Standard C++ library. 9426

//{L} Generators 9427

#include <algorithm> 9428

#include <cstdlib> 9429

#include <ctime> 9430

#include <functional> 9431

#include <iostream> 9432

#include <iterator> 9433

#include <vector> 9434

#include "Generators.h" 9435

#include "PrintSequence.h" 9436

using namespace std; 9437

9438

Page 361: Pensar en C++ (Volumen 2)

361

template<typename Contain, typename UnaryFunc> 9439

void testUnary(Contain& source, Contain& dest, 9440

UnaryFunc f) { 9441

transform(source.begin(), source.end(), dest.begin(), f); 9442

} 9443

9444

template<typename Contain1, typename Contain2, 9445

typename BinaryFunc> 9446

void testBinary(Contain1& src1, Contain1& src2, 9447

Contain2& dest, BinaryFunc f) { 9448

transform(src1.begin(), src1.end(), 9449

src2.begin(), dest.begin(), f); 9450

} 9451

9452

// Executes the expression, then stringizes the 9453

// expression into the print statement: 9454

#define T(EXPR) EXPR; print(r.begin(), r.end(), \ 9455

"After " #EXPR); 9456

// For Boolean tests: 9457

#define B(EXPR) EXPR; print(br.begin(), br.end(), \ 9458

"After " #EXPR); 9459

9460

// Boolean random generator: 9461

struct BRand { 9462

bool operator()() { return rand() % 2 == 0; } 9463

Page 362: Pensar en C++ (Volumen 2)

362

}; 9464

9465

int main() { 9466

const int SZ = 10; 9467

const int MAX = 50; 9468

vector<int> x(SZ), y(SZ), r(SZ); 9469

// An integer random number generator: 9470

URandGen urg(MAX); 9471

srand(time(0)); // Randomize 9472

generate_n(x.begin(), SZ, urg); 9473

generate_n(y.begin(), SZ, urg); 9474

// Add one to each to guarantee nonzero divide: 9475

transform(y.begin(), y.end(), y.begin(), 9476

bind2nd(plus<int>(), 1)); 9477

// Guarantee one pair of elements is ==: 9478

x[0] = y[0]; 9479

print(x.begin(), x.end(), "x"); 9480

print(y.begin(), y.end(), "y"); 9481

// Operate on each element pair of x & y, 9482

// putting the result into r: 9483

T(testBinary(x, y, r, plus<int>())); 9484

T(testBinary(x, y, r, minus<int>())); 9485

T(testBinary(x, y, r, multiplies<int>())); 9486

T(testBinary(x, y, r, divides<int>())); 9487

T(testBinary(x, y, r, modulus<int>())); 9488

Page 363: Pensar en C++ (Volumen 2)

363

T(testUnary(x, r, negate<int>())); 9489

vector<bool> br(SZ); // For Boolean results 9490

B(testBinary(x, y, br, equal_to<int>())); 9491

B(testBinary(x, y, br, not_equal_to<int>())); 9492

B(testBinary(x, y, br, greater<int>())); 9493

B(testBinary(x, y, br, less<int>())); 9494

B(testBinary(x, y, br, greater_equal<int>())); 9495

B(testBinary(x, y, br, less_equal<int>())); 9496

B(testBinary(x, y, br, not2(greater_equal<int>()))); 9497

B(testBinary(x,y,br,not2(less_equal<int>()))); 9498

vector<bool> b1(SZ), b2(SZ); 9499

generate_n(b1.begin(), SZ, BRand()); 9500

generate_n(b2.begin(), SZ, BRand()); 9501

print(b1.begin(), b1.end(), "b1"); 9502

print(b2.begin(), b2.end(), "b2"); 9503

B(testBinary(b1, b2, br, logical_and<int>())); 9504

B(testBinary(b1, b2, br, logical_or<int>())); 9505

B(testUnary(b1, br, logical_not<int>())); 9506

B(testUnary(b1, br, not1(logical_not<int>()))); 9507

} ///:~ 9508

Listado 7.16. C06/FunctionObjects.cpp 9509

9510

//: C06:FBinder.cpp 9511

// Binders aren't limited to producing predicates. 9512

Page 364: Pensar en C++ (Volumen 2)

364

//{L} Generators 9513

#include <algorithm> 9514

#include <cstdlib> 9515

#include <ctime> 9516

#include <functional> 9517

#include <iostream> 9518

#include <iterator> 9519

#include <vector> 9520

#include "Generators.h" 9521

using namespace std; 9522

9523

int main() { 9524

ostream_iterator<int> out(cout," "); 9525

vector<int> v(15); 9526

srand(time(0)); // Randomize 9527

generate(v.begin(), v.end(), URandGen(20)); 9528

copy(v.begin(), v.end(), out); 9529

transform(v.begin(), v.end(), v.begin(), 9530

bind2nd(multiplies<int>(), 10)); 9531

copy(v.begin(), v.end(), out); 9532

} ///:~ 9533

Listado 7.17. C06/FBinder.cpp 9534

9535

//: C06:BinderValue.cpp 9536

Page 365: Pensar en C++ (Volumen 2)

365

// The bound argument can vary. 9537

#include <algorithm> 9538

#include <functional> 9539

#include <iostream> 9540

#include <iterator> 9541

#include <cstdlib> 9542

using namespace std; 9543

9544

int boundedRand() { return rand() % 100; } 9545

9546

int main() { 9547

const int SZ = 20; 9548

int a[SZ], b[SZ] = {0}; 9549

generate(a, a + SZ, boundedRand); 9550

int val = boundedRand(); 9551

int* end = remove_copy_if(a, a + SZ, b, 9552

bind2nd(greater<int>(), val)); 9553

// Sort for easier viewing: 9554

sort(a, a + SZ); 9555

sort(b, end); 9556

ostream_iterator<int> out(cout, " "); 9557

cout << "Original Sequence:" << endl; 9558

copy(a, a + SZ, out); cout << endl; 9559

cout << "Values <= " << val << endl; 9560

copy(b, end, out); cout << endl; 9561

Page 366: Pensar en C++ (Volumen 2)

366

} ///:~ 9562

Listado 7.18. C06/BinderValue.cpp 9563

9564

7.2.5. Adaptadores de puntero a función 9565

//: C06:PtrFun1.cpp 9566

// Using ptr_fun() with a unary function. 9567

#include <algorithm> 9568

#include <cmath> 9569

#include <functional> 9570

#include <iostream> 9571

#include <iterator> 9572

#include <vector> 9573

using namespace std; 9574

9575

int d[] = { 123, 94, 10, 314, 315 }; 9576

const int DSZ = sizeof d / sizeof *d; 9577

9578

bool isEven(int x) { return x % 2 == 0; } 9579

9580

int main() { 9581

vector<bool> vb; 9582

transform(d, d + DSZ, back_inserter(vb), 9583

not1(ptr_fun(isEven))); 9584

Page 367: Pensar en C++ (Volumen 2)

367

copy(vb.begin(), vb.end(), 9585

ostream_iterator<bool>(cout, " ")); 9586

cout << endl; 9587

// Output: 1 0 0 0 1 9588

} ///:~ 9589

Listado 7.19. C06/PtrFun1.cpp 9590

9591

//: C06:PtrFun2.cpp {-edg} 9592

// Using ptr_fun() for a binary function. 9593

#include <algorithm> 9594

#include <cmath> 9595

#include <functional> 9596

#include <iostream> 9597

#include <iterator> 9598

#include <vector> 9599

using namespace std; 9600

9601

double d[] = { 01.23, 91.370, 56.661, 9602

023.230, 19.959, 1.0, 3.14159 }; 9603

const int DSZ = sizeof d / sizeof *d; 9604

9605

int main() { 9606

vector<double> vd; 9607

transform(d, d + DSZ, back_inserter(vd), 9608

Page 368: Pensar en C++ (Volumen 2)

368

bind2nd(ptr_fun<double, double, double>(pow), 2.0)); 9609

copy(vd.begin(), vd.end(), 9610

ostream_iterator<double>(cout, " ")); 9611

cout << endl; 9612

} ///:~ 9613

Listado 7.20. C06/PtrFun2.cpp 9614

9615

//: C06:MemFun1.cpp 9616

// Applying pointers to member functions. 9617

#include <algorithm> 9618

#include <functional> 9619

#include <iostream> 9620

#include <vector> 9621

#include "../purge.h" 9622

using namespace std; 9623

9624

class Shape { 9625

public: 9626

virtual void draw() = 0; 9627

virtual ~Shape() {} 9628

}; 9629

9630

class Circle : public Shape { 9631

public: 9632

Page 369: Pensar en C++ (Volumen 2)

369

virtual void draw() { cout << "Circle::Draw()" << endl; } 9633

~Circle() { cout << "Circle::~Circle()" << endl; } 9634

}; 9635

9636

class Square : public Shape { 9637

public: 9638

virtual void draw() { cout << "Square::Draw()" << endl; } 9639

~Square() { cout << "Square::~Square()" << endl; } 9640

}; 9641

9642

int main() { 9643

vector<Shape*> vs; 9644

vs.push_back(new Circle); 9645

vs.push_back(new Square); 9646

for_each(vs.begin(), vs.end(), mem_fun(&Shape::draw)); 9647

purge(vs); 9648

} ///:~ 9649

Listado 7.21. C06/MemFun1.cpp 9650

9651

//: C06:MemFun2.cpp 9652

// Calling member functions through an object reference. 9653

#include <algorithm> 9654

#include <functional> 9655

#include <iostream> 9656

Page 370: Pensar en C++ (Volumen 2)

370

#include <iterator> 9657

#include <vector> 9658

using namespace std; 9659

9660

class Angle { 9661

int degrees; 9662

public: 9663

Angle(int deg) : degrees(deg) {} 9664

int mul(int times) { return degrees *= times; } 9665

}; 9666

9667

int main() { 9668

vector<Angle> va; 9669

for(int i = 0; i < 50; i += 10) 9670

va.push_back(Angle(i)); 9671

int x[] = { 1, 2, 3, 4, 5 }; 9672

transform(va.begin(), va.end(), x, 9673

ostream_iterator<int>(cout, " "), 9674

mem_fun_ref(&Angle::mul)); 9675

cout << endl; 9676

// Output: 0 20 60 120 200 9677

} ///:~ 9678

Listado 7.22. C06/MemFun2.cpp 9679

9680

Page 371: Pensar en C++ (Volumen 2)

371

//: C06:FindBlanks.cpp 9681

// Demonstrates mem_fun_ref() with string::empty(). 9682

#include <algorithm> 9683

#include <cassert> 9684

#include <cstddef> 9685

#include <fstream> 9686

#include <functional> 9687

#include <string> 9688

#include <vector> 9689

#include "../require.h" 9690

using namespace std; 9691

9692

typedef vector<string>::iterator LSI; 9693

9694

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

char* fname = "FindBlanks.cpp"; 9696

if(argc > 1) fname = argv[1]; 9697

ifstream in(fname); 9698

assure(in, fname); 9699

vector<string> vs; 9700

string s; 9701

while(getline(in, s)) 9702

vs.push_back(s); 9703

vector<string> cpy = vs; // For testing 9704

LSI lsi = find_if(vs.begin(), vs.end(), 9705

Page 372: Pensar en C++ (Volumen 2)

372

mem_fun_ref(&string::empty)); 9706

while(lsi != vs.end()) { 9707

*lsi = "A BLANK LINE"; 9708

lsi = find_if(vs.begin(), vs.end(), 9709

mem_fun_ref(&string::empty)); 9710

} 9711

for(size_t i = 0; i < cpy.size(); i++) 9712

if(cpy[i].size() == 0) 9713

assert(vs[i] == "A BLANK LINE"); 9714

else 9715

assert(vs[i] != "A BLANK LINE"); 9716

} ///:~ 9717

Listado 7.23. C06/FindBlanks.cpp 9718

9719

7.2.6. Escribir sus propios adaptadores de objeto-función 9720

//: C06:NumStringGen.h 9721

// A random number generator that produces 9722

// strings representing floating-point numbers. 9723

#ifndef NUMSTRINGGEN_H 9724

#define NUMSTRINGGEN_H 9725

#include <cstdlib> 9726

#include <string> 9727

9728

Page 373: Pensar en C++ (Volumen 2)

373

class NumStringGen { 9729

const int sz; // Number of digits to make 9730

public: 9731

NumStringGen(int ssz = 5) : sz(ssz) {} 9732

std::string operator()() { 9733

std::string digits("0123456789"); 9734

const int ndigits = digits.size(); 9735

std::string r(sz, ' '); 9736

// Don't want a zero as the first digit 9737

r[0] = digits[std::rand() % (ndigits - 1)] + 1; 9738

// Now assign the rest 9739

for(int i = 1; i < sz; ++i) 9740

if(sz >= 3 && i == sz/2) 9741

r[i] = '.'; // Insert a decimal point 9742

else 9743

r[i] = digits[std::rand() % ndigits]; 9744

return r; 9745

} 9746

}; 9747

#endif // NUMSTRINGGEN_H ///:~ 9748

Listado 7.24. C06/NumStringGen.h 9749

9750

//: C06:MemFun3.cpp 9751

// Using mem_fun(). 9752

Page 374: Pensar en C++ (Volumen 2)

374

#include <algorithm> 9753

#include <cstdlib> 9754

#include <ctime> 9755

#include <functional> 9756

#include <iostream> 9757

#include <iterator> 9758

#include <string> 9759

#include <vector> 9760

#include "NumStringGen.h" 9761

using namespace std; 9762

9763

int main() { 9764

const int SZ = 9; 9765

vector<string> vs(SZ); 9766

// Fill it with random number strings: 9767

srand(time(0)); // Randomize 9768

generate(vs.begin(), vs.end(), NumStringGen()); 9769

copy(vs.begin(), vs.end(), 9770

ostream_iterator<string>(cout, "\t")); 9771

cout << endl; 9772

const char* vcp[SZ]; 9773

transform(vs.begin(), vs.end(), vcp, 9774

mem_fun_ref(&string::c_str)); 9775

vector<double> vd; 9776

transform(vcp, vcp + SZ, back_inserter(vd), 9777

Page 375: Pensar en C++ (Volumen 2)

375

std::atof); 9778

cout.precision(4); 9779

cout.setf(ios::showpoint); 9780

copy(vd.begin(), vd.end(), 9781

ostream_iterator<double>(cout, "\t")); 9782

cout << endl; 9783

} ///:~ 9784

Listado 7.25. C06/MemFun3.cpp 9785

9786

//: C06:ComposeTry.cpp 9787

// A first attempt at implementing function composition. 9788

#include <cassert> 9789

#include <cstdlib> 9790

#include <functional> 9791

#include <iostream> 9792

#include <string> 9793

using namespace std; 9794

9795

template<typename R, typename E, typename F1, typename F2> 9796

class unary_composer { 9797

F1 f1; 9798

F2 f2; 9799

public: 9800

unary_composer(F1 fone, F2 ftwo) : f1(fone), f2(ftwo) {} 9801

Page 376: Pensar en C++ (Volumen 2)

376

R operator()(E x) { return f1(f2(x)); } 9802

}; 9803

9804

template<typename R, typename E, typename F1, typename F2> 9805

unary_composer<R, E, F1, F2> compose(F1 f1, F2 f2) { 9806

return unary_composer<R, E, F1, F2>(f1, f2); 9807

} 9808

9809

int main() { 9810

double x = compose<double, const string&>( 9811

atof, mem_fun_ref(&string::c_str))("12.34"); 9812

assert(x == 12.34); 9813

} ///:~ 9814

Listado 7.26. C06/ComposeTry.cpp 9815

9816

//: C06:ComposeFinal.cpp {-edg} 9817

// An adaptable composer. 9818

#include <algorithm> 9819

#include <cassert> 9820

#include <cstdlib> 9821

#include <functional> 9822

#include <iostream> 9823

#include <iterator> 9824

#include <string> 9825

Page 377: Pensar en C++ (Volumen 2)

377

#include <vector> 9826

#include "NumStringGen.h" 9827

using namespace std; 9828

9829

template<typename F1, typename F2> class unary_composer 9830

: public unary_function<typename F2::argument_type, 9831

typename F1::result_type> { 9832

F1 f1; 9833

F2 f2; 9834

public: 9835

unary_composer(F1 f1, F2 f2) : f1(f1), f2(f2) {} 9836

typename F1::result_type 9837

operator()(typename F2::argument_type x) { 9838

return f1(f2(x)); 9839

} 9840

}; 9841

9842

template<typename F1, typename F2> 9843

unary_composer<F1, F2> compose(F1 f1, F2 f2) { 9844

return unary_composer<F1, F2>(f1, f2); 9845

} 9846

9847

int main() { 9848

const int SZ = 9; 9849

vector<string> vs(SZ); 9850

Page 378: Pensar en C++ (Volumen 2)

378

// Fill it with random number strings: 9851

generate(vs.begin(), vs.end(), NumStringGen()); 9852

copy(vs.begin(), vs.end(), 9853

ostream_iterator<string>(cout, "\t")); 9854

cout << endl; 9855

vector<double> vd; 9856

transform(vs.begin(), vs.end(), back_inserter(vd), 9857

compose(ptr_fun(atof), mem_fun_ref(&string::c_str))); 9858

copy(vd.begin(), vd.end(), 9859

ostream_iterator<double>(cout, "\t")); 9860

cout << endl; 9861

} ///:~ 9862

Listado 7.27. C06/ComposeFinal.cpp 9863

9864

7.3. Un catálogo de algoritmos STL 9865

7.3.1. Herramientas de soporte para la creación de ejemplos 9866

//: C06:PrintSequence.h 9867

// Prints the contents of any sequence. 9868

#ifndef PRINTSEQUENCE_H 9869

#define PRINTSEQUENCE_H 9870

#include <algorithm> 9871

#include <iostream> 9872

#include <iterator> 9873

Page 379: Pensar en C++ (Volumen 2)

379

9874

template<typename Iter> 9875

void print(Iter first, Iter last, const char* nm = "", 9876

const char* sep = "\n", 9877

std::ostream& os = std::cout) { 9878

if(nm != 0 && *nm != '\0') 9879

os << nm << ": " << sep; 9880

typedef typename 9881

std::iterator_traits<Iter>::value_type T; 9882

std::copy(first, last, 9883

std::ostream_iterator<T>(std::cout, sep)); 9884

os << std::endl; 9885

} 9886

#endif // PRINTSEQUENCE_H ///:~ 9887

Listado 7.28. C06/PrintSequence.h 9888

9889

Reordenación estable vs. inestable 9890

//: C06:NString.h 9891

// A "numbered string" that keeps track of the 9892

// number of occurrences of the word it contains. 9893

#ifndef NSTRING_H 9894

#define NSTRING_H 9895

#include <algorithm> 9896

#include <iostream> 9897

Page 380: Pensar en C++ (Volumen 2)

380

#include <string> 9898

#include <utility> 9899

#include <vector> 9900

9901

typedef std::pair<std::string, int> psi; 9902

9903

// Only compare on the first element 9904

bool operator==(const psi& l, const psi& r) { 9905

return l.first == r.first; 9906

} 9907

9908

class NString { 9909

std::string s; 9910

int thisOccurrence; 9911

// Keep track of the number of occurrences: 9912

typedef std::vector<psi> vp; 9913

typedef vp::iterator vpit; 9914

static vp words; 9915

void addString(const std::string& x) { 9916

psi p(x, 0); 9917

vpit it = std::find(words.begin(), words.end(), p); 9918

if(it != words.end()) 9919

thisOccurrence = ++it->second; 9920

else { 9921

thisOccurrence = 0; 9922

Page 381: Pensar en C++ (Volumen 2)

381

words.push_back(p); 9923

} 9924

} 9925

public: 9926

NString() : thisOccurrence(0) {} 9927

NString(const std::string& x) : s(x) { addString(x); } 9928

NString(const char* x) : s(x) { addString(x); } 9929

// Implicit operator= and copy-constructor are OK here. 9930

friend std::ostream& operator<<( 9931

std::ostream& os, const NString& ns) { 9932

return os << ns.s << " [" << ns.thisOccurrence << "]"; 9933

} 9934

// Need this for sorting. Notice it only 9935

// compares strings, not occurrences: 9936

friend bool 9937

operator<(const NString& l, const NString& r) { 9938

return l.s < r.s; 9939

} 9940

friend 9941

bool operator==(const NString& l, const NString& r) { 9942

return l.s == r.s; 9943

} 9944

// For sorting with greater<NString>: 9945

friend bool 9946

operator>(const NString& l, const NString& r) { 9947

Page 382: Pensar en C++ (Volumen 2)

382

return l.s > r.s; 9948

} 9949

// To get at the string directly: 9950

operator const std::string&() const { return s; } 9951

}; 9952

9953

// Because NString::vp is a template and we are using the 9954

// inclusion model, it must be defined in this header file: 9955

NString::vp NString::words; 9956

#endif // NSTRING_H ///:~ 9957

Listado 7.29. C06/NString.h 9958

9959

7.3.2. Relleno y generación 9960

Ejemplo 9961

//: C06:FillGenerateTest.cpp 9962

// Demonstrates "fill" and "generate." 9963

//{L} Generators 9964

#include <vector> 9965

#include <algorithm> 9966

#include <string> 9967

#include "Generators.h" 9968

#include "PrintSequence.h" 9969

using namespace std; 9970

Page 383: Pensar en C++ (Volumen 2)

383

9971

int main() { 9972

vector<string> v1(5); 9973

fill(v1.begin(), v1.end(), "howdy"); 9974

print(v1.begin(), v1.end(), "v1", " "); 9975

vector<string> v2; 9976

fill_n(back_inserter(v2), 7, "bye"); 9977

print(v2.begin(), v2.end(), "v2"); 9978

vector<int> v3(10); 9979

generate(v3.begin(), v3.end(), SkipGen(4,5)); 9980

print(v3.begin(), v3.end(), "v3", " "); 9981

vector<int> v4; 9982

generate_n(back_inserter(v4),15, URandGen(30)); 9983

print(v4.begin(), v4.end(), "v4", " "); 9984

} ///:~ 9985

Listado 7.30. C06/FillGenerateTest.cpp 9986

9987

7.3.3. Conteo 9988

Ejemplo 9989

//: C06:Counting.cpp 9990

// The counting algorithms. 9991

//{L} Generators 9992

#include <algorithm> 9993

Page 384: Pensar en C++ (Volumen 2)

384

#include <functional> 9994

#include <iterator> 9995

#include <set> 9996

#include <vector> 9997

#include "Generators.h" 9998

#include "PrintSequence.h" 9999

using namespace std; 10000

10001

int main() { 10002

vector<char> v; 10003

generate_n(back_inserter(v), 50, CharGen()); 10004

print(v.begin(), v.end(), "v", ""); 10005

// Create a set of the characters in v: 10006

set<char> cs(v.begin(), v.end()); 10007

typedef set<char>::iterator sci; 10008

for(sci it = cs.begin(); it != cs.end(); it++) { 10009

int n = count(v.begin(), v.end(), *it); 10010

cout << *it << ": " << n << ", "; 10011

} 10012

int lc = count_if(v.begin(), v.end(), 10013

bind2nd(greater<char>(), 'a')); 10014

cout << "\nLowercase letters: " << lc << endl; 10015

sort(v.begin(), v.end()); 10016

print(v.begin(), v.end(), "sorted", ""); 10017

} ///:~ 10018

Page 385: Pensar en C++ (Volumen 2)

385

Listado 7.31. C06/Counting.cpp 10019

10020

7.3.4. Manipulación de secuencias 10021

Ejemplo 10022

//: C06:Manipulations.cpp 10023

// Shows basic manipulations. 10024

//{L} Generators 10025

// NString 10026

#include <vector> 10027

#include <string> 10028

#include <algorithm> 10029

#include "PrintSequence.h" 10030

#include "NString.h" 10031

#include "Generators.h" 10032

using namespace std; 10033

10034

int main() { 10035

vector<int> v1(10); 10036

// Simple counting: 10037

generate(v1.begin(), v1.end(), SkipGen()); 10038

print(v1.begin(), v1.end(), "v1", " "); 10039

vector<int> v2(v1.size()); 10040

copy_backward(v1.begin(), v1.end(), v2.end()); 10041

print(v2.begin(), v2.end(), "copy_backward", " "); 10042

reverse_copy(v1.begin(), v1.end(), v2.begin()); 10043

Page 386: Pensar en C++ (Volumen 2)

386

print(v2.begin(), v2.end(), "reverse_copy", " "); 10044

reverse(v1.begin(), v1.end()); 10045

print(v1.begin(), v1.end(), "reverse", " "); 10046

int half = v1.size() / 2; 10047

// Ranges must be exactly the same size: 10048

swap_ranges(v1.begin(), v1.begin() + half, 10049

v1.begin() + half); 10050

print(v1.begin(), v1.end(), "swap_ranges", " "); 10051

// Start with a fresh sequence: 10052

generate(v1.begin(), v1.end(), SkipGen()); 10053

print(v1.begin(), v1.end(), "v1", " "); 10054

int third = v1.size() / 3; 10055

for(int i = 0; i < 10; i++) { 10056

rotate(v1.begin(), v1.begin() + third, v1.end()); 10057

print(v1.begin(), v1.end(), "rotate", " "); 10058

} 10059

cout << "Second rotate example:" << endl; 10060

char c[] = "aabbccddeeffgghhiijj"; 10061

const char CSZ = strlen(c); 10062

for(int i = 0; i < 10; i++) { 10063

rotate(c, c + 2, c + CSZ); 10064

print(c, c + CSZ, "", ""); 10065

} 10066

cout << "All n! permutations of abcd:" << endl; 10067

int nf = 4 * 3 * 2 * 1; 10068

Page 387: Pensar en C++ (Volumen 2)

387

char p[] = "abcd"; 10069

for(int i = 0; i < nf; i++) { 10070

next_permutation(p, p + 4); 10071

print(p, p + 4, "", ""); 10072

} 10073

cout << "Using prev_permutation:" << endl; 10074

for(int i = 0; i < nf; i++) { 10075

prev_permutation(p, p + 4); 10076

print(p, p + 4, "", ""); 10077

} 10078

cout << "random_shuffling a word:" << endl; 10079

string s("hello"); 10080

cout << s << endl; 10081

for(int i = 0; i < 5; i++) { 10082

random_shuffle(s.begin(), s.end()); 10083

cout << s << endl; 10084

} 10085

NString sa[] = { "a", "b", "c", "d", "a", "b", 10086

"c", "d", "a", "b", "c", "d", "a", "b", "c"}; 10087

const int SASZ = sizeof sa / sizeof *sa; 10088

vector<NString> ns(sa, sa + SASZ); 10089

print(ns.begin(), ns.end(), "ns", " "); 10090

vector<NString>::iterator it = 10091

partition(ns.begin(), ns.end(), 10092

bind2nd(greater<NString>(), "b")); 10093

Page 388: Pensar en C++ (Volumen 2)

388

cout << "Partition point: " << *it << endl; 10094

print(ns.begin(), ns.end(), "", " "); 10095

// Reload vector: 10096

copy(sa, sa + SASZ, ns.begin()); 10097

it = stable_partition(ns.begin(), ns.end(), 10098

bind2nd(greater<NString>(), "b")); 10099

cout << "Stable partition" << endl; 10100

cout << "Partition point: " << *it << endl; 10101

print(ns.begin(), ns.end(), "", " "); 10102

} ///:~ 10103

Listado 7.32. C06/Manipulations.cpp 10104

10105

7.3.5. Búsqueda y reemplazo 10106

Ejemplo 10107

//: C06:SearchReplace.cpp 10108

// The STL search and replace algorithms. 10109

#include <algorithm> 10110

#include <functional> 10111

#include <vector> 10112

#include "PrintSequence.h" 10113

using namespace std; 10114

10115

struct PlusOne { 10116

Page 389: Pensar en C++ (Volumen 2)

389

bool operator()(int i, int j) { return j == i + 1; } 10117

}; 10118

10119

class MulMoreThan { 10120

int value; 10121

public: 10122

MulMoreThan(int val) : value(val) {} 10123

bool operator()(int v, int m) { return v * m > value; } 10124

}; 10125

10126

int main() { 10127

int a[] = { 1, 2, 3, 4, 5, 6, 6, 7, 7, 7, 10128

8, 8, 8, 8, 11, 11, 11, 11, 11 }; 10129

const int ASZ = sizeof a / sizeof *a; 10130

vector<int> v(a, a + ASZ); 10131

print(v.begin(), v.end(), "v", " "); 10132

vector<int>::iterator it = find(v.begin(), v.end(), 4); 10133

cout << "find: " << *it << endl; 10134

it = find_if(v.begin(), v.end(), 10135

bind2nd(greater<int>(), 8)); 10136

cout << "find_if: " << *it << endl; 10137

it = adjacent_find(v.begin(), v.end()); 10138

while(it != v.end()) { 10139

cout << "adjacent_find: " << *it 10140

<< ", " << *(it + 1) << endl; 10141

Page 390: Pensar en C++ (Volumen 2)

390

it = adjacent_find(it + 1, v.end()); 10142

} 10143

it = adjacent_find(v.begin(), v.end(), PlusOne()); 10144

while(it != v.end()) { 10145

cout << "adjacent_find PlusOne: " << *it 10146

<< ", " << *(it + 1) << endl; 10147

it = adjacent_find(it + 1, v.end(), PlusOne()); 10148

} 10149

int b[] = { 8, 11 }; 10150

const int BSZ = sizeof b / sizeof *b; 10151

print(b, b + BSZ, "b", " "); 10152

it = find_first_of(v.begin(), v.end(), b, b + BSZ); 10153

print(it, it + BSZ, "find_first_of", " "); 10154

it = find_first_of(v.begin(), v.end(), 10155

b, b + BSZ, PlusOne()); 10156

print(it,it + BSZ,"find_first_of PlusOne"," "); 10157

it = search(v.begin(), v.end(), b, b + BSZ); 10158

print(it, it + BSZ, "search", " "); 10159

int c[] = { 5, 6, 7 }; 10160

const int CSZ = sizeof c / sizeof *c; 10161

print(c, c + CSZ, "c", " "); 10162

it = search(v.begin(), v.end(), c, c + CSZ, PlusOne()); 10163

print(it, it + CSZ,"search PlusOne", " "); 10164

int d[] = { 11, 11, 11 }; 10165

const int DSZ = sizeof d / sizeof *d; 10166

Page 391: Pensar en C++ (Volumen 2)

391

print(d, d + DSZ, "d", " "); 10167

it = find_end(v.begin(), v.end(), d, d + DSZ); 10168

print(it, v.end(),"find_end", " "); 10169

int e[] = { 9, 9 }; 10170

print(e, e + 2, "e", " "); 10171

it = find_end(v.begin(), v.end(), e, e + 2, PlusOne()); 10172

print(it, v.end(),"find_end PlusOne"," "); 10173

it = search_n(v.begin(), v.end(), 3, 7); 10174

print(it, it + 3, "search_n 3, 7", " "); 10175

it = search_n(v.begin(), v.end(), 10176

6, 15, MulMoreThan(100)); 10177

print(it, it + 6, 10178

"search_n 6, 15, MulMoreThan(100)", " "); 10179

cout << "min_element: " 10180

<< *min_element(v.begin(), v.end()) << endl; 10181

cout << "max_element: " 10182

<< *max_element(v.begin(), v.end()) << endl; 10183

vector<int> v2; 10184

replace_copy(v.begin(), v.end(), 10185

back_inserter(v2), 8, 47); 10186

print(v2.begin(), v2.end(), "replace_copy 8 -> 47", " "); 10187

replace_if(v.begin(), v.end(), 10188

bind2nd(greater_equal<int>(), 7), -1); 10189

print(v.begin(), v.end(), "replace_if >= 7 -> -1", " "); 10190

} ///:~ 10191

Page 392: Pensar en C++ (Volumen 2)

392

Listado 7.33. C06/SearchReplace.cpp 10192

10193

7.3.6. Comparación de rangos 10194

Ejemplo 10195

//: C06:Comparison.cpp 10196

// The STL range comparison algorithms. 10197

#include <algorithm> 10198

#include <functional> 10199

#include <string> 10200

#include <vector> 10201

#include "PrintSequence.h" 10202

using namespace std; 10203

10204

int main() { 10205

// Strings provide a convenient way to create 10206

// ranges of characters, but you should 10207

// normally look for native string operations: 10208

string s1("This is a test"); 10209

string s2("This is a Test"); 10210

cout << "s1: " << s1 << endl << "s2: " << s2 << endl; 10211

cout << "compare s1 & s1: " 10212

<< equal(s1.begin(), s1.end(), s1.begin()) << endl; 10213

cout << "compare s1 & s2: " 10214

<< equal(s1.begin(), s1.end(), s2.begin()) << endl; 10215

cout << "lexicographical_compare s1 & s1: " 10216

Page 393: Pensar en C++ (Volumen 2)

393

<< lexicographical_compare(s1.begin(), s1.end(), 10217

s1.begin(), s1.end()) << endl; 10218

cout << "lexicographical_compare s1 & s2: " 10219

<< lexicographical_compare(s1.begin(), s1.end(), 10220

s2.begin(), s2.end()) << endl; 10221

cout << "lexicographical_compare s2 & s1: " 10222

<< lexicographical_compare(s2.begin(), s2.end(), 10223

s1.begin(), s1.end()) << endl; 10224

cout << "lexicographical_compare shortened " 10225

"s1 & full-length s2: " << endl; 10226

string s3(s1); 10227

while(s3.length() != 0) { 10228

bool result = lexicographical_compare( 10229

s3.begin(), s3.end(), s2.begin(),s2.end()); 10230

cout << s3 << endl << s2 << ", result = " 10231

<< result << endl; 10232

if(result == true) break; 10233

s3 = s3.substr(0, s3.length() - 1); 10234

} 10235

pair<string::iterator, string::iterator> p = 10236

mismatch(s1.begin(), s1.end(), s2.begin()); 10237

print(p.first, s1.end(), "p.first", ""); 10238

print(p.second, s2.end(), "p.second",""); 10239

} ///:~ 10240

Page 394: Pensar en C++ (Volumen 2)

394

Listado 7.34. C06/Comparison.cpp 10241

10242

7.3.7. Eliminación de elementos 10243

Ejemplo 10244

//: C06:Removing.cpp 10245

// The removing algorithms. 10246

//{L} Generators 10247

#include <algorithm> 10248

#include <cctype> 10249

#include <string> 10250

#include "Generators.h" 10251

#include "PrintSequence.h" 10252

using namespace std; 10253

10254

struct IsUpper { 10255

bool operator()(char c) { return isupper(c); } 10256

}; 10257

10258

int main() { 10259

string v; 10260

v.resize(25); 10261

generate(v.begin(), v.end(), CharGen()); 10262

print(v.begin(), v.end(), "v original", ""); 10263

// Create a set of the characters in v: 10264

string us(v.begin(), v.end()); 10265

Page 395: Pensar en C++ (Volumen 2)

395

sort(us.begin(), us.end()); 10266

string::iterator it = us.begin(), cit = v.end(), 10267

uend = unique(us.begin(), us.end()); 10268

// Step through and remove everything: 10269

while(it != uend) { 10270

cit = remove(v.begin(), cit, *it); 10271

print(v.begin(), v.end(), "Complete v", ""); 10272

print(v.begin(), cit, "Pseudo v ", " "); 10273

cout << "Removed element:\t" << *it 10274

<< "\nPsuedo Last Element:\t" 10275

<< *cit << endl << endl; 10276

++it; 10277

} 10278

generate(v.begin(), v.end(), CharGen()); 10279

print(v.begin(), v.end(), "v", ""); 10280

cit = remove_if(v.begin(), v.end(), IsUpper()); 10281

print(v.begin(), cit, "v after remove_if IsUpper", " "); 10282

// Copying versions are not shown for remove() 10283

// and remove_if(). 10284

sort(v.begin(), cit); 10285

print(v.begin(), cit, "sorted", " "); 10286

string v2; 10287

v2.resize(cit - v.begin()); 10288

unique_copy(v.begin(), cit, v2.begin()); 10289

print(v2.begin(), v2.end(), "unique_copy", " "); 10290

Page 396: Pensar en C++ (Volumen 2)

396

// Same behavior: 10291

cit = unique(v.begin(), cit, equal_to<char>()); 10292

print(v.begin(), cit, "unique equal_to<char>", " "); 10293

} ///:~ 10294

Listado 7.35. C06/Removing.cpp 10295

10296

7.3.8. Ordenación y operación sobre rangos ordenados 10297

Ordenación 10298

Ejemplo 10299

//: C06:SortedSearchTest.cpp 10300

// Test searching in sorted ranges. 10301

// NString 10302

#include <algorithm> 10303

#include <cassert> 10304

#include <ctime> 10305

#include <cstdlib> 10306

#include <cstddef> 10307

#include <fstream> 10308

#include <iostream> 10309

#include <iterator> 10310

#include <vector> 10311

#include "NString.h" 10312

#include "PrintSequence.h" 10313

Page 397: Pensar en C++ (Volumen 2)

397

#include "../require.h" 10314

using namespace std; 10315

10316

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

typedef vector<NString>::iterator sit; 10318

char* fname = "Test.txt"; 10319

if(argc > 1) fname = argv[1]; 10320

ifstream in(fname); 10321

assure(in, fname); 10322

srand(time(0)); 10323

cout.setf(ios::boolalpha); 10324

vector<NString> original; 10325

copy(istream_iterator<string>(in), 10326

istream_iterator<string>(), back_inserter(original)); 10327

require(original.size() >= 4, "Must have four elements"); 10328

vector<NString> v(original.begin(), original.end()), 10329

w(original.size() / 2); 10330

sort(v.begin(), v.end()); 10331

print(v.begin(), v.end(), "sort"); 10332

v = original; 10333

stable_sort(v.begin(), v.end()); 10334

print(v.begin(), v.end(), "stable_sort"); 10335

v = original; 10336

sit it = v.begin(), it2; 10337

// Move iterator to middle 10338

Page 398: Pensar en C++ (Volumen 2)

398

for(size_t i = 0; i < v.size() / 2; i++) 10339

++it; 10340

partial_sort(v.begin(), it, v.end()); 10341

cout << "middle = " << *it << endl; 10342

print(v.begin(), v.end(), "partial_sort"); 10343

v = original; 10344

// Move iterator to a quarter position 10345

it = v.begin(); 10346

for(size_t i = 0; i < v.size() / 4; i++) 10347

++it; 10348

// Less elements to copy from than to the destination 10349

partial_sort_copy(v.begin(), it, w.begin(), w.end()); 10350

print(w.begin(), w.end(), "partial_sort_copy"); 10351

// Not enough room in destination 10352

partial_sort_copy(v.begin(), v.end(), w.begin(),w.end()); 10353

print(w.begin(), w.end(), "w partial_sort_copy"); 10354

// v remains the same through all this process 10355

assert(v == original); 10356

nth_element(v.begin(), it, v.end()); 10357

cout << "The nth_element = " << *it << endl; 10358

print(v.begin(), v.end(), "nth_element"); 10359

string f = original[rand() % original.size()]; 10360

cout << "binary search: " 10361

<< binary_search(v.begin(), v.end(), f) << endl; 10362

sort(v.begin(), v.end()); 10363

Page 399: Pensar en C++ (Volumen 2)

399

it = lower_bound(v.begin(), v.end(), f); 10364

it2 = upper_bound(v.begin(), v.end(), f); 10365

print(it, it2, "found range"); 10366

pair<sit, sit> ip = equal_range(v.begin(), v.end(), f); 10367

print(ip.first, ip.second, "equal_range"); 10368

} ///:~ 10369

Listado 7.36. C06/SortedSearchTest.cpp 10370

10371

Mezcla de rangos ordenados 10372

Ejemplo 10373

//: C06:MergeTest.cpp 10374

// Test merging in sorted ranges. 10375

//{L} Generators 10376

#include <algorithm> 10377

#include "PrintSequence.h" 10378

#include "Generators.h" 10379

using namespace std; 10380

10381

int main() { 10382

const int SZ = 15; 10383

int a[SZ*2] = {0}; 10384

// Both ranges go in the same array: 10385

generate(a, a + SZ, SkipGen(0, 2)); 10386

Page 400: Pensar en C++ (Volumen 2)

400

a[3] = 4; 10387

a[4] = 4; 10388

generate(a + SZ, a + SZ*2, SkipGen(1, 3)); 10389

print(a, a + SZ, "range1", " "); 10390

print(a + SZ, a + SZ*2, "range2", " "); 10391

int b[SZ*2] = {0}; // Initialize all to zero 10392

merge(a, a + SZ, a + SZ, a + SZ*2, b); 10393

print(b, b + SZ*2, "merge", " "); 10394

// Reset b 10395

for(int i = 0; i < SZ*2; i++) 10396

b[i] = 0; 10397

inplace_merge(a, a + SZ, a + SZ*2); 10398

print(a, a + SZ*2, "inplace_merge", " "); 10399

int* end = set_union(a, a + SZ, a + SZ, a + SZ*2, b); 10400

print(b, end, "set_union", " "); 10401

} ///:~ 10402

Listado 7.37. C06/MergeTest.cpp 10403

10404

Ejemplo 10405

//: C06:SetOperations.cpp 10406

// Set operations on sorted ranges. 10407

//{L} Generators 10408

#include <algorithm> 10409

Page 401: Pensar en C++ (Volumen 2)

401

#include <vector> 10410

#include "Generators.h" 10411

#include "PrintSequence.h" 10412

using namespace std; 10413

10414

int main() { 10415

const int SZ = 30; 10416

char v[SZ + 1], v2[SZ + 1]; 10417

CharGen g; 10418

generate(v, v + SZ, g); 10419

generate(v2, v2 + SZ, g); 10420

sort(v, v + SZ); 10421

sort(v2, v2 + SZ); 10422

print(v, v + SZ, "v", ""); 10423

print(v2, v2 + SZ, "v2", ""); 10424

bool b = includes(v, v + SZ, v + SZ/2, v + SZ); 10425

cout.setf(ios::boolalpha); 10426

cout << "includes: " << b << endl; 10427

char v3[SZ*2 + 1], *end; 10428

end = set_union(v, v + SZ, v2, v2 + SZ, v3); 10429

print(v3, end, "set_union", ""); 10430

end = set_intersection(v, v + SZ, v2, v2 + SZ, v3); 10431

print(v3, end, "set_intersection", ""); 10432

end = set_difference(v, v + SZ, v2, v2 + SZ, v3); 10433

print(v3, end, "set_difference", ""); 10434

Page 402: Pensar en C++ (Volumen 2)

402

end = set_symmetric_difference(v, v + SZ, 10435

v2, v2 + SZ, v3); 10436

print(v3, end, "set_symmetric_difference",""); 10437

} ///:~ 10438

Listado 7.38. C06/SetOperations.cpp 10439

10440

7.3.9. Operaciones sobre el montículo 10441

7.3.10. Aplicando una operación a cada elemento de un rango 10442

Ejemplos 10443

//: C06:Counted.h 10444

// An object that keeps track of itself. 10445

#ifndef COUNTED_H 10446

#define COUNTED_H 10447

#include <vector> 10448

#include <iostream> 10449

10450

class Counted { 10451

static int count; 10452

char* ident; 10453

public: 10454

Counted(char* id) : ident(id) { ++count; } 10455

~Counted() { 10456

std::cout << ident << " count = " 10457

Page 403: Pensar en C++ (Volumen 2)

403

<< --count << std::endl; 10458

} 10459

}; 10460

10461

class CountedVector : public std::vector<Counted*> { 10462

public: 10463

CountedVector(char* id) { 10464

for(int i = 0; i < 5; i++) 10465

push_back(new Counted(id)); 10466

} 10467

}; 10468

#endif // COUNTED_H ///:~ 10469

Listado 7.39. C06/Counted.h 10470

10471

//: C06:ForEach.cpp {-mwcc} 10472

// Use of STL for_each() algorithm. 10473

//{L} Counted 10474

#include <algorithm> 10475

#include <iostream> 10476

#include "Counted.h" 10477

using namespace std; 10478

10479

// Function object: 10480

template<class T> class DeleteT { 10481

Page 404: Pensar en C++ (Volumen 2)

404

public: 10482

void operator()(T* x) { delete x; } 10483

}; 10484

10485

// Template function: 10486

template<class T> void wipe(T* x) { delete x; } 10487

10488

int main() { 10489

CountedVector B("two"); 10490

for_each(B.begin(), B.end(), DeleteT<Counted>()); 10491

CountedVector C("three"); 10492

for_each(C.begin(), C.end(), wipe<Counted>); 10493

} ///:~ 10494

Listado 7.40. C06/ForEach.cpp 10495

10496

//: C06:Transform.cpp {-mwcc} 10497

// Use of STL transform() algorithm. 10498

//{L} Counted 10499

#include <iostream> 10500

#include <vector> 10501

#include <algorithm> 10502

#include "Counted.h" 10503

using namespace std; 10504

10505

Page 405: Pensar en C++ (Volumen 2)

405

template<class T> T* deleteP(T* x) { delete x; return 0; } 10506

10507

template<class T> struct Deleter { 10508

T* operator()(T* x) { delete x; return 0; } 10509

}; 10510

10511

int main() { 10512

CountedVector cv("one"); 10513

transform(cv.begin(), cv.end(), cv.begin(), 10514

deleteP<Counted>); 10515

CountedVector cv2("two"); 10516

transform(cv2.begin(), cv2.end(), cv2.begin(), 10517

Deleter<Counted>()); 10518

} ///:~ 10519

Listado 7.41. C06/Transform.cpp 10520

10521

//: C06:Inventory.h 10522

#ifndef INVENTORY_H 10523

#define INVENTORY_H 10524

#include <iostream> 10525

#include <cstdlib> 10526

using std::rand; 10527

10528

class Inventory { 10529

Page 406: Pensar en C++ (Volumen 2)

406

char item; 10530

int quantity; 10531

int value; 10532

public: 10533

Inventory(char it, int quant, int val) 10534

: item(it), quantity(quant), value(val) {} 10535

// Synthesized operator= & copy-constructor OK 10536

char getItem() const { return item; } 10537

int getQuantity() const { return quantity; } 10538

void setQuantity(int q) { quantity = q; } 10539

int getValue() const { return value; } 10540

void setValue(int val) { value = val; } 10541

friend std::ostream& operator<<( 10542

std::ostream& os, const Inventory& inv) { 10543

return os << inv.item << ": " 10544

<< "quantity " << inv.quantity 10545

<< ", value " << inv.value; 10546

} 10547

}; 10548

10549

// A generator: 10550

struct InvenGen { 10551

Inventory operator()() { 10552

static char c = 'a'; 10553

int q = rand() % 100; 10554

Page 407: Pensar en C++ (Volumen 2)

407

int v = rand() % 500; 10555

return Inventory(c++, q, v); 10556

} 10557

}; 10558

#endif // INVENTORY_H ///:~ 10559

Listado 7.42. C06/Inventory.h 10560

10561

//: C06:CalcInventory.cpp 10562

// More use of for_each(). 10563

#include <algorithm> 10564

#include <ctime> 10565

#include <vector> 10566

#include "Inventory.h" 10567

#include "PrintSequence.h" 10568

using namespace std; 10569

10570

// To calculate inventory totals: 10571

class InvAccum { 10572

int quantity; 10573

int value; 10574

public: 10575

InvAccum() : quantity(0), value(0) {} 10576

void operator()(const Inventory& inv) { 10577

quantity += inv.getQuantity(); 10578

Page 408: Pensar en C++ (Volumen 2)

408

value += inv.getQuantity() * inv.getValue(); 10579

} 10580

friend ostream& 10581

operator<<(ostream& os, const InvAccum& ia) { 10582

return os << "total quantity: " << ia.quantity 10583

<< ", total value: " << ia.value; 10584

} 10585

}; 10586

10587

int main() { 10588

vector<Inventory> vi; 10589

srand(time(0)); // Randomize 10590

generate_n(back_inserter(vi), 15, InvenGen()); 10591

print(vi.begin(), vi.end(), "vi"); 10592

InvAccum ia = for_each(vi.begin(),vi.end(), InvAccum()); 10593

cout << ia << endl; 10594

} ///:~ 10595

Listado 7.43. C06/CalcInventory.cpp 10596

10597

//: C06:TransformNames.cpp 10598

// More use of transform(). 10599

#include <algorithm> 10600

#include <cctype> 10601

#include <ctime> 10602

Page 409: Pensar en C++ (Volumen 2)

409

#include <vector> 10603

#include "Inventory.h" 10604

#include "PrintSequence.h" 10605

using namespace std; 10606

10607

struct NewImproved { 10608

Inventory operator()(const Inventory& inv) { 10609

return Inventory(toupper(inv.getItem()), 10610

inv.getQuantity(), inv.getValue()); 10611

} 10612

}; 10613

10614

int main() { 10615

vector<Inventory> vi; 10616

srand(time(0)); // Randomize 10617

generate_n(back_inserter(vi), 15, InvenGen()); 10618

print(vi.begin(), vi.end(), "vi"); 10619

transform(vi.begin(),vi.end(),vi.begin(),NewImproved()); 10620

print(vi.begin(), vi.end(), "vi"); 10621

} ///:~ 10622

Listado 7.44. C06/TransformNames.cpp 10623

10624

//: C06:SpecialList.cpp 10625

// Using the second version of transform(). 10626

Page 410: Pensar en C++ (Volumen 2)

410

#include <algorithm> 10627

#include <ctime> 10628

#include <vector> 10629

#include "Inventory.h" 10630

#include "PrintSequence.h" 10631

using namespace std; 10632

10633

struct Discounter { 10634

Inventory operator()(const Inventory& inv, 10635

float discount) { 10636

return Inventory(inv.getItem(), inv.getQuantity(), 10637

int(inv.getValue() * (1 - discount))); 10638

} 10639

}; 10640

10641

struct DiscGen { 10642

float operator()() { 10643

float r = float(rand() % 10); 10644

return r / 100.0; 10645

} 10646

}; 10647

10648

int main() { 10649

vector<Inventory> vi; 10650

srand(time(0)); // Randomize 10651

Page 411: Pensar en C++ (Volumen 2)

411

generate_n(back_inserter(vi), 15, InvenGen()); 10652

print(vi.begin(), vi.end(), "vi"); 10653

vector<float> disc; 10654

generate_n(back_inserter(disc), 15, DiscGen()); 10655

print(disc.begin(), disc.end(), "Discounts:"); 10656

vector<Inventory> discounted; 10657

transform(vi.begin(),vi.end(), disc.begin(), 10658

back_inserter(discounted), Discounter()); 10659

print(discounted.begin(), discounted.end(),"discounted"); 10660

} ///:~ 10661

Listado 7.45. C06/SpecialList.cpp 10662

10663

7.3.11. Algoritmos numéricos 10664

Ejemplo 10665

//: C06:NumericTest.cpp 10666

#include <algorithm> 10667

#include <iostream> 10668

#include <iterator> 10669

#include <functional> 10670

#include <numeric> 10671

#include "PrintSequence.h" 10672

using namespace std; 10673

10674

Page 412: Pensar en C++ (Volumen 2)

412

int main() { 10675

int a[] = { 1, 1, 2, 2, 3, 5, 7, 9, 11, 13 }; 10676

const int ASZ = sizeof a / sizeof a[0]; 10677

print(a, a + ASZ, "a", " "); 10678

int r = accumulate(a, a + ASZ, 0); 10679

cout << "accumulate 1: " << r << endl; 10680

// Should produce the same result: 10681

r = accumulate(a, a + ASZ, 0, plus<int>()); 10682

cout << "accumulate 2: " << r << endl; 10683

int b[] = { 1, 2, 3, 4, 1, 2, 3, 4, 1, 2 }; 10684

print(b, b + sizeof b / sizeof b[0], "b", " "); 10685

r = inner_product(a, a + ASZ, b, 0); 10686

cout << "inner_product 1: " << r << endl; 10687

// Should produce the same result: 10688

r = inner_product(a, a + ASZ, b, 0, 10689

plus<int>(), multiplies<int>()); 10690

cout << "inner_product 2: " << r << endl; 10691

int* it = partial_sum(a, a + ASZ, b); 10692

print(b, it, "partial_sum 1", " "); 10693

// Should produce the same result: 10694

it = partial_sum(a, a + ASZ, b, plus<int>()); 10695

print(b, it, "partial_sum 2", " "); 10696

it = adjacent_difference(a, a + ASZ, b); 10697

print(b, it, "adjacent_difference 1"," "); 10698

// Should produce the same result: 10699

Page 413: Pensar en C++ (Volumen 2)

413

it = adjacent_difference(a, a + ASZ, b, minus<int>()); 10700

print(b, it, "adjacent_difference 2"," "); 10701

} ///:~ 10702

Listado 7.46. C06/NumericTest.cpp 10703

10704

7.3.12. Utilidades generales 10705

7.4. Creando sus propios algoritmos tipo STL 10706

// Assumes pred is the incoming condition 10707

replace_copy_if(begin, end, not1(pred)); 10708

//: C06:copy_if.h 10709

// Create your own STL-style algorithm. 10710

#ifndef COPY_IF_H 10711

#define COPY_IF_H 10712

10713

template<typename ForwardIter, 10714

typename OutputIter, typename UnaryPred> 10715

OutputIter copy_if(ForwardIter begin, ForwardIter end, 10716

OutputIter dest, UnaryPred f) { 10717

while(begin != end) { 10718

if(f(*begin)) 10719

*dest++ = *begin; 10720

++begin; 10721

Page 414: Pensar en C++ (Volumen 2)

414

} 10722

return dest; 10723

} 10724

#endif // COPY_IF_H ///:~ 10725

Listado 7.47. C06/copy_if.h 10726

10727

7.5. Resumen 10728

7.6. Ejercicios 10729

10730

10731

[17] N. de T.: Librería Estándar de Plantillas. 10732

Parte III. Temas especiales 10733

La marca de un profesional aparece en su atención a los detalles más 10734

finos del oficio. En esta sección del libro veremos características 10735

avanzadas de C++ junto con técnicas de desarrollo usadas por 10736

profesionales brillantes de C++. 10737

A veces necesita salir de los convencionalismos que suenan a diseño 10738

orientado a objetos, inspeccionando el tipo de un objeto en tiempo de 10739

ejecución. La mayoría de las veces debería dejar que las funciones virtuales 10740

hagan ese trabajo por usted, pero cuando escriba herramientas software 10741

para propósitos especiales, tales como depuradores, visualizadores de bases 10742

de datos, o navegadores de clases, necesitará determinar la información de 10743

tipado en tiempo de ejecución. Ahí es cuando el mecanismo de identificación 10744

de tipo en tiempo de ejecución (RTTI) resulta útil. RTTI es el tema del 10745

Capítulo 8. 10746

Page 415: Pensar en C++ (Volumen 2)

415

La herencia múltiple ha sido maltratado a lo largo de los años, y algunos 10747

lenguajes incluso no la permiten. No obstante, cuando se usa 10748

adecuadamente, puede ser una herramienta potente para conseguir código 10749

eficiente y elegante. Un buen número de prácticas estándar que involucran 10750

herencia múltiple han evolucionado con el tiempo; las veremos en el Capítulo 10751

9. 10752

Quizás la innovación más notable en el desarrollo de software desde las 10753

técnicas de orientación a objetos es el uso de los patrones de diseño. Un 10754

patrón de diseño describe soluciones para muchos problemas comunes del 10755

diseño de software, y se puede aplicar a muchas situaciones e 10756

implementación en cualquier lenguaje. En el Capítulo 10 describiremos una 10757

selección de patrones de diseño y los implementaremos en C++. 10758

El Capítulo 11 explica los beneficios y desafíos de la programación 10759

multihilo. La versión actual de C++ Estándar no especifica soporte para hilos, 10760

aunque la mayoría de los sistema operativos los ofrecen. Usaremos un 10761

librería portable y disponible libremente para ilustrar cómo los programadores 10762

pueden sacar provecho de los hilos para construir aplicaciones más usables 10763

y receptivas. 10764

Tabla de contenidos 10765

8. Herencia múltiple 10766

8.1. Perspectiva 10767

8.2. Herencia de interfaces 10768

8.3. Herencia de implementación 10769

8.4. Subobjetos duplicados 10770

8.5. Clases base virtuales 10771

8.6. Cuestión sobre búsqueda de nombres 10772

8.7. Evitar la MI 10773

8.8. Extender una interface 10774

8.9. Resumen 10775

8.10. Ejercicios 10776

9. Patrones de Diseño 10777

9.1. El Concepto de Patrón 10778

9.2. Clasificación de los patrones 10779

9.3. Simplificación de modismos 10780

9.4. Singleton 10781

9.5. Comando: elegir la operación 10782

9.6. Desacoplamiento de objetos 10783

9.7. Adaptador 10784

9.8. Template Method 10785

9.9. Estrategia: elegir el algoritno en tiempo de ejecución 10786

Page 416: Pensar en C++ (Volumen 2)

416

9.10. Cadena de Responsabilidad: intentar una secuencia de 10787

estrategias 10788

9.11. Factorías: encapsular la creación de objetos 10789

9.12. Builder: creación de objetos complejos 10790

9.13. Observador 10791

9.14. Despachado múltiple 10792

9.15. Resumen 10793

9.16. Ejercicios 10794

10. Concurrencia 10795

10.1. Motivación 10796

10.2. Concurrencia en C++ 10797

10.3. Utilización de los hilos 10798

10.4. Comparición de recursos limitados 10799

10.5. Finalización de tareas 10800

10.6. Cooperación entre hilos 10801

10.7. Bloqueo letal 10802

10.8. Resumen 10803

10.9. Ejercicios 10804

8: Herencia múltiple 10805

Tabla de contenidos 10806

8.1. Perspectiva 10807

8.2. Herencia de interfaces 10808

8.3. Herencia de implementación 10809

8.4. Subobjetos duplicados 10810

8.5. Clases base virtuales 10811

8.6. Cuestión sobre búsqueda de nombres 10812

8.7. Evitar la MI 10813

8.8. Extender una interface 10814

8.9. Resumen 10815

8.10. Ejercicios 10816

El concepto básico de la herencia múltiple (HM) suena bastante simple: 10817

puede crear un nuevo tipo heredando de más una una clase base. La 10818

sintaxis es exactamente la que espera, y en la medida en que los diagramas 10819

de herencia sean simples, la HM puede ser simple también. 10820

Sin embargo, la HM puede presentar un buen número de situaciones 10821

ambiguas y extrañas, que se cubren en este capítulo. Pero primero, es útil 10822

tener algo de perspectiva sobre el asunto. 10823

8.1. Perspectiva 10824

Antes de C++, el lenguaje orientado a objetos más popular era Smaltalk. 10825

Smaltalk fue creado desde cero como un lenguaje orientado a objetos. A 10826

Page 417: Pensar en C++ (Volumen 2)

417

menudo se dice que es puro, mientras que a C++ se le llama lenguaje híbrido 10827

porque soporta múltiples paradigmas de programación, no sólo el paradigma 10828

orientado a objeto. Uno de las decisiones de diseño de Smalltalk fue que 10829

todas las clases tendrían solo herencia simple, empezando en una clase 10830

base (llamada Object - ese es el modelo para la jerarquía basada en 10831

objetos) [18] En Smalltalk no puede crear una nueva clase sin derivar de un 10832

clase existente, que es la razón por la que lleva cierto tiempo ser productivo 10833

con Smalltalk: debe aprender la librería de clases antes de empezar a hacer 10834

clases nuevas. La jerarquía de clases de Smalltalk es por tanto un único 10835

árbol monolítico. 10836

Las clases de Smalltalk normalmente tienen ciertas cosas en común, y 10837

siempre tienen algunas cosas en común (las características y el 10838

comportamiento de Object), de modo que no suelen aparecer situaciones en 10839

las que se necesite heredad de más de una clase base. Sin embargo, con 10840

C++ puede crear tantos árboles de herencia distintos como quiera. Por 10841

completitud lógica el lenguaje debe ser capaz de combinar más de una clase 10842

a la vez - por eso la necesidad de herencia múltiple. 10843

No fue obvio, sin embargo, que los programadores requiriesen herencia 10844

múltiple, y había (y sigue habiendo) mucha discrepancia sobre si es algo 10845

esencial en C++. La HM fue añadida en cfront release 2.0 de AT&T en 1989 10846

y fue el primer cambio significativo en el lenguaje desde la versión 1.0. [19] 10847

Desde entonces, se han añadido muchas características al Estándar C++ 10848

(las plantillas son dignas de mención) que cambian la manera de pensar al 10849

programar y le dan a la HM un papel mucho menos importante. Puede 10850

pensar en la HM como una prestación menor del lenguaje que raramente 10851

está involucrada en las decisiones de diseño diarias. 10852

Uno de los argumentos más convincentes para la HM involucra a los 10853

contenedores. Suponga que quiere crear un contenedor que todo el mundo 10854

pueda usar fácilmente. Una propuesta es usar void* como tipo para el 10855

contenido. La propuesta de Smalltalk, sin embargo, es hacer un contenedor 10856

que aloja Object, dado que Object es el tipo base de la jerarquía de 10857

Smalltalk. Como todo en Smalltalk está derivado de Object, un contenedor 10858

que aloja Objects puede contener cualquier cosa. 10859

Page 418: Pensar en C++ (Volumen 2)

418

Ahora considere la situación en C++. Suponga que el fabricante A crea 10860

una jerarquía basada-en-objetos que incluye un conjunto de contenedores 10861

incluye uno que desea usar llamado Holder. Después, se da cuenta de que 10862

la jerarquía de clases del fabricante B contiene alguna clase que también es 10863

importante para usted, una clase BitImage, por ejemplo, que contiene 10864

imágenes. La única forma de hacer que un Holder de BitImage es derivar de 10865

una nueva clase que derive también de Object, y así poder almacenarlos en 10866

el Holder, y BitImage: 10867

10868

Figura 8.1. 10869

10870

Éste fue un motivo importante para la HM, y muchas librerías de clases 10871

están hechas con este model. Sin embargo, tal como se vio en el Capítulo 5, 10872

la aportación de las plantillas ha cambiado la forma de crear contenedores, y 10873

por eso esta situación ya no es un asunto crucial en favor de la HM. 10874

El otro motivo por el que se necesita la HM está relacionado con el diseño. 10875

Puede usar la HM intencionadamente para hacer un diseño más flexible y útil 10876

(o al menos aparentarlo). Un ejemplo de esto es el diseño de la librería 10877

original iostream (que persiste hoy día, como vio en el Capítulo 4. 10878

10879

Figura 8.2. 10880

10881

Tanto iostream como ostream son clases útiles por si mismas, pero se 10882

pueden derivar simultáneamente por una clase que combina sus 10883

características y comportamientos. La clase ios proporciona una 10884

combinación de las dos clases, y por eso en este caso la HM es un 10885

mecanismo de FIXME:code-factoring. 10886

Sin importar lo que le motive a usar HM, debe saber que es más difícil de 10887

usar de lo que podría parecer. 10888

8.2. Herencia de interfaces 10889

Page 419: Pensar en C++ (Volumen 2)

419

Un uso no controvertido de la herencia múltiple es la herencia de interfaz. 10890

En C++, toda herencia lo es de implementación, dado que todo en una clase 10891

base, interface e implentación, pasa a formar parte de la clase derivada. No 10892

es posible heredar solo una parte de una clase (es decir, la interface 10893

únicamente). Tal como se explica en el [FIXME:enlace en la versión web] 10894

Capítulo 14 del volumen 1, es posible hacer herencia privada y protegida 10895

para restringir el acceso a los miembros heredados desde las clases base 10896

cuando se usa por clientes de instancias de una clase derivada, pero esto no 10897

afecta a la propia clase derivada; esa clase sigue conteniendo todos los 10898

datos de la clase base y puede acceder a todos los miembros no-privados de 10899

la clase base. 10900

La herencia de interfaces. por otra parte, sólo añade declaraciones de 10901

miembros a la interfaz de la clase derivada, algo que no está soportado 10902

directamente en C++. La técnica habitual para simular la herencia de interfaz 10903

en C++ es derivar de una clase interfaz, que es una clase que sólo contiene 10904

declaraciones (ni datos ni cuerpos de funciones). Estas declaraciones serán 10905

funciones virtuales puras, excepto el destructor. Aquí hay un ejemplo: 10906

//: C09:Interfaces.cpp 10907

// Multiple interface inheritance. 10908

#include <iostream> 10909

#include <sstream> 10910

#include <string> 10911

using namespace std; 10912

10913

class Printable { 10914

public: 10915

virtual ~Printable() {} 10916

virtual void print(ostream&) const = 0; 10917

}; 10918

Page 420: Pensar en C++ (Volumen 2)

420

10919

class Intable { 10920

public: 10921

virtual ~Intable() {} 10922

virtual int toInt() const = 0; 10923

}; 10924

10925

class Stringable { 10926

public: 10927

virtual ~Stringable() {} 10928

virtual string toString() const = 0; 10929

}; 10930

10931

class Able : public Printable, public Intable, 10932

public Stringable { 10933

int myData; 10934

public: 10935

Able(int x) { myData = x; } 10936

void print(ostream& os) const { os << myData; } 10937

int toInt() const { return myData; } 10938

string toString() const { 10939

ostringstream os; 10940

os << myData; 10941

return os.str(); 10942

} 10943

Page 421: Pensar en C++ (Volumen 2)

421

}; 10944

10945

void testPrintable(const Printable& p) { 10946

p.print(cout); 10947

cout << endl; 10948

} 10949

10950

void testIntable(const Intable& n) { 10951

cout << n.toInt() + 1 << endl; 10952

} 10953

10954

void testStringable(const Stringable& s) { 10955

cout << s.toString() + "th" << endl; 10956

} 10957

10958

int main() { 10959

Able a(7); 10960

testPrintable(a); 10961

testIntable(a); 10962

testStringable(a); 10963

} ///:~ 10964

Listado 8.1. C09/Interfaces.cpp 10965

10966

La clase Able «implementa» las interfaces Printable, Intable y 10967

Stringable dado que proporciona implementaciones para las funciones que 10968

éstas declaran. Dado que Able deriva de las tres clases, los objetos Able 10969

Page 422: Pensar en C++ (Volumen 2)

422

tienen múltiples relaciones «es-un». Por ejemplo, el objeto a puede actuar 10970

como un objeto Printable dado que su clase, Able, deriva públicamente de 10971

Printable y proporciona una implementación para print(). Las funciones de 10972

prueba no necesitan saber el tipo más derivado de su parámetro; sólo 10973

necesitan un objeto que sea substituible por el tipo de su parámetro. 10974

Como es habitual, una plantilla es una solución más compacta: 10975

//: C09:Interfaces2.cpp 10976

// Implicit interface inheritance via templates. 10977

#include <iostream> 10978

#include <sstream> 10979

#include <string> 10980

using namespace std; 10981

10982

class Able { 10983

int myData; 10984

public: 10985

Able(int x) { myData = x; } 10986

void print(ostream& os) const { os << myData; } 10987

int toInt() const { return myData; } 10988

string toString() const { 10989

ostringstream os; 10990

os << myData; 10991

return os.str(); 10992

} 10993

}; 10994

10995

Page 423: Pensar en C++ (Volumen 2)

423

template<class Printable> 10996

void testPrintable(const Printable& p) { 10997

p.print(cout); 10998

cout << endl; 10999

} 11000

11001

template<class Intable> 11002

void testIntable(const Intable& n) { 11003

cout << n.toInt() + 1 << endl; 11004

} 11005

11006

template<class Stringable> 11007

void testStringable(const Stringable& s) { 11008

cout << s.toString() + "th" << endl; 11009

} 11010

11011

int main() { 11012

Able a(7); 11013

testPrintable(a); 11014

testIntable(a); 11015

testStringable(a); 11016

} ///:~ 11017

Listado 8.2. C09/Interfaces2.cpp 11018

11019

Page 424: Pensar en C++ (Volumen 2)

424

Los nombres Printable, Intable y Stringable ahora no son mas que 11020

parámetros de la plantilla que asume la existencia de las operaciones 11021

indicadas en sus respectivos argumentos. En otras palabras, las funciones 11022

de prueba pueden aceptar argumentos de cualquier tipo que proporciona una 11023

definición de método con la signatura y tipo de retorno correctos. Hay gente 11024

que encuentra más cómoda la primera versión porque los nombres de tipo 11025

garantizan que las interfaces esperadas están implementadas. Otros están 11026

contentos con el hecho de que si las operaciones requeridas por las 11027

funciones de prueba no se satisfacen por los argumentos de la plantilla, el 11028

error puede ser capturado en la compilación. Esta segunda es una forma de 11029

comprobación de tipos técnicamente más débil que el primer enfoque 11030

(herencia), pero el efecto para el programador (y el programa) es el mismo. 11031

Se trata de una forma de comprobación débil de tipo que es aceptable para 11032

muchos de los programadores C++ de hoy en día. 11033

8.3. Herencia de implementación 11034

//: C09:Database.h 11035

// A prototypical resource class. 11036

#ifndef DATABASE_H 11037

#define DATABASE_H 11038

#include <iostream> 11039

#include <stdexcept> 11040

#include <string> 11041

11042

struct DatabaseError : std::runtime_error { 11043

DatabaseError(const std::string& msg) 11044

: std::runtime_error(msg) {} 11045

}; 11046

11047

Page 425: Pensar en C++ (Volumen 2)

425

class Database { 11048

std::string dbid; 11049

public: 11050

Database(const std::string& dbStr) : dbid(dbStr) {} 11051

virtual ~Database() {} 11052

void open() throw(DatabaseError) { 11053

std::cout << "Connected to " << dbid << std::endl; 11054

} 11055

void close() { 11056

std::cout << dbid << " closed" << std::endl; 11057

} 11058

// Other database functions... 11059

}; 11060

#endif // DATABASE_H ///:~ 11061

Listado 8.3. C09/Database.h 11062

11063

//: C09:UseDatabase.cpp 11064

#include "Database.h" 11065

11066

int main() { 11067

Database db("MyDatabase"); 11068

db.open(); 11069

// Use other db functions... 11070

db.close(); 11071

Page 426: Pensar en C++ (Volumen 2)

426

} 11072

/* Output: 11073

connected to MyDatabase 11074

MyDatabase closed 11075

*/ ///:~ 11076

Listado 8.4. C09/UseDatabase.cpp 11077

11078

//: C09:Countable.h 11079

// A "mixin" class. 11080

#ifndef COUNTABLE_H 11081

#define COUNTABLE_H 11082

#include <cassert> 11083

11084

class Countable { 11085

long count; 11086

protected: 11087

Countable() { count = 0; } 11088

virtual ~Countable() { assert(count == 0); } 11089

public: 11090

long attach() { return ++count; } 11091

long detach() { 11092

return (--count > 0) ? count : (delete this, 0); 11093

} 11094

long refCount() const { return count; } 11095

Page 427: Pensar en C++ (Volumen 2)

427

}; 11096

#endif // COUNTABLE_H ///:~ 11097

Listado 8.5. C09/Countable.h 11098

11099

//: C09:DBConnection.h 11100

// Uses a "mixin" class. 11101

#ifndef DBCONNECTION_H 11102

#define DBCONNECTION_H 11103

#include <cassert> 11104

#include <string> 11105

#include "Countable.h" 11106

#include "Database.h" 11107

using std::string; 11108

11109

class DBConnection : public Database, public Countable { 11110

DBConnection(const DBConnection&); // Disallow copy 11111

DBConnection& operator=(const DBConnection&); 11112

protected: 11113

DBConnection(const string& dbStr) throw(DatabaseError) 11114

: Database(dbStr) { open(); } 11115

~DBConnection() { close(); } 11116

public: 11117

static DBConnection* 11118

create(const string& dbStr) throw(DatabaseError) { 11119

Page 428: Pensar en C++ (Volumen 2)

428

DBConnection* con = new DBConnection(dbStr); 11120

con->attach(); 11121

assert(con->refCount() == 1); 11122

return con; 11123

} 11124

// Other added functionality as desired... 11125

}; 11126

#endif // DBCONNECTION_H ///:~ 11127

Listado 8.6. C09/DBConnection.h 11128

11129

//: C09:UseDatabase2.cpp 11130

// Tests the Countable "mixin" class. 11131

#include <cassert> 11132

#include "DBConnection.h" 11133

11134

class DBClient { 11135

DBConnection* db; 11136

public: 11137

DBClient(DBConnection* dbCon) { 11138

db = dbCon; 11139

db->attach(); 11140

} 11141

~DBClient() { db->detach(); } 11142

// Other database requests using db... 11143

Page 429: Pensar en C++ (Volumen 2)

429

}; 11144

11145

int main() { 11146

DBConnection* db = DBConnection::create("MyDatabase"); 11147

assert(db->refCount() == 1); 11148

DBClient c1(db); 11149

assert(db->refCount() == 2); 11150

DBClient c2(db); 11151

assert(db->refCount() == 3); 11152

// Use database, then release attach from original create 11153

db->detach(); 11154

assert(db->refCount() == 2); 11155

} ///:~ 11156

Listado 8.7. C09/UseDatabase2.cpp 11157

11158

//: C09:DBConnection2.h 11159

// A parameterized mixin. 11160

#ifndef DBCONNECTION2_H 11161

#define DBCONNECTION2_H 11162

#include <cassert> 11163

#include <string> 11164

#include "Database.h" 11165

using std::string; 11166

11167

Page 430: Pensar en C++ (Volumen 2)

430

template<class Counter> 11168

class DBConnection : public Database, public Counter { 11169

DBConnection(const DBConnection&); // Disallow copy 11170

DBConnection& operator=(const DBConnection&); 11171

protected: 11172

DBConnection(const string& dbStr) throw(DatabaseError) 11173

: Database(dbStr) { open(); } 11174

~DBConnection() { close(); } 11175

public: 11176

static DBConnection* create(const string& dbStr) 11177

throw(DatabaseError) { 11178

DBConnection* con = new DBConnection(dbStr); 11179

con->attach(); 11180

assert(con->refCount() == 1); 11181

return con; 11182

} 11183

// Other added functionality as desired... 11184

}; 11185

#endif // DBCONNECTION2_H ///:~ 11186

Listado 8.8. C09/DBConnection2.h 11187

11188

//: C09:UseDatabase3.cpp 11189

// Tests a parameterized "mixin" class. 11190

#include <cassert> 11191

Page 431: Pensar en C++ (Volumen 2)

431

#include "Countable.h" 11192

#include "DBConnection2.h" 11193

11194

class DBClient { 11195

DBConnection<Countable>* db; 11196

public: 11197

DBClient(DBConnection<Countable>* dbCon) { 11198

db = dbCon; 11199

db->attach(); 11200

} 11201

~DBClient() { db->detach(); } 11202

}; 11203

11204

int main() { 11205

DBConnection<Countable>* db = 11206

DBConnection<Countable>::create("MyDatabase"); 11207

assert(db->refCount() == 1); 11208

DBClient c1(db); 11209

assert(db->refCount() == 2); 11210

DBClient c2(db); 11211

assert(db->refCount() == 3); 11212

db->detach(); 11213

assert(db->refCount() == 2); 11214

} ///:~ 11215

Page 432: Pensar en C++ (Volumen 2)

432

Listado 8.9. C09/UseDatabase3.cpp 11216

11217

template<class Mixin1, class Mixin2, ?? , class MixinK> 11218

class Subject : public Mixin1, 11219

public Mixin2, 11220

?? 11221

public MixinK {??}; 11222

8.4. Subobjetos duplicados 11223

//: C09:Offset.cpp 11224

// Illustrates layout of subobjects with MI. 11225

#include <iostream> 11226

using namespace std; 11227

11228

class A { int x; }; 11229

class B { int y; }; 11230

class C : public A, public B { int z; }; 11231

11232

int main() { 11233

cout << "sizeof(A) == " << sizeof(A) << endl; 11234

cout << "sizeof(B) == " << sizeof(B) << endl; 11235

cout << "sizeof(C) == " << sizeof(C) << endl; 11236

C c; 11237

cout << "&c == " << &c << endl; 11238

Page 433: Pensar en C++ (Volumen 2)

433

A* ap = &c; 11239

B* bp = &c; 11240

cout << "ap == " << static_cast<void*>(ap) << endl; 11241

cout << "bp == " << static_cast<void*>(bp) << endl; 11242

C* cp = static_cast<C*>(bp); 11243

cout << "cp == " << static_cast<void*>(cp) << endl; 11244

cout << "bp == cp? " << boolalpha << (bp == cp) << endl; 11245

cp = 0; 11246

bp = cp; 11247

cout << bp << endl; 11248

} 11249

/* Output: 11250

sizeof(A) == 4 11251

sizeof(B) == 4 11252

sizeof(C) == 12 11253

&c == 1245052 11254

ap == 1245052 11255

bp == 1245056 11256

cp == 1245052 11257

bp == cp? true 11258

0 11259

*/ ///:~ 11260

Listado 8.10. C09/Offset.cpp 11261

11262

Page 434: Pensar en C++ (Volumen 2)

434

//: C09:Duplicate.cpp 11263

// Shows duplicate subobjects. 11264

#include <iostream> 11265

using namespace std; 11266

11267

class Top { 11268

int x; 11269

public: 11270

Top(int n) { x = n; } 11271

}; 11272

11273

class Left : public Top { 11274

int y; 11275

public: 11276

Left(int m, int n) : Top(m) { y = n; } 11277

}; 11278

11279

class Right : public Top { 11280

int z; 11281

public: 11282

Right(int m, int n) : Top(m) { z = n; } 11283

}; 11284

11285

class Bottom : public Left, public Right { 11286

int w; 11287

Page 435: Pensar en C++ (Volumen 2)

435

public: 11288

Bottom(int i, int j, int k, int m) 11289

: Left(i, k), Right(j, k) { w = m; } 11290

}; 11291

11292

int main() { 11293

Bottom b(1, 2, 3, 4); 11294

cout << sizeof b << endl; // 20 11295

} ///:~ 11296

Listado 8.11. C09/Duplicate.cpp 11297

11298

8.5. Clases base virtuales 11299

//: C09:VirtualBase.cpp 11300

// Shows a shared subobject via a virtual base. 11301

#include <iostream> 11302

using namespace std; 11303

11304

class Top { 11305

protected: 11306

int x; 11307

public: 11308

Top(int n) { x = n; } 11309

virtual ~Top() {} 11310

Page 436: Pensar en C++ (Volumen 2)

436

friend ostream& 11311

operator<<(ostream& os, const Top& t) { 11312

return os << t.x; 11313

} 11314

}; 11315

11316

class Left : virtual public Top { 11317

protected: 11318

int y; 11319

public: 11320

Left(int m, int n) : Top(m) { y = n; } 11321

}; 11322

11323

class Right : virtual public Top { 11324

protected: 11325

int z; 11326

public: 11327

Right(int m, int n) : Top(m) { z = n; } 11328

}; 11329

11330

class Bottom : public Left, public Right { 11331

int w; 11332

public: 11333

Bottom(int i, int j, int k, int m) 11334

: Top(i), Left(0, j), Right(0, k) { w = m; } 11335

Page 437: Pensar en C++ (Volumen 2)

437

friend ostream& 11336

operator<<(ostream& os, const Bottom& b) { 11337

return os << b.x << ',' << b.y << ',' << b.z 11338

<< ',' << b.w; 11339

} 11340

}; 11341

11342

int main() { 11343

Bottom b(1, 2, 3, 4); 11344

cout << sizeof b << endl; 11345

cout << b << endl; 11346

cout << static_cast<void*>(&b) << endl; 11347

Top* p = static_cast<Top*>(&b); 11348

cout << *p << endl; 11349

cout << static_cast<void*>(p) << endl; 11350

cout << dynamic_cast<void*>(p) << endl; 11351

} ///:~ 11352

Listado 8.12. C09/VirtualBase.cpp 11353

11354

36 11355 1,2,3,4 11356 1245032 11357

1 11358 1245060 11359 1245032 11360

//: C09:VirtualBase2.cpp 11361

// How NOT to implement operator<<. 11362

Page 438: Pensar en C++ (Volumen 2)

438

#include <iostream> 11363

using namespace std; 11364

11365

class Top { 11366

int x; 11367

public: 11368

Top(int n) { x = n; } 11369

virtual ~Top() {} 11370

friend ostream& operator<<(ostream& os, const Top& t) { 11371

return os << t.x; 11372

} 11373

}; 11374

11375

class Left : virtual public Top { 11376

int y; 11377

public: 11378

Left(int m, int n) : Top(m) { y = n; } 11379

friend ostream& operator<<(ostream& os, const Left& l) { 11380

return os << static_cast<const Top&>(l) << ',' << l.y; 11381

} 11382

}; 11383

11384

class Right : virtual public Top { 11385

int z; 11386

public: 11387

Page 439: Pensar en C++ (Volumen 2)

439

Right(int m, int n) : Top(m) { z = n; } 11388

friend ostream& operator<<(ostream& os, const Right& r) { 11389

return os << static_cast<const Top&>(r) << ',' << r.z; 11390

} 11391

}; 11392

11393

class Bottom : public Left, public Right { 11394

int w; 11395

public: 11396

Bottom(int i, int j, int k, int m) 11397

: Top(i), Left(0, j), Right(0, k) { w = m; } 11398

friend ostream& operator<<(ostream& os, const Bottom& b){ 11399

return os << static_cast<const Left&>(b) 11400

<< ',' << static_cast<const Right&>(b) 11401

<< ',' << b.w; 11402

} 11403

}; 11404

11405

int main() { 11406

Bottom b(1, 2, 3, 4); 11407

cout << b << endl; // 1,2,1,3,4 11408

} ///:~ 11409

Listado 8.13. C09/VirtualBase2.cpp 11410

11411

Page 440: Pensar en C++ (Volumen 2)

440

//: C09:VirtualBase3.cpp 11412

// A correct stream inserter. 11413

#include <iostream> 11414

using namespace std; 11415

11416

class Top { 11417

int x; 11418

public: 11419

Top(int n) { x = n; } 11420

virtual ~Top() {} 11421

friend ostream& operator<<(ostream& os, const Top& t) { 11422

return os << t.x; 11423

} 11424

}; 11425

11426

class Left : virtual public Top { 11427

int y; 11428

protected: 11429

void specialPrint(ostream& os) const { 11430

// Only print Left's part 11431

os << ','<< y; 11432

} 11433

public: 11434

Left(int m, int n) : Top(m) { y = n; } 11435

friend ostream& operator<<(ostream& os, const Left& l) { 11436

Page 441: Pensar en C++ (Volumen 2)

441

return os << static_cast<const Top&>(l) << ',' << l.y; 11437

} 11438

}; 11439

11440

class Right : virtual public Top { 11441

int z; 11442

protected: 11443

void specialPrint(ostream& os) const { 11444

// Only print Right's part 11445

os << ','<< z; 11446

} 11447

public: 11448

Right(int m, int n) : Top(m) { z = n; } 11449

friend ostream& operator<<(ostream& os, const Right& r) { 11450

return os << static_cast<const Top&>(r) << ',' << r.z; 11451

} 11452

}; 11453

11454

class Bottom : public Left, public Right { 11455

int w; 11456

public: 11457

Bottom(int i, int j, int k, int m) 11458

: Top(i), Left(0, j), Right(0, k) { w = m; } 11459

friend ostream& operator<<(ostream& os, const Bottom& b){ 11460

os << static_cast<const Top&>(b); 11461

Page 442: Pensar en C++ (Volumen 2)

442

b.Left::specialPrint(os); 11462

b.Right::specialPrint(os); 11463

return os << ',' << b.w; 11464

} 11465

}; 11466

11467

int main() { 11468

Bottom b(1, 2, 3, 4); 11469

cout << b << endl; // 1,2,3,4 11470

} ///:~ 11471

Listado 8.14. C09/VirtualBase3.cpp 11472

11473

//: C09:VirtInit.cpp 11474

// Illustrates initialization order with virtual bases. 11475

#include <iostream> 11476

#include <string> 11477

using namespace std; 11478

11479

class M { 11480

public: 11481

M(const string& s) { cout << "M " << s << endl; } 11482

}; 11483

11484

class A { 11485

Page 443: Pensar en C++ (Volumen 2)

443

M m; 11486

public: 11487

A(const string& s) : m("in A") { 11488

cout << "A " << s << endl; 11489

} 11490

virtual ~A() {} 11491

}; 11492

11493

class B { 11494

M m; 11495

public: 11496

B(const string& s) : m("in B") { 11497

cout << "B " << s << endl; 11498

} 11499

virtual ~B() {} 11500

}; 11501

11502

class C { 11503

M m; 11504

public: 11505

C(const string& s) : m("in C") { 11506

cout << "C " << s << endl; 11507

} 11508

virtual ~C() {} 11509

}; 11510

Page 444: Pensar en C++ (Volumen 2)

444

11511

class D { 11512

M m; 11513

public: 11514

D(const string& s) : m("in D") { 11515

cout << "D " << s << endl; 11516

} 11517

virtual ~D() {} 11518

}; 11519

11520

class E : public A, virtual public B, virtual public C { 11521

M m; 11522

public: 11523

E(const string& s) : A("from E"), B("from E"), 11524

C("from E"), m("in E") { 11525

cout << "E " << s << endl; 11526

} 11527

}; 11528

11529

class F : virtual public B, virtual public C, public D { 11530

M m; 11531

public: 11532

F(const string& s) : B("from F"), C("from F"), 11533

D("from F"), m("in F") { 11534

cout << "F " << s << endl; 11535

Page 445: Pensar en C++ (Volumen 2)

445

} 11536

}; 11537

11538

class G : public E, public F { 11539

M m; 11540

public: 11541

G(const string& s) : B("from G"), C("from G"), 11542

E("from G"), F("from G"), m("in G") { 11543

cout << "G " << s << endl; 11544

} 11545

}; 11546

11547

int main() { 11548

G g("from main"); 11549

} ///:~ 11550

Listado 8.15. C09/VirtInit.cpp 11551

11552

M in B 11553 B from G 11554 M in C 11555

C from G 11556 M in A 11557

A from E 11558 M in E 11559

E from G 11560 M in D 11561

D from F 11562 M in F 11563

F from G 11564 M in G 11565

G from main 11566

Page 446: Pensar en C++ (Volumen 2)

446

8.6. Cuestión sobre búsqueda de nombres 11567

//: C09:AmbiguousName.cpp {-xo} 11568

11569

class Top { 11570

public: 11571

virtual ~Top() {} 11572

}; 11573

11574

class Left : virtual public Top { 11575

public: 11576

void f() {} 11577

}; 11578

11579

class Right : virtual public Top { 11580

public: 11581

void f() {} 11582

}; 11583

11584

class Bottom : public Left, public Right {}; 11585

11586

int main() { 11587

Bottom b; 11588

b.f(); // Error here 11589

} ///:~ 11590

Page 447: Pensar en C++ (Volumen 2)

447

Listado 8.16. C09/AmbiguousName.cpp 11591

11592

//: C09:BreakTie.cpp 11593

11594

class Top { 11595

public: 11596

virtual ~Top() {} 11597

}; 11598

11599

class Left : virtual public Top { 11600

public: 11601

void f() {} 11602

}; 11603

11604

class Right : virtual public Top { 11605

public: 11606

void f() {} 11607

}; 11608

11609

class Bottom : public Left, public Right { 11610

public: 11611

using Left::f; 11612

}; 11613

11614

int main() { 11615

Page 448: Pensar en C++ (Volumen 2)

448

Bottom b; 11616

b.f(); // Calls Left::f() 11617

} ///:~ 11618

Listado 8.17. C09/BreakTie.cpp 11619

11620

//: C09:Dominance.cpp 11621

11622

class Top { 11623

public: 11624

virtual ~Top() {} 11625

virtual void f() {} 11626

}; 11627

11628

class Left : virtual public Top { 11629

public: 11630

void f() {} 11631

}; 11632

11633

class Right : virtual public Top {}; 11634

11635

class Bottom : public Left, public Right {}; 11636

11637

int main() { 11638

Bottom b; 11639

Page 449: Pensar en C++ (Volumen 2)

449

b.f(); // Calls Left::f() 11640

} ///:~ 11641

Listado 8.18. C09/Dominance.cpp 11642

11643

//: C09:Dominance2.cpp 11644

#include <iostream> 11645

using namespace std; 11646

11647

class A { 11648

public: 11649

virtual ~A() {} 11650

virtual void f() { cout << "A::f\n"; } 11651

}; 11652

11653

class B : virtual public A { 11654

public: 11655

void f() { cout << "B::f\n"; } 11656

}; 11657

11658

class C : public B {}; 11659

class D : public C, virtual public A {}; 11660

11661

int main() { 11662

B* p = new D; 11663

Page 450: Pensar en C++ (Volumen 2)

450

p->f(); // Calls B::f() 11664

delete p; 11665

} ///:~ 11666

Listado 8.19. C09/Dominance2.cpp 11667

11668

8.7. Evitar la MI 11669

8.8. Extender una interface 11670

//: C09:Vendor.h 11671

// Vendor-supplied class header 11672

// You only get this & the compiled Vendor.obj. 11673

#ifndef VENDOR_H 11674

#define VENDOR_H 11675

11676

class Vendor { 11677

public: 11678

virtual void v() const; 11679

void f() const; // Might want this to be virtual... 11680

~Vendor(); // Oops! Not virtual! 11681

}; 11682

11683

class Vendor1 : public Vendor { 11684

public: 11685

void v() const; 11686

Page 451: Pensar en C++ (Volumen 2)

451

void f() const; 11687

~Vendor1(); 11688

}; 11689

11690

void A(const Vendor&); 11691

void B(const Vendor&); 11692

// Etc. 11693

#endif // VENDOR_H ///:~ 11694

Listado 8.20. C09/Vendor.h 11695

11696

//: C09:Vendor.cpp {O} 11697

// Assume this is compiled and unavailable to you. 11698

#include "Vendor.h" 11699

#include <iostream> 11700

using namespace std; 11701

11702

void Vendor::v() const { cout << "Vendor::v()" << endl; } 11703

11704

void Vendor::f() const { cout << "Vendor::f()" << endl; } 11705

11706

Vendor::~Vendor() { cout << "~Vendor()" << endl; } 11707

11708

void Vendor1::v() const { cout << "Vendor1::v()" << endl; } 11709

11710

Page 452: Pensar en C++ (Volumen 2)

452

void Vendor1::f() const { cout << "Vendor1::f()" << endl; } 11711

11712

Vendor1::~Vendor1() { cout << "~Vendor1()" << endl; } 11713

11714

void A(const Vendor& v) { 11715

// ... 11716

v.v(); 11717

v.f(); 11718

// ... 11719

} 11720

11721

void B(const Vendor& v) { 11722

// ... 11723

v.v(); 11724

v.f(); 11725

// ... 11726

} ///:~ 11727

Listado 8.21. C09/Vendor.cpp 11728

11729

//: C09:Paste.cpp 11730

//{L} Vendor 11731

// Fixing a mess with MI. 11732

#include <iostream> 11733

#include "Vendor.h" 11734

Page 453: Pensar en C++ (Volumen 2)

453

using namespace std; 11735

11736

class MyBase { // Repair Vendor interface 11737

public: 11738

virtual void v() const = 0; 11739

virtual void f() const = 0; 11740

// New interface function: 11741

virtual void g() const = 0; 11742

virtual ~MyBase() { cout << "~MyBase()" << endl; } 11743

}; 11744

11745

class Paste1 : public MyBase, public Vendor1 { 11746

public: 11747

void v() const { 11748

cout << "Paste1::v()" << endl; 11749

Vendor1::v(); 11750

} 11751

void f() const { 11752

cout << "Paste1::f()" << endl; 11753

Vendor1::f(); 11754

} 11755

void g() const { cout << "Paste1::g()" << endl; } 11756

~Paste1() { cout << "~Paste1()" << endl; } 11757

}; 11758

11759

Page 454: Pensar en C++ (Volumen 2)

454

int main() { 11760

Paste1& p1p = *new Paste1; 11761

MyBase& mp = p1p; // Upcast 11762

cout << "calling f()" << endl; 11763

mp.f(); // Right behavior 11764

cout << "calling g()" << endl; 11765

mp.g(); // New behavior 11766

cout << "calling A(p1p)" << endl; 11767

A(p1p); // Same old behavior 11768

cout << "calling B(p1p)" << endl; 11769

B(p1p); // Same old behavior 11770

cout << "delete mp" << endl; 11771

// Deleting a reference to a heap object: 11772

delete &mp; // Right behavior 11773

} ///:~ 11774

Listado 8.22. C09/Paste.cpp 11775

11776

MyBase* mp = p1p; // Upcast 11777

calling f() 11778 Paste1::f() 11779 Vendor1::f() 11780 calling g() 11781 Paste1::g() 11782

calling A(p1p) 11783 Paste1::v() 11784 Vendor1::v() 11785 Vendor::f() 11786

calling B(p1p) 11787 Paste1::v() 11788

Page 455: Pensar en C++ (Volumen 2)

455

Vendor1::v() 11789 Vendor::f() 11790 delete mp 11791 ~Paste1() 11792 ~Vendor1() 11793 ~Vendor() 11794 ~MyBase() 11795

8.9. Resumen 11796

8.10. Ejercicios 11797

11798

11799

[18] Esto también ocurre en Java, y en otros lenguajes orientados a objetos. 11800

[19] Son números de versión internos de AT&T. 11801

9: Patrones de Diseño 11802

Tabla de contenidos 11803

9.1. El Concepto de Patrón 11804

9.2. Clasificación de los patrones 11805

9.3. Simplificación de modismos 11806

9.4. Singleton 11807

9.5. Comando: elegir la operación 11808

9.6. Desacoplamiento de objetos 11809

9.7. Adaptador 11810

9.8. Template Method 11811

9.9. Estrategia: elegir el algoritno en tiempo de ejecución 11812

9.10. Cadena de Responsabilidad: intentar una secuencia de estrategias 11813

9.11. Factorías: encapsular la creación de objetos 11814

9.12. Builder: creación de objetos complejos 11815

9.13. Observador 11816

9.14. Despachado múltiple 11817

9.15. Resumen 11818

9.16. Ejercicios 11819

"...describa un problema que sucede una y otra vez en nuestro entorno, y 11820

luego describa el núcleo de la solución a ese problema, de tal forma que 11821

pueda utilizar esa solución un millón de veces más, sin siquiera hacerlo dos 11822

veces de la misma manera." - Christopher Alexander 11823

Page 456: Pensar en C++ (Volumen 2)

456

Este capítulo presenta el importante y aún no tradicional enfoque de los 11824

patrones para el diseño de programas. 11825

El avance reciente más importante en el diseño orientado a objetos es 11826

probablemente el movimiento de los patrones de diseño, inicialmente narrado 11827

en "Design Patterns", por Gamma, Helm, Johnson y Vlissides (Addison 11828

Wesley, 1995), que suele llamarse el libro de la "Banda de los Cuatro" (en 11829

inglés, GoF: Gang of Four). El GoF muestra 23 soluciones para clases de 11830

problemas muy particulares. En este capítulo se discuten los conceptos 11831

básicos de los patrones de diseño y se ofrecen ejemplos de código que 11832

ilustran los patrones escogidos. Esto debería abrirle el apetito para leer más 11833

acerca de los patrones de diseño, una fuente de lo que se ha convertido en 11834

vocabulario esencial, casi obligatorio, para la programación orientada a 11835

objetos. 11836

9.1. El Concepto de Patrón 11837

En principio, puede pensar en un patrón como una manera especialmente 11838

inteligente e intuitiva de resolver una clase de problema en particular. Parece 11839

que un equipo de personas han estudiado todos los ángulos de un problema 11840

y han dado con la solución más general y flexible para ese tipo de problema. 11841

Este problema podría ser uno que usted ha visto y resuelto antes, pero su 11842

solución probablemente no tenía la clase de completitud que verá plasmada 11843

en un patrón. Es más, el patrón existe independientemente de cualquier 11844

implementación particular y puede implementarse de numerosas maneras. 11845

Aunque se llaman "patrones de diseño", en realidad no están ligados al 11846

ámbito del diseño. Un patrón parece apartarse de la manera tradicional de 11847

pensar sobre el análisis, diseño e implementación. En cambio, un patrón 11848

abarca una idea completa dentro de un programa, y por lo tanto puede 11849

también abarcar las fases de análisis y diseño de alto nivel. Sin embargo, 11850

dado que un patrón a menudo tiene una implementación directa en código, 11851

podría no mostrarse hasta el diseño de bajo nivel o la implementación (y 11852

usted no se daría cuenta de que necesita ese patrón hasta que llegase a 11853

esas fases). 11854

El concepto básico de un patrón puede verse también como el concepto 11855

básico del diseño de programas en general: añadir capas de abstracción. 11856

Page 457: Pensar en C++ (Volumen 2)

457

Cuando se abstrae algo, se están aislando detalles concretos, y una de las 11857

razones de mayor peso para hacerlo es separar las cosas que cambian de 11858

las cosas que no. Otra forma de verlo es que una vez que encuentra una 11859

parte de su programa que es susceptible de cambiar, querrá prevenir que 11860

esos cambios propagen efectos colaterales por su código. Si lo consigue, su 11861

código no sólo será más fácil de leer y comprender, también será más fácil 11862

de mantener, lo que a la larga, siempre redunda en menores costes. 11863

La parte más difícil de desarrollar un diseño elegante y mantenible a 11864

menudo es descubrir lo que llamamos el "vector de cambio". (Aquí "vector" 11865

se refiere al mayor gradiente tal y como se entiende en ciencias, no como la 11866

clase contenedora.) Esto implica encontrar la cosa más importante que 11867

cambia en su sistema o, dicho de otra forma, descubrir dónde están sus 11868

mayores costes. Una vez que descubra el vector de cambios, tendrá el punto 11869

focal alrededor del cual estructurar su diseño. 11870

Por lo tanto, el objetivo de los patrones de diseño es encapsular el cambio. 11871

Si lo enfoca de esta forma, ya habrá visto algunos patrones de diseño en 11872

este libro. Por ejemplo, la herencia podría verse como un patrón de diseño 11873

(aunque uno implementado por el compilador). Expresa diferencias de 11874

comportamiento (eso es lo que cambia) en objetos que tienen todos la misma 11875

interfaz (esto es lo que no cambia). La composición también podría 11876

considerarse un patrón, ya que puede cambiar dinámica o estáticamente los 11877

objetos que implementan su clase, y por lo tanto, la forma en la que funciona 11878

la clase. Normalmente, sin embargo, las características que los lenguajes de 11879

programación soportan directamente no se han clasificado como patrones de 11880

diseño. 11881

También ha visto ya otro patrón que aparece en el GoF: el «iterador». Esta 11882

es la herramienta fundamental usada en el diseño del STL, descrito en 11883

capítulos anteriores. El iterador esconde la implementación concreta del 11884

contenedor a medida que se avanza y se seleccionan los elementos uno a 11885

uno. Los iteradores le ayudan a escribir código genérico que realiza una 11886

operación en todos los elementos de un rango sin tener en cuenta el 11887

contenedor que contiene el rango. Por lo tanto, cualquier contenedor que 11888

pueda producir iteradores puede utilizar su código genérico. 11889

9.1.1. La composición es preferible a la herencia 11890

Page 458: Pensar en C++ (Volumen 2)

458

La contribución más importante del GoF puede que no sea un patrón, si no 11891

una máxima que introducen en el Capítulo 1: «Prefiera siempre la 11892

composición de objetos antes que la herencia de clases». Entender la 11893

herecia y el polimorfismo es un reto tal, que podría empezar a otorgarle una 11894

importancia excesiva a estas técnicas. Se ven muchos diseños 11895

excesivamente complicados (el nuestro incluído) como resultado de ser 11896

demasiado indulgentes con la herencia - por ejemplo, muchos diseños de 11897

herencia múltiple se desarrollan por insistir en usar la herencia en todas 11898

partes. 11899

Una de las directrices en la «Programación Extrema» es "haga la cosa 11900

más simple que pueda funcionar". Un diseño que parece requerir de herencia 11901

puede a menudo simplificarse drásticamente usando una composición en su 11902

lugar, y descubrirá también que el resultado es más flexible, como 11903

comprenderá al estudiar algunos de los patrones de diseño de este capítulo. 11904

Por lo tanto, al considerar un diseño, pregúntese: ¿Podría ser más simple si 11905

usara Composición? ¿De verdad necesito Herencia aquí, y qué me aporta? 11906

9.2. Clasificación de los patrones 11907

El GoF describe 23 patrones, clasificados según tres propósitos FIXME: 11908

(all of which revolve around the particular aspect that can vary): 11909

1. Creacional: Cómo se puede crear un objeto. Habitualmente esto incluye 11910

aislar los detalles de la creación del objeto, de forma que su código no 11911

dependa de los tipos de objeto que hay y por lo tantok, no tenga que 11912

cambiarlo cuando añada un nuevo tipo de objeto. Este capítulo presenta los 11913

patrones Singleton, Fábricas (Factories), y Constructor (Builder). 11914

2. Estructural: Esto afecta a la manera en que los objetos se conectan con 11915

otros objetos para asegurar que los cambios del sistema no requieren 11916

cambiar esas conexiones. Los patrones estructurales suelen imponerlos las 11917

restricciones del proyecto. En este capítulo verá el Proxy y el Adaptador 11918

(Adapter). 11919

3. Comportacional: Objetos que manejan tipos particulares de acciones 11920

dentro de un programa. Éstos encapsulan procesos que quiere que se 11921

ejecuten, como interpretar un lenguaje, completar una petición, moverse a 11922

Page 459: Pensar en C++ (Volumen 2)

459

través de una secuencia (como en un iterador) o implementar un algoritmo. 11923

Este capítulo contiene ejemplos de Comando (Command), Método Plantilla 11924

(Template Method), Estado (State), Estrategia (Strategy), Cadena de 11925

Responsabilidad (Chain of Responsibility), Observador (Observer), FIXME: 11926

Despachador Múltiple (Multiple Dispatching) y Visitador (Visitor). 11927

El GoF incluye una sección sobre cada uno de los 23 patrones, junto con 11928

uno o más ejemplos de cada uno, típicamente en C++ aunque a veces en 11929

SmallTalk. Este libro no repite los detalles de los patrones mostrados en 11930

GoF, ya que aquél FIXME: "stands on its own" y debería estudiarse aparte. 11931

La descripción y los ejemplos que se dan aquí intentan darle una visión de 11932

los patrones, de forma que pueda hacerse una idea de lo que tratan y de 11933

porqué son importantes. 11934

9.2.1. Características, modismos patrones 11935

El trabajo va más allá de lo que se muestra en el libro del GoF. Desde su 11936

publicación, hay más patrones y un proceso más refinado para definir 11937

patrones de diseño.[135] Esto es importante porque no es fácil identificar 11938

nuevos patrones ni describirlos adecuadamente. Hay mucha confusión en la 11939

literatura popular acerca de qué es un patrón de diseño, por ejemplo. Los 11940

patrones no son triviales, ni están representados por características 11941

implementadas en un lenguaje de programación. Los constructores y 11942

destructores, por ejemplo, podrían llamarse el patrón de inicialización 11943

garantizada y el de limpieza. Hay constructores importantes y esenciales, 11944

pero son características del lenguaje rutinarias, y no son lo suficientemente 11945

ricas como para ser consideradas patrones. 11946

Otro FIXME: (no-ejemplo? anti-ejemplo?) viene de varias formas de 11947

agregación. La agregación es un principio completamente fundamental en la 11948

programación orientada a objetos: se hacen objetos a partir de otros objetos. 11949

Aunque a veces, esta idea se clasifica erróneamente como un patrón. Esto 11950

no es bueno, porque contamina la idea del patrón de diseño, y sugiere que 11951

cualquier cosa que le sorprenda la primera vez que la ve debería convertirse 11952

en un patrón de diseño. 11953

El lenguaje Java da otro ejemplo equivocado: Los diseñadores de la 11954

especificación de JavaBeans decidieron referirse a la notación get/set como 11955

Page 460: Pensar en C++ (Volumen 2)

460

un patrón de diseño (por ejemplo, getInfo() devuelve una propiedad Info y 11956

setInfo() la modifica). Esto es únicamente una convención de nombrado, y de 11957

ninguna manera constituye un patrón de diseño. 11958

9.3. Simplificación de modismos 11959

Antes de adentrarnos en técnicas más complejas, es útil echar un vistazo a 11960

algunos métodos básicos de mantener el código simple y sencillo. 11961

9.3.1. Mensajero 11962

El más trivial es el Mensajero (Messenger), [136] que empaqueta 11963

información en un objeto que se envia, en lugar de ir enviando todas las 11964

piezas independientemente. Nótese que sin el Mensajero, el código para la 11965

función translate() sería mucho más confuso: 11966

//: C10:MessengerDemo.cpp 11967

#include <iostream> 11968

#include <string> 11969

using namespace std; 11970

11971

class Point { // A messenger 11972

public: 11973

int x, y, z; // Since it's just a carrier 11974

Point(int xi, int yi, int zi) : x(xi), y(yi), z(zi) {} 11975

Point(const Point& p) : x(p.x), y(p.y), z(p.z) {} 11976

Point& operator=(const Point& rhs) { 11977

x = rhs.x; 11978

y = rhs.y; 11979

z = rhs.z; 11980

Page 461: Pensar en C++ (Volumen 2)

461

return *this; 11981

} 11982

friend ostream& 11983

operator<<(ostream& os, const Point& p) { 11984

return os << "x=" << p.x << " y=" << p.y 11985

<< " z=" << p.z; 11986

} 11987

}; 11988

11989

class Vector { // Mathematical vector 11990

public: 11991

int magnitude, direction; 11992

Vector(int m, int d) : magnitude(m), direction(d) {} 11993

}; 11994

11995

class Space { 11996

public: 11997

static Point translate(Point p, Vector v) { 11998

// Copy-constructor prevents modifying the original. 11999

// A dummy calculation: 12000

p.x += v.magnitude + v.direction; 12001

p.y += v.magnitude + v.direction; 12002

p.z += v.magnitude + v.direction; 12003

return p; 12004

} 12005

Page 462: Pensar en C++ (Volumen 2)

462

}; 12006

12007

int main() { 12008

Point p1(1, 2, 3); 12009

Point p2 = Space::translate(p1, Vector(11, 47)); 12010

cout << "p1: " << p1 << " p2: " << p2 << endl; 12011

} ///:~ 12012

Listado 9.1. C10/MessengerDemo.cpp 12013

12014

El código ha sido simplificado para evitar distracciones. 12015

Como el objetivo del Mensajero es simplemente llevar datos, dichos datos 12016

se hacen públicos para facilitar el acceso. Sin embargo, podría tener razones 12017

para hacer estos campos privados. 12018

9.3.2. Parámetro de Recolección 12019

El hermano mayor del Mensajero es el parámetro de recolección, cuyo 12020

trabajo es capturar información sobre la función a la que es pasado. 12021

Generalmente se usa cuando el parámetro de recolección se pasa a 12022

múltiples funciones; es como una abeja recogiendo polen. 12023

Un contenedor (container) es un parámetro de recolección especialmente 12024

útil, ya que está configurado para añadir objetos dinámicamente: 12025

//: C10:CollectingParameterDemo.cpp 12026

#include <iostream> 12027

#include <string> 12028

#include <vector> 12029

using namespace std; 12030

Page 463: Pensar en C++ (Volumen 2)

463

12031

class CollectingParameter : public vector<string> {}; 12032

12033

class Filler { 12034

public: 12035

void f(CollectingParameter& cp) { 12036

cp.push_back("accumulating"); 12037

} 12038

void g(CollectingParameter& cp) { 12039

cp.push_back("items"); 12040

} 12041

void h(CollectingParameter& cp) { 12042

cp.push_back("as we go"); 12043

} 12044

}; 12045

12046

int main() { 12047

Filler filler; 12048

CollectingParameter cp; 12049

filler.f(cp); 12050

filler.g(cp); 12051

filler.h(cp); 12052

vector<string>::iterator it = cp.begin(); 12053

while(it != cp.end()) 12054

cout << *it++ << " "; 12055

Page 464: Pensar en C++ (Volumen 2)

464

cout << endl; 12056

} ///:~ 12057

Listado 9.2. C10/CollectingParameterDemo.cpp 12058

12059

El parámetro de recolección debe tener alguna forma de establecer o 12060

insertar valores. Nótese que por esta definición, un Mensajero podría usarse 12061

como parámetro de recolección. La clave reside en que el parámetro de 12062

recolección se pasa y es modificado por la función que lo recibe. 12063

9.4. Singleton 12064

Posiblemente, el patrón de diseño más simple del GoF es el Singleton, 12065

que es una forma de asegurar una única instancia de una clase. El siguiente 12066

programa muestra cómo implementar un Singleton en C++: 12067

//: C10:SingletonPattern.cpp 12068

#include <iostream> 12069

using namespace std; 12070

12071

class Singleton { 12072

static Singleton s; 12073

int i; 12074

Singleton(int x) : i(x) { } 12075

Singleton& operator=(Singleton&); // Disallowed 12076

Singleton(const Singleton&); // Disallowed 12077

public: 12078

static Singleton& instance() { return s; } 12079

int getValue() { return i; } 12080

Page 465: Pensar en C++ (Volumen 2)

465

void setValue(int x) { i = x; } 12081

}; 12082

12083

Singleton Singleton::s(47); 12084

12085

int main() { 12086

Singleton& s = Singleton::instance(); 12087

cout << s.getValue() << endl; 12088

Singleton& s2 = Singleton::instance(); 12089

s2.setValue(9); 12090

cout << s.getValue() << endl; 12091

} ///:~ 12092

Listado 9.3. C10/SingletonPattern.cpp 12093

12094

La clave para crear un Singleton es evitar que el programador cliente tenga 12095

control sobre el ciclo de vida del objeto. Para lograrlo, declare todos los 12096

constructores privados, y evite que el compilador genere implícitamente 12097

cualquier constructor. Fíjese que el FIXME: constructor de copia? y el 12098

operador de asignación (que intencionadamente carecen de implementación 12099

alguna, ya que nunca van a ser llamados) están declarados como privados, 12100

para evitar que se haga cualquier tipo de copia. 12101

También debe decidir cómo va a crear el objeto. Aquí, se crea de forma 12102

estática, pero también puede esperar a que el programador cliente pida uno 12103

y crearlo bajo demanda. Esto se llama "inicialización vaga", y sólo tiene 12104

sentido si resulta caro crear el objeto y no siempre se necesita. 12105

Si devuelve un puntero en lugar de una referencia, el usuario podría borrar 12106

el puntero sin darse cuenta, por lo que la implementación citada 12107

anteriormente es más segura (el destructor también podría declararse 12108

Page 466: Pensar en C++ (Volumen 2)

466

privado o protegido para solventar el problema). En cualquier caso, el objeto 12109

debería almacenarse de forma privada. 12110

Usted da acceso a través de FIXME (funciones de miembros) públicas. 12111

Aquí, instance() genera una referencia al objeto Singleton. El resto de la 12112

interfaz (getValue() y setValue()) es la interfaz regular de la clase. 12113

Fíjese en que no está restringido a crear un único objeto. Esta técnica 12114

también soporta la creacion de un pool limitado de objetos. En este caso, sin 12115

embargo, puede enfrentarse al problema de compartir objetos del pool. Si 12116

esto supone un problema, puede crear una solución que incluya un check-12117

out y un check-in de los objetos compartidos. 12118

9.4.1. Variantes del Singleton 12119

Cualquier miembro static dentro de una clase es una forma de Singleton 12120

se hará uno y sólo uno. En cierto modo, el lenguaje da soporte directo a esta 12121

idea; se usa de forma regular. Sin embargo, los objetos estáticos tienen un 12122

problema (ya miembros o no): el orden de inicialización, tal y como se 12123

describe en el volumen 1 de este libro. Si un objeto static depende de otro, 12124

es importante que los objetos se inicializen en el orden correcto. 12125

En el volumen 1, se mostró cómo controlar el orden de inicialización 12126

definiendo un objeto estático dentro de una función. Esto retrasa la 12127

inicialización del objeto hasta la primera vez que se llama a la función. Si la 12128

función devuelve una referencia al objeto estático, hace las veces de 12129

Singleton a la vez que elimina gran parte de la preocupación de la 12130

inicialización estática. Por ejemplo, suponga que quiere crear un fichero de 12131

log en la primera llamada a una función que devuelve una referencia a dicho 12132

fichero. Basta con este fichero de cabecera: 12133

//: C10:LogFile.h 12134

#ifndef LOGFILE_H 12135

#define LOGFILE_H 12136

#include <fstream> 12137

std::ofstream& logfile(); 12138

Page 467: Pensar en C++ (Volumen 2)

467

#endif // LOGFILE_H ///:~ 12139

Listado 9.4. C10/LogFile.h 12140

12141

La implementación no debe FIXME: hacerse en la misma línea, porque eso 12142

significaría que la función entera, incluída la definición del objeto estático que 12143

contiene, podría ser duplicada en cualquier unidad de traducción donde se 12144

incluya, lo que viola la regla de única definición de C++. [137] Con toda 12145

seguridad, esto frustraría cualquier intento de controlar el orden de 12146

inicialización (pero potencialmente de una forma sutil y difícil de detectar). De 12147

forma que la implementación debe separarse: 12148

//: C10:LogFile.cpp {O} 12149

#include "LogFile.h" 12150

std::ofstream& logfile() { 12151

static std::ofstream log("Logfile.log"); 12152

return log; 12153

} ///:~ 12154

Listado 9.5. C10/LogFile.cpp 12155

12156

Ahora el objeto log no se inicializará hasta la primera vez que se llame a 12157

logfile(). Así que, si crea una función: 12158

//: C10:UseLog1.h 12159

#ifndef USELOG1_H 12160

#define USELOG1_H 12161

void f(); 12162

#endif // USELOG1_H ///:~ 12163

Page 468: Pensar en C++ (Volumen 2)

468

Listado 9.6. C10/UseLog1.h 12164

12165

que use logfile() en su implementación: 12166

//: C10:UseLog1.cpp {O} 12167

#include "UseLog1.h" 12168

#include "LogFile.h" 12169

void f() { 12170

logfile() << __FILE__ << std::endl; 12171

} ///:~ 12172

Listado 9.7. C10/UseLog1.cpp 12173

12174

y utiliza logfile() otra vez en otro fichero: 12175

//: C10:UseLog2.cpp 12176

//{L} LogFile UseLog1 12177

#include "UseLog1.h" 12178

#include "LogFile.h" 12179

using namespace std; 12180

void g() { 12181

logfile() << __FILE__ << endl; 12182

} 12183

12184

int main() { 12185

f(); 12186

g(); 12187

Page 469: Pensar en C++ (Volumen 2)

469

} ///:~ 12188

Listado 9.8. C10/UseLog2.cpp 12189

12190

el objecto log no se crea hasta la primera llamada a f(). 12191

Puede combinar fácilmente la creación de objetos estáticos dentro de una 12192

función miembro con la clase Singleton. SingletonPattern.cpp puede 12193

modificarse para usar esta aproximación:[138] 12194

//: C10:SingletonPattern2.cpp 12195

// Meyers' Singleton. 12196

#include <iostream> 12197

using namespace std; 12198

12199

class Singleton { 12200

int i; 12201

Singleton(int x) : i(x) { } 12202

void operator=(Singleton&); 12203

Singleton(const Singleton&); 12204

public: 12205

static Singleton& instance() { 12206

static Singleton s(47); 12207

return s; 12208

} 12209

int getValue() { return i; } 12210

void setValue(int x) { i = x; } 12211

}; 12212

Page 470: Pensar en C++ (Volumen 2)

470

12213

int main() { 12214

Singleton& s = Singleton::instance(); 12215

cout << s.getValue() << endl; 12216

Singleton& s2 = Singleton::instance(); 12217

s2.setValue(9); 12218

cout << s.getValue() << endl; 12219

} ///:~ 12220

Listado 9.9. C10/SingletonPattern2.cpp 12221

12222

Se da un caso especialmente interesante cuando dos Singletons 12223

dependen mutuamente el uno del otro, de esta forma: 12224

//: C10:FunctionStaticSingleton.cpp 12225

12226

class Singleton1 { 12227

Singleton1() {} 12228

public: 12229

static Singleton1& ref() { 12230

static Singleton1 single; 12231

return single; 12232

} 12233

}; 12234

12235

class Singleton2 { 12236

Page 471: Pensar en C++ (Volumen 2)

471

Singleton1& s1; 12237

Singleton2(Singleton1& s) : s1(s) {} 12238

public: 12239

static Singleton2& ref() { 12240

static Singleton2 single(Singleton1::ref()); 12241

return single; 12242

} 12243

Singleton1& f() { return s1; } 12244

}; 12245

12246

int main() { 12247

Singleton1& s1 = Singleton2::ref().f(); 12248

} ///:~ 12249

Listado 9.10. C10/FunctionStaticSingleton.cpp 12250

12251

Cuando se llama a Singleton2::ref(), hace que se cree su único objeto 12252

Singleton2. En el proceso de esta creación, se llama a Singleton1::ref(), y 12253

esto hace que se cree su objeto único Singleton1. Como esta técnica no se 12254

basa en el orden de linkado ni el de carga, el programador tiene mucho 12255

mayor control sobre la inicialización, lo que redunda en menos problemas. 12256

Otra variación del Singleton separa la unicidad de un objeto de su 12257

implementación. Esto se logra usando el "Patrón Plantilla Curiosamente 12258

Recursivo" mencionado en el Capítulo 5: 12259

//: C10:CuriousSingleton.cpp 12260

// Separates a class from its Singleton-ness (almost). 12261

#include <iostream> 12262

Page 472: Pensar en C++ (Volumen 2)

472

using namespace std; 12263

12264

template<class T> class Singleton { 12265

Singleton(const Singleton&); 12266

Singleton& operator=(const Singleton&); 12267

protected: 12268

Singleton() {} 12269

virtual ~Singleton() {} 12270

public: 12271

static T& instance() { 12272

static T theInstance; 12273

return theInstance; 12274

} 12275

}; 12276

12277

// A sample class to be made into a Singleton 12278

class MyClass : public Singleton<MyClass> { 12279

int x; 12280

protected: 12281

friend class Singleton<MyClass>; 12282

MyClass() { x = 0; } 12283

public: 12284

void setValue(int n) { x = n; } 12285

int getValue() const { return x; } 12286

}; 12287

Page 473: Pensar en C++ (Volumen 2)

473

12288

int main() { 12289

MyClass& m = MyClass::instance(); 12290

cout << m.getValue() << endl; 12291

m.setValue(1); 12292

cout << m.getValue() << endl; 12293

} ///:~ 12294

Listado 9.11. C10/CuriousSingleton.cpp 12295

12296

MyClass se convierte en Singleton: 12297

1. Haciendo que su constructor sea private o protected. 12298

2. Haciéndose amigo de Singleton<MyClass>. 12299

3. Derivando MyClass desde Singleton<MyClass>. 12300

La auto-referencia del paso 3 podría sonar inversímil, pero tal como se 12301

explicó en el Capítulo 5, funciona porque sólo hay una dependencia estática 12302

sobre el argumento plantilla de la plantilla Singleton. En otras palabras, el 12303

código de la clase Singleton<MyClass> puede ser instanciado por el 12304

compilador porque no depende del tamaño de MyClass. Es después, cuando 12305

se a Singleton<MyClass>::instance(), cuando se necesita el tamaño de 12306

MyClass, y para entonces MyClass ya se ha compilado y su tamaño se 12307

conoce.[139] 12308

Es interesante lo intrincado que un patrón tan simple como el Singleton 12309

puede llegar a ser, y ni siquiera se han tratado todavía asuntos de seguridad 12310

de hilos. Por último, el patrón Singleton debería usarse lo justo y necesario. 12311

Los verdaderos objetos Singleton rara vez aparecen, y la última cosa para la 12312

que debe usarse un Singleton es para remplazar a una variable global. [140] 12313

9.5. Comando: elegir la operación 12314

Page 474: Pensar en C++ (Volumen 2)

474

El patrón Comando es estructuralmente muy sencillo, pero puede tener un 12315

impacto importante en el desacoplamiento (y, por ende, en la limpieza) de su 12316

código. 12317

En "Advanced C++: Programming Styles And Idioms (Addison Wesley, 12318

1992)", Jim Coplien acuña el término functor, que es un objeto cuyo único 12319

propósito es encapsular una función (dado que functor tiene su significado 12320

en matemáticas, usaremos el término "objeto función", que es más explícito). 12321

El quid está en desacoplar la elección de la función que hay que llamar del 12322

sitio donde se llama a dicha función. 12323

Este término se menciona en el GoF, pero no se usa. Sin embargo, el 12324

concepto de "objeto función" se repite en numerosos patrones del libro. 12325

Un Comando es un objeto función en su estado más puro: una función que 12326

tiene un objeto. Al envolver una función en un objeto, puede pasarla a otras 12327

funciones u objetos como parámetro, para decirles que realicen esta 12328

operación concreta mientras llevan a cabo su petición. Se podría decir que 12329

un Comando es un Mensajero que lleva un comportamiento. 12330

//: C10:CommandPattern.cpp 12331

#include <iostream> 12332

#include <vector> 12333

using namespace std; 12334

12335

class Command { 12336

public: 12337

virtual void execute() = 0; 12338

}; 12339

12340

class Hello : public Command { 12341

public: 12342

Page 475: Pensar en C++ (Volumen 2)

475

void execute() { cout << "Hello "; } 12343

}; 12344

12345

class World : public Command { 12346

public: 12347

void execute() { cout << "World! "; } 12348

}; 12349

12350

class IAm : public Command { 12351

public: 12352

void execute() { cout << "I'm the command pattern!"; } 12353

}; 12354

12355

// An object that holds commands: 12356

class Macro { 12357

vector<Command*> commands; 12358

public: 12359

void add(Command* c) { commands.push_back(c); } 12360

void run() { 12361

vector<Command*>::iterator it = commands.begin(); 12362

while(it != commands.end()) 12363

(*it++)->execute(); 12364

} 12365

}; 12366

12367

Page 476: Pensar en C++ (Volumen 2)

476

int main() { 12368

Macro macro; 12369

macro.add(new Hello); 12370

macro.add(new World); 12371

macro.add(new IAm); 12372

macro.run(); 12373

} ///:~ 12374

Listado 9.12. C10/CommandPattern.cpp 12375

12376

El punto principal del Comando es permitirle dar una acción deseada a una 12377

función u objeto. En el ejemplo anterior, esto provee una manera de encolar 12378

un conjunto de acciones que se deben ejecutar colectivamente. Aquí, puede 12379

crear dinámicamente nuevos comportamientos, algo que puede hacer 12380

normalmente escribiendo nuevo código, pero en el ejemplo anterior podría 12381

hacerse interpretando un script (vea el patrón Intérprete si lo que necesita 12382

hacer se vuelve demasiado complicado). 12383

Según el GoF, los Comandos son un sustituto orientado a objetos de las 12384

retrollamadas (callbacks). [141] Sin embargo, pensamos que la palabra 12385

"retro" es una parte esencial del concepto de retrollamada -una retrollamada 12386

retorna al creador de la misma. Por otro lado, un objeto Comando, 12387

simplemente se crea y se entrega a alguna función u objeto, y no se 12388

permanece conectado de por vida al objecto Comando. 12389

Un ejemplo habitual del patrón Comando es la implementación de la 12390

funcionalidad de "deshacer" en una aplicación. Cada vez que el usuario 12391

realiza una operación, se coloca el correspondiente objeto Comando de 12392

deshacer en una cola. Cada objeto Comando que se ejecuta guarda el 12393

estado del programa en el paso anterior. 12394

9.5.1. Desacoplar la gestión de eventos con Comando 12395

Page 477: Pensar en C++ (Volumen 2)

477

Como se verá en el siguiente capítulo, una de las razones para emplear 12396

técnicas de concurrencia es facilitar la gestión de la programación dirigida 12397

por eventos, donde los eventos pueden aparecer en el programa de forma 12398

impredecible. Por ejemplo, un usuario que pulsa un botón de "Salir" mientras 12399

se está realizando una operación espera que el programa responda 12400

rápidamente. 12401

Un motivo para usar concurrencia es que previene el aclopamiento entre 12402

los bloques del código. Es decir, si está ejecutando un hilo aparte para vigilar 12403

el botónde salida, las operaciones normales de su programa no necesitan 12404

saber nada sobre el botón ni sobe ninguna de las demás operaciones que se 12405

están vigilando. 12406

Sin embargo, una vez que comprenda que el quiz está en el acoplamiento, 12407

puede evitarlo usando el patrón Comando. Cada operación normal debe 12408

llamar periódicamente a una función para que compruebe el estado de los 12409

eventos, pero con el patrón Comando, estas operaciones normales no tienen 12410

porqué saber nada sobre lo que están comprobando, y por lo tanto, están 12411

desacopladas del código de manejo de eventos: 12412

//: C10:MulticastCommand.cpp {RunByHand} 12413

// Decoupling event management with the Command pattern. 12414

#include <iostream> 12415

#include <vector> 12416

#include <string> 12417

#include <ctime> 12418

#include <cstdlib> 12419

using namespace std; 12420

12421

// Framework for running tasks: 12422

class Task { 12423

public: 12424

Page 478: Pensar en C++ (Volumen 2)

478

virtual void operation() = 0; 12425

}; 12426

12427

class TaskRunner { 12428

static vector<Task*> tasks; 12429

TaskRunner() {} // Make it a Singleton 12430

TaskRunner& operator=(TaskRunner&); // Disallowed 12431

TaskRunner(const TaskRunner&); // Disallowed 12432

static TaskRunner tr; 12433

public: 12434

static void add(Task& t) { tasks.push_back(&t); } 12435

static void run() { 12436

vector<Task*>::iterator it = tasks.begin(); 12437

while(it != tasks.end()) 12438

(*it++)->operation(); 12439

} 12440

}; 12441

12442

TaskRunner TaskRunner::tr; 12443

vector<Task*> TaskRunner::tasks; 12444

12445

class EventSimulator { 12446

clock_t creation; 12447

clock_t delay; 12448

public: 12449

Page 479: Pensar en C++ (Volumen 2)

479

EventSimulator() : creation(clock()) { 12450

delay = CLOCKS_PER_SEC/4 * (rand() % 20 + 1); 12451

cout << "delay = " << delay << endl; 12452

} 12453

bool fired() { 12454

return clock() > creation + delay; 12455

} 12456

}; 12457

12458

// Something that can produce asynchronous events: 12459

class Button { 12460

bool pressed; 12461

string id; 12462

EventSimulator e; // For demonstration 12463

public: 12464

Button(string name) : pressed(false), id(name) {} 12465

void press() { pressed = true; } 12466

bool isPressed() { 12467

if(e.fired()) press(); // Simulate the event 12468

return pressed; 12469

} 12470

friend ostream& 12471

operator<<(ostream& os, const Button& b) { 12472

return os << b.id; 12473

} 12474

Page 480: Pensar en C++ (Volumen 2)

480

}; 12475

12476

// The Command object 12477

class CheckButton : public Task { 12478

Button& button; 12479

bool handled; 12480

public: 12481

CheckButton(Button & b) : button(b), handled(false) {} 12482

void operation() { 12483

if(button.isPressed() && !handled) { 12484

cout << button << " pressed" << endl; 12485

handled = true; 12486

} 12487

} 12488

}; 12489

12490

// The procedures that perform the main processing. These 12491

// need to be occasionally "interrupted" in order to 12492

// check the state of the buttons or other events: 12493

void procedure1() { 12494

// Perform procedure1 operations here. 12495

// ... 12496

TaskRunner::run(); // Check all events 12497

} 12498

12499

Page 481: Pensar en C++ (Volumen 2)

481

void procedure2() { 12500

// Perform procedure2 operations here. 12501

// ... 12502

TaskRunner::run(); // Check all events 12503

} 12504

12505

void procedure3() { 12506

// Perform procedure3 operations here. 12507

// ... 12508

TaskRunner::run(); // Check all events 12509

} 12510

12511

int main() { 12512

srand(time(0)); // Randomize 12513

Button b1("Button 1"), b2("Button 2"), b3("Button 3"); 12514

CheckButton cb1(b1), cb2(b2), cb3(b3); 12515

TaskRunner::add(cb1); 12516

TaskRunner::add(cb2); 12517

TaskRunner::add(cb3); 12518

cout << "Control-C to exit" << endl; 12519

while(true) { 12520

procedure1(); 12521

procedure2(); 12522

procedure3(); 12523

} 12524

Page 482: Pensar en C++ (Volumen 2)

482

} ///:~ 12525

Listado 9.13. C10/MulticastCommand.cpp 12526

12527

Aquí, el objeto Comando está representado por Tareas ejecutadas por el 12528

Singleton TaskRunner. EventSimulator crea un retraso aleatorio, de modo 12529

que si se llama periódicamente a la función fired() el resultado cambiará de 12530

false a true en algún momento aleatorio. Los objetos EventSimulator se 12531

utilizan dentro de los Botones para simular que ocurre un evento de usuario 12532

en un momento impredecible. CheckButton es la implementación de la Tarea 12533

que es comprobada periódicamente por todo el código "normal" del progama. 12534

Puede ver cómo ocurre al final de procedure1(), procedure2() y procedure3(). 12535

Aunque esto requiere un poco más de razonamiento para establecerlo, 12536

verá en el Capítulo 11 que utilizar hilos requiere mucho pensamiento y 12537

cuidado para prevenir las muchas dificultades inhenerentes a la 12538

programación concurrente, por lo que la solución más simple puede ser 12539

preferible. También puede crear un esquema de hilos muy simple moviendo 12540

las llamadas a TaskRunner::run() a un objeto temporizador multi-hilo. Al 12541

hacer esto, se elimina todo el acoplamiento entre las operaciones "normales" 12542

(los "procedures" en el ejemplo anterior) y el código de eventos. 12543

9.6. Desacoplamiento de objetos 12544

Tanto el Proxy como el Estado proporcionen una clase sucedánea. El 12545

código habla a esta clase sucedánea, y la verdadera clase que hace el 12546

trabajo está escondida detrás de la sucedánea. Cuando usted llama a una 12547

función en la clase sucedánea, simplemente da un rodeo y llama a la función 12548

en la clase implementadora. Estos dos patrones son tan familiares que, 12549

estructuralmente, Proxy es un caso especial de Estado. Uno está tentado de 12550

juntar ambos en un patrón llamado Sucedánea, pero la intención de los dos 12551

patrones es distinta. Puede ser fácil caer en la trampa de pensar que si la 12552

estructura es la misma, los patrones son el mismo. Debe mirar siempre la 12553

intención del patrón para tener claro lo que hace. 12554

Page 483: Pensar en C++ (Volumen 2)

483

La idea básica es simple: cree una clase base, la sucedánea se deriva 12555

junto con la clase o clases que aportan la siguiente implementación: 12556

Cuando se crea una clase sucedánea, se le da una implementación a la 12557

que envía las llamadas a función. 12558

Estructuralmente, la diferencia entre Proxy y Estado es simple: un Proxy 12559

sólo tiene una implementación, mientras que Estado tiene más de una. La 12560

aplicación de los patrones se considera (en el GoF) distinta: Proxy controla el 12561

acceso a su implementación, mientras que Estado cambia la implementación 12562

dinámicamente. Sin embargo, si se amplía la noción de "controlar el acceso a 12563

la implementación", entonces los dos parecen ser parte de un todo. 12564

9.6.1. Proxy: FIXME: hablando en nombre de otro objeto 12565

Si se implementa un Proxy usando el diagrama anterior, tiene esta pinta: 12566

//: C10:ProxyDemo.cpp 12567

// Simple demonstration of the Proxy pattern. 12568

#include <iostream> 12569

using namespace std; 12570

12571

class ProxyBase { 12572

public: 12573

virtual void f() = 0; 12574

virtual void g() = 0; 12575

virtual void h() = 0; 12576

virtual ~ProxyBase() {} 12577

}; 12578

12579

class Implementation : public ProxyBase { 12580

public: 12581

Page 484: Pensar en C++ (Volumen 2)

484

void f() { cout << "Implementation.f()" << endl; } 12582

void g() { cout << "Implementation.g()" << endl; } 12583

void h() { cout << "Implementation.h()" << endl; } 12584

}; 12585

12586

class Proxy : public ProxyBase { 12587

ProxyBase* implementation; 12588

public: 12589

Proxy() { implementation = new Implementation(); } 12590

~Proxy() { delete implementation; } 12591

// Forward calls to the implementation: 12592

void f() { implementation->f(); } 12593

void g() { implementation->g(); } 12594

void h() { implementation->h(); } 12595

}; 12596

12597

int main() { 12598

Proxy p; 12599

p.f(); 12600

p.g(); 12601

p.h(); 12602

} ///:~ 12603

Listado 9.14. C10/ProxyDemo.cpp 12604

12605

Page 485: Pensar en C++ (Volumen 2)

485

En algunos casos, Implementation no necesita la misma interfaz que 12606

Proxy, siempre y cuando Proxy esté de alguna forma hablando en nombre de 12607

la clase Implementación y referenciando llamadas a función hacia ella, 12608

entonces la idea básica se satisface (note que esta afirmación está reñida 12609

con la definición de Proxy del GoF). Sin embargo, con una interfaz común es 12610

posible realizar un reemplazo FIXME: drop-in del proxy en el código del 12611

cliente -el código del cliente está escrito para hablar al objeto original, y no 12612

necesita ser cambiado para aceptar el proxy (éste es probablemente el quiz 12613

principal de Proxy). Además, se fuerza a que Implementation complete, a 12614

través de la interfaz común, todas las funciones que Proxy necesita llamar. 12615

La diferencia entre Proxy y Estado está en los problemas que pueden 12616

resolver. Los usos más comunes para Proxy que describe el GoF son: 12617

1. Proxy remoto. Representan a objetos en un espacio de direcciones 12618

distinto. Lo implementan algunas tecnologías de objetos remotos. 12619

2. Proxy virtual. Proporciona inicialización FIXME: vaga para crear objetos 12620

costosos bajo demanda. 12621

3. Proxy de protección. Se usa cuando no se desea que el programador 12622

cliente tenga acceso completo al objecto representado. 12623

4. Referencia inteligente. Para añadir acciones adicionales cuando se 12624

acceda al objeto representado. El conteo de referencias es un buen ejemplo: 12625

mantiene un registro del número de referencias que se mantienen para un 12626

objeto en particular, para implementar el FIXME: copy-on-write idiom y para 12627

prevenir el FIXME: object aliasing. 12628

9.6.2. Estado: cambiar el comportamiento del objeto 12629

El patrón Estado produce un objeto que parece que cambia su clase, y le 12630

será útil cuando descubra que tiene código condicional en todas o casi todas 12631

sus funciones. Al igual que Proxy, un Estado se crea teniendo un objeto 12632

front-end que usa un objeto back-end de implementación para completar sus 12633

tareas. Sin embargo, el patrón Estado alterna entre una implementación y 12634

otra durante la vida del objeto front-end, para mostrar un comportamiento 12635

distinto ante las mismas llamadas a función. Es una forma de mejorar la 12636

implementación de su código cuando realiza un montón de pruebas en cada 12637

Page 486: Pensar en C++ (Volumen 2)

486

una de sus funciones antes de decidir qué hacer con esa función. Por 12638

ejemplo, el cuento del príncipe convertido en rana contiene un objeto (la 12639

criatura) que se comporta de modo distinto dependiendo del estado en el que 12640

se encuentre. Podría implementar esto comprobando un bool: 12641

//: C10:KissingPrincess.cpp 12642

#include <iostream> 12643

using namespace std; 12644

12645

class Creature { 12646

bool isFrog; 12647

public: 12648

Creature() : isFrog(true) {} 12649

void greet() { 12650

if(isFrog) 12651

cout << "Ribbet!" << endl; 12652

else 12653

cout << "Darling!" << endl; 12654

} 12655

void kiss() { isFrog = false; } 12656

}; 12657

12658

int main() { 12659

Creature creature; 12660

creature.greet(); 12661

creature.kiss(); 12662

creature.greet(); 12663

Page 487: Pensar en C++ (Volumen 2)

487

} ///:~ 12664

Listado 9.15. C10/KissingPrincess.cpp 12665

12666

Sin embargo, la función greet(), y cualquier otra función que tenga que 12667

comprobar isFrog antes de realizar sus operaciones, acaban con código 12668

poco elegante, especialmente cuando haya que añadir estados adicionales al 12669

sistema. Delegando las operaciones a un objeto Estado que puede 12670

cambiarse, el código se simplifica. 12671

//: C10:KissingPrincess2.cpp 12672

// The State pattern. 12673

#include <iostream> 12674

#include <string> 12675

using namespace std; 12676

12677

class Creature { 12678

class State { 12679

public: 12680

virtual string response() = 0; 12681

}; 12682

class Frog : public State { 12683

public: 12684

string response() { return "Ribbet!"; } 12685

}; 12686

class Prince : public State { 12687

public: 12688

Page 488: Pensar en C++ (Volumen 2)

488

string response() { return "Darling!"; } 12689

}; 12690

State* state; 12691

public: 12692

Creature() : state(new Frog()) {} 12693

void greet() { 12694

cout << state->response() << endl; 12695

} 12696

void kiss() { 12697

delete state; 12698

state = new Prince(); 12699

} 12700

}; 12701

12702

int main() { 12703

Creature creature; 12704

creature.greet(); 12705

creature.kiss(); 12706

creature.greet(); 12707

} ///:~ 12708

Listado 9.16. C10/KissingPrincess2.cpp 12709

12710

No es necesario hacer las clases FIXME: implementadoras anidadas ni 12711

privadas, pero si lo hace, el código será más limpio. 12712

Page 489: Pensar en C++ (Volumen 2)

489

Note que los cambios en las clases Estado se propagan automáticamente 12713

por todo su código, en lugar de requerir una edición de las clases para 12714

efectuar los cambios. 12715

9.7. Adaptador 12716

Un Adaptador coge un tipo y genera una interfaz para algún otro tipo. Es 12717

útil cuando se tiene una librería o trozo de código que tiene una interfaz 12718

particular, y otra librería o trozo de código que usa las mismas ideas básicas 12719

que la primera librería, pero se expresa de forma diferente. Si se adaptan las 12720

formas de expresión entre sí, se puede crear una solución rápidamente. 12721

Suponga que tiene una clase productora que genera los números de 12722

Fibonacci: 12723

//: C10:FibonacciGenerator.h 12724

#ifndef FIBONACCIGENERATOR_H 12725

#define FIBONACCIGENERATOR_H 12726

12727

class FibonacciGenerator { 12728

int n; 12729

int val[2]; 12730

public: 12731

FibonacciGenerator() : n(0) { val[0] = val[1] = 0; } 12732

int operator()() { 12733

int result = n > 2 ? val[0] + val[1] : n > 0 ? 1 : 0; 12734

++n; 12735

val[0] = val[1]; 12736

val[1] = result; 12737

return result; 12738

Page 490: Pensar en C++ (Volumen 2)

490

} 12739

int count() { return n; } 12740

}; 12741

#endif // FIBONACCIGENERATOR_H ///:~ 12742

Listado 9.17. C10/FibonacciGenerator.h 12743

12744

Como es un productor, se usa llamando al operador(), de esta forma: 12745

//: C10:FibonacciGeneratorTest.cpp 12746

#include <iostream> 12747

#include "FibonacciGenerator.h" 12748

using namespace std; 12749

12750

int main() { 12751

FibonacciGenerator f; 12752

for(int i =0; i < 20; i++) 12753

cout << f.count() << ": " << f() << endl; 12754

} ///:~ 12755

Listado 9.18. C10/FibonacciGeneratorTest.cpp 12756

12757

A lo mejor le gustaría coger este generador y realizar operaciones de 12758

algoritmos numéricos STL con él. Desafortunadamente, los algoritmos STL 12759

sólo trabajan con iteradores, así que tiene dos interfaces que no casan. La 12760

solución es crear un adaptador que coja el FibonacciGenerator y produzca 12761

un iterador para los algoritmos STL a usar. Dado que los algoritmos 12762

numéricos sólo necesitan un iterador de entrada, el Adaptador es bastante 12763

directo (para algo que produce un iterador STL, es decir): 12764

Page 491: Pensar en C++ (Volumen 2)

491

//: C10:FibonacciAdapter.cpp 12765

// Adapting an interface to something you already have. 12766

#include <iostream> 12767

#include <numeric> 12768

#include "FibonacciGenerator.h" 12769

#include "../C06/PrintSequence.h" 12770

using namespace std; 12771

12772

class FibonacciAdapter { // Produce an iterator 12773

FibonacciGenerator f; 12774

int length; 12775

public: 12776

FibonacciAdapter(int size) : length(size) {} 12777

class iterator; 12778

friend class iterator; 12779

class iterator : public std::iterator< 12780

std::input_iterator_tag, FibonacciAdapter, ptrdiff_t> { 12781

FibonacciAdapter& ap; 12782

public: 12783

typedef int value_type; 12784

iterator(FibonacciAdapter& a) : ap(a) {} 12785

bool operator==(const iterator&) const { 12786

return ap.f.count() == ap.length; 12787

} 12788

bool operator!=(const iterator& x) const { 12789

Page 492: Pensar en C++ (Volumen 2)

492

return !(*this == x); 12790

} 12791

int operator*() const { return ap.f(); } 12792

iterator& operator++() { return *this; } 12793

iterator operator++(int) { return *this; } 12794

}; 12795

iterator begin() { return iterator(*this); } 12796

iterator end() { return iterator(*this); } 12797

}; 12798

12799

int main() { 12800

const int SZ = 20; 12801

FibonacciAdapter a1(SZ); 12802

cout << "accumulate: " 12803

<< accumulate(a1.begin(), a1.end(), 0) << endl; 12804

FibonacciAdapter a2(SZ), a3(SZ); 12805

cout << "inner product: " 12806

<< inner_product(a2.begin(), a2.end(), a3.begin(), 0) 12807

<< endl; 12808

FibonacciAdapter a4(SZ); 12809

int r1[SZ] = {0}; 12810

int* end = partial_sum(a4.begin(), a4.end(), r1); 12811

print(r1, end, "partial_sum", " "); 12812

FibonacciAdapter a5(SZ); 12813

int r2[SZ] = {0}; 12814

Page 493: Pensar en C++ (Volumen 2)

493

end = adjacent_difference(a5.begin(), a5.end(), r2); 12815

print(r2, end, "adjacent_difference", " "); 12816

} ///:~ 12817

Listado 9.19. C10/FibonacciAdapter.cpp 12818

12819

Se inicializa un FibonacciAdapter diciéndole cuán largo puede ser la 12820

secuencia de Fibonacci. Cuando se crea un iterador, simplemente captura 12821

una referencia al FibonacciAdapter que lo contiene para que pueda acceder 12822

al FibonacciGenerator y la longitud. Observe que la comparación de 12823

equivalencia ignora el valor de la derecha, porque el único asunto importante 12824

es si el generador ha alcanzado su longitud. Además, el operator++() no 12825

modifica el iterador; la única operación que cambia el estado del 12826

FibonacciAdapter es llamar a la función operator() del generador en el 12827

FibonacciGenerator. Puede aceptarse esta versión extremadamente simple 12828

del iterador porque las restricciones de un Input Iterator son muy estrictas; 12829

concretamente, sólo se puede leer cada valor de la secuencia una vez. 12830

En main(), puede verse que los cuatro tipos distintos de algoritmos 12831

numéricos se testan satisfactoriamente con el FibonacciAdapter. 12832

9.8. Template Method 12833

El marco de trabajo de una aplicación nos permite heredar de una clase o 12834

conjunto de ellas y crear una nueva aplicación, reutilizando la mayoría del 12835

código de las clases existentes y sobreescribiendo una o más funciones para 12836

adaptar la aplicación a nuestras necesidades. 12837

Una característica importante de Template Method es que está definido en 12838

la clase base (a veces como una función privada) y no puede cambiarse -el 12839

Template Method es lo que permanece invariable. Llama a otras funciones 12840

de clase base (las que se sobreescriben) para hacer su trabajo, pero el 12841

programador cliente no es necesariamente capaz de llamarlo directamente, 12842

como puede verse aquí: 12843

Page 494: Pensar en C++ (Volumen 2)

494

//: C10:TemplateMethod.cpp 12844

// Simple demonstration of Template Method. 12845

#include <iostream> 12846

using namespace std; 12847

12848

class ApplicationFramework { 12849

protected: 12850

virtual void customize1() = 0; 12851

virtual void customize2() = 0; 12852

public: 12853

void templateMethod() { 12854

for(int i = 0; i < 5; i++) { 12855

customize1(); 12856

customize2(); 12857

} 12858

} 12859

}; 12860

12861

// Create a new "application": 12862

class MyApp : public ApplicationFramework { 12863

protected: 12864

void customize1() { cout << "Hello "; } 12865

void customize2() { cout << "World!" << endl; } 12866

}; 12867

12868

Page 495: Pensar en C++ (Volumen 2)

495

int main() { 12869

MyApp app; 12870

app.templateMethod(); 12871

} ///:~ 12872

Listado 9.20. C10/TemplateMethod.cpp 12873

12874

El motor que ejecuta la aplicación es el Template Method. En una 12875

aplicación gráfica, este motor sería el bucle principal de eventos. El 12876

programador cliente simplemente proporciona las definiciones para 12877

customize1() y customize2(), y la aplicación está lista para ejecutarse. 12878

9.9. Estrategia: elegir el algoritno en tiempo de 12879

ejecución 12880

Observe que el Template Method es el código que no cambia, y las 12881

funciones que sobreescribe son el código cambiante. Sin embargo, este 12882

cambio está fijado en tiempo de compilación, a través de la herencia. 12883

Siguiendo la máxima de preferir composición a herencia, se puede usar una 12884

composición para aproximar el problema de separar código que cambia de 12885

código que permanece, y generar el patrón Estrategia. Esta aproximación 12886

tiene un beneficio único: en tiempo de ejecución se puede insertar el código 12887

que cambia. Estrategia también añade un Contexto que puede ser una clase 12888

sucedánea que controla la selección y uso del objeto estrategia -¡igual que 12889

Estado!. 12890

Estrategia significa exactamente eso: se puede resolver un problema de 12891

muchas maneras. Imagine que ha olvidado el nombre de alguien. Estas son 12892

las diferentes maneras para lidiar con esa situación: 12893

//: C10:Strategy.cpp 12894

// The Strategy design pattern. 12895

#include <iostream> 12896

Page 496: Pensar en C++ (Volumen 2)

496

using namespace std; 12897

12898

class NameStrategy { 12899

public: 12900

virtual void greet() = 0; 12901

}; 12902

12903

class SayHi : public NameStrategy { 12904

public: 12905

void greet() { 12906

cout << "Hi! How's it going?" << endl; 12907

} 12908

}; 12909

12910

class Ignore : public NameStrategy { 12911

public: 12912

void greet() { 12913

cout << "(Pretend I don't see you)" << endl; 12914

} 12915

}; 12916

12917

class Admission : public NameStrategy { 12918

public: 12919

void greet() { 12920

cout << "I'm sorry. I forgot your name." << endl; 12921

Page 497: Pensar en C++ (Volumen 2)

497

} 12922

}; 12923

12924

// The "Context" controls the strategy: 12925

class Context { 12926

NameStrategy& strategy; 12927

public: 12928

Context(NameStrategy& strat) : strategy(strat) {} 12929

void greet() { strategy.greet(); } 12930

}; 12931

12932

int main() { 12933

SayHi sayhi; 12934

Ignore ignore; 12935

Admission admission; 12936

Context c1(sayhi), c2(ignore), c3(admission); 12937

c1.greet(); 12938

c2.greet(); 12939

c3.greet(); 12940

} ///:~ 12941

Listado 9.21. C10/Strategy.cpp 12942

12943

Normalmente, Context::greet() sería más complejo; es el análogo de 12944

Template Method porque contiene el código que no cambia. Pero puede ver 12945

en main() que la elección de la estrategia puede realizarse en tiempo de 12946

Page 498: Pensar en C++ (Volumen 2)

498

ejecución. Llendo un paso más allá, se puede combinar esto con el patrón 12947

Estado y cambiar la Estrategia durante el tiempo de vida del objeto Contexto. 12948

9.10. Cadena de Responsabilidad: intentar una 12949

secuencia de estrategias 12950

Debe pensarse en Cadena de Responsabilidad como en una 12951

generalización dinámica de la recursión, usando objetos Estrategia. Se hace 12952

una llamada y cada Estrategia de la secuencia intenta satisfacer la llamada. 12953

El proceso termina cuando una de las Estrategias tiene éxito o la cadena 12954

termina. En la recursión, una función se llama a sí misma una y otra vez 12955

hasta que se alcanza una condición de finalización; con Cadena de 12956

Responsabilidad, una función se llama a sí misma, la cual (moviendo la 12957

cadena de Estrategias) llama a una implementación diferente de la función, 12958

etc, hasta que se alcanza la condición de finalización. Dicha condición puede 12959

ser que se ha llegado al final de la cadena (lo que devuelve un objeto por 12960

defecto; puede que no sea capaz de proporcionar un resultado por defecto, 12961

así que debe ser capaz de determinar el éxito o fracaso de la cadena) o que 12962

una de las Estrategias ha tenido éxito. 12963

En lugar de llamar a una única función para satisfacer una petición, hay 12964

múltiples funciones en la cadetna que tienen la oportunidad de hacerlo, de 12965

manera que tiene el aspecto de un sistema experto. Dado que la cadena es 12966

en la práctica una lista, puede crearse dinámicamente, así que podría verse 12967

como una sentencia switch más general y construida dinámicamente. 12968

En el GoF, hay bastante discusión sobre cómo crear la cadena de 12969

responsabilidad como una lista enlazada. Sin embargo, cuando se estudia el 12970

patrón, no debería importar cómo se crea la cadena; eso es un detalle de 12971

implementación. Como el GoF se escribió antes de que los contenedores 12972

STL estuvieran disponibles en la mayoría de los compiladores de C++, las 12973

razones más probables son (1) que no había listas incluídas y por lo tanto 12974

tenían que crear una y (2) que las estructuras de datos suelen verse como 12975

una habilidad fundamental en las Escuelas (o Facultades), y a los autores del 12976

GoF no se les ocurrió la idea de que las estructuras de datos fueran 12977

herramientas estándar disponibles junto con el lenguaje de programación.. 12978

Los detalles del contenedor usado para implementar la Cadena de 12979

Page 499: Pensar en C++ (Volumen 2)

499

Responsabilidad como una cadena (una lista enlazada en el GoF) no añaden 12980

nada a la solución, y puede implementarse usando un contenedor STL, como 12981

se muestra abajo. 12982

Aquí puede ver una Cadena de Responsabilidad que encuentra 12983

automáticamente una solución usando un mecanismo para recorrer 12984

automática y recursivamente cada Estrategia de la cadena: 12985

//: C10:ChainOfReponsibility.cpp 12986

// The approach of the five-year-old. 12987

#include <iostream> 12988

#include <vector> 12989

#include "../purge.h" 12990

using namespace std; 12991

12992

enum Answer { NO, YES }; 12993

12994

class GimmeStrategy { 12995

public: 12996

virtual Answer canIHave() = 0; 12997

virtual ~GimmeStrategy() {} 12998

}; 12999

13000

class AskMom : public GimmeStrategy { 13001

public: 13002

Answer canIHave() { 13003

cout << "Mooom? Can I have this?" << endl; 13004

return NO; 13005

Page 500: Pensar en C++ (Volumen 2)

500

} 13006

}; 13007

13008

class AskDad : public GimmeStrategy { 13009

public: 13010

Answer canIHave() { 13011

cout << "Dad, I really need this!" << endl; 13012

return NO; 13013

} 13014

}; 13015

13016

class AskGrandpa : public GimmeStrategy { 13017

public: 13018

Answer canIHave() { 13019

cout << "Grandpa, is it my birthday yet?" << endl; 13020

return NO; 13021

} 13022

}; 13023

13024

class AskGrandma : public GimmeStrategy { 13025

public: 13026

Answer canIHave() { 13027

cout << "Grandma, I really love you!" << endl; 13028

return YES; 13029

} 13030

Page 501: Pensar en C++ (Volumen 2)

501

}; 13031

13032

class Gimme : public GimmeStrategy { 13033

vector<GimmeStrategy*> chain; 13034

public: 13035

Gimme() { 13036

chain.push_back(new AskMom()); 13037

chain.push_back(new AskDad()); 13038

chain.push_back(new AskGrandpa()); 13039

chain.push_back(new AskGrandma()); 13040

} 13041

Answer canIHave() { 13042

vector<GimmeStrategy*>::iterator it = chain.begin(); 13043

while(it != chain.end()) 13044

if((*it++)->canIHave() == YES) 13045

return YES; 13046

// Reached end without success... 13047

cout << "Whiiiiinnne!" << endl; 13048

return NO; 13049

} 13050

~Gimme() { purge(chain); } 13051

}; 13052

13053

int main() { 13054

Gimme chain; 13055

Page 502: Pensar en C++ (Volumen 2)

502

chain.canIHave(); 13056

} ///:~ 13057

Listado 9.22. C10/ChainOfReponsibility.cpp 13058

13059

Observe que la clase de Contexto Gimme y todas las clases Estrategia 13060

derivan de la misma clase base, GimmeStrategy. 13061

Si estudia la sección sobre Cadena de Responsabilidad del GoF, verá que 13062

la estructura difiere significativamente de la que se muestra más arriba, 13063

porque ellos se centran en crear su propia lista enlazada. Sin embargo, si 13064

mantiene en mente que la esencia de Cadena de Responsabilidad es probar 13065

muchas soluciones hasta que encuentre la que funciona, se dará cuenta de 13066

que la implementación del mecanismo de secuenciación no es parte esencial 13067

del patrón. 13068

9.11. Factorías: encapsular la creación de objetos 13069

Cuando se descubre que se necesitan añadir nuevos tipos a un sistema, el 13070

primer paso más sensato es usar polimorfismo para crear una interfaz común 13071

para esos nuevos tipos. Así, se separa el resto del código en el sistema del 13072

conocimiento de los tipos específicos que se están añadiendo. Los tipos 13073

nuevos pueden añadirse sin "molestar" al código existente, o eso parece. A 13074

primera vista, podría parecer que hace falta cambiar el código únicamente en 13075

los lugares donde se hereda un tipo nuevo, pero esto no es del todo cierto. 13076

Todavía hay que crear un objeto de este nuevo tipo, y en el momento de la 13077

creación hay que especificar qué constructor usar. Por lo tanto, si el codigo 13078

que crea objetos está distribuido por toda la aplicación, se obtiene el mismo 13079

problema que cuando se añaden tipos -hay que localizar todos los puntos del 13080

código donde el tipo tiene importancia. Lo que imoporta es la creación del 13081

tipo, más que el uso del mismo (de eso se encarga el polimorfismo), pero el 13082

efecto es el mismo: añadir un nuevo tipo puede causar problemas. 13083

La solución es forzar a que la creación de objetos se lleve a cabo a través 13084

de una factoría común, en lugar de permitir que el código creacional se 13085

Page 503: Pensar en C++ (Volumen 2)

503

disperse por el sistema. Si todo el código del programa debe ir a esta factoría 13086

cada vez que necesita crear uno de esos objetos, todo lo que hay que hacer 13087

para añadir un objeto es modificar la factoría. Este diseño es una variación 13088

del patrón conocido comúnmente como Factory Method. Dado que todo 13089

programa orientado a objetos crea objetos, y como es probable que haya que 13090

extender el programa añadiendo nuevos tipos, las factorías pueden ser el 13091

más útil de todos los patrones de diseño. 13092

Como ejemplo, considere el ampliamente usado ejemplo de figura 13093

(Shape). Una aproximación para implementar una factoría es definir una 13094

función miembro estática en la clase base: 13095

//: C10:ShapeFactory1.cpp 13096

#include <iostream> 13097

#include <stdexcept> 13098

#include <cstddef> 13099

#include <string> 13100

#include <vector> 13101

#include "../purge.h" 13102

using namespace std; 13103

13104

class Shape { 13105

public: 13106

virtual void draw() = 0; 13107

virtual void erase() = 0; 13108

virtual ~Shape() {} 13109

class BadShapeCreation : public logic_error { 13110

public: 13111

BadShapeCreation(string type) 13112

Page 504: Pensar en C++ (Volumen 2)

504

: logic_error("Cannot create type " + type) {} 13113

}; 13114

static Shape* factory(const string& type) 13115

throw(BadShapeCreation); 13116

}; 13117

13118

class Circle : public Shape { 13119

Circle() {} // Private constructor 13120

friend class Shape; 13121

public: 13122

void draw() { cout << "Circle::draw" << endl; } 13123

void erase() { cout << "Circle::erase" << endl; } 13124

~Circle() { cout << "Circle::~Circle" << endl; } 13125

}; 13126

13127

class Square : public Shape { 13128

Square() {} 13129

friend class Shape; 13130

public: 13131

void draw() { cout << "Square::draw" << endl; } 13132

void erase() { cout << "Square::erase" << endl; } 13133

~Square() { cout << "Square::~Square" << endl; } 13134

}; 13135

13136

Shape* Shape::factory(const string& type) 13137

Page 505: Pensar en C++ (Volumen 2)

505

throw(Shape::BadShapeCreation) { 13138

if(type == "Circle") return new Circle; 13139

if(type == "Square") return new Square; 13140

throw BadShapeCreation(type); 13141

} 13142

13143

char* sl[] = { "Circle", "Square", "Square", 13144

"Circle", "Circle", "Circle", "Square" }; 13145

13146

int main() { 13147

vector<Shape*> shapes; 13148

try { 13149

for(size_t i = 0; i < sizeof sl / sizeof sl[0]; i++) 13150

shapes.push_back(Shape::factory(sl[i])); 13151

} catch(Shape::BadShapeCreation e) { 13152

cout << e.what() << endl; 13153

purge(shapes); 13154

return EXIT_FAILURE; 13155

} 13156

for(size_t i = 0; i < shapes.size(); i++) { 13157

shapes[i]->draw(); 13158

shapes[i]->erase(); 13159

} 13160

purge(shapes); 13161

} ///:~ 13162

Page 506: Pensar en C++ (Volumen 2)

506

Listado 9.23. C10/ShapeFactory1.cpp 13163

13164

La función factory() toma un argumento que le permite determinar qué tipo 13165

de figura crear. Aquí, el argumento es una cadena, pero podría ser cualquier 13166

conjunto de datos. El método factory() es el único código del sistema que hay 13167

que cambiar cuando se añade un nuevo tipo de figura. (Los datos de 13168

inicialización para los objetos vendrán supuestamente de algún sitio fuera del 13169

sistema y no serán un FIXME: hard-coded array como en el ejemplo.) 13170

Para asegurar que la creación sólo puede realizarse en factory(), los 13171

constructores de cada tipo específico de figura se hacen privados, y Shape 13172

se declara como friend de forma que factory() tiene acceso a los mismos. 13173

(También se podría declarar sólamente Shape::factory() como friend, pero 13174

parece razonablemente inocuo declarar la clase base entera.) Hay otra 13175

implicación importante de este diseño -la clase base, Shape, debe conocer 13176

ahora los detalles de todas las clases derivadas -una propiedad que el 13177

diseño orientado a objetos intenta evitar. Para frameworks o cualquier librería 13178

de clases que deban poder extenderse, esto hace que se convierta 13179

rápidamente en algo difícil de manejar, ya que la clase base debe 13180

actualizarse en cuanto se añada un tipo nuevo a la jerarquía. Las factorías 13181

polimórficas, descritas en la siguiente subsección, se pueden usar para evitar 13182

esta dependencia circular tan poco deseada. 13183

9.11.1. Factorías polimórficas 13184

La función estática factory() en el ejemplo anterior fuerza que las 13185

operaciones de creación se centren en un punto, de forma que sea el único 13186

sitio en el que haya que cambiar código. Esto es, sin duda, una solución 13187

razonable, ya que encapsula amablemente el proceso de crear objetos. Sin 13188

embargo, el GoF enfatiza que la razón de ser del patrón Factory Method es 13189

que diferentes tipos de factorías se puedan derivar de la factoría básica. 13190

Factory Method es, de hecho, un tipo especial de factoría polimórfica. Esto 13191

es ShapeFactory1.cpp modificado para que los Factory Methods estén en 13192

una clase aparte como funciones virtuales. 13193

//: C10:ShapeFactory2.cpp 13194

Page 507: Pensar en C++ (Volumen 2)

507

// Polymorphic Factory Methods. 13195

#include <iostream> 13196

#include <map> 13197

#include <string> 13198

#include <vector> 13199

#include <stdexcept> 13200

#include <cstddef> 13201

#include "../purge.h" 13202

using namespace std; 13203

13204

class Shape { 13205

public: 13206

virtual void draw() = 0; 13207

virtual void erase() = 0; 13208

virtual ~Shape() {} 13209

}; 13210

13211

class ShapeFactory { 13212

virtual Shape* create() = 0; 13213

static map<string, ShapeFactory*> factories; 13214

public: 13215

virtual ~ShapeFactory() {} 13216

friend class ShapeFactoryInitializer; 13217

class BadShapeCreation : public logic_error { 13218

public: 13219

Page 508: Pensar en C++ (Volumen 2)

508

BadShapeCreation(string type) 13220

: logic_error("Cannot create type " + type) {} 13221

}; 13222

static Shape* 13223

createShape(const string& id) throw(BadShapeCreation) { 13224

if(factories.find(id) != factories.end()) 13225

return factories[id]->create(); 13226

else 13227

throw BadShapeCreation(id); 13228

} 13229

}; 13230

13231

// Define the static object: 13232

map<string, ShapeFactory*> ShapeFactory::factories; 13233

13234

class Circle : public Shape { 13235

Circle() {} // Private constructor 13236

friend class ShapeFactoryInitializer; 13237

class Factory; 13238

friend class Factory; 13239

class Factory : public ShapeFactory { 13240

public: 13241

Shape* create() { return new Circle; } 13242

friend class ShapeFactoryInitializer; 13243

}; 13244

Page 509: Pensar en C++ (Volumen 2)

509

public: 13245

void draw() { cout << "Circle::draw" << endl; } 13246

void erase() { cout << "Circle::erase" << endl; } 13247

~Circle() { cout << "Circle::~Circle" << endl; } 13248

}; 13249

13250

class Square : public Shape { 13251

Square() {} 13252

friend class ShapeFactoryInitializer; 13253

class Factory; 13254

friend class Factory; 13255

class Factory : public ShapeFactory { 13256

public: 13257

Shape* create() { return new Square; } 13258

friend class ShapeFactoryInitializer; 13259

}; 13260

public: 13261

void draw() { cout << "Square::draw" << endl; } 13262

void erase() { cout << "Square::erase" << endl; } 13263

~Square() { cout << "Square::~Square" << endl; } 13264

}; 13265

13266

// Singleton to initialize the ShapeFactory: 13267

class ShapeFactoryInitializer { 13268

static ShapeFactoryInitializer si; 13269

Page 510: Pensar en C++ (Volumen 2)

510

ShapeFactoryInitializer() { 13270

ShapeFactory::factories["Circle"]= new Circle::Factory; 13271

ShapeFactory::factories["Square"]= new Square::Factory; 13272

} 13273

~ShapeFactoryInitializer() { 13274

map<string, ShapeFactory*>::iterator it = 13275

ShapeFactory::factories.begin(); 13276

while(it != ShapeFactory::factories.end()) 13277

delete it++->second; 13278

} 13279

}; 13280

13281

// Static member definition: 13282

ShapeFactoryInitializer ShapeFactoryInitializer::si; 13283

13284

char* sl[] = { "Circle", "Square", "Square", 13285

"Circle", "Circle", "Circle", "Square" }; 13286

13287

int main() { 13288

vector<Shape*> shapes; 13289

try { 13290

for(size_t i = 0; i < sizeof sl / sizeof sl[0]; i++) 13291

shapes.push_back(ShapeFactory::createShape(sl[i])); 13292

} catch(ShapeFactory::BadShapeCreation e) { 13293

cout << e.what() << endl; 13294

Page 511: Pensar en C++ (Volumen 2)

511

return EXIT_FAILURE; 13295

} 13296

for(size_t i = 0; i < shapes.size(); i++) { 13297

shapes[i]->draw(); 13298

shapes[i]->erase(); 13299

} 13300

purge(shapes); 13301

} ///:~ 13302

Listado 9.24. C10/ShapeFactory2.cpp 13303

13304

Ahora, Factory Method aparece en su propia clase, ShapeFactory, como 13305

virtual create(). Es una función miembro privada, lo que significa que no 13306

puede ser llamada directametne, pero puede ser sobreescrita. Las subclases 13307

de Shape deben crear cada una su propias subclases de ShapeFactory y 13308

sobreescribir el método create para crear un objeto de su propio tipe. Estas 13309

factorías son privadas, de forma que sólo pueden ser accedidas desde el 13310

Factory Method principal. De esta forma, todo el código cliente debe pasar a 13311

través del Factory Method para crear objetos. 13312

La verdadera creación de figuras se realiza llamando a 13313

ShapeFactory::createShape( ), que es una función estática que usa el mapa 13314

en ShapeFactory para encontrar la objeto factoría apropiado basándose en el 13315

identificador que se le pasa. La factoría crea el objeto figura directamente, 13316

pero podría imaginarse un problema más complejo en el que el objeto 13317

factoría apropiado se devuelve y luego lo usa quien lo ha llamado para crear 13318

un objeto de una manera más sofisticada. Sin embargo, parece que la 13319

mayoría del tiempo no hacen falta las complejidades del Factory Method 13320

polimórfico, y bastará con una única función estática en la clase base (como 13321

se muestra en ShapeFactory1.cpp). 13322

Observe que el ShapeFactory debe ser inicializado cargando su mapa con 13323

objetos factory, lo que tiene lugar en el Singleton ShapeFactoryInitializer. Así 13324

Page 512: Pensar en C++ (Volumen 2)

512

que para añadir un nuevo tipo a este diseño debe definir el tipo, crear una 13325

factoría, y modificar ShapeFactoryInitializer para que se inserte una instancia 13326

de su factoría en el mapa. Esta complejidad extra, sugiere de nuevo el uso 13327

de un Factory Method estático si no necesita crear objetos factoría 13328

individuales. 13329

9.11.2. Factorías abstractas 13330

El patrón Factoría Abstracta se parece a las factorías que hemos visto 13331

anteriormente, pero con varios Factory Methods. Cada uno de los Factory 13332

Method crea una clase distinta de objeto. Cuando se crea el objecto factoría, 13333

se decide cómo se usarán todos los objetos creados con esa factoría. El 13334

ejemplo del GoF implementa la portabilidad a través de varias interfaces 13335

gráficas de usuario (GUI): se crea el objeto factoría apropiado para la GUI 13336

con la que se está trabajando y desde ahí en adelante, cuando le pida un 13337

menú, botón, barra deslizante y demás, creará automáticamente la versión 13338

apropiada para la GUI de ese elemento. Por lo tanto, es posible aislar, en un 13339

solo lugar, el efecto de cambiar de una GUI a otra. 13340

Por ejemplo, suponga que está creando un entorno para juegos de 13341

propósito general y quiere ser capaz de soportar diferentes tipos de juegos. 13342

Así es como sería usando una Factoría Abstracta: 13343

//: C10:AbstractFactory.cpp 13344

// A gaming environment. 13345

#include <iostream> 13346

using namespace std; 13347

13348

class Obstacle { 13349

public: 13350

virtual void action() = 0; 13351

}; 13352

Page 513: Pensar en C++ (Volumen 2)

513

13353

class Player { 13354

public: 13355

virtual void interactWith(Obstacle*) = 0; 13356

}; 13357

13358

class Kitty: public Player { 13359

virtual void interactWith(Obstacle* ob) { 13360

cout << "Kitty has encountered a "; 13361

ob->action(); 13362

} 13363

}; 13364

13365

class KungFuGuy: public Player { 13366

virtual void interactWith(Obstacle* ob) { 13367

cout << "KungFuGuy now battles against a "; 13368

ob->action(); 13369

} 13370

}; 13371

13372

class Puzzle: public Obstacle { 13373

public: 13374

void action() { cout << "Puzzle" << endl; } 13375

}; 13376

13377

Page 514: Pensar en C++ (Volumen 2)

514

class NastyWeapon: public Obstacle { 13378

public: 13379

void action() { cout << "NastyWeapon" << endl; } 13380

}; 13381

13382

// The abstract factory: 13383

class GameElementFactory { 13384

public: 13385

virtual Player* makePlayer() = 0; 13386

virtual Obstacle* makeObstacle() = 0; 13387

}; 13388

13389

// Concrete factories: 13390

class KittiesAndPuzzles : public GameElementFactory { 13391

public: 13392

virtual Player* makePlayer() { return new Kitty; } 13393

virtual Obstacle* makeObstacle() { return new Puzzle; } 13394

}; 13395

13396

class KillAndDismember : public GameElementFactory { 13397

public: 13398

virtual Player* makePlayer() { return new KungFuGuy; } 13399

virtual Obstacle* makeObstacle() { 13400

return new NastyWeapon; 13401

} 13402

Page 515: Pensar en C++ (Volumen 2)

515

}; 13403

13404

class GameEnvironment { 13405

GameElementFactory* gef; 13406

Player* p; 13407

Obstacle* ob; 13408

public: 13409

GameEnvironment(GameElementFactory* factory) 13410

: gef(factory), p(factory->makePlayer()), 13411

ob(factory->makeObstacle()) {} 13412

void play() { p->interactWith(ob); } 13413

~GameEnvironment() { 13414

delete p; 13415

delete ob; 13416

delete gef; 13417

} 13418

}; 13419

13420

int main() { 13421

GameEnvironment 13422

g1(new KittiesAndPuzzles), 13423

g2(new KillAndDismember); 13424

g1.play(); 13425

g2.play(); 13426

} 13427

Page 516: Pensar en C++ (Volumen 2)

516

/* Output: 13428

Kitty has encountered a Puzzle 13429

KungFuGuy now battles against a NastyWeapon */ ///:~ 13430

Listado 9.25. C10/AbstractFactory.cpp 13431

13432

En este entorno, los objetos Player interactúan con objetos Obstacle, pero 13433

los tipos de los jugadores y los obstáculos dependen del juego. El tipo de 13434

juego se determina eligiendo un GameElementFactory concreto, y luego el 13435

GameEnvironment controla la configuración y ejecución del juego. En este 13436

ejemplo, la configuración y ejecución son simples, pero dichas actividades 13437

(las condiciones iniciales y los cambios de estado) pueden determinar gran 13438

parte del resultado del juego. Aquí, GameEnvironment no está diseñado para 13439

ser heredado, aunque puede tener sentido hacerlo. 13440

Este ejemplo también ilustra el despachado doble, que se explicará más 13441

adelante. 13442

9.11.3. Constructores virtuales 13443

//: C10:VirtualConstructor.cpp 13444

#include <iostream> 13445

#include <string> 13446

#include <stdexcept> 13447

#include <stdexcept> 13448

#include <cstddef> 13449

#include <vector> 13450

#include "../purge.h" 13451

using namespace std; 13452

13453

Page 517: Pensar en C++ (Volumen 2)

517

class Shape { 13454

Shape* s; 13455

// Prevent copy-construction & operator= 13456

Shape(Shape&); 13457

Shape operator=(Shape&); 13458

protected: 13459

Shape() { s = 0; } 13460

public: 13461

virtual void draw() { s->draw(); } 13462

virtual void erase() { s->erase(); } 13463

virtual void test() { s->test(); } 13464

virtual ~Shape() { 13465

cout << "~Shape" << endl; 13466

if(s) { 13467

cout << "Making virtual call: "; 13468

s->erase(); // Virtual call 13469

} 13470

cout << "delete s: "; 13471

delete s; // The polymorphic deletion 13472

// (delete 0 is legal; it produces a no-op) 13473

} 13474

class BadShapeCreation : public logic_error { 13475

public: 13476

BadShapeCreation(string type) 13477

: logic_error("Cannot create type " + type) {} 13478

Page 518: Pensar en C++ (Volumen 2)

518

}; 13479

Shape(string type) throw(BadShapeCreation); 13480

}; 13481

13482

class Circle : public Shape { 13483

Circle(Circle&); 13484

Circle operator=(Circle&); 13485

Circle() {} // Private constructor 13486

friend class Shape; 13487

public: 13488

void draw() { cout << "Circle::draw" << endl; } 13489

void erase() { cout << "Circle::erase" << endl; } 13490

void test() { draw(); } 13491

~Circle() { cout << "Circle::~Circle" << endl; } 13492

}; 13493

13494

class Square : public Shape { 13495

Square(Square&); 13496

Square operator=(Square&); 13497

Square() {} 13498

friend class Shape; 13499

public: 13500

void draw() { cout << "Square::draw" << endl; } 13501

void erase() { cout << "Square::erase" << endl; } 13502

void test() { draw(); } 13503

Page 519: Pensar en C++ (Volumen 2)

519

~Square() { cout << "Square::~Square" << endl; } 13504

}; 13505

13506

Shape::Shape(string type) throw(Shape::BadShapeCreation) { 13507

if(type == "Circle") 13508

s = new Circle; 13509

else if(type == "Square") 13510

s = new Square; 13511

else throw BadShapeCreation(type); 13512

draw(); // Virtual call in the constructor 13513

} 13514

13515

char* sl[] = { "Circle", "Square", "Square", 13516

"Circle", "Circle", "Circle", "Square" }; 13517

13518

int main() { 13519

vector<Shape*> shapes; 13520

cout << "virtual constructor calls:" << endl; 13521

try { 13522

for(size_t i = 0; i < sizeof sl / sizeof sl[0]; i++) 13523

shapes.push_back(new Shape(sl[i])); 13524

} catch(Shape::BadShapeCreation e) { 13525

cout << e.what() << endl; 13526

purge(shapes); 13527

return EXIT_FAILURE; 13528

Page 520: Pensar en C++ (Volumen 2)

520

} 13529

for(size_t i = 0; i < shapes.size(); i++) { 13530

shapes[i]->draw(); 13531

cout << "test" << endl; 13532

shapes[i]->test(); 13533

cout << "end test" << endl; 13534

shapes[i]->erase(); 13535

} 13536

Shape c("Circle"); // Create on the stack 13537

cout << "destructor calls:" << endl; 13538

purge(shapes); 13539

} ///:~ 13540

Listado 9.26. C10/VirtualConstructor.cpp 13541

13542

9.12. Builder: creación de objetos complejos 13543

//: C10:Bicycle.h 13544

// Defines classes to build bicycles; 13545

// Illustrates the Builder design pattern. 13546

#ifndef BICYCLE_H 13547

#define BICYCLE_H 13548

#include <iostream> 13549

#include <string> 13550

#include <vector> 13551

Page 521: Pensar en C++ (Volumen 2)

521

#include <cstddef> 13552

#include "../purge.h" 13553

using std::size_t; 13554

13555

class BicyclePart { 13556

public: 13557

enum BPart { FRAME, WHEEL, SEAT, DERAILLEUR, 13558

HANDLEBAR, SPROCKET, RACK, SHOCK, NPARTS }; 13559

private: 13560

BPart id; 13561

static std::string names[NPARTS]; 13562

public: 13563

BicyclePart(BPart bp) { id = bp; } 13564

friend std::ostream& 13565

operator<<(std::ostream& os, const BicyclePart& bp) { 13566

return os << bp.names[bp.id]; 13567

} 13568

}; 13569

13570

class Bicycle { 13571

std::vector<BicyclePart*> parts; 13572

public: 13573

~Bicycle() { purge(parts); } 13574

void addPart(BicyclePart* bp) { parts.push_back(bp); } 13575

friend std::ostream& 13576

Page 522: Pensar en C++ (Volumen 2)

522

operator<<(std::ostream& os, const Bicycle& b) { 13577

os << "{ "; 13578

for(size_t i = 0; i < b.parts.size(); ++i) 13579

os << *b.parts[i] << ' '; 13580

return os << '}'; 13581

} 13582

}; 13583

13584

class BicycleBuilder { 13585

protected: 13586

Bicycle* product; 13587

public: 13588

BicycleBuilder() { product = 0; } 13589

void createProduct() { product = new Bicycle; } 13590

virtual void buildFrame() = 0; 13591

virtual void buildWheel() = 0; 13592

virtual void buildSeat() = 0; 13593

virtual void buildDerailleur() = 0; 13594

virtual void buildHandlebar() = 0; 13595

virtual void buildSprocket() = 0; 13596

virtual void buildRack() = 0; 13597

virtual void buildShock() = 0; 13598

virtual std::string getBikeName() const = 0; 13599

Bicycle* getProduct() { 13600

Bicycle* temp = product; 13601

Page 523: Pensar en C++ (Volumen 2)

523

product = 0; // Relinquish product 13602

return temp; 13603

} 13604

}; 13605

13606

class MountainBikeBuilder : public BicycleBuilder { 13607

public: 13608

void buildFrame(); 13609

void buildWheel(); 13610

void buildSeat(); 13611

void buildDerailleur(); 13612

void buildHandlebar(); 13613

void buildSprocket(); 13614

void buildRack(); 13615

void buildShock(); 13616

std::string getBikeName() const { return "MountainBike";} 13617

}; 13618

13619

class TouringBikeBuilder : public BicycleBuilder { 13620

public: 13621

void buildFrame(); 13622

void buildWheel(); 13623

void buildSeat(); 13624

void buildDerailleur(); 13625

void buildHandlebar(); 13626

Page 524: Pensar en C++ (Volumen 2)

524

void buildSprocket(); 13627

void buildRack(); 13628

void buildShock(); 13629

std::string getBikeName() const { return "TouringBike"; } 13630

}; 13631

13632

class RacingBikeBuilder : public BicycleBuilder { 13633

public: 13634

void buildFrame(); 13635

void buildWheel(); 13636

void buildSeat(); 13637

void buildDerailleur(); 13638

void buildHandlebar(); 13639

void buildSprocket(); 13640

void buildRack(); 13641

void buildShock(); 13642

std::string getBikeName() const { return "RacingBike"; } 13643

}; 13644

13645

class BicycleTechnician { 13646

BicycleBuilder* builder; 13647

public: 13648

BicycleTechnician() { builder = 0; } 13649

void setBuilder(BicycleBuilder* b) { builder = b; } 13650

void construct(); 13651

Page 525: Pensar en C++ (Volumen 2)

525

}; 13652

#endif // BICYCLE_H ///:~ 13653

Listado 9.27. C10/Bicycle.h 13654

13655

//: C10:Bicycle.cpp {O} {-mwcc} 13656

#include "Bicycle.h" 13657

#include <cassert> 13658

#include <cstddef> 13659

using namespace std; 13660

13661

std::string BicyclePart::names[NPARTS] = { 13662

"Frame", "Wheel", "Seat", "Derailleur", 13663

"Handlebar", "Sprocket", "Rack", "Shock" }; 13664

13665

// MountainBikeBuilder implementation 13666

void MountainBikeBuilder::buildFrame() { 13667

product->addPart(new BicyclePart(BicyclePart::FRAME)); 13668

} 13669

void MountainBikeBuilder::buildWheel() { 13670

product->addPart(new BicyclePart(BicyclePart::WHEEL)); 13671

} 13672

void MountainBikeBuilder::buildSeat() { 13673

product->addPart(new BicyclePart(BicyclePart::SEAT)); 13674

} 13675

Page 526: Pensar en C++ (Volumen 2)

526

void MountainBikeBuilder::buildDerailleur() { 13676

product->addPart( 13677

new BicyclePart(BicyclePart::DERAILLEUR)); 13678

} 13679

void MountainBikeBuilder::buildHandlebar() { 13680

product->addPart( 13681

new BicyclePart(BicyclePart::HANDLEBAR)); 13682

} 13683

void MountainBikeBuilder::buildSprocket() { 13684

product->addPart(new BicyclePart(BicyclePart::SPROCKET)); 13685

} 13686

void MountainBikeBuilder::buildRack() {} 13687

void MountainBikeBuilder::buildShock() { 13688

product->addPart(new BicyclePart(BicyclePart::SHOCK)); 13689

} 13690

13691

// TouringBikeBuilder implementation 13692

void TouringBikeBuilder::buildFrame() { 13693

product->addPart(new BicyclePart(BicyclePart::FRAME)); 13694

} 13695

void TouringBikeBuilder::buildWheel() { 13696

product->addPart(new BicyclePart(BicyclePart::WHEEL)); 13697

} 13698

void TouringBikeBuilder::buildSeat() { 13699

product->addPart(new BicyclePart(BicyclePart::SEAT)); 13700

Page 527: Pensar en C++ (Volumen 2)

527

} 13701

void TouringBikeBuilder::buildDerailleur() { 13702

product->addPart( 13703

new BicyclePart(BicyclePart::DERAILLEUR)); 13704

} 13705

void TouringBikeBuilder::buildHandlebar() { 13706

product->addPart( 13707

new BicyclePart(BicyclePart::HANDLEBAR)); 13708

} 13709

void TouringBikeBuilder::buildSprocket() { 13710

product->addPart(new BicyclePart(BicyclePart::SPROCKET)); 13711

} 13712

void TouringBikeBuilder::buildRack() { 13713

product->addPart(new BicyclePart(BicyclePart::RACK)); 13714

} 13715

void TouringBikeBuilder::buildShock() {} 13716

13717

// RacingBikeBuilder implementation 13718

void RacingBikeBuilder::buildFrame() { 13719

product->addPart(new BicyclePart(BicyclePart::FRAME)); 13720

} 13721

void RacingBikeBuilder::buildWheel() { 13722

product->addPart(new BicyclePart(BicyclePart::WHEEL)); 13723

} 13724

void RacingBikeBuilder::buildSeat() { 13725

Page 528: Pensar en C++ (Volumen 2)

528

product->addPart(new BicyclePart(BicyclePart::SEAT)); 13726

} 13727

void RacingBikeBuilder::buildDerailleur() {} 13728

void RacingBikeBuilder::buildHandlebar() { 13729

product->addPart( 13730

new BicyclePart(BicyclePart::HANDLEBAR)); 13731

} 13732

void RacingBikeBuilder::buildSprocket() { 13733

product->addPart(new BicyclePart(BicyclePart::SPROCKET)); 13734

} 13735

void RacingBikeBuilder::buildRack() {} 13736

void RacingBikeBuilder::buildShock() {} 13737

13738

// BicycleTechnician implementation 13739

void BicycleTechnician::construct() { 13740

assert(builder); 13741

builder->createProduct(); 13742

builder->buildFrame(); 13743

builder->buildWheel(); 13744

builder->buildSeat(); 13745

builder->buildDerailleur(); 13746

builder->buildHandlebar(); 13747

builder->buildSprocket(); 13748

builder->buildRack(); 13749

builder->buildShock(); 13750

Page 529: Pensar en C++ (Volumen 2)

529

} ///:~ 13751

Listado 9.28. C10/Bicycle.cpp 13752

13753

//: C10:BuildBicycles.cpp 13754

//{L} Bicycle 13755

// The Builder design pattern. 13756

#include <cstddef> 13757

#include <iostream> 13758

#include <map> 13759

#include <vector> 13760

#include "Bicycle.h" 13761

#include "../purge.h" 13762

using namespace std; 13763

13764

// Constructs a bike via a concrete builder 13765

Bicycle* buildMeABike( 13766

BicycleTechnician& t, BicycleBuilder* builder) { 13767

t.setBuilder(builder); 13768

t.construct(); 13769

Bicycle* b = builder->getProduct(); 13770

cout << "Built a " << builder->getBikeName() << endl; 13771

return b; 13772

} 13773

13774

Page 530: Pensar en C++ (Volumen 2)

530

int main() { 13775

// Create an order for some bicycles 13776

map <string, size_t> order; 13777

order["mountain"] = 2; 13778

order["touring"] = 1; 13779

order["racing"] = 3; 13780

13781

// Build bikes 13782

vector<Bicycle*> bikes; 13783

BicycleBuilder* m = new MountainBikeBuilder; 13784

BicycleBuilder* t = new TouringBikeBuilder; 13785

BicycleBuilder* r = new RacingBikeBuilder; 13786

BicycleTechnician tech; 13787

map<string, size_t>::iterator it = order.begin(); 13788

while(it != order.end()) { 13789

BicycleBuilder* builder; 13790

if(it->first == "mountain") 13791

builder = m; 13792

else if(it->first == "touring") 13793

builder = t; 13794

else if(it->first == "racing") 13795

builder = r; 13796

for(size_t i = 0; i < it->second; ++i) 13797

bikes.push_back(buildMeABike(tech, builder)); 13798

++it; 13799

Page 531: Pensar en C++ (Volumen 2)

531

} 13800

delete m; 13801

delete t; 13802

delete r; 13803

13804

// Display inventory 13805

for(size_t i = 0; i < bikes.size(); ++i) 13806

cout << "Bicycle: " << *bikes[i] << endl; 13807

purge(bikes); 13808

} 13809

13810

/* Output: 13811

Built a MountainBike 13812

Built a MountainBike 13813

Built a RacingBike 13814

Built a RacingBike 13815

Built a RacingBike 13816

Built a TouringBike 13817

Bicycle: { 13818

Frame Wheel Seat Derailleur Handlebar Sprocket Shock } 13819

Bicycle: { 13820

Frame Wheel Seat Derailleur Handlebar Sprocket Shock } 13821

Bicycle: { Frame Wheel Seat Handlebar Sprocket } 13822

Bicycle: { Frame Wheel Seat Handlebar Sprocket } 13823

Bicycle: { Frame Wheel Seat Handlebar Sprocket } 13824

Page 532: Pensar en C++ (Volumen 2)

532

Bicycle: { 13825

Frame Wheel Seat Derailleur Handlebar Sprocket Rack } 13826

*/ ///:~ 13827

Listado 9.29. C10/BuildBicycles.cpp 13828

13829

9.13. Observador 13830

//: C10:Observer.h 13831

// The Observer interface. 13832

#ifndef OBSERVER_H 13833

#define OBSERVER_H 13834

13835

class Observable; 13836

class Argument {}; 13837

13838

class Observer { 13839

public: 13840

// Called by the observed object, whenever 13841

// the observed object is changed: 13842

virtual void update(Observable* o, Argument* arg) = 0; 13843

virtual ~Observer() {} 13844

}; 13845

#endif // OBSERVER_H ///:~ 13846

Listado 9.30. C10/Observer.h 13847

Page 533: Pensar en C++ (Volumen 2)

533

13848

//: C10:Observable.h 13849

// The Observable class. 13850

#ifndef OBSERVABLE_H 13851

#define OBSERVABLE_H 13852

#include <set> 13853

#include "Observer.h" 13854

13855

class Observable { 13856

bool changed; 13857

std::set<Observer*> observers; 13858

protected: 13859

virtual void setChanged() { changed = true; } 13860

virtual void clearChanged() { changed = false; } 13861

public: 13862

virtual void addObserver(Observer& o) { 13863

observers.insert(&o); 13864

} 13865

virtual void deleteObserver(Observer& o) { 13866

observers.erase(&o); 13867

} 13868

virtual void deleteObservers() { 13869

observers.clear(); 13870

} 13871

virtual int countObservers() { 13872

Page 534: Pensar en C++ (Volumen 2)

534

return observers.size(); 13873

} 13874

virtual bool hasChanged() { return changed; } 13875

// If this object has changed, notify all 13876

// of its observers: 13877

virtual void notifyObservers(Argument* arg = 0) { 13878

if(!hasChanged()) return; 13879

clearChanged(); // Not "changed" anymore 13880

std::set<Observer*>::iterator it; 13881

for(it = observers.begin();it != observers.end(); it++) 13882

(*it)->update(this, arg); 13883

} 13884

virtual ~Observable() {} 13885

}; 13886

#endif // OBSERVABLE_H ///:~ 13887

Listado 9.31. C10/Observable.h 13888

13889

//: C10:InnerClassIdiom.cpp 13890

// Example of the "inner class" idiom. 13891

#include <iostream> 13892

#include <string> 13893

using namespace std; 13894

13895

class Poingable { 13896

Page 535: Pensar en C++ (Volumen 2)

535

public: 13897

virtual void poing() = 0; 13898

}; 13899

13900

void callPoing(Poingable& p) { 13901

p.poing(); 13902

} 13903

13904

class Bingable { 13905

public: 13906

virtual void bing() = 0; 13907

}; 13908

13909

void callBing(Bingable& b) { 13910

b.bing(); 13911

} 13912

13913

class Outer { 13914

string name; 13915

// Define one inner class: 13916

class Inner1; 13917

friend class Outer::Inner1; 13918

class Inner1 : public Poingable { 13919

Outer* parent; 13920

public: 13921

Page 536: Pensar en C++ (Volumen 2)

536

Inner1(Outer* p) : parent(p) {} 13922

void poing() { 13923

cout << "poing called for " 13924

<< parent->name << endl; 13925

// Accesses data in the outer class object 13926

} 13927

} inner1; 13928

// Define a second inner class: 13929

class Inner2; 13930

friend class Outer::Inner2; 13931

class Inner2 : public Bingable { 13932

Outer* parent; 13933

public: 13934

Inner2(Outer* p) : parent(p) {} 13935

void bing() { 13936

cout << "bing called for " 13937

<< parent->name << endl; 13938

} 13939

} inner2; 13940

public: 13941

Outer(const string& nm) 13942

: name(nm), inner1(this), inner2(this) {} 13943

// Return reference to interfaces 13944

// implemented by the inner classes: 13945

operator Poingable&() { return inner1; } 13946

Page 537: Pensar en C++ (Volumen 2)

537

operator Bingable&() { return inner2; } 13947

}; 13948

13949

int main() { 13950

Outer x("Ping Pong"); 13951

// Like upcasting to multiple base types!: 13952

callPoing(x); 13953

callBing(x); 13954

} ///:~ 13955

Listado 9.32. C10/InnerClassIdiom.cpp 13956

13957

9.13.1. El ejemplo de observador 13958

//: C10:ObservedFlower.cpp 13959

// Demonstration of "observer" pattern. 13960

#include <algorithm> 13961

#include <iostream> 13962

#include <string> 13963

#include <vector> 13964

#include "Observable.h" 13965

using namespace std; 13966

13967

class Flower { 13968

bool isOpen; 13969

public: 13970

Page 538: Pensar en C++ (Volumen 2)

538

Flower() : isOpen(false), 13971

openNotifier(this), closeNotifier(this) {} 13972

void open() { // Opens its petals 13973

isOpen = true; 13974

openNotifier.notifyObservers(); 13975

closeNotifier.open(); 13976

} 13977

void close() { // Closes its petals 13978

isOpen = false; 13979

closeNotifier.notifyObservers(); 13980

openNotifier.close(); 13981

} 13982

// Using the "inner class" idiom: 13983

class OpenNotifier; 13984

friend class Flower::OpenNotifier; 13985

class OpenNotifier : public Observable { 13986

Flower* parent; 13987

bool alreadyOpen; 13988

public: 13989

OpenNotifier(Flower* f) : parent(f), 13990

alreadyOpen(false) {} 13991

void notifyObservers(Argument* arg = 0) { 13992

if(parent->isOpen && !alreadyOpen) { 13993

setChanged(); 13994

Observable::notifyObservers(); 13995

Page 539: Pensar en C++ (Volumen 2)

539

alreadyOpen = true; 13996

} 13997

} 13998

void close() { alreadyOpen = false; } 13999

} openNotifier; 14000

class CloseNotifier; 14001

friend class Flower::CloseNotifier; 14002

class CloseNotifier : public Observable { 14003

Flower* parent; 14004

bool alreadyClosed; 14005

public: 14006

CloseNotifier(Flower* f) : parent(f), 14007

alreadyClosed(false) {} 14008

void notifyObservers(Argument* arg = 0) { 14009

if(!parent->isOpen && !alreadyClosed) { 14010

setChanged(); 14011

Observable::notifyObservers(); 14012

alreadyClosed = true; 14013

} 14014

} 14015

void open() { alreadyClosed = false; } 14016

} closeNotifier; 14017

}; 14018

14019

class Bee { 14020

Page 540: Pensar en C++ (Volumen 2)

540

string name; 14021

// An "inner class" for observing openings: 14022

class OpenObserver; 14023

friend class Bee::OpenObserver; 14024

class OpenObserver : public Observer { 14025

Bee* parent; 14026

public: 14027

OpenObserver(Bee* b) : parent(b) {} 14028

void update(Observable*, Argument *) { 14029

cout << "Bee " << parent->name 14030

<< "'s breakfast time!" << endl; 14031

} 14032

} openObsrv; 14033

// Another "inner class" for closings: 14034

class CloseObserver; 14035

friend class Bee::CloseObserver; 14036

class CloseObserver : public Observer { 14037

Bee* parent; 14038

public: 14039

CloseObserver(Bee* b) : parent(b) {} 14040

void update(Observable*, Argument *) { 14041

cout << "Bee " << parent->name 14042

<< "'s bed time!" << endl; 14043

} 14044

} closeObsrv; 14045

Page 541: Pensar en C++ (Volumen 2)

541

public: 14046

Bee(string nm) : name(nm), 14047

openObsrv(this), closeObsrv(this) {} 14048

Observer& openObserver() { return openObsrv; } 14049

Observer& closeObserver() { return closeObsrv;} 14050

}; 14051

14052

class Hummingbird { 14053

string name; 14054

class OpenObserver; 14055

friend class Hummingbird::OpenObserver; 14056

class OpenObserver : public Observer { 14057

Hummingbird* parent; 14058

public: 14059

OpenObserver(Hummingbird* h) : parent(h) {} 14060

void update(Observable*, Argument *) { 14061

cout << "Hummingbird " << parent->name 14062

<< "'s breakfast time!" << endl; 14063

} 14064

} openObsrv; 14065

class CloseObserver; 14066

friend class Hummingbird::CloseObserver; 14067

class CloseObserver : public Observer { 14068

Hummingbird* parent; 14069

public: 14070

Page 542: Pensar en C++ (Volumen 2)

542

CloseObserver(Hummingbird* h) : parent(h) {} 14071

void update(Observable*, Argument *) { 14072

cout << "Hummingbird " << parent->name 14073

<< "'s bed time!" << endl; 14074

} 14075

} closeObsrv; 14076

public: 14077

Hummingbird(string nm) : name(nm), 14078

openObsrv(this), closeObsrv(this) {} 14079

Observer& openObserver() { return openObsrv; } 14080

Observer& closeObserver() { return closeObsrv;} 14081

}; 14082

14083

int main() { 14084

Flower f; 14085

Bee ba("A"), bb("B"); 14086

Hummingbird ha("A"), hb("B"); 14087

f.openNotifier.addObserver(ha.openObserver()); 14088

f.openNotifier.addObserver(hb.openObserver()); 14089

f.openNotifier.addObserver(ba.openObserver()); 14090

f.openNotifier.addObserver(bb.openObserver()); 14091

f.closeNotifier.addObserver(ha.closeObserver()); 14092

f.closeNotifier.addObserver(hb.closeObserver()); 14093

f.closeNotifier.addObserver(ba.closeObserver()); 14094

f.closeNotifier.addObserver(bb.closeObserver()); 14095

Page 543: Pensar en C++ (Volumen 2)

543

// Hummingbird B decides to sleep in: 14096

f.openNotifier.deleteObserver(hb.openObserver()); 14097

// Something changes that interests observers: 14098

f.open(); 14099

f.open(); // It's already open, no change. 14100

// Bee A doesn't want to go to bed: 14101

f.closeNotifier.deleteObserver( 14102

ba.closeObserver()); 14103

f.close(); 14104

f.close(); // It's already closed; no change 14105

f.openNotifier.deleteObservers(); 14106

f.open(); 14107

f.close(); 14108

} ///:~ 14109

Listado 9.33. C10/ObservedFlower.cpp 14110

14111

9.14. Despachado múltiple 14112

//: C10:PaperScissorsRock.cpp 14113

// Demonstration of multiple dispatching. 14114

#include <algorithm> 14115

#include <iostream> 14116

#include <iterator> 14117

#include <vector> 14118

Page 544: Pensar en C++ (Volumen 2)

544

#include <ctime> 14119

#include <cstdlib> 14120

#include "../purge.h" 14121

using namespace std; 14122

14123

class Paper; 14124

class Scissors; 14125

class Rock; 14126

14127

enum Outcome { WIN, LOSE, DRAW }; 14128

14129

ostream& operator<<(ostream& os, const Outcome out) { 14130

switch(out) { 14131

default: 14132

case WIN: return os << "win"; 14133

case LOSE: return os << "lose"; 14134

case DRAW: return os << "draw"; 14135

} 14136

} 14137

14138

class Item { 14139

public: 14140

virtual Outcome compete(const Item*) = 0; 14141

virtual Outcome eval(const Paper*) const = 0; 14142

virtual Outcome eval(const Scissors*) const= 0; 14143

Page 545: Pensar en C++ (Volumen 2)

545

virtual Outcome eval(const Rock*) const = 0; 14144

virtual ostream& print(ostream& os) const = 0; 14145

virtual ~Item() {} 14146

friend ostream& operator<<(ostream& os, const Item* it) { 14147

return it->print(os); 14148

} 14149

}; 14150

14151

class Paper : public Item { 14152

public: 14153

Outcome compete(const Item* it) { return it->eval(this);} 14154

Outcome eval(const Paper*) const { return DRAW; } 14155

Outcome eval(const Scissors*) const { return WIN; } 14156

Outcome eval(const Rock*) const { return LOSE; } 14157

ostream& print(ostream& os) const { 14158

return os << "Paper "; 14159

} 14160

}; 14161

14162

class Scissors : public Item { 14163

public: 14164

Outcome compete(const Item* it) { return it->eval(this);} 14165

Outcome eval(const Paper*) const { return LOSE; } 14166

Outcome eval(const Scissors*) const { return DRAW; } 14167

Outcome eval(const Rock*) const { return WIN; } 14168

Page 546: Pensar en C++ (Volumen 2)

546

ostream& print(ostream& os) const { 14169

return os << "Scissors"; 14170

} 14171

}; 14172

14173

class Rock : public Item { 14174

public: 14175

Outcome compete(const Item* it) { return it->eval(this);} 14176

Outcome eval(const Paper*) const { return WIN; } 14177

Outcome eval(const Scissors*) const { return LOSE; } 14178

Outcome eval(const Rock*) const { return DRAW; } 14179

ostream& print(ostream& os) const { 14180

return os << "Rock "; 14181

} 14182

}; 14183

14184

struct ItemGen { 14185

Item* operator()() { 14186

switch(rand() % 3) { 14187

default: 14188

case 0: return new Scissors; 14189

case 1: return new Paper; 14190

case 2: return new Rock; 14191

} 14192

} 14193

Page 547: Pensar en C++ (Volumen 2)

547

}; 14194

14195

struct Compete { 14196

Outcome operator()(Item* a, Item* b) { 14197

cout << a << "\t" << b << "\t"; 14198

return a->compete(b); 14199

} 14200

}; 14201

14202

int main() { 14203

srand(time(0)); // Seed the random number generator 14204

const int sz = 20; 14205

vector<Item*> v(sz*2); 14206

generate(v.begin(), v.end(), ItemGen()); 14207

transform(v.begin(), v.begin() + sz, 14208

v.begin() + sz, 14209

ostream_iterator<Outcome>(cout, "\n"), 14210

Compete()); 14211

purge(v); 14212

} ///:~ 14213

Listado 9.34. C10/PaperScissorsRock.cpp 14214

14215

9.14.1. Despachado múltiple con Visitor 14216

//: C10:BeeAndFlowers.cpp 14217

Page 548: Pensar en C++ (Volumen 2)

548

// Demonstration of "visitor" pattern. 14218

#include <algorithm> 14219

#include <iostream> 14220

#include <string> 14221

#include <vector> 14222

#include <ctime> 14223

#include <cstdlib> 14224

#include "../purge.h" 14225

using namespace std; 14226

14227

class Gladiolus; 14228

class Renuculus; 14229

class Chrysanthemum; 14230

14231

class Visitor { 14232

public: 14233

virtual void visit(Gladiolus* f) = 0; 14234

virtual void visit(Renuculus* f) = 0; 14235

virtual void visit(Chrysanthemum* f) = 0; 14236

virtual ~Visitor() {} 14237

}; 14238

14239

class Flower { 14240

public: 14241

virtual void accept(Visitor&) = 0; 14242

Page 549: Pensar en C++ (Volumen 2)

549

virtual ~Flower() {} 14243

}; 14244

14245

class Gladiolus : public Flower { 14246

public: 14247

virtual void accept(Visitor& v) { 14248

v.visit(this); 14249

} 14250

}; 14251

14252

class Renuculus : public Flower { 14253

public: 14254

virtual void accept(Visitor& v) { 14255

v.visit(this); 14256

} 14257

}; 14258

14259

class Chrysanthemum : public Flower { 14260

public: 14261

virtual void accept(Visitor& v) { 14262

v.visit(this); 14263

} 14264

}; 14265

14266

// Add the ability to produce a string: 14267

Page 550: Pensar en C++ (Volumen 2)

550

class StringVal : public Visitor { 14268

string s; 14269

public: 14270

operator const string&() { return s; } 14271

virtual void visit(Gladiolus*) { 14272

s = "Gladiolus"; 14273

} 14274

virtual void visit(Renuculus*) { 14275

s = "Renuculus"; 14276

} 14277

virtual void visit(Chrysanthemum*) { 14278

s = "Chrysanthemum"; 14279

} 14280

}; 14281

14282

// Add the ability to do "Bee" activities: 14283

class Bee : public Visitor { 14284

public: 14285

virtual void visit(Gladiolus*) { 14286

cout << "Bee and Gladiolus" << endl; 14287

} 14288

virtual void visit(Renuculus*) { 14289

cout << "Bee and Renuculus" << endl; 14290

} 14291

virtual void visit(Chrysanthemum*) { 14292

Page 551: Pensar en C++ (Volumen 2)

551

cout << "Bee and Chrysanthemum" << endl; 14293

} 14294

}; 14295

14296

struct FlowerGen { 14297

Flower* operator()() { 14298

switch(rand() % 3) { 14299

default: 14300

case 0: return new Gladiolus; 14301

case 1: return new Renuculus; 14302

case 2: return new Chrysanthemum; 14303

} 14304

} 14305

}; 14306

14307

int main() { 14308

srand(time(0)); // Seed the random number generator 14309

vector<Flower*> v(10); 14310

generate(v.begin(), v.end(), FlowerGen()); 14311

vector<Flower*>::iterator it; 14312

// It's almost as if I added a virtual function 14313

// to produce a Flower string representation: 14314

StringVal sval; 14315

for(it = v.begin(); it != v.end(); it++) { 14316

(*it)->accept(sval); 14317

Page 552: Pensar en C++ (Volumen 2)

552

cout << string(sval) << endl; 14318

} 14319

// Perform "Bee" operation on all Flowers: 14320

Bee bee; 14321

for(it = v.begin(); it != v.end(); it++) 14322

(*it)->accept(bee); 14323

purge(v); 14324

} ///:~ 14325

Listado 9.35. C10/BeeAndFlowers.cpp 14326

14327

9.15. Resumen 14328

9.16. Ejercicios 14329

10: Concurrencia 14330

Tabla de contenidos 14331

10.1. Motivación 14332

10.2. Concurrencia en C++ 14333

10.3. Utilización de los hilos 14334

10.4. Comparición de recursos limitados 14335

10.5. Finalización de tareas 14336

10.6. Cooperación entre hilos 14337

10.7. Bloqueo letal 14338

10.8. Resumen 14339

10.9. Ejercicios 14340

Los objetos ofrecen una forma de dividir un programa en diferentes 14341

secciones. A menudo, también es necesario dividir un programa, 14342

independientemente de las subtareas en ejecución. 14343

Utilizando multihilado, un hilo de ejecución dirige cada una esas subtareas 14344

independientes, y puedes programar como si cada hilo tuviera su propia 14345

Page 553: Pensar en C++ (Volumen 2)

553

CPU. Un mecanismo interno reparte el tiempo de CPU por ti, pero en 14346

general, no necesitas pensar acerca de eso, lo que ayuda a simplificar la 14347

programación con múltiples hilos. 14348

Un proceso es un programa autocontenido en ejecución con su propio 14349

espacio de direcciones. Un sistema operativo multitarea puede ejecutar más 14350

de un proceso (programa) en el mismo tiempo, mientras ....., por medio de 14351

cambios periódicos de CPU de una tarea a otra. Un hilo es una simple flujo 14352

de control secuencial con un proceso. Un proceso puede tener de este modo 14353

múltiples hilos en ejecución concurrentes. Puesto que los hilos se ejecutan 14354

con un proceso simple, pueden compartir memoria y otros recursos. La 14355

dificultad fundamental de escribir programas multihilados está en coordinar el 14356

uso de esos recursos entre los diferentes hilos. 14357

Hay muchas aplicaciones posibles para el multihilado, pero lo más usual 14358

es querer usarlo cuando tienes alguna parte de tu programa vinculada a un 14359

evento o recurso particular. Para evitar bloquear el resto de tu programa, 14360

creas un hilo asociado a ese evento o recurso y le permites ejecutarse 14361

independientemente del programa principal. 14362

La programación concurrente se como caminar en un mundo 14363

completamente nuevo y aprender un nuevo lenguaje de programación, o por 14364

lo menos un nuevo conjunto de conceptos del lenguaje. Con la aparición del 14365

soporte para los hilos en la mayoría de los sistemas operativos para 14366

microcomputadores, han aparecido también en los lenguajes de 14367

programación o librerías extensiones para los hilos. En cualquier caso, 14368

programación hilada: 14369

1. Parece misteriosa y requiere un esfuerzo en la forma de pensar acerca 14370

de la programación. 14371

2. En otros lenguajes el soporte a los hilos es similar. Cuando entiendas 14372

los hilos, comprenderás una jerga común. 14373

Comprender la programación concurrente está al mismo nivel de dificultad 14374

que comprender el polimorfismo. Si pones un poco de esfuerzo, podrás 14375

entender el mecanismo básico, pero generalmente necesitará de un 14376

entendimiento y estudio profundo para desarrollar una comprensión auténtica 14377

sobre el tema. La meta de este capítulo es darte una base sólida en los 14378

principios de concurrencia para que puedas entender los conceptos y escribir 14379

Page 554: Pensar en C++ (Volumen 2)

554

programas multihilados razonables. Sé consciente de que puedes confiarte 14380

fácilmente. Si vas a escribir algo complejo, necesitarás estudiar libros 14381

específicos sobre el tema. 14382

10.1. Motivación 14383

Una de las razones más convincentes para usar concurrencia es crear una 14384

interfaz sensible al usuario. Considera un programa que realiza una 14385

operación de CPU intensiva y, de esta forma, termina ignorando la entrada 14386

del usuario y comienza a no responder. El programa necesita continuar 14387

controlandor sus operaciones, y al mismo tiempo necesita devolver el control 14388

al botón de la interfaz de usuario para que el programa pueda responder al 14389

usuario. Si tienes un botón de "Salir", no querrás estar forzado a sondearlo 14390

en todas las partes de código que escribas en tu programa. (Esto acoplaría 14391

tu botón de salir a lo largo del programa y sería un quebradero de cabeza a 14392

la hora de mantenerlo). ..... 14393

Una función convencional no puede continuar realizando sus operaciones 14394

y al mismo tiempo devolver el control al resto del programa. De hecho, suena 14395

a imposible, como si la CPU estuviera en dos lugares a la vez, pero esto es 14396

precisamente la "ilusión" que la concurrencia permite (en el caso de un 14397

sistema multiprocesador, debe haber más de una "ilusión"). 14398

También puedes usar concurrencia para optimizar la carga de trabajo. Por 14399

ejemplo, podrías necesitar hacer algo importante mientras estás estancado 14400

esperando la llegada de una entrada del puerto I/O. Sin hilos, la única 14401

solución razonable es sondear los puertos I/O, que es costoso y puede ser 14402

difícil. 14403

Si tienes una máquina multiprocesador, los múltiples hilos pueden ser 14404

distribuídos a lo largo de los múltiples procesadores, pudiendo mejorar 14405

considerablemente la carga de trabajo. Este es el típico caso de los potentes 14406

servidores web multiprocesdor, que pueden distribuir un gran número de 14407

peticiones de usuario por todas las CPUs en un programa que asigna un hilo 14408

por petición. 14409

Un programa que usa hilos en una máquina monoprocesador hará una 14410

cosa en un tiempo dado, por lo que es teóricamente posible escribir el mismo 14411

programa sin el uso de hilos. Sin embargo, el multihilado proporciona un 14412

Page 555: Pensar en C++ (Volumen 2)

555

beneficio de optimización importante: El diseño de un programa puede ser 14413

maravillosamente simple. Algunos tipos de problemas, como la simulación - 14414

un video juego, por ejemplo - son difíciles de resolver sin el soporte de la 14415

concurrencia. 14416

El modelo hilado es una comodidad de la programación para simplificar el 14417

manejo de muchas operaciones al mismo tiempo con un simple programa: La 14418

CPU desapilará y dará a cada hilo algo de su tiempo. Cada hilo tiene 14419

consciencia de que tiene un tiempo constante de uso de CPU, pero el tiempo 14420

de CPU está actualmente repartido entre todo los hilos. La excepción es un 14421

programa que se ejecuta sobre múltiples CPU's. Pero una de las cosas 14422

fabulosas que tiene el hilado es que te abstrae de esta capa, por lo que tu 14423

código no necesita saber si está ejecutándose sobre una sóla CPU o sobre 14424

varias.[149] De este modo, usar hilos es una manera de crear programas 14425

escalables de forma transparente - si un programa se está ejecutando 14426

demasiado despacio, puedes acelerarlo fácilmente añadiendo CPUs a tu 14427

ordenador. La multitarea y el multihilado tienden a ser las mejores opciones a 14428

utilizar en un sistema multiprocesador. 14429

El uso de hilos puede reducir la eficiencia computacional un poco, pero el 14430

aumento neto en el diseño del programa, balanceo de recursos, y la 14431

comodidad del usuario a menudo es más valorado. En general, los hilos te 14432

permiten crear diseñor más desacoplados; de lo contrario, las partes de tu 14433

código estaría obligadas a prestar atención a tareas que podrías manejarlas 14434

con hilos normalmente. 14435

10.2. Concurrencia en C++ 14436

Cuando el Comité de Estándares de C++ estaba creando el estándar 14437

inicial de C++, el mecanismo de concurrencia fue excluído de forma explícita 14438

porque C no tenía uno y también porque había varíos enfoques rivales 14439

acerca de su implementación. Parecía demasiado restrictivo forzar a los 14440

programadores a usar una sola alternativa. 14441

Sin embargo, la alternativa resultó ser peor. Para usar concurriencia, 14442

tenías que encontrar y aprender una librería y ocuparte de su indiosincrasia y 14443

las incertidumbres de trabajar con un vendedor particular. Además, no había 14444

garantía de que una librería funcionaría en diferentes compiladores o en 14445

Page 556: Pensar en C++ (Volumen 2)

556

distintas plataformas. También, desde que la concurrencia no formaba parte 14446

del estándar del lenguaje, fue más difícil encontrar programadores C++ que 14447

también entendieran la programación concurrente. 14448

Otra influencia pudo ser el lenguaje Java, que incluyó concurrencia en el 14449

núcleo del lenguaje. Aunque el multihilado is aún complicado, los 14450

programadores de Java tienden a empezar a aprenderlo y usarlo desde el 14451

principio. 14452

El Comité de Estándares de C++ está considerando incluir el soporte a la 14453

concurrencia en la siguiente iteración de C++, pero en el momento de este 14454

escrito no estaba claro qué aspecto tendrá la librería. Decidimos usar la 14455

librería ZThread como base para este capítulo. La escogimos por su diseño, 14456

y es open-source y gratuitamente descargable desde 14457

http://zthread.sourceforge.net. Eric Crahen de IBM, el autor de la librería 14458

ZThread, fue decisivo para crear este capítulo.[150] 14459

Este capítulo utiliza sólo un subgrupo de la librería ZThread, de acuerdo 14460

con el convenio de ideas fundamentales sobre los hilos. La librería ZThread 14461

contiene un soporte a los hilos significativamente más sofisticado que el que 14462

se muestra aquí, y deberías estudiar esa librería más profundamente para 14463

comprender completamente sus posibilidades. 14464

10.2.1. Instalación de ZThreads 14465

Por favor, note que la librería ZThread es un proyecto independiente y no 14466

está soportada por el autor de este libro; simplemente estamos usando la 14467

librería en este capítulo y no podemos dar soporte técnico a las 14468

características de la instalación. Mira el sitio web de ZThread para obtener 14469

soporte en la instalación y reporte de errores. 14470

La librería ZThread se distribuye como código fuente. Después de 14471

descargarla (versión 2.3 o superior) desde la web de ZThread, debes 14472

compilar la librería primero, y después configurar tu proyecto para que use la 14473

librería. --> 14474

El método habitual para compilar la librería ZThreads para los distintos 14475

sabores de UNIX (Linux, SunOS, Cygwin, etc) es usar un script de 14476

configuración. Después de desempaquetar los archivos (usando tar), 14477

simplemente ejecuta: ./configure && make install 14478

Page 557: Pensar en C++ (Volumen 2)

557

en el directorio principal de ZThreads para compilar e instalar una copia de 14479

la librería en directorio /usr/local. Puedes personalizar algunas opciones 14480

cuando uses el script, incluída la localización de los ficheros. Para más 14481

detalles, utiliza este comando: ./configure ?help 14482

El código de ZThreads está estructurado para simplificar la compilación 14483

para otras plataformas (como Borland, Microsoft y Metrowerks). Para hacer 14484

esto, crea un nuevo proyecto y añade todos los archivos .cxx en el directorio 14485

src de ZThreads a la lista de archivos a compilar. Además, asegúrate de 14486

incluir el directorio incluído del archivo en la ruta de búsqueda de la cabecera 14487

para tu proyecto???. Los detalles exactos variarán de compilador en 14488

compilador, por lo que necesitarás estar algo familiarizado con tu conjunto de 14489

herramientas para ser capaz de utilizar esta opción. 14490

Una vez la compilación ha finalizado con éxito, el siguiente paso es crear 14491

un proyecto que use la nueva librería compilada. Primero, permite al 14492

compilador saber donde están localizadas las cabeceras, por lo que tu 14493

instrucción #include funcionará correctamente. Habitualmente, necesitarás 14494

en tu proyecto una opción como se muestra: 14495

-I/path/to/installation/include 14496

Si utilizaste el script de configuración, la ruta de instalación será el prefijo 14497

de la que definiste (por defecto, /usr/local). Si utilizaste uno de los archivos 14498

de proyecto en la creación del directorio, la ruta instalación debería ser 14499

simplemente la ruta al directorio principal del archivo ZThreads. 14500

Después, necesitarás añadir una opción a tu proyecto que permitirá al 14501

enlazador saber donde está la librería. Si usaste el script de configuración, se 14502

parecerá a lo siguiente: 14503

-L/path/to/installation/lib ?lZThread 14504

Si usaste uno de los archivos del proyecto proporcionados, será similar a: 14505

Page 558: Pensar en C++ (Volumen 2)

558

-L/path/to/installation/Debug ZThread.lib 14506

De nuevo, si usaste el script de configuración, la ruta de instalación s será 14507

el prefijo de la que definistes. Si su utilizaste un archivo del proyecto, la ruta 14508

será la misma que la del directorio principal de ZThreads. 14509

Nota que si estás utilizando Linux, o Cygwin (www.cygwin.com) bajo 14510

Windows, no deberías necesitar modificar la ruta de include o de la librería; el 14511

proceso por defecto de instalación tendrá cuidado para hacerlo por ti, 14512

normalmente. 14513

En GNU/Linux, es posible que necesites añadir lo siguiente a tu .bashrc 14514

para que el sistema pueda encontrar la el archivo de la librería compartida 14515

LibZThread-x.x.so.0 cuando ejecute programas de este capítulo. 14516

export LD_LIBRARY_PATH=/usr/local/lib:${LD_LIBRARY_PATH} 14517

(Asumiendo que utilizas el proceso de instalación por defecto y la librería 14518

compartida acaba en /user/local/lib/; en otro caso, cambia la ruta por tu 14519

localización. 14520

10.2.2. Definición de tareas 14521

Un hilo cumple con una tarea, por lo que necesitas un manera de describir 14522

esa tarea. La clase Runnable proporciona unas interfaces comunes a 14523

ejecutar para cualquier tarea. Aquí está el núcleo de las clase Runnable de 14524

ZThread, que la encontrarás en el archivo Runnable.h dentro del directorio 14525

incluído, después de instalar la librería ZThread: 14526

class Runnable { 14527

public: 14528

virtual void run() = 0; 14529

virtual ~Runnable() {} 14530

Page 559: Pensar en C++ (Volumen 2)

559

}; 14531

Al hacerla una clase base abstracta, Runnable es fácilmente combinable 14532

con una clase básica u otras clases. 14533

Para definir una tarea, simplemente hereda de la clase Runnable y 14534

sobreescribe run( ) para que la tarea haga lo que quieres. 14535

Por ejecomplo, la tarea LiftOff siguiente muestra la cuenta atrás antes de 14536

despegar: 14537

//: C11:LiftOff.h 14538

// Demonstration of the Runnable interface. 14539

#ifndef LIFTOFF_H 14540

#define LIFTOFF_H 14541

#include <iostream> 14542

#include "zthread/Runnable.h" 14543

14544

class LiftOff : public ZThread::Runnable { 14545

int countDown; 14546

int id; 14547

public: 14548

LiftOff(int count, int ident = 0) : 14549

countDown(count), id(ident) {} 14550

~LiftOff() { 14551

std::cout << id << " completed" << std::endl; 14552

} 14553

void run() { 14554

while(countDown--) 14555

Page 560: Pensar en C++ (Volumen 2)

560

std::cout << id << ":" << countDown << std::endl; 14556

std::cout << "Liftoff!" << std::endl; 14557

} 14558

}; 14559

#endif // LIFTOFF_H ///:~ 14560

Listado 10.1. C11/LiftOff.h 14561

14562

El identificador id sirve como distinción entre multiples instancias de la 14563

tarea. Si sólo quieres hacer una instancia, debes utilizar el valor por defecto 14564

para identificarla. El destructor te permitirá ver que tarea está destruída 14565

correctamente. 14566

En el siguiente ejemplo, las tareas de run( ) no están dirigidas por hilos 14567

separados; directamente es una simple llamada en main( ): 14568

//: C11:NoThread.cpp 14569

#include "LiftOff.h" 14570

14571

int main() { 14572

LiftOff launch(10); 14573

launch.run(); 14574

} ///:~ 14575

Listado 10.2. C11/NoThread.cpp 14576

14577

Cuando una clase deriva de Runnable, debe tene una función run( ), pero 14578

no tiene nada de especial - no produce ninguna habibilidad innata en el hilo. 14579

Para llevar a cabo el funcionamiento de los hilos, debes utilizas la clase 14580

Thread. 14581

Page 561: Pensar en C++ (Volumen 2)

561

10.3. Utilización de los hilos 14582

Para controlar un objeto Runnable con un hilo, crea un objeto Thread 14583

separado y utiliza un pontero Runnable al constructor de Thread. Esto lleva a 14584

cabo la inicialización del hilo y, después, llama a run ( ) de Runnable como 14585

un hilo capaz de ser interrumpido. Manejando LiftOff con un hilo, el ejemplo 14586

siguiente muestra como cualquier tarea puede ser ejecutada en el contexto 14587

de cualquier otro hilo: 14588

//: C11:BasicThreads.cpp 14589

// The most basic use of the Thread class. 14590

//{L} ZThread 14591

#include <iostream> 14592

#include "LiftOff.h" 14593

#include "zthread/Thread.h" 14594

using namespace ZThread; 14595

using namespace std; 14596

14597

int main() { 14598

try { 14599

Thread t(new LiftOff(10)); 14600

cout << "Waiting for LiftOff" << endl; 14601

} catch(Synchronization_Exception& e) { 14602

cerr << e.what() << endl; 14603

} 14604

} ///:~ 14605

Listado 10.3. C11/BasicThreads.cpp 14606

Page 562: Pensar en C++ (Volumen 2)

562

14607

Synchronization_Exception forma parte de la librería ZThread y la clase 14608

base para todas las excepciones de ZThread. Se lanzará si hay un error al 14609

crear o usar un hilo. 14610

Un constructor de Thread sólo necesita un puntero a un objeto Runnable. 14611

Al crear un objeto Thread se efectuará la incialización necesaria del hilo y 14612

después se llamará a Runnable::run() 14613

Puede añadir más hilos fácilmente para controlar más tareas. A 14614

continuación, puede ver cómo los hilos se ejecutan con algún otro: 14615

//: C11:MoreBasicThreads.cpp 14616

// Adding more threads. 14617

//{L} ZThread 14618

#include <iostream> 14619

#include "LiftOff.h" 14620

#include "zthread/Thread.h" 14621

using namespace ZThread; 14622

using namespace std; 14623

14624

int main() { 14625

const int SZ = 5; 14626

try { 14627

for(int i = 0; i < SZ; i++) 14628

Thread t(new LiftOff(10, i)); 14629

cout << "Waiting for LiftOff" << endl; 14630

} catch(Synchronization_Exception& e) { 14631

cerr << e.what() << endl; 14632

} 14633

Page 563: Pensar en C++ (Volumen 2)

563

} ///:~ 14634

Listado 10.4. C11/MoreBasicThreads.cpp 14635

14636

El segundo argumento del constructor de LiftOff identifica cada tarea. 14637

Cuando ejecute el programa, verá que la ejecución de las distintas tareas se 14638

mezclan a medida que los hilos entran y salen de su ejecución. Este 14639

intercambio está controlado automáticamente por el planificador de hilos. Si 14640

tiene múltiples procesadores en su máquina, el planificador de hilos 14641

distribuirá los hilos entre los procesadores de forma transparente. 14642

El bucle for puede parecer un poco extraño a priori ya que se crea 14643

localmente dentro del bucle for e inmediatamente después sale del ámbito y 14644

es destruído. Esto hace que parezca que el hilo propiamente dicho pueda 14645

perderse inmediatamente, pero puede ver por la salida que los hilos, en 14646

efecto, están en ejecución hasta su finalización. Cuando crea un objeto 14647

Thread, el hilo asociado se registra en el sistema de hilos, que lo mantiene 14648

vivo. A pesar de que el objeto Thread local se pierde, el hilo sigue vivo hasta 14649

que su tarea asociada termina. Aunque puede ser poco intuitivo desde el 14650

punto de vista de C++, el concepto de hilos es la excepción de la regla: un 14651

hilo crea un hilo de ejecución separado que persiste después de que la 14652

llamada a función finalice. Esta excepción se refleja en la persistencia del hilo 14653

subyacente después de que el objeto desaparezca. 14654

10.3.1. Creación de interfaces de usuarios interactivas 14655

Como se dijo anteriormente, uno de las motivaciones para usar hilos es 14656

crear interfaces de usuario interactivas. Aunque en este libro no cubriremos 14657

las interfaces gráficas de usuario, verá un ejemplo sencillo de una interfaz de 14658

usuario basada en consola. 14659

El siguiente ejemplo lee líneas de un archivo y las imprime a la consola, 14660

durmiéndose (suspender el hilo actual) durante un segundo después de que 14661

cada línea sea mostrada. (Aprenderá más sobre el proceso de dormir hilos 14662

en el capítulo.) Durante este proceso, el programa no busca la entrada del 14663

usuario, por lo que la IU no es interactiva: 14664

Page 564: Pensar en C++ (Volumen 2)

564

//: C11:UnresponsiveUI.cpp {RunByHand} 14665

// Lack of threading produces an unresponsive UI. 14666

//{L} ZThread 14667

#include <iostream> 14668

#include <fstream> 14669

#include <string> 14670

#include "zthread/Thread.h" 14671

using namespace std; 14672

using namespace ZThread; 14673

14674

int main() { 14675

cout << "Press <Enter> to quit:" << endl; 14676

ifstream file("UnresponsiveUI.cpp"); 14677

string line; 14678

while(getline(file, line)) { 14679

cout << line << endl; 14680

Thread::sleep(1000); // Time in milliseconds 14681

} 14682

// Read input from the console 14683

cin.get(); 14684

cout << "Shutting down..." << endl; 14685

} ///:~ 14686

Listado 10.5. C11/UnresponsiveUI.cpp 14687

14688

Page 565: Pensar en C++ (Volumen 2)

565

Para hacer este programa interactivo, puede ejecutar una tarea que 14689

muestre el archivo en un hilo separado. De esta forma, el hilo principal puede 14690

leer la entrada del usuario, por lo que el programa se vuelve interactivo: 14691

//: C11:ResponsiveUI.cpp {RunByHand} 14692

// Threading for a responsive user interface. 14693

//{L} ZThread 14694

#include <iostream> 14695

#include <fstream> 14696

#include <string> 14697

#include "zthread/Thread.h" 14698

using namespace ZThread; 14699

using namespace std; 14700

14701

class DisplayTask : public Runnable { 14702

ifstream in; 14703

string line; 14704

bool quitFlag; 14705

public: 14706

DisplayTask(const string& file) : quitFlag(false) { 14707

in.open(file.c_str()); 14708

} 14709

~DisplayTask() { in.close(); } 14710

void run() { 14711

while(getline(in, line) && !quitFlag) { 14712

cout << line << endl; 14713

Page 566: Pensar en C++ (Volumen 2)

566

Thread::sleep(1000); 14714

} 14715

} 14716

void quit() { quitFlag = true; } 14717

}; 14718

14719

int main() { 14720

try { 14721

cout << "Press <Enter> to quit:" << endl; 14722

DisplayTask* dt = new DisplayTask("ResponsiveUI.cpp"); 14723

Thread t(dt); 14724

cin.get(); 14725

dt->quit(); 14726

} catch(Synchronization_Exception& e) { 14727

cerr << e.what() << endl; 14728

} 14729

cout << "Shutting down..." << endl; 14730

} ///:~ 14731

Listado 10.6. C11/ResponsiveUI.cpp 14732

14733

Ahora el hilo main() puede responder inmediatamente cuando pulse Return 14734

e invocar quit() sobre DisplayTask. 14735

Este ejemplo también muestra la necesidad de una comunicación entre 14736

tareas - la tarea en el hilo main() necesita parar al DisplayTask. Dado que 14737

tenemos un puntero a DisplayTask, puede pensar que bastaría con llamar al 14738

destructor de ese puntero para matar la tarea, pero esto hace que los 14739

Page 567: Pensar en C++ (Volumen 2)

567

programas sean poco fiables. El problema es que la tarea podría estar en 14740

mitad de algo importante cuando lo destruye y, por lo tanto, es probable que 14741

ponga el programa en un estado inestable. En este sentido, la propia tarea 14742

decide cuando es seguro terminar. La manera más sencilla de hacer esto es 14743

simplemente notificar a la tarea que desea detener mediante una bandera 14744

booleana. Cuando la tarea se encuentre en un punto estable puede consultar 14745

esa bandera y hacer lo que sea necesario para limpiar el estado después de 14746

regresar de run(). Cuando la tarea vuelve de run(), Thread sabe que la tarea 14747

se ha completado. 14748

Aunque este programa es lo suficientemente simple para que no haya 14749

problemas, hay algunos pequeños defectos respecto a la comunicación entre 14750

pilas. Es un tema importante que se cubrirá más tarde en este capítulo. 14751

10.3.2. Simplificación con Ejecutores 14752

Utilizando los Ejecutores de ZThread, puede simplificar su código. Los 14753

Ejecutores proporcionan una capa de indirección entre un cliente y la 14754

ejecución de una tarea; a diferencia de un cliente que ejecuta una tarea 14755

directamente, un objeto intermediario ejecuta la tarea. 14756

Podemos verlo utilizando un Ejecutor en vez de la creación explícita de 14757

objetos Thread en MoreBasicThreads.cpp. Un objeto LiftOff conoce cómo 14758

ejecutar una tarea específica; como el patrón Command, expone una única 14759

función a ejecutar. Un objeto Executor conoce como construir el contexto 14760

apropiado para lanzar objetos Runnable. En el siguiente ejemplo, 14761

ThreadedExecutor crea un hilo por tarea: 14762

//: c11:ThreadedExecutor.cpp 14763

//{L} ZThread 14764

#include <iostream> 14765

#include "zthread/ThreadedExecutor.h" 14766

#include "LiftOff.h" 14767

using namespace ZThread; 14768

Page 568: Pensar en C++ (Volumen 2)

568

using namespace std; 14769

14770

int main() { 14771

try { 14772

ThreadedExecutor executor; 14773

for(int i = 0; i < 5; i++) 14774

executor.execute(new LiftOff(10, i)); 14775

} catch(Synchronization_Exception& e) { 14776

cerr << e.what() << endl; 14777

} 14778

} ///:~ 14779

Listado 10.7. C11/ThreadedExecutor.cpp 14780

14781

Note que algunos casos un Executor individual puede ser usado para crear 14782

y gestionar todo los hilos en su sistema. Debe colocar el código 14783

correspondiente a los hilos dentro de un bloque try porque el método 14784

execute() de un Executor puede lanzar una Synchronization_Exception si 14785

algo va mal. Esto es válido para cualquier función que implique cambiar el 14786

estado de un objeto de sincronización (arranque de hilos, la adquisición de 14787

mutexes, esperas en condiciones, etc.), tal y como aprenderá más adelante 14788

en este capítulo. 14789

El programa finalizará cuando todas las tareas en el Executor hayan 14790

concluido. 14791

En el siguiente ejemplo, ThreadedExecutor crea un hilo para cada tarea 14792

que quiera ejecutar, pero puede cambiar fácilmente la forma en la que esas 14793

tareas son ejecutadas reemplazando el ThreadedExecutor por un tipo 14794

diferente de Executor. En este capítulo, usar un ThreadedExecutor está bien, 14795

pero para código en producción puede resultar excesivamente costoso para 14796

la creación de muchos hilos. En ese caso, puede reemplazarlo por un 14797

Page 569: Pensar en C++ (Volumen 2)

569

PoolExecutor, que utilizará un conjunto limitado de hilos para lanzar las 14798

tareas registradas en paralelo: 14799

//: C11:PoolExecutor.cpp 14800

//{L} ZThread 14801

#include <iostream> 14802

#include "zthread/PoolExecutor.h" 14803

#include "LiftOff.h" 14804

using namespace ZThread; 14805

using namespace std; 14806

14807

int main() { 14808

try { 14809

// Constructor argument is minimum number of threads: 14810

PoolExecutor executor(5); 14811

for(int i = 0; i < 5; i++) 14812

executor.execute(new LiftOff(10, i)); 14813

} catch(Synchronization_Exception& e) { 14814

cerr << e.what() << endl; 14815

} 14816

} ///:~ 14817

Listado 10.8. C11/PoolExecutor.cpp 14818

14819

Con PoolExecutor puede realizar una asignación inicial de hilos costosa de 14820

una sola vez, por adelantado, y los hilos se reutilizan cuando sea posible. 14821

Esto ahorra tiempo porque no está pagando el gasto de la creación de hilos 14822

por cada tarea individual de forma constante. Además, en un sistema dirigido 14823

Page 570: Pensar en C++ (Volumen 2)

570

por eventos, los eventos que requieren hilos para manejarlos puede ser 14824

generados tan rápido como quiera, basta con traerlos del pool. No excederá 14825

los recursos disponibles porque PoolExecutor utiliza un número limitado de 14826

objetos Thread. Así, aunque en este libro se utilizará ThreadedExecutors, 14827

tenga en cuenta utilizar PoolExecutor para código en producción. 14828

ConcurrentExecutor es como PoolExecutor pero con un tamaño fijo de 14829

hilos. Es útil para cualquier cosa que quiera lanzar en otro hilo de forma 14830

continua (una tarea de larga duración), como una tare que escucha 14831

conexiones entrantes en un socket. También es útil para tareas cortas que 14832

quiera lanzar en un hilo, por ejemplo, pequeñas tareas que actualizar un log 14833

local o remoto, o para un hilo que atienda a eventos. 14834

Si hay más de una tarea registrada en un ConcurrentExecutor, cada una 14835

de ellas se ejecutará completamente hasta que la siguiente empiece; todas 14836

utilizando el mismo hilo. En el ejemplo siguiente, verá que cada tarea se 14837

completa, en el orden en el que fue registrada, antes de que la siguiente 14838

comience. De esta forma, un ConcurrentExecutor serializa las tareas que le 14839

fueron asignadas. 14840

//: C11:ConcurrentExecutor.cpp 14841

//{L} ZThread 14842

#include <iostream> 14843

#include "zthread/ConcurrentExecutor.h" 14844

#include "LiftOff.h" 14845

using namespace ZThread; 14846

using namespace std; 14847

14848

int main() { 14849

try { 14850

ConcurrentExecutor executor; 14851

for(int i = 0; i < 5; i++) 14852

Page 571: Pensar en C++ (Volumen 2)

571

executor.execute(new LiftOff(10, i)); 14853

} catch(Synchronization_Exception& e) { 14854

cerr << e.what() << endl; 14855

} 14856

} ///:~ 14857

Listado 10.9. C11/ConcurrentExecutor.cpp 14858

14859

Como un ConcurrentExecutor, un SynchronousExecutor se usa cuando 14860

quiera una única tarea se ejecute al mismo tiempo, en serie en lugar de 14861

concurrente. A diferencia de ConcurrentExecutor, un SynchronousExecutor 14862

no crea ni gestiona hilos sobre si mismo. Utiliza el hilo que añadió la tarea y, 14863

así, únicamente actúa como un punto focal para la sincronización. Si tiene n 14864

tareas registradas en un SynchronousExecutor, nunca habrá 2 tareas que se 14865

ejecuten a la vez. En lugar de eso, cada una se ejecutará hasta su 14866

finalización y la siguiente en la cola comenzará. 14867

Por ejemplo, suponga que tiene un número de hilos ejecutando tareas que 14868

usan un sistema de archivos, pero está escribiendo código portable luego no 14869

quiere utilizar flock() u otra llamada al sistema operativo específica para 14870

bloquear un archivo. Puede lanzar esas tareas con un SynchronousExecutor 14871

para asegurar que solamente una de ellas, en un tiempo determinado, está 14872

ejecutándose desde cualquier hilo. De esta manera, no necesita preocuparse 14873

por la sincronización del recurso compartido (y, de paso, no se cargará el 14874

sistema de archivos). Una mejor solución pasa por sincronizar el recurso (lo 14875

cual aprenderá más adelante en este capítulo), sin embargo un 14876

SynchronousExecutor le permite evitar las molestias de obtener una 14877

coordinación adecuada para prototipar algo. 14878

//: C11:SynchronousExecutor.cpp 14879

//{L} ZThread 14880

#include <iostream> 14881

Page 572: Pensar en C++ (Volumen 2)

572

#include "zthread/SynchronousExecutor.h" 14882

#include "LiftOff.h" 14883

using namespace ZThread; 14884

using namespace std; 14885

14886

int main() { 14887

try { 14888

SynchronousExecutor executor; 14889

for(int i = 0; i < 5; i++) 14890

executor.execute(new LiftOff(10, i)); 14891

} catch(Synchronization_Exception& e) { 14892

cerr << e.what() << endl; 14893

} 14894

} ///:~ 14895

Listado 10.10. C11/SynchronousExecutor.cpp 14896

14897

Cuando ejecuta el programa verá que las tareas son lanzadas en el orden 14898

en el que fueron registradas, y cada tarea se ejecuta completamente antes 14899

de que la siguiente empiece. ¿Qué es lo que no ve y que hace que no se 14900

creen nuevos hilos? El hilo main() se usa para cada tarea, y debido a este 14901

ejemplo, ese es el hilo que registra todas las tareas. Podría no utilizar un 14902

SynchronousExecutor en código en producción porque, principalmente, es 14903

para prototipado. 14904

10.3.3. Ceder el paso 14905

Si sabe que ha logrado realizar lo que necesita durante una pasada a 14906

través de un bucle en su función run() (la mayoría de las funciones run() 14907

suponen un periodo largo de tiempo de ejecución), puede darle un toque al 14908

Page 573: Pensar en C++ (Volumen 2)

573

mecanismo de planificación de hilos, decirle que ya ha hecho suficiente y que 14909

algún otro hilo puede tener la CPU. Este toque (y es un toque - no hay 14910

garantía de que su implementación vaya a escucharlo) adopta la forma de la 14911

función yield(). 14912

Podemos construir una versión modificada de los ejemplos de LiftOff 14913

cediendo el paso después de cada bucle: 14914

//: C11:YieldingTask.cpp 14915

// Suggesting when to switch threads with yield(). 14916

//{L} ZThread 14917

#include <iostream> 14918

#include "zthread/Thread.h" 14919

#include "zthread/ThreadedExecutor.h" 14920

using namespace ZThread; 14921

using namespace std; 14922

14923

class YieldingTask : public Runnable { 14924

int countDown; 14925

int id; 14926

public: 14927

YieldingTask(int ident = 0) : countDown(5), id(ident) {} 14928

~YieldingTask() { 14929

cout << id << " completed" << endl; 14930

} 14931

friend ostream& 14932

operator<<(ostream& os, const YieldingTask& yt) { 14933

return os << "#" << yt.id << ": " << yt.countDown; 14934

Page 574: Pensar en C++ (Volumen 2)

574

} 14935

void run() { 14936

while(true) { 14937

cout << *this << endl; 14938

if(--countDown == 0) return; 14939

Thread::yield(); 14940

} 14941

} 14942

}; 14943

14944

int main() { 14945

try { 14946

ThreadedExecutor executor; 14947

for(int i = 0; i < 5; i++) 14948

executor.execute(new YieldingTask(i)); 14949

} catch(Synchronization_Exception& e) { 14950

cerr << e.what() << endl; 14951

} 14952

} ///:~ 14953

Listado 10.11. C11/YieldingTask.cpp 14954

14955

Puede ver que la tarea del método run() es en un bucle infinito en su 14956

totalidad. Utilizando yield(), la salida se equilibra bastante que en el caso en 14957

el que no se cede el paso. Pruebe a comentar la llamada a Thread::yield() 14958

para ver la diferencia. Sin embargo, en general, yield() es útil en raras 14959

Page 575: Pensar en C++ (Volumen 2)

575

ocasiones, y no puede contar con ella para realizar un afinamiento serio 14960

sobre su aplicación. 14961

10.3.4. Dormido 14962

Otra forma con la que puede tener control sobre el comportamiento de su 14963

hilos es llamando a sleep() para cesar la ejecución de uno de ellos durante 14964

un número de milisegundos dado. En el ejemplo que viene a continuación, si 14965

cambia la llamada a yield() por una a sleep(), obtendrá lo siguiente: 14966

//: C11:SleepingTask.cpp 14967

// Calling sleep() to pause for awhile. 14968

//{L} ZThread 14969

#include <iostream> 14970

#include "zthread/Thread.h" 14971

#include "zthread/ThreadedExecutor.h" 14972

using namespace ZThread; 14973

using namespace std; 14974

14975

class SleepingTask : public Runnable { 14976

int countDown; 14977

int id; 14978

public: 14979

SleepingTask(int ident = 0) : countDown(5), id(ident) {} 14980

~SleepingTask() { 14981

cout << id << " completed" << endl; 14982

} 14983

friend ostream& 14984

Page 576: Pensar en C++ (Volumen 2)

576

operator<<(ostream& os, const SleepingTask& st) { 14985

return os << "#" << st.id << ": " << st.countDown; 14986

} 14987

void run() { 14988

while(true) { 14989

try { 14990

cout << *this << endl; 14991

if(--countDown == 0) return; 14992

Thread::sleep(100); 14993

} catch(Interrupted_Exception& e) { 14994

cerr << e.what() << endl; 14995

} 14996

} 14997

} 14998

}; 14999

15000

int main() { 15001

try { 15002

ThreadedExecutor executor; 15003

for(int i = 0; i < 5; i++) 15004

executor.execute(new SleepingTask(i)); 15005

} catch(Synchronization_Exception& e) { 15006

cerr << e.what() << endl; 15007

} 15008

} ///:~ 15009

Page 577: Pensar en C++ (Volumen 2)

577

Listado 10.12. C11/SleepingTask.cpp 15010

15011

Thread::sleep() puede lanzar una Interrupted_Exception(sobre las 15012

interrupciones aprenderá más adelante), y puede ver que esta excepción se 15013

captura en run(). Sin embargo, la tarea se crea y se ejecuta dentro de un 15014

bloque try en main() que captura Interrupted_Exception (la clase base para 15015

todas las excepciones de ZThread), por lo que ¿no sería posible ignorar la 15016

excepción en run() y asumir que se propagará al manejador en main()?. Esto 15017

no funcionará porque las excepciones no se propagarán a lo largo de los 15018

hilos para volver hacia main(). De esta forma, debe manejar cualquier 15019

excepción que pueda ocurrir dentro de una tarea de forma local. 15020

Notará que los hilos tienden a ejecutarse en cualquier orden, lo que quiere 15021

decir que sleep() tampoco es una forma de controlar el orden de la ejecución 15022

de los hilos. Simplemente para la ejecución del hilo durante un rato. La única 15023

garantía que tiene es que el hilo se dormirá durante, al menos, 100 15024

milisegundos (en este ejemplo), pero puede que tarde más después de que 15025

el hilo reinicie la ejecución ya que el planificador de hilos tiene que volver a él 15026

tras haber expirado el intervalo. 15027

Si debe tener control sobre el orden de la ejecución de hilos, su mejor baza 15028

es el uso de controles de sincronización (descritos más adelante) o, en 15029

algunos casos, no usar hilos en todo, FIXMEbut instead to write your own 15030

cooperative routines that hand control to each other in a specified order. 15031

10.3.5. Prioridad 15032

La prioridad de un hilo representa la importancia de ese hilo para el 15033

planificador. Pese a que el orden en que la CPU ejecuta un conjunto de hilos 15034

es indeterminado, el planificador tenderá a ejecutar el hilo con mayor 15035

prioridad de los que estén esperando. Sin embargo, no quiere decir que hilos 15036

con menos prioridad no se ejecutarán (es decir, no tendrá bloqueo de un hilo 15037

a causa de las prioridades). Simplemente, los hilos con menos prioridad 15038

tenderán a ejecutarse menos frecuentemente. 15039

Se ha modificado MoreBasicThreads.cpp para mostrar los niveles de 15040

prioridad. Las prioridades se ajustan utilizando la función setPriority() de 15041

Thread. 15042

Page 578: Pensar en C++ (Volumen 2)

578

//: C11:SimplePriorities.cpp 15043

// Shows the use of thread priorities. 15044

//{L} ZThread 15045

#include <iostream> 15046

#include "zthread/Thread.h" 15047

using namespace ZThread; 15048

using namespace std; 15049

15050

const double pi = 3.14159265358979323846; 15051

const double e = 2.7182818284590452354; 15052

15053

class SimplePriorities : public Runnable { 15054

int countDown; 15055

volatile double d; // No optimization 15056

int id; 15057

public: 15058

SimplePriorities(int ident=0): countDown(5), id(ident) {} 15059

~SimplePriorities() { 15060

cout << id << " completed" << endl; 15061

} 15062

friend ostream& 15063

operator<<(ostream& os, const SimplePriorities& sp) { 15064

return os << "#" << sp.id << " priority: " 15065

<< Thread().getPriority() 15066

<< " count: "<< sp.countDown; 15067

Page 579: Pensar en C++ (Volumen 2)

579

} 15068

void run() { 15069

while(true) { 15070

// An expensive, interruptable operation: 15071

for(int i = 1; i < 100000; i++) 15072

d = d + (pi + e) / double(i); 15073

cout << *this << endl; 15074

if(--countDown == 0) return; 15075

} 15076

} 15077

}; 15078

15079

int main() { 15080

try { 15081

Thread high(new SimplePriorities); 15082

high.setPriority(High); 15083

for(int i = 0; i < 5; i++) { 15084

Thread low(new SimplePriorities(i)); 15085

low.setPriority(Low); 15086

} 15087

} catch(Synchronization_Exception& e) { 15088

cerr << e.what() << endl; 15089

} 15090

} ///:~ 15091

Page 580: Pensar en C++ (Volumen 2)

580

Listado 10.13. C11/SimplePriorities.cpp 15092

15093

En este ejemplo, el operador <<() se sobreescribe para mostrar el 15094

identificador, la prioridad y el valor de countDown de la tarea. 15095

Puede ver que el nivel de prioridad del hilo es el más alto, y que el resto de 15096

hilos tienen el nivel más bajo. No utilizamos Executor en este ejemplo porque 15097

necesitamos acceder directamente al hilo para configurar sus propiedades. 15098

Dentro de SimplePriorities::run() se ejecutan 100,000 veces un costoso 15099

conjunto de cálculos en punto flotante. La variable d es volátil para intentar 15100

garantizar que ningún compilador hace optimizaciones. Sin este cálculo, no 15101

comprobará el efecto de la configuración de los niveles de prioridad. 15102

(Pruébelo: comente el bucle for que contiene los cálculos en doble precisión.) 15103

Con el cálculo puede ver que el planificador de hilos al hilo high se le da más 15104

preferencia. (Al menos, este fue el comportamiento sobre una máquina 15105

Windows). El cálculo tarda lo suficiente para que el mecanismo de 15106

planificación de hilos lo salte, cambie hilos y tome en cuenta las prioridades 15107

para que el hilo high tenga preferencia. 15108

También, puede leer la prioridad de un hilo existente con getPriority() y 15109

cambiarla en cualquier momento (no sólo antes de que el hilo se ejecute, 15110

como en SimplePriorities.cpp) con setPriority(). 15111

La correspondencia de las prioridades con el sistema operativo es un 15112

problema. Por ejemplo, Windows 2000 tiene siete niveles de prioridades, 15113

mientras que Solaris de Sun tiene 231. El único enfoque portable es ceñirse 15114

a los niveles discretos de prioridad, como Low, Medium y High utilizados en 15115

la librería ZThread. 15116

10.4. Comparición de recursos limitados 15117

Piense en un programa con un único hilo como una solitaria entidad 15118

moviéndose a lo largo del espacio de su problema y haciendo una cosa en 15119

cada instante. Debido a que sólo hay una entidad, no tiene que preocuparse 15120

por el problema de dos entidades intentando usar el mismo recurso al mismo 15121

tiempo: problemas como dos personas intentando aparcar en el mismo sitio, 15122

pasar por una puerta al mismo tiempo o incluso hablar al mismo tiempo. 15123

Page 581: Pensar en C++ (Volumen 2)

581

Con multihilado las cosas ya no son solitarias, pero ahora tiene la 15124

posibilidad de tener dos o más hilos intentando utilizar un mismo recurso a la 15125

vez. Esto puede causar dos problemas distintos. El primero es que el recurso 15126

necesario podría no existir. En C++, el programador tiene un control total 15127

sobre la vida de los objetos, y es fácil crear hilos que intenten usar objetos 15128

que han sido destruidos antes de que esos hilos hayan finalizado. 15129

El segundo problema es que dos o más hilos podrían chocar cuando 15130

intenten acceder al mismo dispositivo al mismo tiempo. Si no previene esta 15131

colisión, tendrá dos hilos intentando acceder a la misma cuenta bancaria al 15132

mismo tiempo, imprimir en la misma impresora, ajustar la misma válvula, etc. 15133

Esta sección presenta el problema de los objetos que desaparecen 15134

mientras las tareas aún están usándolos y el problema del choque entre 15135

tareas sobre recursos compartidos. Aprenderá sobre las herramientas que se 15136

usan para solucionar esos problemas. 15137

10.4.1. Aseguramiento de la existencia de objetos 15138

La gestión de memoria y recursos son las principales preocupaciones en 15139

C++. Cuando crea cualquier programa en C++, tiene la opción de crear 15140

objetos en la pila o en el heap (utilizando new). En un programa con un solo 15141

hilo, normalmente es sencillo seguir la vida de los objetos con el fin de que 15142

no tenga que utilizar objetos que ya están destruidos. 15143

Los ejemplos mostrados en este capítulo crean objetos Runnable en el 15144

heap utilizando new, se dará cuenta que esos objetos nunca son destruidos 15145

explícitamente. Sin embargo, podrá por la salida cuando ejecuta el programa 15146

que la biblioteca de hilos sigue la pista a cada tarea y, eventualmente, las 15147

destruye. Esto ocurre cuando el método Runnable::run() finaliza - volver de 15148

run() indica que la tarea ha finalizado. 15149

Recargar el hilo al destruir una tarea es un problema. Ese hilo sabe 15150

necesariamente si otro necesita hacer referencia a ese Runnable, y por ello 15151

el Runnable podría ser destruido prematuramente. Para ocuparse de este 15152

problema, el mecanismo de la biblioteca ZThread mantiene un conteo de 15153

referencias sobre las tareas. Una tarea se mantiene viva hasta que su 15154

contador de referencias se pone a cero, en este punto la tarea se destruye. 15155

Esto quiere decir que las tareas tienen que ser destruidas dinámicamente 15156

Page 582: Pensar en C++ (Volumen 2)

582

siempre, por lo que no pueden ser creadas en la pila. En vez de eso, las 15157

tareas deben ser creadas utilizando new, tal y como puede ver en todos los 15158

ejemplos de este capítulo. tareas in ZThreads 15159

Además, también debe asegurar que los objetos que no son tareas estarán 15160

vivos tanto tiempo como el que las tareas necesiten de ellos. Por otro lado, 15161

resulta sencillo para los objetos utilizados por las tareas salir del ámbito 15162

antes de que las tareas hayan concluido. Si ocurre esto, las tareas intentarán 15163

acceder zonas de almacenamiento ilegales y provocará que el programa 15164

falle. He aquí un simple ejemplo: 15165

//: C11:Incrementer.cpp {RunByHand} 15166

// Destroying objects while threads are still 15167

// running will cause serious problems. 15168

//{L} ZThread 15169

#include <iostream> 15170

#include "zthread/Thread.h" 15171

#include "zthread/ThreadedExecutor.h" 15172

using namespace ZThread; 15173

using namespace std; 15174

15175

class Count { 15176

enum { SZ = 100 }; 15177

int n[SZ]; 15178

public: 15179

void increment() { 15180

for(int i = 0; i < SZ; i++) 15181

n[i]++; 15182

} 15183

Page 583: Pensar en C++ (Volumen 2)

583

}; 15184

15185

class Incrementer : public Runnable { 15186

Count* count; 15187

public: 15188

Incrementer(Count* c) : count(c) {} 15189

void run() { 15190

for(int n = 100; n > 0; n--) { 15191

Thread::sleep(250); 15192

count->increment(); 15193

} 15194

} 15195

}; 15196

15197

int main() { 15198

cout << "This will cause a segmentation fault!" << endl; 15199

Count count; 15200

try { 15201

Thread t0(new Incrementer(&count)); 15202

Thread t1(new Incrementer(&count)); 15203

} catch(Synchronization_Exception& e) { 15204

cerr << e.what() << endl; 15205

} 15206

} ///:~ 15207

Page 584: Pensar en C++ (Volumen 2)

584

Listado 10.14. C11/Incrementer.cpp 15208

15209

Podría parecer a priori que la clase Count es excesiva, pero si únicamente 15210

n es un int (en lugar de una matriz), el compilador puede ponerlo dentro de 15211

un registro y ese almacenamiento seguirá estando disponible (aunque 15212

técnicamente es ilegal) después de que el objeto Count salga del ámbito. Es 15213

difícil detectar la violación de memoria en este caso. Sus resultados podrían 15214

variar dependiendo de su compilador y de su sistema operativo, pero pruebe 15215

a que n sea un int y verá qué ocurre. En cualquier evento, si Count contiene 15216

una matriz de ints y como antes, el compilador está obligado a ponerlo en la 15217

pila y no en un registro. 15218

Incrementer es una tarea sencilla que utiliza un objeto Count. En main(), 15219

puede ver que las tareas Incrementer se ejecutan el tiempo suficiente para 15220

que el salga del ámbito, por lo que la tarea intentará acceder a un objeto que 15221

no existe. Esto produce un fallo en el programa. objeto Count 15222

Para solucionar este problema, debemos garantizar que cualquiera de los 15223

objetos compartidos entre tareas estarán accesibles tanto tiempo como las 15224

tareas los necesiten. (Si los objetos no fueran compartidos, podrían estar 15225

directamente dentro de las clases de las tareas y, así, unir su tiempo de vida 15226

a la tarea.) Dado que no queremos que el propio ámbito estático del 15227

programa controle el tiempo de vida del objeto, pondremos el en heap. Y 15228

para asegurar que el objeto no se destruye hasta que no haya objetos 15229

(tareas, en este caso) que lo estén utilizando, utilizaremos el conteo de 15230

referencias. 15231

El conteo de referencias se ha explicado a lo largo del volumen uno de 15232

este libro y además se revisará en este volumen. La librería ZThread incluye 15233

una plantilla llamada CountedPtr que automáticamente realiza el conteo de 15234

referencias y destruye un objeto cuando su contador de referencias vale 15235

cero. A continuación, se ha modificado el programa para que utilice 15236

CountedPtr para evitar el fallo: 15237

//: C11:ReferenceCounting.cpp 15238

// A CountedPtr prevents too-early destruction. 15239

Page 585: Pensar en C++ (Volumen 2)

585

//{L} ZThread 15240

#include <iostream> 15241

#include "zthread/Thread.h" 15242

#include "zthread/CountedPtr.h" 15243

using namespace ZThread; 15244

using namespace std; 15245

15246

class Count { 15247

enum { SZ = 100 }; 15248

int n[SZ]; 15249

public: 15250

void increment() { 15251

for(int i = 0; i < SZ; i++) 15252

n[i]++; 15253

} 15254

}; 15255

15256

class Incrementer : public Runnable { 15257

CountedPtr<Count> count; 15258

public: 15259

Incrementer(const CountedPtr<Count>& c ) : count(c) {} 15260

void run() { 15261

for(int n = 100; n > 0; n--) { 15262

Thread::sleep(250); 15263

count->increment(); 15264

Page 586: Pensar en C++ (Volumen 2)

586

} 15265

} 15266

}; 15267

15268

int main() { 15269

CountedPtr<Count> count(new Count); 15270

try { 15271

Thread t0(new Incrementer(count)); 15272

Thread t1(new Incrementer(count)); 15273

} catch(Synchronization_Exception& e) { 15274

cerr << e.what() << endl; 15275

} 15276

} ///:~ 15277

Listado 10.15. C11/ReferenceCounting.cpp 15278

15279

Ahora Incrementer contiene un objeto CountedPtr, que gestiona un Count. 15280

En la función main(), los objetos CountedPtr se pasan a los dos objetos 15281

Incrementer por valor, por lo que se llama el constructor de copia, 15282

incrementando el conteo de referencias. Mientras la tarea esté ejecutándose, 15283

el contador de referencias no valdrá cero, por lo que el objeto Count utilizado 15284

por CountedPtr no será destruído. Solamente cuando todas las tareas que 15285

utilice el Count terminen se llamará al destructor (automáticamente) sobre el 15286

objeto Count por el CountedPtr. 15287

Siempre que tenga una tarea que utilice más de un objeto, casi siempre 15288

necesitará controlar aquellos objetos utilizando la plantilla CountedPtr para 15289

evitar problemas derivados del tiempo de vida de los objetos. 15290

10.4.2. Acceso no apropiado a recursos 15291

Page 587: Pensar en C++ (Volumen 2)

587

Considere el siguiente ejemplo, donde una tarea genera números 15292

constantes y otras tareas consumen esos números. Ahora, el único trabajo 15293

de los hilos consumidores es probar la validez de los números constantes. 15294

Primeramente, definiremos EvenChecker, el hilo consumidor, puesto que 15295

será reutilizado en todos los ejemplos siguientes. Para desacoplar 15296

EvenChecker de los varios tipos de generadores con los que 15297

experimentaremos, crearemos una interfaz llamada Generator que contiene 15298

el número mínimo de funciones que EvenChecker necesita conocer: por lo 15299

que tiene una función nextValue() y que puede ser cancelada. 15300

//: C11:EvenChecker.h 15301

#ifndef EVENCHECKER_H 15302

#define EVENCHECKER_H 15303

#include <iostream> 15304

#include "zthread/CountedPtr.h" 15305

#include "zthread/Thread.h" 15306

#include "zthread/Cancelable.h" 15307

#include "zthread/ThreadedExecutor.h" 15308

15309

class Generator : public ZThread::Cancelable { 15310

bool canceled; 15311

public: 15312

Generator() : canceled(false) {} 15313

virtual int nextValue() = 0; 15314

void cancel() { canceled = true; } 15315

bool isCanceled() { return canceled; } 15316

}; 15317

15318

Page 588: Pensar en C++ (Volumen 2)

588

class EvenChecker : public ZThread::Runnable { 15319

ZThread::CountedPtr<Generator> generator; 15320

int id; 15321

public: 15322

EvenChecker(ZThread::CountedPtr<Generator>& g, int ident) 15323

: generator(g), id(ident) {} 15324

~EvenChecker() { 15325

std::cout << "~EvenChecker " << id << std::endl; 15326

} 15327

void run() { 15328

while(!generator->isCanceled()) { 15329

int val = generator->nextValue(); 15330

if(val % 2 != 0) { 15331

std::cout << val << " not even!" << std::endl; 15332

generator->cancel(); // Cancels all EvenCheckers 15333

} 15334

} 15335

} 15336

// Test any type of generator: 15337

template<typename GenType> static void test(int n = 10) { 15338

std::cout << "Press Control-C to exit" << std::endl; 15339

try { 15340

ZThread::ThreadedExecutor executor; 15341

ZThread::CountedPtr<Generator> gp(new GenType); 15342

for(int i = 0; i < n; i++) 15343

Page 589: Pensar en C++ (Volumen 2)

589

executor.execute(new EvenChecker(gp, i)); 15344

} catch(ZThread::Synchronization_Exception& e) { 15345

std::cerr << e.what() << std::endl; 15346

} 15347

} 15348

}; 15349

#endif // EVENCHECKER_H ///:~ 15350

Listado 10.16. C11/EvenChecker.h 15351

15352

La clase Generator presenta la clase abstracta Cancelable, que es parte 15353

de la biblioteca de ZThread. El propósito de Cancelable es proporcionar una 15354

interfaz consistente para cambiar el estado de un objeto via cancel() y ver si 15355

el objeto ha sido cancelado con la función isCanceled(). Aquí utilizamos el 15356

enfoque simple de una bandera de cancelación booleana similar a quitFlag, 15357

vista previamente en ResponsiveUI.cpp. Note que en este ejemplo la clase 15358

que es Cancelable no es Runnable. En su lugar, toda tarea EvenChecker que 15359

dependa de un objeto Cancelable (el Generator) lo comprueba para ver que 15360

ha sido cancelado, como puede ver en run(). De esta manera, las tareas que 15361

comparten recursos comunes (el Cancelable Generator) estén atentos a la 15362

señal de ese recurso para terminar. Esto elimina la también conocida 15363

condición de carrera, donde dos o más tareas compiten por responder una 15364

condición y, así, colisionar o producir resultados inconsistentes. Debe pensar 15365

sobre esto cuidadosamente y protegerse de todas las formas posible de los 15366

fallos de un sistema concurrente. Por ejemplo, una tarea no puede depender 15367

de otra porque el orden de finalización de las tareas no está garantizado. En 15368

este sentido, eliminamos la potencial condición de carrera haciendo que las 15369

tareas dependan de objetos que no son tareas (que son contados 15370

referencialmente utilizando CountedPtr. 15371

En las secciones posteriores, verá que la librería ZThread contiene más 15372

mecanismos generales para la terminación de hilos. 15373

Page 590: Pensar en C++ (Volumen 2)

590

Debido a que muchos objetos EvenChecker podrían terminar compartiendo 15374

un Generator, la plantilla CountedPtr se usa para contar las referencias de 15375

los objetos Generator. 15376

El último método en EvenChecker es un miembro estático de la plantilla que 15377

configura y realiza una comprobación de los tipos de Generator creando un 15378

CountedPtr dentro y, seguidamente, lanzar un número de EvenCheckers que 15379

usan ese Generator. Si el Generator provoca un fallo, test() lo reportará y 15380

volverá; en otro caso, deberá pulsar Control-C para finalizarlo. 15381

Las tareas EvenChecker leen constantemente y comprueban que los 15382

valores de sus Generators asociados. Vea que si generator->isCanceled() es 15383

verdadero, run() retorna, con lo que se le dice al Executor de 15384

EvenChecker::test() que la tarea se ha completado. Cualquier tarea 15385

EvenChecker puede llamar a cancel() sobre su Generator asociado, lo que 15386

causará que todos los demás EvenCheckers que utilicen ese Generator 15387

finalicen con elegancia. 15388

EvenGenerator es simple - nextValue() produce el siguiente valor 15389

constante: 15390

//: C11:EvenGenerator.cpp 15391

// When threads collide. 15392

//{L} ZThread 15393

#include <iostream> 15394

#include "EvenChecker.h" 15395

#include "zthread/ThreadedExecutor.h" 15396

using namespace ZThread; 15397

using namespace std; 15398

15399

class EvenGenerator : public Generator { 15400

unsigned int currentEvenValue; // Unsigned can't overflow 15401

Page 591: Pensar en C++ (Volumen 2)

591

public: 15402

EvenGenerator() { currentEvenValue = 0; } 15403

~EvenGenerator() { cout << "~EvenGenerator" << endl; } 15404

int nextValue() { 15405

++currentEvenValue; // Danger point here! 15406

++currentEvenValue; 15407

return currentEvenValue; 15408

} 15409

}; 15410

15411

int main() { 15412

EvenChecker::test<EvenGenerator>(); 15413

} ///:~ 15414

Listado 10.17. C11/EvenGenerator.cpp 15415

15416

Es posible que un hilo llame a nextValue() después de el primer 15417

incremento de currentEvenValue y antes del segundo (en el lugar "Danger 15418

point here!" del código comentado), que pone el valor en un estado 15419

"incorrecto". Para probar que esto puede ocurrir, EventChecker::test() crea 15420

un grupo de objetos EventChecker para leer continuamente la salida de un 15421

EvenGenerator y ver si cada valor es constante. Si no es así, el error se 15422

reporta y el programa finaliza. 15423

Este programa podría no detectar el problema hasta que EvenGenerator ha 15424

completado varios ciclos, dependiendo de las particuliaridades de su sistema 15425

operativo y otros detalles de implementación. Si quiere ver que falla mucho 15426

más rápido, pruebe a poner una llamada a yield() entre el primero y segundo 15427

incremento. En algún evento, fallará puntualmente a causa de que los hilos 15428

Page 592: Pensar en C++ (Volumen 2)

592

EvenChecker pueden acceder a la información en EvenGenerator mientras se 15429

encuentra en un estado "incorrecto". 15430

10.4.3. Control de acceso 15431

En el ejemplo anterior se muestra el problema fundamental a la hora de 15432

utilizar hilos: nunca sabrá cuándo un hilo puede ser ejecutado. Imagínese 15433

sentado en la mesa con un tenedor, a punto de coger el último pedazo de 15434

comida de un plato y tan pronto como su tenedor lo alcanza, de repente, la 15435

comida se desvanece (debido a que su hilo fue suspendido y otro vino y se 15436

comió la comida). Ese es el problema que estamos tratando a la hora de 15437

escribir programas concurrentes. 15438

En ocasiones no le importará si un recurso está siendo accedido a la vez 15439

que intenta usarlo. Pero en la mayoría de los casos sí, y para trabajar con 15440

múltiples hilos necesitará alguna forma de evitar que dos hilos accedan al 15441

mismo recurso, al menos durante períodos críticos. 15442

Para prevenir este tipo de colisiones existe una manera sencilla que 15443

consiste en poner un bloqueo sobre un recursos cuando un hilo trata de 15444

usarlo. El primer hilo que accede al recursos lo bloquea y, así, otro hilo no 15445

puede acceder al recurso hasta que no sea desbloqueado, momento en el 15446

que este hilo lo vuelve a bloquear y lo vuelve a usar, y así sucesivamente. 15447

De esta forma, tenemos que ser capaces de evitar cualquier tarea de 15448

acceso a memoria mientras ese almacenamiento no esté en un estado 15449

adecuado. Esto es, necesitamos tener un mecanismo que excluya una 15450

segunda tarea sobre el acceso a memoria cuando una primera tarea ya está 15451

usándola. Esta idea es fundamental para todo sistema multihilado y se 15452

conoce como exclusión mutua; abreviado como mutex. La biblioteca ZThread 15453

tiene un mecanismo de mutex en el fichero de cabecera Mutex.h. 15454

Para solucionar el problema en el programa anterior, identificaremos las 15455

secciones críticas donde debe aplicarse la exclusión mutua; posteriormente, 15456

antes de entrar en la sección crítica adquiriremos el mutex y lo liberaremos 15457

cuando finalice la sección crítica. Únicamente un hilo podrá adquirir el mutex 15458

al mismo tiempo, por lo que se logra exclusión mutua: 15459

Page 593: Pensar en C++ (Volumen 2)

593

//: C11:MutexEvenGenerator.cpp {RunByHand} 15460

// Preventing thread collisions with mutexes. 15461

//{L} ZThread 15462

#include <iostream> 15463

#include "EvenChecker.h" 15464

#include "zthread/ThreadedExecutor.h" 15465

#include "zthread/Mutex.h" 15466

using namespace ZThread; 15467

using namespace std; 15468

15469

class MutexEvenGenerator : public Generator { 15470

unsigned int currentEvenValue; 15471

Mutex lock; 15472

public: 15473

MutexEvenGenerator() { currentEvenValue = 0; } 15474

~MutexEvenGenerator() { 15475

cout << "~MutexEvenGenerator" << endl; 15476

} 15477

int nextValue() { 15478

lock.acquire(); 15479

++currentEvenValue; 15480

Thread::yield(); // Cause failure faster 15481

++currentEvenValue; 15482

int rval = currentEvenValue; 15483

lock.release(); 15484

Page 594: Pensar en C++ (Volumen 2)

594

return rval; 15485

} 15486

}; 15487

15488

int main() { 15489

EvenChecker::test<MutexEvenGenerator>(); 15490

} ///:~ 15491

Listado 10.18. C11/MutexEvenGenerator.cpp 15492

15493

MutexEvenGenerator añade un Mutex llamado lock y utiliza acquire() y 15494

release() para crear una sección crítica con nextValue(). Además, se ha 15495

insertado una llamada a Thread::yield() entre los dos incrementos, para 15496

aumentar la probabilidad de que haya un cambio de contexto mientras 15497

currentEvenValue se encuentra en un estado extraño. Este hecho no 15498

producirá un fallo ya que el mutex evita que más de un hilo esté en la sección 15499

crítica al mismo tiempo, pero llamar a yield() es una buena forma de provocar 15500

un fallo si este ocurriera. 15501

Note que nextValue() debe capturar el valor de retorno dentro de la 15502

sección crítica porque si lo devolviera dentro de la sección critica no liberaría 15503

lock y así evitar que fuera adquirido. (Normalmente, esto conllevaría un 15504

interbloqueo, de lo cual aprenderá sobre ello al final de este capítulo.) 15505

El primer hilo que entre en nextValue() adquirirá lock y cualquier otro hilo 15506

que intente adquirirlo será bloqueado hasta que el primer hilo libere lock. En 15507

ese momento, el mecanismo de planificación selecciona otro hilo que esté 15508

esperando en lock. De esta manera, solo un hilo puede pasar a través del 15509

código custodiado por el mutex al mismo tiempo. 15510

10.4.4. Código simplificado mediante guardas 15511

El uso de mutexes se convierte rápidamente complicado cuando se 15512

introducen excepciones. Para estar seguro de que el mutes siempre se 15513

Page 595: Pensar en C++ (Volumen 2)

595

libera, debe asegurar que cualquier camino a una excepción incluya una 15514

llamada a release(). Además, cualquier función que tenga múltiples caminos 15515

para retornar debe asegurar cuidadosamente que se llama a release() en el 15516

momento adecuado. 15517

Esos problemas pueden se fácilmente solucionados utilizando el hecho de 15518

que los objetos de la pila (automáticos) tiene un destructor que siempre se 15519

llama sea cual sea la forma en que salga del ámbito de la función. En la 15520

librería ZThread, esto se implementa en la plantilla Guard. La plantilla Guard 15521

crea objetos que adquieren un objeto Lockable cuando se construyen y lo 15522

liberan cuando son destruidos. Los objetos Guard creados en la pila local 15523

serán eliminados automáticamente independientemente de la forma en el 15524

que la función finalice y siempre desbloqueará el objeto Lockable. A 15525

continuación, el ejemplo anterior reimplementado para utilizar Guards: 15526

//: C11:GuardedEvenGenerator.cpp {RunByHand} 15527

// Simplifying mutexes with the Guard template. 15528

//{L} ZThread 15529

#include <iostream> 15530

#include "EvenChecker.h" 15531

#include "zthread/ThreadedExecutor.h" 15532

#include "zthread/Mutex.h" 15533

#include "zthread/Guard.h" 15534

using namespace ZThread; 15535

using namespace std; 15536

15537

class GuardedEvenGenerator : public Generator { 15538

unsigned int currentEvenValue; 15539

Mutex lock; 15540

public: 15541

Page 596: Pensar en C++ (Volumen 2)

596

GuardedEvenGenerator() { currentEvenValue = 0; } 15542

~GuardedEvenGenerator() { 15543

cout << "~GuardedEvenGenerator" << endl; 15544

} 15545

int nextValue() { 15546

Guard<Mutex> g(lock); 15547

++currentEvenValue; 15548

Thread::yield(); 15549

++currentEvenValue; 15550

return currentEvenValue; 15551

} 15552

}; 15553

15554

int main() { 15555

EvenChecker::test<GuardedEvenGenerator>(); 15556

} ///:~ 15557

Listado 10.19. C11/GuardedEvenGenerator.cpp 15558

15559

Note que el valor de retorno temporal ya no es necesario en nextValue(). 15560

En general, hay menos código que escribir y la probabilidad de errores por 15561

parte del usuario se reduce en gran medida. 15562

Una característica interesante de la plantilla Guard es que puede ser usada 15563

para manipular otros elementos de seguridad. Por ejemplo, un segundo 15564

Guard puede ser utilizado temporalmente para desbloquear un elemento de 15565

seguridad: 15566

//: C11:TemporaryUnlocking.cpp 15567

Page 597: Pensar en C++ (Volumen 2)

597

// Temporarily unlocking another guard. 15568

//{L} ZThread 15569

#include "zthread/Thread.h" 15570

#include "zthread/Mutex.h" 15571

#include "zthread/Guard.h" 15572

using namespace ZThread; 15573

15574

class TemporaryUnlocking { 15575

Mutex lock; 15576

public: 15577

void f() { 15578

Guard<Mutex> g(lock); 15579

// lock is acquired 15580

// ... 15581

{ 15582

Guard<Mutex, UnlockedScope> h(g); 15583

// lock is released 15584

// ... 15585

// lock is acquired 15586

} 15587

// ... 15588

// lock is released 15589

} 15590

}; 15591

15592

Page 598: Pensar en C++ (Volumen 2)

598

int main() { 15593

TemporaryUnlocking t; 15594

t.f(); 15595

} ///:~ 15596

Listado 10.20. C11/TemporaryUnlocking.cpp 15597

15598

Un Guard también puede utilizarse para adquirir un lock durante un 15599

determinado tiempo y, después, liberarlo: 15600

//: C11:TimedLocking.cpp 15601

// Limited time locking. 15602

//{L} ZThread 15603

#include "zthread/Thread.h" 15604

#include "zthread/Mutex.h" 15605

#include "zthread/Guard.h" 15606

using namespace ZThread; 15607

15608

class TimedLocking { 15609

Mutex lock; 15610

public: 15611

void f() { 15612

Guard<Mutex, TimedLockedScope<500> > g(lock); 15613

// ... 15614

} 15615

}; 15616

Page 599: Pensar en C++ (Volumen 2)

599

15617

int main() { 15618

TimedLocking t; 15619

t.f(); 15620

} ///:~ 15621

Listado 10.21. C11/TimedLocking.cpp 15622

15623

En este ejemplo, se lanzará una Timeout_Exception si el lock no puede ser 15624

adquirido en 500 milisegundos. 15625

Sincronización de clases completas 15626

La librería ZThread también proporciona la plantilla GuardedClass para 15627

crear automáticamente un recubrimiento de sincronización para toda una 15628

clase. Esto quiere decir que cualquier método de una clase estará 15629

automáticamente protegido: 15630

//: C11:SynchronizedClass.cpp {-dmc} 15631

//{L} ZThread 15632

#include "zthread/GuardedClass.h" 15633

using namespace ZThread; 15634

15635

class MyClass { 15636

public: 15637

void func1() {} 15638

void func2() {} 15639

}; 15640

15641

Page 600: Pensar en C++ (Volumen 2)

600

int main() { 15642

MyClass a; 15643

a.func1(); // Not synchronized 15644

a.func2(); // Not synchronized 15645

GuardedClass<MyClass> b(new MyClass); 15646

// Synchronized calls, only one thread at a time allowed: 15647

b->func1(); 15648

b->func2(); 15649

} ///:~ 15650

Listado 10.22. C11/SynchronizedClass.cpp 15651

15652

El objeto a no está sincronizado, por lo que func1() y func2() pueden ser 15653

llamadas en cualquier momento por cualquier número de hilos. El objeto b 15654

está protegido por el recubrimiento GuardedClass, así que cada método se 15655

sincroniza automáticamente y solo se puede llamar a una función por objeto 15656

en cualquier instante. 15657

El recubrimiento bloquea un tipo de nivel de granularidad, que podría 15658

afectar al rendimiento.[151] Si una clase contiene funciones no vinculadas, 15659

puede ser mejor sincronizarlas internamente con 2 locks diferentes. Sin 15660

embargo, si se encuentra haciendo esto, significa que la clase contiene 15661

grupos de datos que puede no estar fuertemente asociados. Considere 15662

dividir la clase en dos. 15663

Proteger todos los métodos de una clase con un mutex no hace que esa 15664

clase sea segura automáticamente cuando se utilicen hilos. Debe tener 15665

cuidado con estas cuestiones para garantizar la seguridad cuando se usan 15666

hilos. 15667

10.4.5. Almacenamiento local al hilo 15668

Page 601: Pensar en C++ (Volumen 2)

601

Una segunda forma de eliminar el problema de colisión de tareas sobre 15669

recursos compartidos es la eliminación de las variables compartidas, lo cual 15670

puede realizarse mediante la creación de diferentes almacenamientos para la 15671

misma variable, uno por cada hilo que use el objeto. De esta forma, si tiene 15672

cinco hilos que usan un objeto con una variable x, el almacenamiento local al 15673

hilo genera automáticamente cinco porciones de memoria distintas para 15674

almacenar x. Afortunadamente, la creación y gestión del almacenamiento 15675

local al hilo la lleva a cabo una plantilla de ZThread llamada ThreadLocal, tal 15676

y como se puede ver aquí: 15677

//: C11:ThreadLocalVariables.cpp {RunByHand} 15678

// Automatically giving each thread its own storage. 15679

//{L} ZThread 15680

#include <iostream> 15681

#include "zthread/Thread.h" 15682

#include "zthread/Mutex.h" 15683

#include "zthread/Guard.h" 15684

#include "zthread/ThreadedExecutor.h" 15685

#include "zthread/Cancelable.h" 15686

#include "zthread/ThreadLocal.h" 15687

#include "zthread/CountedPtr.h" 15688

using namespace ZThread; 15689

using namespace std; 15690

15691

class ThreadLocalVariables : public Cancelable { 15692

ThreadLocal<int> value; 15693

bool canceled; 15694

Mutex lock; 15695

Page 602: Pensar en C++ (Volumen 2)

602

public: 15696

ThreadLocalVariables() : canceled(false) { 15697

value.set(0); 15698

} 15699

void increment() { value.set(value.get() + 1); } 15700

int get() { return value.get(); } 15701

void cancel() { 15702

Guard<Mutex> g(lock); 15703

canceled = true; 15704

} 15705

bool isCanceled() { 15706

Guard<Mutex> g(lock); 15707

return canceled; 15708

} 15709

}; 15710

15711

class Accessor : public Runnable { 15712

int id; 15713

CountedPtr<ThreadLocalVariables> tlv; 15714

public: 15715

Accessor(CountedPtr<ThreadLocalVariables>& tl, int idn) 15716

: id(idn), tlv(tl) {} 15717

void run() { 15718

while(!tlv->isCanceled()) { 15719

tlv->increment(); 15720

Page 603: Pensar en C++ (Volumen 2)

603

cout << *this << endl; 15721

} 15722

} 15723

friend ostream& 15724

operator<<(ostream& os, Accessor& a) { 15725

return os << "#" << a.id << ": " << a.tlv->get(); 15726

} 15727

}; 15728

15729

int main() { 15730

cout << "Press <Enter> to quit" << endl; 15731

try { 15732

CountedPtr<ThreadLocalVariables> 15733

tlv(new ThreadLocalVariables); 15734

const int SZ = 5; 15735

ThreadedExecutor executor; 15736

for(int i = 0; i < SZ; i++) 15737

executor.execute(new Accessor(tlv, i)); 15738

cin.get(); 15739

tlv->cancel(); // All Accessors will quit 15740

} catch(Synchronization_Exception& e) { 15741

cerr << e.what() << endl; 15742

} 15743

} ///:~ 15744

Page 604: Pensar en C++ (Volumen 2)

604

Listado 10.23. C11/ThreadLocalVariables.cpp 15745

15746

Cuando crea un objeto ThreadLocal instanciando la plantilla, únicamente 15747

puede acceder al contenido del objeto utilizando los métodos set() y get(). El 15748

método get() devuelve una copia del objeto que está asociado a ese hilo, y 15749

set() inserta su argumento dentro del objeto almacenado para ese hilo, 15750

devolviendo el objeto antiguo que se encontraba almacenado. Puede 15751

comprobar que esto se utiliza en increment() y get() de 15752

ThreadLocalVariables. 15753

Ya que tlv se comparte en múltiples objetos Accessor, está escrito como un 15754

Cancelable, por lo que los Accessors puede recibir señales cuando queramos 15755

parar el sistema. 15756

Cuando ejecute este programa se evidenciará que se reserva para cada 15757

hilo su propio almacenamiento. 15758

10.5. Finalización de tareas 15759

En los ejemplos anteriores, hemos visto el uso de una "bandera de 15760

terminación" o de la interfaz Cancelable para finalizar una tarea. Este es un 15761

enfoque razonable para el problema. Sin embargo, en algunas ocasiones la 15762

tarea tiene que ser finalizada más abruptamente. En esta sección, aprenderá 15763

sobre las cuestiones y problemas de este tipo de finalización. 15764

Primeramente, veamos en un ejemplo que no sólo demuestra el problema 15765

de la finalización sino que, además, es un ejemplo adicional de comparición 15766

de recursos. Para mostrar el ejemplo, primero necesitaremos resolver el 15767

problema de la colisión de iostream. 15768

10.5.1. Prevención de coliciones en iostream 15769

FIXME: dos versiones: Podría haberse dado cuenta en los anteriores 15770

ejemplos que la salida es confusa en algunas ocasiones. Los iostreams de 15771

C++ no fueron creados pensando en el sistema de hilos, por lo que no hay 15772

Puede haberse dado cuenta en los ejemplos anteriores que la salida es 15773

FIXMEconfusa. El sistema iostream de C++ no fue creado con el sistema de 15774

hilos en mente, por no lo que no hay nada que prevenga que la salida de un 15775

Page 605: Pensar en C++ (Volumen 2)

605

hilo interfiera con la salida de otro hilo. Por ello, debe escribir aplicaciones de 15776

tal forma que sincronicen el uso de iostreams. 15777

Para solucionar el problema, primero necesitamos crear un paquete 15778

completo de salida y, después, decidir explícitamente cuando intentamos 15779

mandarlo a la consola. Una sencilla solución pasa por escribir la informacion 15780

en un ostringstream y posteriormente utilizar un único objeto con un mutex 15781

como punto de salida de todos los hilos, para evitar que más de un hilo 15782

escriba al mismo tiempo: 15783

//: C11:Display.h 15784

// Prevents ostream collisions. 15785

#ifndef DISPLAY_H 15786

#define DISPLAY_H 15787

#include <iostream> 15788

#include <sstream> 15789

#include "zthread/Mutex.h" 15790

#include "zthread/Guard.h" 15791

15792

class Display { // Share one of these among all threads 15793

ZThread::Mutex iolock; 15794

public: 15795

void output(std::ostringstream& os) { 15796

ZThread::Guard<ZThread::Mutex> g(iolock); 15797

std::cout << os.str(); 15798

} 15799

}; 15800

#endif // DISPLAY_H ///:~ 15801

Page 606: Pensar en C++ (Volumen 2)

606

Listado 10.24. C11/Display.h 15802

15803

De esta manera, predefinimos la función estandar operator<<() y el objeto 15804

puede ser construido en memoria utilizando operadores habituales de 15805

ostream. Cuando una tarea quiere mostrar una salida, crea un objeto 15806

ostringstream temporal que utiliza FIXME. Cuando llama a output(), el mutex 15807

evita que varios hilos escriban a este objeto Display. (Debe usar solo un 15808

objeto Display en su programa, tal y como verá en los siguientes ejemplos.) 15809

Todo esto muestra la idea básica pero, si es necesario, puede construir un 15810

entorno más elaborado. Por ejemplo, podría forzar el requisito de que solo 15811

haya un objeto Display en un programa haciéndolo Singleton. (La librería 15812

ZThread tiene una plantilla Singleton para dar soporte a Singletons). 15813

10.5.2. El jardín ornamental 15814

En esta simulación, al comité del jardín le gustaría saber cuanta gente 15815

entra en el jardín cada día a través de distintas puertas. Cada puerta tiene un 15816

FIXMEturnstile o algún otro tipo de contador, y después de que el contador 15817

FIXMEturnstile se incrementa, aumenta una cuenta compartida que 15818

representa el número total de gente en el jardín. 15819

//: C11:OrnamentalGarden.cpp {RunByHand} 15820

//{L} ZThread 15821

#include <vector> 15822

#include <cstdlib> 15823

#include <ctime> 15824

#include "Display.h" 15825

#include "zthread/Thread.h" 15826

#include "zthread/FastMutex.h" 15827

#include "zthread/Guard.h" 15828

#include "zthread/ThreadedExecutor.h" 15829

Page 607: Pensar en C++ (Volumen 2)

607

#include "zthread/CountedPtr.h" 15830

using namespace ZThread; 15831

using namespace std; 15832

15833

class Count : public Cancelable { 15834

FastMutex lock; 15835

int count; 15836

bool paused, canceled; 15837

public: 15838

Count() : count(0), paused(false), canceled(false) {} 15839

int increment() { 15840

// Comment the following line to see counting fail: 15841

Guard<FastMutex> g(lock); 15842

int temp = count ; 15843

if(rand() % 2 == 0) // Yield half the time 15844

Thread::yield(); 15845

return (count = ++temp); 15846

} 15847

int value() { 15848

Guard<FastMutex> g(lock); 15849

return count; 15850

} 15851

void cancel() { 15852

Guard<FastMutex> g(lock); 15853

canceled = true; 15854

Page 608: Pensar en C++ (Volumen 2)

608

} 15855

bool isCanceled() { 15856

Guard<FastMutex> g(lock); 15857

return canceled; 15858

} 15859

void pause() { 15860

Guard<FastMutex> g(lock); 15861

paused = true; 15862

} 15863

bool isPaused() { 15864

Guard<FastMutex> g(lock); 15865

return paused; 15866

} 15867

}; 15868

15869

class Entrance : public Runnable { 15870

CountedPtr<Count> count; 15871

CountedPtr<Display> display; 15872

int number; 15873

int id; 15874

bool waitingForCancel; 15875

public: 15876

Entrance(CountedPtr<Count>& cnt, 15877

CountedPtr<Display>& disp, int idn) 15878

: count(cnt), display(disp), number(0), id(idn), 15879

Page 609: Pensar en C++ (Volumen 2)

609

waitingForCancel(false) {} 15880

void run() { 15881

while(!count->isPaused()) { 15882

++number; 15883

{ 15884

ostringstream os; 15885

os << *this << " Total: " 15886

<< count->increment() << endl; 15887

display->output(os); 15888

} 15889

Thread::sleep(100); 15890

} 15891

waitingForCancel = true; 15892

while(!count->isCanceled()) // Hold here... 15893

Thread::sleep(100); 15894

ostringstream os; 15895

os << "Terminating " << *this << endl; 15896

display->output(os); 15897

} 15898

int getValue() { 15899

while(count->isPaused() && !waitingForCancel) 15900

Thread::sleep(100); 15901

return number; 15902

} 15903

friend ostream& 15904

Page 610: Pensar en C++ (Volumen 2)

610

operator<<(ostream& os, const Entrance& e) { 15905

return os << "Entrance " << e.id << ": " << e.number; 15906

} 15907

}; 15908

15909

int main() { 15910

srand(time(0)); // Seed the random number generator 15911

cout << "Press <ENTER> to quit" << endl; 15912

CountedPtr<Count> count(new Count); 15913

vector<Entrance*> v; 15914

CountedPtr<Display> display(new Display); 15915

const int SZ = 5; 15916

try { 15917

ThreadedExecutor executor; 15918

for(int i = 0; i < SZ; i++) { 15919

Entrance* task = new Entrance(count, display, i); 15920

executor.execute(task); 15921

// Save the pointer to the task: 15922

v.push_back(task); 15923

} 15924

cin.get(); // Wait for user to press <Enter> 15925

count->pause(); // Causes tasks to stop counting 15926

int sum = 0; 15927

vector<Entrance*>::iterator it = v.begin(); 15928

while(it != v.end()) { 15929

Page 611: Pensar en C++ (Volumen 2)

611

sum += (*it)->getValue(); 15930

++it; 15931

} 15932

ostringstream os; 15933

os << "Total: " << count->value() << endl 15934

<< "Sum of Entrances: " << sum << endl; 15935

display->output(os); 15936

count->cancel(); // Causes threads to quit 15937

} catch(Synchronization_Exception& e) { 15938

cerr << e.what() << endl; 15939

} 15940

} ///:~ 15941

Listado 10.25. C11/OrnamentalGarden.cpp 15942

15943

Count es la clase que conserva el contador principal de los visitantes del 15944

jardín. El objeto único Count definido en main() como contador FIXME is held 15945

como un CountedPtr en Entrance y, así, se comparte entre todos los objetos 15946

Entrance. En este ejemplo, se utiliza un FastMutex llamado lock en vez de un 15947

Mutex ordinario ya que un FastMutex usa el mutex nativo del sistema 15948

operativo y, por ello, aportará resultados más interesantes. 15949

Se utiliza un Guard con bloqueo en increment() para sincronizar el acceso a 15950

count. Esta función usa rand() para realizar una carga de trabajo alta 15951

(mediante yield()) FIXME la mitad del tiempo, .... 15952

La clase Entrance también mantiene una variable local numbre con el 15953

número de visitantes que han pasado a través de una entrada concreta. Esto 15954

proporciona un chequeo doble contra el objeto count para asegurar que el 15955

verdadero número de visitantes es el que se está almacenando. 15956

Page 612: Pensar en C++ (Volumen 2)

612

Entrance::run() simplemente incrementa number y el objeto count y se 15957

duerme durante 100 milisegundos. 15958

En main, se carga un vector<Entrance*> con cada Entrance que se crean. 15959

Después de que el usuario pulse Enter, el vector se utiliza para iterar sobre el 15960

valor de cada Entrance y calcular el total. 15961

FIXMEThis program goes to quite a bit of extra trouble to shut everything 15962

down in a stable fashion. 15963

Toda la comunicación entre los objetos Entrance ocurre a través de un 15964

único objeto Count. Cuando el usuario pulsa Enter, main() manda el mensaje 15965

pause() a count. Como cada Entrance::run() está vigilando a que el objeto 15966

count esté pausado, esto hace que cada Entrance se mueva al estado 15967

waitingForCancel, donde no se cuenta más, pero aún sigue vivo. Esto es 15968

esencial porque main() debe poder seguir iterando de forma segura sobre los 15969

objetos del vector<Entrace*>. Note que debido a que existe una 15970

FIXMEpequeña posibilidad que la iteración pueda ocurrir antes de que un 15971

Entrance haya terminado de contar y haya ido al estado de 15972

waitingForCancel, la función getValue() itera a lo largo de las llamadas a 15973

sleep() hasta que el objeto vaya al estado de waitingForCancel. (Esta es una 15974

forma, que se conoce como espera activa, y es indeseable. Verá un enfoque 15975

más apropiado utilizando wait(), más adelante en el capítulo). Una vez que 15976

main() completa una iteración a lo largo del vector<Entrance*>, se manda el 15977

mensaje de cancel() al objeto count, y de nuevo todos los objetos Entrance 15978

esperan a este cambio de estado. En ese instante, imprimen un mensaje de 15979

finalización y salen de run(), por lo que el mecanismo de hilado destruye 15980

cada tarea. 15981

Tal y como este programa se ejecuta, verá que la cuenta total y la de cada 15982

una de las entradas se muestran a la vez que la gente pasa a través de un 15983

FIXMEturnstile. Si comenta el objeto Guard en Count::increment(), se dará 15984

cuenta que el número total de personas no es el que espera que sea. El 15985

número de personas contadas por cada FIXMEturnstile será diferente del 15986

valor de count. Tan pronto como haya un Mutex para sincronizar el acceso al 15987

Counter, las cosas funcionarán correctamente. Tenga en cuenta que 15988

Count::increment() exagera la situación potencial de fallo que supone utilizar 15989

temp y yield(). En problemas reales de hilado, la probabilidad de fallo puede 15990

Page 613: Pensar en C++ (Volumen 2)

613

ser estadísticamente menor, por lo que puede caer fácilmente en la trampa 15991

de creer que las cosas funcionan correctamente. Tal y como muestra el 15992

problema anterior, existen FIXMElikely problemas ocultos que no le han 15993

ocurrido, por lo que debe ser excepcionalmente diligente cuando revise 15994

código concurrente. 15995

Operaciones atómicas 15996

Note que Count::value() devuelve el valor de count utilizando un objeto 15997

Guard para la sincronización. Esto ofrece un aspecto interesante porque este 15998

código probablemente funcionará bien con la mayoría de los compiladores y 15999

sistemas sin sincronización. El motivo es que, en general, una operación 16000

simple como devolver un int será una operación atómica, que quiere decir 16001

que probablemente se llevará a cabo con una única instrucción de 16002

microprocesador y no será interrumpida. (El mecanismo de multihilado no 16003

puede parar un hilo en mitad de una instrucción de microprocesador.) Esto 16004

es, las operaciones atómicas no son interrumpibles por el mecanismo de 16005

hilado y, así, no necesitan ser protegidas.[152] De hecho, si elimináramos la 16006

asignación de count en temp y quitáramos yield(), y en su lugar simplemente 16007

incrementáramos count directamente, probablemente no necesitaríamos un 16008

lock ya que la operación de incremento es, normalmente, atómica. [153] 16009

El problema es que el estándar de C++ no garantiza la atomicidad para 16010

ninguna de esas operaciones. Sin embargo, pese a que operaciones como 16011

devolver un int e incrementar un int son atómicas en la mayoría de las 16012

máquinas no hay garantías. Y puesto que no hay garantía, debe asumir lo 16013

peor. En algunas ocasiones podría investigar el funcionamiento de la 16014

atomicidad para una máquina en particular (normalmente mirando el lenguaje 16015

ensamblador) y escribir código basado en esas asunciones. Esto es siempre 16016

peligroso y FIXMEill-advised. Es muy fácil que esta información se pierda o 16017

esté oculta, y la siguiente persona que venga podría asumir que el código 16018

puede ser portado a otra máquina y, por ello, volverse loco siguiendo la pista 16019

al FIXMEoccsional glitch provocado por la colisión de hilos. 16020

Por ello, aunque quitar el guarda en Count::value() parezca que funciona 16021

no es FIXMEairtight y, así, en algunas máquinas puede ver un 16022

comportamiento aberrante. 16023

Page 614: Pensar en C++ (Volumen 2)

614

10.5.3. FIXME:Teminación al bloquear 16024

En el ejemplo anterior, Entrance::run() incluye una llamada a sleep() en el 16025

bucle principal. Sabemos que sleep() se despertará eventualmente y que la 16026

tarea llegará al principio del bucle donde tiene una oportunidad para salir de 16027

ese bucle chequeando el estado isPaused(). Sin embargo, sleep() es 16028

simplemente una situación donde un hilo en ejecución se bloquea, y a veces 16029

necesita terminar una tarea que está bloqueada. 16030

Un hilo puede estar en alguno de los cuatro estados: 16031

1. Nuevo: Un hilo permanece en este estado solamente de forma 16032

momentánea, tan solo cuando se crea. Reserva todos los recursos del 16033

sistema necesarios y ejecuta la inicialización. En este momento se convierte 16034

en FIXMEcandidato para recibir tiempo de CPU. A continuación, el 16035

planificador llevará a este hilo al estado de ejecución o de bloqueo. 16036

2. Ejecutable: Esto significa que un hilo puede ser ejecutado cuando el 16037

mecanismo de fraccionador de tiempo tenga ciclos de CPU disponibles para 16038

el hilo. Así, el hilo podría o no ejecutarse en cualquier momento, pero no hay 16039

nada que evite FIXME 16040

3. Bloqueado: El hilo pudo ser ejecutado, pero algo lo impidió. (Podría 16041

estar esperando a que se complete una operación de entrada/salida, por 16042

ejemplo.) Mientras un hilo esté en el estado de bloqueo, el planificador 16043

simplemente lo ignorará y no le dará tiempo de CPU. Hasta que un hilo no 16044

vuelva a entrar en el estado de ejecución, no ejecutará ninguna operación. 16045

4. FIXMEMuerte: Un hilo en el estado de muerto no será planificable y no 16046

recibirá tiempo de CPU. Sus tareas han finalizado, y no será ejecutable 16047

nunca más. La forma normal que un hilo tiene para morir es volviendo de su 16048

función run(). 16049

Un hilo está bloqueado cuando no puede continuar su ejecución. Un hilo 16050

puede bloquearse debido a los siguientes motivos: 16051

Puso el hilo a dormir llamando a sleep(milisegundos), en cuyo caso no 16052

será ejecutado durante el tiempo especificado. 16053

Suspendió la ejecución del hilo con wait(). No volverá a ser ejecutable 16054

hasta que el hilo no obtenga el mensaje signal() o broadcast(). Estudiaremos 16055

esto en una sección más adelante. 16056

Page 615: Pensar en C++ (Volumen 2)

615

El hilo está esperando a que una operación de entrada/salida finalice. 16057

El hilo está intentando entrar en un bloque de código controlado por un 16058

mutex, y el mutex ha sido ya adquirido por otro hilo. 16059

El problema que tenemos ahora es el siguiente: algunas veces quiere 16060

terminar un hilo que está en el estado de bloqueo. Si no puede esperar a que 16061

el hilo llegue a un punto en el código donde pueda comprobar el valor del 16062

estado y decidir si terminar por sus propios medios, debe forzar a que el hilo 16063

salga de su estado de bloqueo. 16064

10.5.4. Interrupción 16065

Tal y como podría imaginar, es mucho más FIXMEmessier salir de forma 16066

brusca en mitad de la función Runnable::run() que si espera a que esa 16067

función llegue al test de isCanceled() (o a algún otro lugar donde el 16068

programador esté preparado para salir de la función). Cuando sale de una 16069

tarea bloqueada, podría necesitar destruir objetos y liberar recursos. Debido 16070

a esto, salir en mitad de un run() de una tarea, más que otra cosa, consiste 16071

en lanzar una excepción, por lo que en ZThreads, las excepciones se utilizan 16072

para este tipo de terminación. (Esto roza el límite de un uso inapropiado de 16073

las excepciones, porque significa que las utiliza para el control de flujo.)[154] 16074

Para volver de esta manera a un buen estado conocido a la hora de terminar 16075

una tarea, tenga en cuenta cuidadosamente los caminos de ejecución de su 16076

código y libere todo correctamente dentro de los bloques catch. Veremos 16077

esta técnica en la presente sección. 16078

Para finalizar un hilo bloqueado, la librería ZThread proporciona la función 16079

Thread::interrupted(). Esta configura el estado de interrupción para ese hilo. 16080

Un hilo con su estado de interrupción configurado lanzará una 16081

Interrupted_Exception si está bloqueado o si espera una operación 16082

bloqueante. El estado de interrupción será restaurado cuando se haya 16083

lanzado la excepción o si la tarea llama a Thread::interrupted(). Como puede 16084

ver, Thread::interrupted() proporciona otra forma de salir de su bucle run(), 16085

sin lanzar una excepción. 16086

A continuación, un ejemplo que ilustra las bases de interrupt(): 16087

Page 616: Pensar en C++ (Volumen 2)

616

//: C11:Interrupting.cpp 16088

// Interrupting a blocked thread. 16089

//{L} ZThread 16090

#include <iostream> 16091

#include "zthread/Thread.h" 16092

using namespace ZThread; 16093

using namespace std; 16094

16095

class Blocked : public Runnable { 16096

public: 16097

void run() { 16098

try { 16099

Thread::sleep(1000); 16100

cout << "Waiting for get() in run():"; 16101

cin.get(); 16102

} catch(Interrupted_Exception&) { 16103

cout << "Caught Interrupted_Exception" << endl; 16104

// Exit the task 16105

} 16106

} 16107

}; 16108

16109

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

try { 16111

Thread t(new Blocked); 16112

Page 617: Pensar en C++ (Volumen 2)

617

if(argc > 1) 16113

Thread::sleep(1100); 16114

t.interrupt(); 16115

} catch(Synchronization_Exception& e) { 16116

cerr << e.what() << endl; 16117

} 16118

} ///:~ 16119

Listado 10.26. C11/Interrupting.cpp 16120

16121

Puede ver que, además de la inserción de cout, run() tiene dos puntos 16122

donde puede ocurrir el bloqueo: la llamada a Thread::sleep(1000) y la 16123

llamada a cin.get(). Dando cualquier argumento por línea de comandos al 16124

programa, dirá a main() que se duerma lo suficiente para que la tarea finalice 16125

su sleep() y llame a cin.get().[155] Si no le da un argumento, el sleep() de 16126

main() se ignora. Ahora, la llamada a interrupt() ocurrirá mientras la tarea 16127

está dormida, y verá que esto provoca que una Interrupted_Exception se 16128

lance. Si le da un argumento por línea de comandos al programa, descubrirá 16129

que la tarea no puede ser interrumpida si está bloqueada en la 16130

entrada/salida. Esto es, puede interrumpir cualquier operación bloqueante a 16131

excepción de una entrada/salida.[156] 16132

Esto es un poco desconcertante si está creando un hilo que ejecuta 16133

entrada/salida porque quiere decir que la entrada/salida tiene posibilidades 16134

de bloquear su programa multihilado. El problema es que, de nuevo, C++ no 16135

fue diseñado con el sistema de hilos en mente; muy al contrario, 16136

FIXMEpresupone que el hilado no existe. Por ello, la librería iostream no ses 16137

thread-friendly. Si el nuevo estándar de C++ decide añadir soporte a hilos, la 16138

librería iostream podría necesitar ser reconsiderada en el proceso. Bloqueo 16139

debido a un mutex. 16140

Si intenta llamar a una función cuyo mutes ha sido adquirido, la tarea que 16141

llama será suspendida hasta que el mutex esté accesible. El siguiente 16142

ejemplo comprueba si este tipo de bloqueo es interrumpible: 16143

Page 618: Pensar en C++ (Volumen 2)

618

//: C11:Interrupting2.cpp 16144

// Interrupting a thread blocked 16145

// with a synchronization guard. 16146

//{L} ZThread 16147

#include <iostream> 16148

#include "zthread/Thread.h" 16149

#include "zthread/Mutex.h" 16150

#include "zthread/Guard.h" 16151

using namespace ZThread; 16152

using namespace std; 16153

16154

class BlockedMutex { 16155

Mutex lock; 16156

public: 16157

BlockedMutex() { 16158

lock.acquire(); 16159

} 16160

void f() { 16161

Guard<Mutex> g(lock); 16162

// This will never be available 16163

} 16164

}; 16165

16166

class Blocked2 : public Runnable { 16167

BlockedMutex blocked; 16168

Page 619: Pensar en C++ (Volumen 2)

619

public: 16169

void run() { 16170

try { 16171

cout << "Waiting for f() in BlockedMutex" << endl; 16172

blocked.f(); 16173

} catch(Interrupted_Exception& e) { 16174

cerr << e.what() << endl; 16175

// Exit the task 16176

} 16177

} 16178

}; 16179

16180

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

try { 16182

Thread t(new Blocked2); 16183

t.interrupt(); 16184

} catch(Synchronization_Exception& e) { 16185

cerr << e.what() << endl; 16186

} 16187

} ///:~ 16188

Listado 10.27. C11/Interrupting2.cpp 16189

16190

La clase BlockedMutex tiene un constructor que adquiere su propio objeto 16191

Mutex y nunca lo libera. Por esa razón, si intenta llamara a f(), siempre será 16192

bloqueado porque el Mutex no puede ser adquirido. En Blocked2, la función 16193

run() se parará en la llamada blocked.f(). Cuando ejecute el programa verá 16194

Page 620: Pensar en C++ (Volumen 2)

620

que, a diferencia de la llamada a iostream, interrupt() puede salir de una 16195

llamada que está bloqueada por un mutex.[157] Comprobación de una una 16196

interrupción. 16197

Note que cuando llama a interrupt() sobre un hilo, la única vez que ocurre 16198

la interrupción es cuando la tarea entra, o ya está dentro, de una operación 16199

bloqueante (a excepción, como ya ha visto, del caso de la entrada/salida, 16200

donde simplemente 16201

//: C11:Interrupting3.cpp {RunByHand} 16202

// General idiom for interrupting a task. 16203

//{L} ZThread 16204

#include <iostream> 16205

#include "zthread/Thread.h" 16206

using namespace ZThread; 16207

using namespace std; 16208

16209

const double PI = 3.14159265358979323846; 16210

const double E = 2.7182818284590452354; 16211

16212

class NeedsCleanup { 16213

int id; 16214

public: 16215

NeedsCleanup(int ident) : id(ident) { 16216

cout << "NeedsCleanup " << id << endl; 16217

} 16218

~NeedsCleanup() { 16219

cout << "~NeedsCleanup " << id << endl; 16220

Page 621: Pensar en C++ (Volumen 2)

621

} 16221

}; 16222

16223

class Blocked3 : public Runnable { 16224

volatile double d; 16225

public: 16226

Blocked3() : d(0.0) {} 16227

void run() { 16228

try { 16229

while(!Thread::interrupted()) { 16230

point1: 16231

NeedsCleanup n1(1); 16232

cout << "Sleeping" << endl; 16233

Thread::sleep(1000); 16234

point2: 16235

NeedsCleanup n2(2); 16236

cout << "Calculating" << endl; 16237

// A time-consuming, non-blocking operation: 16238

for(int i = 1; i < 100000; i++) 16239

d = d + (PI + E) / (double)i; 16240

} 16241

cout << "Exiting via while() test" << endl; 16242

} catch(Interrupted_Exception&) { 16243

cout << "Exiting via Interrupted_Exception" << endl; 16244

} 16245

Page 622: Pensar en C++ (Volumen 2)

622

} 16246

}; 16247

16248

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

if(argc != 2) { 16250

cerr << "usage: " << argv[0] 16251

<< " delay-in-milliseconds" << endl; 16252

exit(1); 16253

} 16254

int delay = atoi(argv[1]); 16255

try { 16256

Thread t(new Blocked3); 16257

Thread::sleep(delay); 16258

t.interrupt(); 16259

} catch(Synchronization_Exception& e) { 16260

cerr << e.what() << endl; 16261

} 16262

} ///:~ 16263

Listado 10.28. C11/Interrupting3.cpp 16264

16265

10.6. Cooperación entre hilos 16266

10.6.1. Wait y signal 16267

//: C11:WaxOMatic.cpp {RunByHand} 16268

Page 623: Pensar en C++ (Volumen 2)

623

// Basic thread cooperation. 16269

//{L} ZThread 16270

#include <iostream> 16271

#include <string> 16272

#include "zthread/Thread.h" 16273

#include "zthread/Mutex.h" 16274

#include "zthread/Guard.h" 16275

#include "zthread/Condition.h" 16276

#include "zthread/ThreadedExecutor.h" 16277

using namespace ZThread; 16278

using namespace std; 16279

16280

class Car { 16281

Mutex lock; 16282

Condition condition; 16283

bool waxOn; 16284

public: 16285

Car() : condition(lock), waxOn(false) {} 16286

void waxed() { 16287

Guard<Mutex> g(lock); 16288

waxOn = true; // Ready to buff 16289

condition.signal(); 16290

} 16291

void buffed() { 16292

Guard<Mutex> g(lock); 16293

Page 624: Pensar en C++ (Volumen 2)

624

waxOn = false; // Ready for another coat of wax 16294

condition.signal(); 16295

} 16296

void waitForWaxing() { 16297

Guard<Mutex> g(lock); 16298

while(waxOn == false) 16299

condition.wait(); 16300

} 16301

void waitForBuffing() { 16302

Guard<Mutex> g(lock); 16303

while(waxOn == true) 16304

condition.wait(); 16305

} 16306

}; 16307

16308

class WaxOn : public Runnable { 16309

CountedPtr<Car> car; 16310

public: 16311

WaxOn(CountedPtr<Car>& c) : car(c) {} 16312

void run() { 16313

try { 16314

while(!Thread::interrupted()) { 16315

cout << "Wax On!" << endl; 16316

Thread::sleep(200); 16317

car->waxed(); 16318

Page 625: Pensar en C++ (Volumen 2)

625

car->waitForBuffing(); 16319

} 16320

} catch(Interrupted_Exception&) { /* Exit */ } 16321

cout << "Ending Wax On process" << endl; 16322

} 16323

}; 16324

16325

class WaxOff : public Runnable { 16326

CountedPtr<Car> car; 16327

public: 16328

WaxOff(CountedPtr<Car>& c) : car(c) {} 16329

void run() { 16330

try { 16331

while(!Thread::interrupted()) { 16332

car->waitForWaxing(); 16333

cout << "Wax Off!" << endl; 16334

Thread::sleep(200); 16335

car->buffed(); 16336

} 16337

} catch(Interrupted_Exception&) { /* Exit */ } 16338

cout << "Ending Wax Off process" << endl; 16339

} 16340

}; 16341

16342

int main() { 16343

Page 626: Pensar en C++ (Volumen 2)

626

cout << "Press <Enter> to quit" << endl; 16344

try { 16345

CountedPtr<Car> car(new Car); 16346

ThreadedExecutor executor; 16347

executor.execute(new WaxOff(car)); 16348

executor.execute(new WaxOn(car)); 16349

cin.get(); 16350

executor.interrupt(); 16351

} catch(Synchronization_Exception& e) { 16352

cerr << e.what() << endl; 16353

} 16354

} ///:~ 16355

Listado 10.29. C11/WaxOMatic.cpp 16356

16357

10.6.2. Relación de productor/consumidor 16358

//: C11:ToastOMatic.cpp {RunByHand} 16359

// Problems with thread cooperation. 16360

//{L} ZThread 16361

#include <iostream> 16362

#include <cstdlib> 16363

#include <ctime> 16364

#include "zthread/Thread.h" 16365

#include "zthread/Mutex.h" 16366

Page 627: Pensar en C++ (Volumen 2)

627

#include "zthread/Guard.h" 16367

#include "zthread/Condition.h" 16368

#include "zthread/ThreadedExecutor.h" 16369

using namespace ZThread; 16370

using namespace std; 16371

16372

// Apply jam to buttered toast: 16373

class Jammer : public Runnable { 16374

Mutex lock; 16375

Condition butteredToastReady; 16376

bool gotButteredToast; 16377

int jammed; 16378

public: 16379

Jammer() : butteredToastReady(lock) { 16380

gotButteredToast = false; 16381

jammed = 0; 16382

} 16383

void moreButteredToastReady() { 16384

Guard<Mutex> g(lock); 16385

gotButteredToast = true; 16386

butteredToastReady.signal(); 16387

} 16388

void run() { 16389

try { 16390

while(!Thread::interrupted()) { 16391

Page 628: Pensar en C++ (Volumen 2)

628

{ 16392

Guard<Mutex> g(lock); 16393

while(!gotButteredToast) 16394

butteredToastReady.wait(); 16395

++jammed; 16396

} 16397

cout << "Putting jam on toast " << jammed << endl; 16398

{ 16399

Guard<Mutex> g(lock); 16400

gotButteredToast = false; 16401

} 16402

} 16403

} catch(Interrupted_Exception&) { /* Exit */ } 16404

cout << "Jammer off" << endl; 16405

} 16406

}; 16407

16408

// Apply butter to toast: 16409

class Butterer : public Runnable { 16410

Mutex lock; 16411

Condition toastReady; 16412

CountedPtr<Jammer> jammer; 16413

bool gotToast; 16414

int buttered; 16415

public: 16416

Page 629: Pensar en C++ (Volumen 2)

629

Butterer(CountedPtr<Jammer>& j) 16417

: toastReady(lock), jammer(j) { 16418

gotToast = false; 16419

buttered = 0; 16420

} 16421

void moreToastReady() { 16422

Guard<Mutex> g(lock); 16423

gotToast = true; 16424

toastReady.signal(); 16425

} 16426

void run() { 16427

try { 16428

while(!Thread::interrupted()) { 16429

{ 16430

Guard<Mutex> g(lock); 16431

while(!gotToast) 16432

toastReady.wait(); 16433

++buttered; 16434

} 16435

cout << "Buttering toast " << buttered << endl; 16436

jammer->moreButteredToastReady(); 16437

{ 16438

Guard<Mutex> g(lock); 16439

gotToast = false; 16440

} 16441

Page 630: Pensar en C++ (Volumen 2)

630

} 16442

} catch(Interrupted_Exception&) { /* Exit */ } 16443

cout << "Butterer off" << endl; 16444

} 16445

}; 16446

16447

class Toaster : public Runnable { 16448

CountedPtr<Butterer> butterer; 16449

int toasted; 16450

public: 16451

Toaster(CountedPtr<Butterer>& b) : butterer(b) { 16452

toasted = 0; 16453

} 16454

void run() { 16455

try { 16456

while(!Thread::interrupted()) { 16457

Thread::sleep(rand()/(RAND_MAX/5)*100); 16458

// ... 16459

// Create new toast 16460

// ... 16461

cout << "New toast " << ++toasted << endl; 16462

butterer->moreToastReady(); 16463

} 16464

} catch(Interrupted_Exception&) { /* Exit */ } 16465

cout << "Toaster off" << endl; 16466

Page 631: Pensar en C++ (Volumen 2)

631

} 16467

}; 16468

16469

int main() { 16470

srand(time(0)); // Seed the random number generator 16471

try { 16472

cout << "Press <Return> to quit" << endl; 16473

CountedPtr<Jammer> jammer(new Jammer); 16474

CountedPtr<Butterer> butterer(new Butterer(jammer)); 16475

ThreadedExecutor executor; 16476

executor.execute(new Toaster(butterer)); 16477

executor.execute(butterer); 16478

executor.execute(jammer); 16479

cin.get(); 16480

executor.interrupt(); 16481

} catch(Synchronization_Exception& e) { 16482

cerr << e.what() << endl; 16483

} 16484

} ///:~ 16485

Listado 10.30. C11/ToastOMatic.cpp 16486

16487

10.6.3. Resolución de problemas de hilos mediante colas 16488

//: C11:TQueue.h 16489

Page 632: Pensar en C++ (Volumen 2)

632

#ifndef TQUEUE_H 16490

#define TQUEUE_H 16491

#include <deque> 16492

#include "zthread/Thread.h" 16493

#include "zthread/Condition.h" 16494

#include "zthread/Mutex.h" 16495

#include "zthread/Guard.h" 16496

16497

template<class T> class TQueue { 16498

ZThread::Mutex lock; 16499

ZThread::Condition cond; 16500

std::deque<T> data; 16501

public: 16502

TQueue() : cond(lock) {} 16503

void put(T item) { 16504

ZThread::Guard<ZThread::Mutex> g(lock); 16505

data.push_back(item); 16506

cond.signal(); 16507

} 16508

T get() { 16509

ZThread::Guard<ZThread::Mutex> g(lock); 16510

while(data.empty()) 16511

cond.wait(); 16512

T returnVal = data.front(); 16513

data.pop_front(); 16514

Page 633: Pensar en C++ (Volumen 2)

633

return returnVal; 16515

} 16516

}; 16517

#endif // TQUEUE_H ///:~ 16518

Listado 10.31. C11/TQueue.h 16519

16520

//: C11:TestTQueue.cpp {RunByHand} 16521

//{L} ZThread 16522

#include <string> 16523

#include <iostream> 16524

#include "TQueue.h" 16525

#include "zthread/Thread.h" 16526

#include "LiftOff.h" 16527

using namespace ZThread; 16528

using namespace std; 16529

16530

class LiftOffRunner : public Runnable { 16531

TQueue<LiftOff*> rockets; 16532

public: 16533

void add(LiftOff* lo) { rockets.put(lo); } 16534

void run() { 16535

try { 16536

while(!Thread::interrupted()) { 16537

LiftOff* rocket = rockets.get(); 16538

Page 634: Pensar en C++ (Volumen 2)

634

rocket->run(); 16539

} 16540

} catch(Interrupted_Exception&) { /* Exit */ } 16541

cout << "Exiting LiftOffRunner" << endl; 16542

} 16543

}; 16544

16545

int main() { 16546

try { 16547

LiftOffRunner* lor = new LiftOffRunner; 16548

Thread t(lor); 16549

for(int i = 0; i < 5; i++) 16550

lor->add(new LiftOff(10, i)); 16551

cin.get(); 16552

lor->add(new LiftOff(10, 99)); 16553

cin.get(); 16554

t.interrupt(); 16555

} catch(Synchronization_Exception& e) { 16556

cerr << e.what() << endl; 16557

} 16558

} ///:~ 16559

Listado 10.32. C11/TestTQueue.cpp 16560

16561

//: C11:ToastOMaticMarkII.cpp {RunByHand} 16562

Page 635: Pensar en C++ (Volumen 2)

635

// Solving the problems using TQueues. 16563

//{L} ZThread 16564

#include <iostream> 16565

#include <string> 16566

#include <cstdlib> 16567

#include <ctime> 16568

#include "zthread/Thread.h" 16569

#include "zthread/Mutex.h" 16570

#include "zthread/Guard.h" 16571

#include "zthread/Condition.h" 16572

#include "zthread/ThreadedExecutor.h" 16573

#include "TQueue.h" 16574

using namespace ZThread; 16575

using namespace std; 16576

16577

class Toast { 16578

enum Status { DRY, BUTTERED, JAMMED }; 16579

Status status; 16580

int id; 16581

public: 16582

Toast(int idn) : status(DRY), id(idn) {} 16583

#ifdef __DMC__ // Incorrectly requires default 16584

Toast() { assert(0); } // Should never be called 16585

#endif 16586

void butter() { status = BUTTERED; } 16587

Page 636: Pensar en C++ (Volumen 2)

636

void jam() { status = JAMMED; } 16588

string getStatus() const { 16589

switch(status) { 16590

case DRY: return "dry"; 16591

case BUTTERED: return "buttered"; 16592

case JAMMED: return "jammed"; 16593

default: return "error"; 16594

} 16595

} 16596

int getId() { return id; } 16597

friend ostream& operator<<(ostream& os, const Toast& t) { 16598

return os << "Toast " << t.id << ": " << t.getStatus(); 16599

} 16600

}; 16601

16602

typedef CountedPtr< TQueue<Toast> > ToastQueue; 16603

16604

class Toaster : public Runnable { 16605

ToastQueue toastQueue; 16606

int count; 16607

public: 16608

Toaster(ToastQueue& tq) : toastQueue(tq), count(0) {} 16609

void run() { 16610

try { 16611

while(!Thread::interrupted()) { 16612

Page 637: Pensar en C++ (Volumen 2)

637

int delay = rand()/(RAND_MAX/5)*100; 16613

Thread::sleep(delay); 16614

// Make toast 16615

Toast t(count++); 16616

cout << t << endl; 16617

// Insert into queue 16618

toastQueue->put(t); 16619

} 16620

} catch(Interrupted_Exception&) { /* Exit */ } 16621

cout << "Toaster off" << endl; 16622

} 16623

}; 16624

16625

// Apply butter to toast: 16626

class Butterer : public Runnable { 16627

ToastQueue dryQueue, butteredQueue; 16628

public: 16629

Butterer(ToastQueue& dry, ToastQueue& buttered) 16630

: dryQueue(dry), butteredQueue(buttered) {} 16631

void run() { 16632

try { 16633

while(!Thread::interrupted()) { 16634

// Blocks until next piece of toast is available: 16635

Toast t = dryQueue->get(); 16636

t.butter(); 16637

Page 638: Pensar en C++ (Volumen 2)

638

cout << t << endl; 16638

butteredQueue->put(t); 16639

} 16640

} catch(Interrupted_Exception&) { /* Exit */ } 16641

cout << "Butterer off" << endl; 16642

} 16643

}; 16644

16645

// Apply jam to buttered toast: 16646

class Jammer : public Runnable { 16647

ToastQueue butteredQueue, finishedQueue; 16648

public: 16649

Jammer(ToastQueue& buttered, ToastQueue& finished) 16650

: butteredQueue(buttered), finishedQueue(finished) {} 16651

void run() { 16652

try { 16653

while(!Thread::interrupted()) { 16654

// Blocks until next piece of toast is available: 16655

Toast t = butteredQueue->get(); 16656

t.jam(); 16657

cout << t << endl; 16658

finishedQueue->put(t); 16659

} 16660

} catch(Interrupted_Exception&) { /* Exit */ } 16661

cout << "Jammer off" << endl; 16662

Page 639: Pensar en C++ (Volumen 2)

639

} 16663

}; 16664

16665

// Consume the toast: 16666

class Eater : public Runnable { 16667

ToastQueue finishedQueue; 16668

int counter; 16669

public: 16670

Eater(ToastQueue& finished) 16671

: finishedQueue(finished), counter(0) {} 16672

void run() { 16673

try { 16674

while(!Thread::interrupted()) { 16675

// Blocks until next piece of toast is available: 16676

Toast t = finishedQueue->get(); 16677

// Verify that the toast is coming in order, 16678

// and that all pieces are getting jammed: 16679

if(t.getId() != counter++ || 16680

t.getStatus() != "jammed") { 16681

cout << ">>>> Error: " << t << endl; 16682

exit(1); 16683

} else 16684

cout << "Chomp! " << t << endl; 16685

} 16686

} catch(Interrupted_Exception&) { /* Exit */ } 16687

Page 640: Pensar en C++ (Volumen 2)

640

cout << "Eater off" << endl; 16688

} 16689

}; 16690

16691

int main() { 16692

srand(time(0)); // Seed the random number generator 16693

try { 16694

ToastQueue dryQueue(new TQueue<Toast>), 16695

butteredQueue(new TQueue<Toast>), 16696

finishedQueue(new TQueue<Toast>); 16697

cout << "Press <Return> to quit" << endl; 16698

ThreadedExecutor executor; 16699

executor.execute(new Toaster(dryQueue)); 16700

executor.execute(new Butterer(dryQueue,butteredQueue)); 16701

executor.execute( 16702

new Jammer(butteredQueue, finishedQueue)); 16703

executor.execute(new Eater(finishedQueue)); 16704

cin.get(); 16705

executor.interrupt(); 16706

} catch(Synchronization_Exception& e) { 16707

cerr << e.what() << endl; 16708

} 16709

} ///:~ 16710

Listado 10.33. C11/ToastOMaticMarkII.cpp 16711

Page 641: Pensar en C++ (Volumen 2)

641

16712

10.6.4. Broadcast 16713

//: C11:CarBuilder.cpp {RunByHand} 16714

// How broadcast() works. 16715

//{L} ZThread 16716

#include <iostream> 16717

#include <string> 16718

#include "zthread/Thread.h" 16719

#include "zthread/Mutex.h" 16720

#include "zthread/Guard.h" 16721

#include "zthread/Condition.h" 16722

#include "zthread/ThreadedExecutor.h" 16723

#include "TQueue.h" 16724

using namespace ZThread; 16725

using namespace std; 16726

16727

class Car { 16728

int id; 16729

bool engine, driveTrain, wheels; 16730

public: 16731

Car(int idn) : id(idn), engine(false), 16732

driveTrain(false), wheels(false) {} 16733

// Empty Car object: 16734

Car() : id(-1), engine(false), 16735

driveTrain(false), wheels(false) {} 16736

Page 642: Pensar en C++ (Volumen 2)

642

// Unsynchronized -- assumes atomic bool operations: 16737

int getId() { return id; } 16738

void addEngine() { engine = true; } 16739

bool engineInstalled() { return engine; } 16740

void addDriveTrain() { driveTrain = true; } 16741

bool driveTrainInstalled() { return driveTrain; } 16742

void addWheels() { wheels = true; } 16743

bool wheelsInstalled() { return wheels; } 16744

friend ostream& operator<<(ostream& os, const Car& c) { 16745

return os << "Car " << c.id << " [" 16746

<< " engine: " << c.engine 16747

<< " driveTrain: " << c.driveTrain 16748

<< " wheels: " << c.wheels << " ]"; 16749

} 16750

}; 16751

16752

typedef CountedPtr< TQueue<Car> > CarQueue; 16753

16754

class ChassisBuilder : public Runnable { 16755

CarQueue carQueue; 16756

int counter; 16757

public: 16758

ChassisBuilder(CarQueue& cq) : carQueue(cq),counter(0) {} 16759

void run() { 16760

try { 16761

Page 643: Pensar en C++ (Volumen 2)

643

while(!Thread::interrupted()) { 16762

Thread::sleep(1000); 16763

// Make chassis: 16764

Car c(counter++); 16765

cout << c << endl; 16766

// Insert into queue 16767

carQueue->put(c); 16768

} 16769

} catch(Interrupted_Exception&) { /* Exit */ } 16770

cout << "ChassisBuilder off" << endl; 16771

} 16772

}; 16773

16774

class Cradle { 16775

Car c; // Holds current car being worked on 16776

bool occupied; 16777

Mutex workLock, readyLock; 16778

Condition workCondition, readyCondition; 16779

bool engineBotHired, wheelBotHired, driveTrainBotHired; 16780

public: 16781

Cradle() 16782

: workCondition(workLock), readyCondition(readyLock) { 16783

occupied = false; 16784

engineBotHired = true; 16785

wheelBotHired = true; 16786

Page 644: Pensar en C++ (Volumen 2)

644

driveTrainBotHired = true; 16787

} 16788

void insertCar(Car chassis) { 16789

c = chassis; 16790

occupied = true; 16791

} 16792

Car getCar() { // Can only extract car once 16793

if(!occupied) { 16794

cerr << "No Car in Cradle for getCar()" << endl; 16795

return Car(); // "Null" Car object 16796

} 16797

occupied = false; 16798

return c; 16799

} 16800

// Access car while in cradle: 16801

Car* operator->() { return &c; } 16802

// Allow robots to offer services to this cradle: 16803

void offerEngineBotServices() { 16804

Guard<Mutex> g(workLock); 16805

while(engineBotHired) 16806

workCondition.wait(); 16807

engineBotHired = true; // Accept the job 16808

} 16809

void offerWheelBotServices() { 16810

Guard<Mutex> g(workLock); 16811

Page 645: Pensar en C++ (Volumen 2)

645

while(wheelBotHired) 16812

workCondition.wait(); 16813

wheelBotHired = true; // Accept the job 16814

} 16815

void offerDriveTrainBotServices() { 16816

Guard<Mutex> g(workLock); 16817

while(driveTrainBotHired) 16818

workCondition.wait(); 16819

driveTrainBotHired = true; // Accept the job 16820

} 16821

// Tell waiting robots that work is ready: 16822

void startWork() { 16823

Guard<Mutex> g(workLock); 16824

engineBotHired = false; 16825

wheelBotHired = false; 16826

driveTrainBotHired = false; 16827

workCondition.broadcast(); 16828

} 16829

// Each robot reports when their job is done: 16830

void taskFinished() { 16831

Guard<Mutex> g(readyLock); 16832

readyCondition.signal(); 16833

} 16834

// Director waits until all jobs are done: 16835

void waitUntilWorkFinished() { 16836

Page 646: Pensar en C++ (Volumen 2)

646

Guard<Mutex> g(readyLock); 16837

while(!(c.engineInstalled() && c.driveTrainInstalled() 16838

&& c.wheelsInstalled())) 16839

readyCondition.wait(); 16840

} 16841

}; 16842

16843

typedef CountedPtr<Cradle> CradlePtr; 16844

16845

class Director : public Runnable { 16846

CarQueue chassisQueue, finishingQueue; 16847

CradlePtr cradle; 16848

public: 16849

Director(CarQueue& cq, CarQueue& fq, CradlePtr cr) 16850

: chassisQueue(cq), finishingQueue(fq), cradle(cr) {} 16851

void run() { 16852

try { 16853

while(!Thread::interrupted()) { 16854

// Blocks until chassis is available: 16855

cradle->insertCar(chassisQueue->get()); 16856

// Notify robots car is ready for work 16857

cradle->startWork(); 16858

// Wait until work completes 16859

cradle->waitUntilWorkFinished(); 16860

// Put car into queue for further work 16861

Page 647: Pensar en C++ (Volumen 2)

647

finishingQueue->put(cradle->getCar()); 16862

} 16863

} catch(Interrupted_Exception&) { /* Exit */ } 16864

cout << "Director off" << endl; 16865

} 16866

}; 16867

16868

class EngineRobot : public Runnable { 16869

CradlePtr cradle; 16870

public: 16871

EngineRobot(CradlePtr cr) : cradle(cr) {} 16872

void run() { 16873

try { 16874

while(!Thread::interrupted()) { 16875

// Blocks until job is offered/accepted: 16876

cradle->offerEngineBotServices(); 16877

cout << "Installing engine" << endl; 16878

(*cradle)->addEngine(); 16879

cradle->taskFinished(); 16880

} 16881

} catch(Interrupted_Exception&) { /* Exit */ } 16882

cout << "EngineRobot off" << endl; 16883

} 16884

}; 16885

16886

Page 648: Pensar en C++ (Volumen 2)

648

class DriveTrainRobot : public Runnable { 16887

CradlePtr cradle; 16888

public: 16889

DriveTrainRobot(CradlePtr cr) : cradle(cr) {} 16890

void run() { 16891

try { 16892

while(!Thread::interrupted()) { 16893

// Blocks until job is offered/accepted: 16894

cradle->offerDriveTrainBotServices(); 16895

cout << "Installing DriveTrain" << endl; 16896

(*cradle)->addDriveTrain(); 16897

cradle->taskFinished(); 16898

} 16899

} catch(Interrupted_Exception&) { /* Exit */ } 16900

cout << "DriveTrainRobot off" << endl; 16901

} 16902

}; 16903

16904

class WheelRobot : public Runnable { 16905

CradlePtr cradle; 16906

public: 16907

WheelRobot(CradlePtr cr) : cradle(cr) {} 16908

void run() { 16909

try { 16910

while(!Thread::interrupted()) { 16911

Page 649: Pensar en C++ (Volumen 2)

649

// Blocks until job is offered/accepted: 16912

cradle->offerWheelBotServices(); 16913

cout << "Installing Wheels" << endl; 16914

(*cradle)->addWheels(); 16915

cradle->taskFinished(); 16916

} 16917

} catch(Interrupted_Exception&) { /* Exit */ } 16918

cout << "WheelRobot off" << endl; 16919

} 16920

}; 16921

16922

class Reporter : public Runnable { 16923

CarQueue carQueue; 16924

public: 16925

Reporter(CarQueue& cq) : carQueue(cq) {} 16926

void run() { 16927

try { 16928

while(!Thread::interrupted()) { 16929

cout << carQueue->get() << endl; 16930

} 16931

} catch(Interrupted_Exception&) { /* Exit */ } 16932

cout << "Reporter off" << endl; 16933

} 16934

}; 16935

16936

Page 650: Pensar en C++ (Volumen 2)

650

int main() { 16937

cout << "Press <Enter> to quit" << endl; 16938

try { 16939

CarQueue chassisQueue(new TQueue<Car>), 16940

finishingQueue(new TQueue<Car>); 16941

CradlePtr cradle(new Cradle); 16942

ThreadedExecutor assemblyLine; 16943

assemblyLine.execute(new EngineRobot(cradle)); 16944

assemblyLine.execute(new DriveTrainRobot(cradle)); 16945

assemblyLine.execute(new WheelRobot(cradle)); 16946

assemblyLine.execute( 16947

new Director(chassisQueue, finishingQueue, cradle)); 16948

assemblyLine.execute(new Reporter(finishingQueue)); 16949

// Start everything running by producing chassis: 16950

assemblyLine.execute(new ChassisBuilder(chassisQueue)); 16951

cin.get(); 16952

assemblyLine.interrupt(); 16953

} catch(Synchronization_Exception& e) { 16954

cerr << e.what() << endl; 16955

} 16956

} ///:~ 16957

Listado 10.34. C11/CarBuilder.cpp 16958

16959

10.7. Bloqueo letal 16960

Page 651: Pensar en C++ (Volumen 2)

651

//: C11:DiningPhilosophers.h 16961

// Classes for Dining Philosophers. 16962

#ifndef DININGPHILOSOPHERS_H 16963

#define DININGPHILOSOPHERS_H 16964

#include <string> 16965

#include <iostream> 16966

#include <cstdlib> 16967

#include "zthread/Condition.h" 16968

#include "zthread/Guard.h" 16969

#include "zthread/Mutex.h" 16970

#include "zthread/Thread.h" 16971

#include "Display.h" 16972

16973

class Chopstick { 16974

ZThread::Mutex lock; 16975

ZThread::Condition notTaken; 16976

bool taken; 16977

public: 16978

Chopstick() : notTaken(lock), taken(false) {} 16979

void take() { 16980

ZThread::Guard<ZThread::Mutex> g(lock); 16981

while(taken) 16982

notTaken.wait(); 16983

taken = true; 16984

} 16985

Page 652: Pensar en C++ (Volumen 2)

652

void drop() { 16986

ZThread::Guard<ZThread::Mutex> g(lock); 16987

taken = false; 16988

notTaken.signal(); 16989

} 16990

}; 16991

16992

class Philosopher : public ZThread::Runnable { 16993

Chopstick& left; 16994

Chopstick& right; 16995

int id; 16996

int ponderFactor; 16997

ZThread::CountedPtr<Display> display; 16998

int randSleepTime() { 16999

if(ponderFactor == 0) return 0; 17000

return rand()/(RAND_MAX/ponderFactor) * 250; 17001

} 17002

void output(std::string s) { 17003

std::ostringstream os; 17004

os << *this << " " << s << std::endl; 17005

display->output(os); 17006

} 17007

public: 17008

Philosopher(Chopstick& l, Chopstick& r, 17009

ZThread::CountedPtr<Display>& disp, int ident,int ponder) 17010

Page 653: Pensar en C++ (Volumen 2)

653

: left(l), right(r), id(ident), ponderFactor(ponder), 17011

display(disp) {} 17012

virtual void run() { 17013

try { 17014

while(!ZThread::Thread::interrupted()) { 17015

output("thinking"); 17016

ZThread::Thread::sleep(randSleepTime()); 17017

// Hungry 17018

output("grabbing right"); 17019

right.take(); 17020

output("grabbing left"); 17021

left.take(); 17022

output("eating"); 17023

ZThread::Thread::sleep(randSleepTime()); 17024

right.drop(); 17025

left.drop(); 17026

} 17027

} catch(ZThread::Synchronization_Exception& e) { 17028

output(e.what()); 17029

} 17030

} 17031

friend std::ostream& 17032

operator<<(std::ostream& os, const Philosopher& p) { 17033

return os << "Philosopher " << p.id; 17034

} 17035

Page 654: Pensar en C++ (Volumen 2)

654

}; 17036

#endif // DININGPHILOSOPHERS_H ///:~ 17037

Listado 10.35. C11/DiningPhilosophers.h 17038

17039

//: C11:DeadlockingDiningPhilosophers.cpp {RunByHand} 17040

// Dining Philosophers with Deadlock. 17041

//{L} ZThread 17042

#include <ctime> 17043

#include "DiningPhilosophers.h" 17044

#include "zthread/ThreadedExecutor.h" 17045

using namespace ZThread; 17046

using namespace std; 17047

17048

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

srand(time(0)); // Seed the random number generator 17050

int ponder = argc > 1 ? atoi(argv[1]) : 5; 17051

cout << "Press <ENTER> to quit" << endl; 17052

enum { SZ = 5 }; 17053

try { 17054

CountedPtr<Display> d(new Display); 17055

ThreadedExecutor executor; 17056

Chopstick c[SZ]; 17057

for(int i = 0; i < SZ; i++) { 17058

executor.execute( 17059

Page 655: Pensar en C++ (Volumen 2)

655

new Philosopher(c[i], c[(i+1) % SZ], d, i,ponder)); 17060

} 17061

cin.get(); 17062

executor.interrupt(); 17063

executor.wait(); 17064

} catch(Synchronization_Exception& e) { 17065

cerr << e.what() << endl; 17066

} 17067

} ///:~ 17068

Listado 10.36. C11/DeadlockingDiningPhilosophers.cpp 17069

17070

//: C11:FixedDiningPhilosophers.cpp {RunByHand} 17071

// Dining Philosophers without Deadlock. 17072

//{L} ZThread 17073

#include <ctime> 17074

#include "DiningPhilosophers.h" 17075

#include "zthread/ThreadedExecutor.h" 17076

using namespace ZThread; 17077

using namespace std; 17078

17079

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

srand(time(0)); // Seed the random number generator 17081

int ponder = argc > 1 ? atoi(argv[1]) : 5; 17082

cout << "Press <ENTER> to quit" << endl; 17083

Page 656: Pensar en C++ (Volumen 2)

656

enum { SZ = 5 }; 17084

try { 17085

CountedPtr<Display> d(new Display); 17086

ThreadedExecutor executor; 17087

Chopstick c[SZ]; 17088

for(int i = 0; i < SZ; i++) { 17089

if(i < (SZ-1)) 17090

executor.execute( 17091

new Philosopher(c[i], c[i + 1], d, i, ponder)); 17092

else 17093

executor.execute( 17094

new Philosopher(c[0], c[i], d, i, ponder)); 17095

} 17096

cin.get(); 17097

executor.interrupt(); 17098

executor.wait(); 17099

} catch(Synchronization_Exception& e) { 17100

cerr << e.what() << endl; 17101

} 17102

} ///:~ 17103

Listado 10.37. C11/FixedDiningPhilosophers.cpp 17104

17105

10.8. Resumen 17106

Page 657: Pensar en C++ (Volumen 2)

657

10.9. Ejercicios 17107