En realidad, crear una copia de los objetos dentro de un contenedor genérico es un problema complejo si no se tienen plantillas. Con las plantillas 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 hace la mayor parte del trabajo al pasar y devolver los objetos por valor. Dentro de push(), el almacenamiento del objeto en el array de Stack se realiza con T::operator=. Para garantizar que funciona, una clase llamada SelfCounter lleva la cuenta 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, se llama al constructor por defecto del objeto contenido para cada objeto del 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 hay forma de evitarlo en un diseño simple como éste. Se produce una situación aún más compleja si se hace Stack más general permitiendo que su tamaño crezca dinámicamente, porque en la implementación mostrada anteriormente esto implicaría crear un nuevo array (más grande), copiar el antiguo en el nuevo y destruir el antiguo (de hecho, así es como lo hace la clase vector de la Librería Estándar de C++).