Volvamos al problema de la propiedad. Los contenedores que manejan objetos por valor normalmente no se preocupan por la propiedad porque claramente poseen los objetos que contienen. Pero si el contenedor gestiona punteros (lo cual es más común en C++, especialmente con el polimorfismo), entonces es bastante probable que esos punteros se usen también en algún otro lugar del programa, y no necesariamente se quiere borrar el objeto porque entonces los otros punteros del programa estarían referenciando un objeto destruido. Para evitar que esto ocurra, hay que tener en cuenta la propiedad cuando se diseña y se usa un contenedor.
Muchos programas son mucho más simples, y no se encuentran con el problema de la propiedad: un contenedor maneja punteros a objetos que sólo usa ese contenedor. En este caso la propiedad es evidente: el contenedor posee sus objetos.
La mejor aproximación para gestionar el problema de la propiedad es dar al programador cliente la posibilidad de elegir. Esto se consigue a menudo con un argumento del constructor que por defecto indica propiedad (el caso más sencillo). Además puede haber funciones «get» y «set» para ver y modificar la propiedad del contenedor. Si el contenedor tiene funciones para eliminar un objeto, el estado de propiedad normalmente afecta a esa eliminación, por lo que también se pueden encontrar opciones para controlar la destrucción en la función de eliminación. Es concebible añadir datos de propiedad para cada elemento del contenedor, de modo que cada posición sepa si es necesario destruirla; esto es una variante del conteo de referencias, excepto en que es el contenedor y no el objeto el que conoce el número de referencias a un objeto.
//: C16:OwnerStack.h // Stack with runtime conrollable ownership #ifndef OWNERSTACK_H #define OWNERSTACK_H template<class T> class Stack { struct Link { T* data; Link* next; Link(T* dat, Link* nxt) : data(dat), next(nxt) {} }* head; bool own; public: Stack(bool own = true) : head(0), own(own) {} ~Stack(); void push(T* dat) { head = new Link(dat,head); } T* peek() const { return head ? head->data : 0; } T* pop(); bool owns() const { return own; } void owns(bool newownership) { own = newownership; } // Auto-type conversion: true if not empty: operator bool() const { return head != 0; } }; template<class T> T* Stack<T>::pop() { if(head == 0) return 0; T* result = head->data; Link* oldHead = head; head = head->next; delete oldHead; return result; } template<class T> Stack<T>::~Stack() { if(!own) return; while(head) delete pop(); } #endif // OWNERSTACK_H ///:~
Listado 16.15. C16/OwnerStack.h
El comportamiento por defecto del contenedor consiste en destruir sus objetos, pero se puede cambiar modificando el argumento del constructor o usando las funciones miembro de lectura/escritura owns().
Como con la mayoría de las plantillas que se verán, la implementación entera se encuentra en el fichero de cabecera. Aquí tenemos una pequeña prueba que ejercita las capacidades de gestión de la propiedad:
//: C16:OwnerStackTest.cpp //{L} AutoCounter #include "AutoCounter.h" #include "OwnerStack.h" #include "../require.h" #include <iostream> #include <fstream> #include <string> using namespace std; int main() { Stack<AutoCounter> ac; // Ownership on Stack<AutoCounter> ac2(false); // Turn it off AutoCounter* ap; for(int i = 0; i < 10; i++) { ap = AutoCounter::create(); ac.push(ap); if(i % 2 == 0) ac2.push(ap); } while(ac2) cout << ac2.pop() << endl; // No destruction necessary since // ac "owns" all the objects } ///:~
Listado 16.16. C16/OwnerStackTest.cpp
El objeto ac2 no posee los objetos que se ponen en él, por tanto ac es el contenedor «maestro» que asume la responsabilidad de la propiedad. Si a mitad de la vida de un contenedor se quiere cambiar si posee o no sus objetos, se puede hacer usando owns().
También sería posible cambiar la granularidad de la propiedad para que se gestionase objeto por objeto, pero eso probablemente haría la solución del problema de la propiedad más compleja que el propio problema.