Compartilhar via


Tempo de vida do objeto e gerenciamento de recursos (RAII)

Ao contrário das linguagens gerenciadas, C++ não possui coleta de lixo automática, um processo interno que libera memória heap e outros recursos à medida que um programa é executado. Um programa C++ é responsável por retornar todos os recursos adquiridos para o sistema operacional. Chama-se vazamento a falha ao liberar um recurso não utilizado. Os recursos vazados não estarão disponíveis para outros programas até que o processo seja encerrado. As perdas de memória, em particular, são uma causa comum de bugs na programação no estilo C.

O C++ moderno evita usar a memória de heap o máximo possível declarando objetos na pilha. Quando um recurso é muito grande para a pilha, ele deve pertencer a um objeto. À medida que o objeto é inicializado, ele adquire o recurso que lhe pertence. O objeto é então responsável por liberar o recurso em seu destruidor. O próprio objeto proprietário é declarado na pilha. O princípio que diz que objetos possuem recursos também é conhecido como "aquisição de recursos é inicialização" ou RAII.

Quando um objeto de pilha que possui recursos sai do escopo, seu destruidor é invocado automaticamente. Dessa forma, a coleta de lixo em C++ está intimamente relacionada ao tempo de vida do objeto e é determinística. Um recurso é sempre lançado em um ponto conhecido do programa, que você pode controlar. Somente destruidores determinísticos, como os do C++, podem lidar com recursos de memória e não memória igualmente.

O exemplo a seguir mostra um objeto simples w. Ele é declarado na pilha no escopo da função e é destruído no final do bloco de funções. O objeto w não possui recursos (como memória alocada por heap). Seu único membro g é declarado na pilha e simplesmente sai do escopo junto com w. Não é necessário nenhum código especial no destruidor 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(); }"

No exemplo a seguir, w possui um recurso de memória e, portanto, deve ter código em seu destruidor para excluir a memória.

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 o C++11, há uma maneira melhor de escrever o exemplo anterior: usando um ponteiro inteligente da biblioteca padrão. O ponteiro inteligente lida com a alocação e exclusão da memória que ele possui. O uso de um ponteiro inteligente elimina a necessidade de um destruidor explícito na classe 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

Usando ponteiros inteligentes para alocação de memória, você pode eliminar a possibilidade de perdas de memória. Esse modelo funciona para outros recursos, como identificadores de arquivo ou soquetes. Você pode gerenciar seus próprios recursos de maneira semelhante em suas aulas. Para obter mais informações, consulte Ponteiros inteligentes.

O design do C++ garante que os objetos sejam destruídos quando saem do escopo. Ou seja, eles são destruídos à medida que os blocos saem, na ordem inversa da construção. Quando um objeto é destruído, suas bases e membros são destruídos em uma ordem específica. Objetos declarados fora de qualquer bloco, no escopo global, podem gerar problemas. Eles podem dificultar a depuração, se o construtor de um objeto global gerar uma exceção.

Confira também

Bem-vindo outra vez ao C++
Referência da linguagem C++
Biblioteca Padrão do C++