Duración de objetos y administración de recursos (RAII)

A diferencia de los lenguajes administrados, C++ no tiene recolección automática de elementos no utilizados, un proceso interno que libera la memoria del montón y otros recursos a medida que se ejecuta un programa. Un programa de C++ es responsable de devolver todos los recursos adquiridos al sistema operativo. Si no se libera un recurso sin usar, se denomina filtración. Los recursos filtrados no están disponibles para otros programas hasta que se cierre el proceso. Las fugas de memoria en particular son una causa común de errores en la programación de estilo C.

El C++ moderno evita el uso de la memoria del montón tanto como sea posible mediante la declaración de objetos en la pila. Cuando un recurso es demasiado grande para la pila, debe ser propiedad de un objeto. A medida que se inicializa el objeto, adquiere el recurso que posee. A continuación, el objeto es responsable de liberar el recurso en su destructor. El propio objeto propietario se declara en la pila. El principio de que los objetos poseen recursos también se conocen como "la adquisición de recursos es la inicialización" o RAII.

Cuando un objeto de pila propietario de recursos sale del ámbito, se invoca automáticamente su destructor. De este modo, la recolección de elementos no utilizados en C++ está estrechamente relacionada con la duración del objeto y es determinista. Un recurso siempre se libera en un punto conocido del programa, que puede controlar. Solo los destructores deterministas como los de C++ pueden controlar igualmente los recursos de memoria y no memoria.

En el siguiente ejemplo se muestra un objeto simple w. Se declara en la pila en el ámbito de la función y se destruye al final del bloque de función. El objeto w no posee ningún recurso (por ejemplo, memoria asignada por montón). Su único miembro g se declara en la pila y simplemente sale del ámbito junto con w. No se necesita ningún código especial en el destructor widget.

class widget {
private:
    gadget g;   // lifetime automatically tied to enclosing object
public:
    void draw();
};

void functionUsingWidget () {
    widget w;   // lifetime automatically tied to enclosing scope
                // constructs w, including the w.g gadget member
    // ...
    w.draw();
    // ...
} // automatic destruction and deallocation for w and w.g
  // automatic exception safety,
  // as if "finally { w.dispose(); w.g.dispose(); }"

En el ejemplo siguiente, w posee un recurso de memoria y, por tanto, debe tener código en su destructor para eliminar la memoria.

class widget
{
private:
    int* data;
public:
    widget(const int size) { data = new int[size]; } // acquire
    ~widget() { delete[] data; } // release
    void do_something() {}
};

void functionUsingWidget() {
    widget w(1000000);  // lifetime automatically tied to enclosing scope
                        // constructs w, including the w.data member
    w.do_something();

} // automatic destruction and deallocation for w and w.data

Desde C++11, hay una mejor manera de escribir el ejemplo anterior: mediante un puntero inteligente de la biblioteca estándar. Un puntero inteligente controla la asignación y la eliminación de la memoria de la que es propietario. El uso de un puntero inteligente elimina la necesidad de un destructor explícito en la clase widget.

#include <memory>
class widget
{
private:
    std::unique_ptr<int[]> data;
public:
    widget(const int size) { data = std::make_unique<int[]>(size); }
    void do_something() {}
};

void functionUsingWidget() {
    widget w(1000000);  // lifetime automatically tied to enclosing scope
                        // constructs w, including the w.data gadget member
    // ...
    w.do_something();
    // ...
} // automatic destruction and deallocation for w and w.data

Mediante el uso de punteros inteligentes para la asignación de memoria, puede eliminar la posibilidad de pérdidas de memoria. Este modelo funciona para otros recursos, como identificadores de archivo o sockets. Puede administrar sus propios recursos de forma similar en las clases. Para obtener más información, consulte Punteros inteligentes.

El diseño de C++ garantiza que los objetos se destruyen cuando salen del ámbito. Es decir, se destruyen a medida que salen bloques, en orden inverso de construcción. Cuando se destruye un objeto, sus bases y miembros se destruyen en un orden determinado. Los objetos declarados fuera de cualquier bloque, en el ámbito global, pueden provocar problemas. Puede ser difícil depurarlo si el constructor de un objeto global produce una excepción.

Consulte también

Aquí está otra vez C++
Referencia del lenguaje C++
Biblioteca estándar de C++