Tabla de contenidos
La herencia y la composición proporcionan una forma de retilizar código objeto. Las plantillas de C++ proporcionan una manera de reutilizar el código fuente.
Aunque las plantillas (o templates) son una herramienta de programación de propósito general, cuando fueron introducidos en el lenguaje, parecían oponerse al uso de las jerarquías de clases contenedoras basadas en objetos (demostrado al final del Capítulo 15). Además, los contenedores y algoritmos del C++ Standard (explicados en dos capítulos del Volumen 2 de este libro, que se puede bajar de www.BruceEckel.com) están construidos exclusivamente con plantillas y son relativamente fáciles de usar por el programador.
Este capítulo no sólo muestra los fundamentos de los templates,
también es una introducción a los contenedores, que son componentes
fundamentales de la programación orientada a objetos lo cual se
evidencia a través de los contenedores de la librería estándar de
C++. Se verá que este libro ha estado usando ejemplos contenedores -
Stash
y Stack
- para
hacer más sencillo el concepto de los contenedores; en este capítulo
se sumará el concepto del iterator
. Aunque
los contenedores son el ejemplo ideal para usarlos con las
plantillas, en el Volumen 2 (que tiene un capítulo con plantillas
avanzadas) se aprenderá que también hay otros usos para los
templates.
Supóngase que se quiere crear una pila, como se ha estado haciendo a través de este libro. Para hacerlo sencillo, esta clase manejará enteros.
//: C16:IntStack.cpp // Simple integer stack //{L} fibonacci #include "fibonacci.h" #include "../require.h" #include <iostream> using namespace std; class IntStack { enum { ssize = 100 }; int stack[ssize]; int top; public: IntStack() : top(0) {} void push(int i) { require(top < ssize, "Too many push()es"); stack[top++] = i; } int pop() { require(top > 0, "Too many pop()s"); return stack[--top]; } }; int main() { IntStack is; // Add some Fibonacci numbers, for interest: for(int i = 0; i < 20; i++) is.push(fibonacci(i)); // Pop & print them: for(int k = 0; k < 20; k++) cout << is.pop() << endl; } ///:~
Listado 16.1. C16/IntStack.cpp
La clase IntStack
es un ejemplo trivial de
una pila. Para mantener la simplicidad ha sido creada con un
tamaño fijo, pero se podría modificar para que automáticamente se
expanda usando la memoria del montón, como en la clase
Stack
que ha sido examinada a través del
libro.
main()
añade algunos enteros a la pila, y
posteriormente los extrae. Para hacer el ejemplo más interesante,
los enteros son creados con la función
fibonacci()
, que genera los tradicionales
números de la reproducción del conejo. Aquí está el archivo de
cabecera que declara la función:
//: C16:fibonacci.h // Fibonacci number generator int fibonacci(int n); ///:~
Listado 16.2. C16/fibonacci.h
Aquí está la implementación:
//: C16:fibonacci.cpp {O} #include "../require.h" int fibonacci(int n) { const int sz = 100; require(n < sz); static int f[sz]; // Initialized to zero f[0] = f[1] = 1; // Scan for unfilled array elements: int i; for(i = 0; i < sz; i++) if(f[i] == 0) break; while(i <= n) { f[i] = f[i-1] + f[i-2]; i++; } return f[n]; } ///:~
Listado 16.3. C16/fibonacci.cpp
Esta es una implementación bastante eficiente, porque nunca se
generan los números más de una vez. Se usa un array
static
de int
, y se basa en el hecho de que
el compilador inicializará el array estático a cero. El primer
bucle for
mueve el índice i
a la
primera posición del array que sea cero, entonces un bucle
while
añade números Fibonacci al array hasta que se
alcance el elemento deseado. Hay que hacer notar que si los
números Fibonacci hasta el elemento n
ya están
inicializados, entonces también se salta el bucle while
.
Obviamente, una pila de enteros no es una herramienta
crucial. La necesidad real de los contenedores viene cuando se
empizan a crear objetos en el montón (heap) usando new
y se destruyen con delete
. En un problema general de
programación no se saben cuantos objetos van a ser necesarios
cuando se está escribiendo el programa. Por ejemplo, en un
sistema de control de tráfico aéreo no se quiere limitar el
número de aviones que el sistema pueda gestionar. No puede ser
que el programa se aborte sólo porque se excede algún
número. En un sistema de diseño asistido por computadora, se
están manejando montones de formas, pero únicamente el usuario
determina (en tiempo de ejecución) cuantas formas serán
necesarias. Una vez apreciemos estas tendencias, se
descubrirán montones de ejemplos en otras situaciones de
programación.
Los programadores de C que dependen de la memoria virtual para
manejar su "gestión de memoria" encuentran a menudo como
perturbantentes las ideas del new
, delete
y de los contenedores de
clases. Aparentemente, una práctica en C es crear un enorme
array global, más grande que cualquier cosa que el programa
parezca necesitar. Para esto no es necesario pensar demasiado
(o hay que meterse en el uso de malloc()
y free()
), pero se producen programas que
no se pueden portar bien y que esconden sutiles errores.
Además, si se crea un enorme array global de objetos en C++,
la sobrecarga de los constructores y de los destructores
pueden enlentecer las cosas de forma significativa. La
aproximación de C++ funciona mucho mejor: Cuando se necesite
un objeto, se crea con new
,
y se pone su puntero en un contenedor. Más tarde, se saca y se
hace algo con él. De esta forma, sólo se crean los objetos
cuando sea necesario. Y normalmente no se dan todas las
condiciones para la inicialización al principio del
programa. new
permite
esperar hasta que suceda algo en el entorno para poder crear
el objeto.
Así, en la situación más común, se creará un contenedor que
almacene los punteros de algunos objetos de interés. Se
crearán esos objetos usando new
y se pondrá el puntero resultante
en el contenedor (potencialmete haciendo upcasting en el
proceso), más tarde el objeto se puede recuperar cuando sea
necesario. Esta técnica produce el tipo de programas más
flexible y general.