Actualmente crear una copia de los objetos dentro de un contenedor genérico sería un problema complejo si no se tuvieran plantillas. Con los templates las cosas se vuelven relativamente sencillas - sólo hay que indicar que se están manejando objetos en vez de punteros:
//: C16:ValueStack.h // Holding objects by value in a Stack #ifndef VALUESTACK_H #define VALUESTACK_H #include "../require.h" template<class T, int ssize = 100> class Stack { // Default constructor performs object // initialization for each element in array: T stack[ssize]; int top; public: Stack() : top(0) {} // Copy-constructor copies object into array: void push(const T& x) { require(top < ssize, "Too many push()es"); stack[top++] = x; } T peek() const { return stack[top]; } // Object still exists when you pop it; // it just isn't available anymore: T pop() { require(top > 0, "Too many pop()s"); return stack[--top]; } }; #endif // VALUESTACK_H ///:~
Listado 16.17. C16/ValueStack.h
El constructor de copia de los objetos contenidos hacen la
mayoría del trabajo pasando y devolviendo objetos por
valor. Dentro de push()
, el almacenamiento
del objeto en el array Stack
viene
acompañado con T::operator=
. Para
garantizar que funciona, una clase llamada
SelfCounter
mantiene una lista de las
creaciones y construcciones de copia de los objetos.
//: C16:SelfCounter.h #ifndef SELFCOUNTER_H #define SELFCOUNTER_H #include "ValueStack.h" #include <iostream> class SelfCounter { static int counter; int id; public: SelfCounter() : id(counter++) { std::cout << "Created: " << id << std::endl; } SelfCounter(const SelfCounter& rv) : id(rv.id){ std::cout << "Copied: " << id << std::endl; } SelfCounter operator=(const SelfCounter& rv) { std::cout << "Assigned " << rv.id << " to " << id << std::endl; return *this; } ~SelfCounter() { std::cout << "Destroyed: "<< id << std::endl; } friend std::ostream& operator<<( std::ostream& os, const SelfCounter& sc){ return os << "SelfCounter: " << sc.id; } }; #endif // SELFCOUNTER_H ///:~
Listado 16.18. C16/SelfCounter.h
//: C16:SelfCounter.cpp {O} #include "SelfCounter.h" int SelfCounter::counter = 0; ///:~
Listado 16.19. C16/SelfCounter.cpp
//: C16:ValueStackTest.cpp //{L} SelfCounter #include "ValueStack.h" #include "SelfCounter.h" #include <iostream> using namespace std; int main() { Stack<SelfCounter> sc; for(int i = 0; i < 10; i++) sc.push(SelfCounter()); // OK to peek(), result is a temporary: cout << sc.peek() << endl; for(int k = 0; k < 10; k++) cout << sc.pop() << endl; } ///:~
Listado 16.20. C16/ValueStackTest.cpp
Cuando se crea un contenedor Stack
, el
constructor por defecto del objeto a contener es ejecutado por
cada objeto en el array. Inicialmente se verán 100 objetos
SelfCounter
creados sin ningún motivo
aparente, pero esto es justamente la inicialización del
array. Esto puede resultar un poco caro, pero no existe ningún
problema en un diseño simple como este. Incluso en situaciones
más complejas si se hace a Stack
más general
permitiendo que crezca dinámicamente, porque en la
implementación mostrada anteriormente esto implicaría crear un
nuevo array más grande, copiando el anterior al nuevo y
destruyendo el antiguo array (de hecho, así es como lo hace la
clase vector
de la Librería Estándar de
C++).