Volvamos al problema del propietario. 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 común en C++, especialmente con el polimorfismo), entonces es bastante probable que esos punteros sean usados en algún otro lado del programa, y no necesariamente se quiere borrar el objeto porque los otros punteros del programa estarán referenciando a un objeto destruido. Para prevenir que esto ocurra, hay que considerar al propietario cuando se está diseñando y usando un contenedor.
Muchos programas son más simples que este, y no se encuentran con el problema de la propiedad: Un contenedor que maneja punteros a objetos y que son usados sólo por ese contenedor. En este caso el propietario es evidente: El contenedor posee sus objetos.
La mejor aproximación para gestionar quién es el propietario es dar al programador cliente una elección. Esto se puede realizar con un argumento en el constructor que por defecto defina al propietario (el caso más sencillo). Además habrá que poner las funciones «get» y «set» para poder ver y modificar al propietario del contenedor. Si el contenedor tiene funciones para eliminar un objeto, el estado de propiedad normalmente afecta a la función de eliminación, por lo que se deberían encontrar opciones para controlar la destrucción en la función de eliminación. Es concebible que se añadan datos propietarios por cada elemento que contenga el contenedor, por lo que cada posición debería saber cuando es necesario ser destruido; 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 o modificando el
argumento del constructor o usando las funciones miembro de
owns()
.
Como con la mayoría de las plantillas que se verán, la implementación entera se encuentra en el archivo de cabecera. Aquí tenemos un pequeño test que muestra las capacidades 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
pusimos en él, sin embargo ac
es un
contenedor «maestro» que tiene la responsabilidad
de ser el propietario de los objetos. Si en algún momento de la
vida de un contenedor se quiere cambiar el que un contenedor
posea a sus objetos, se puede hacer usando
owns()
.
También sería posible cambiar la granularidad de la propiedad para que estuviera en la base, es decir, objeto por objeto. Esto, sin embargo, probablemente haría a la solución del problema del propietario más complejo que el propio problema.