Aquisição de Recurso é Inicialização
Aquisição de Recurso é Inicialização (conhecido pelo acrônimo RAII para o termo em língua inglesa Resource Acquisition Is Initialization) é um padrão de projeto de software para C++, D e Rust que combina a aquisição e liberação de recursos com inicialização e destruição de objetos.
Segundo a RAII, o uso de memória de um objeto inicia-se na sua declaração, e termina quando o objeto sai do escopo, seja porque a execução chegou ao final do bloco, ou porque uma exceção foi lançada. A liberação de memória é possível através do uso dos métodos destrutores da classe – a liberação da memória no destrutor garante que ela será realizada em qualquer caminho que o fluxo seguir, tornando o código seguro.[1][2]
Exemplo
[editar | editar código-fonte]Alocação dinâmica de memória
[editar | editar código-fonte]
Usualmente em C++, quando se é necessário alocar um array e outros objetos de forma dinâmica, isto é, durante a execução do programa, o operador new
é utilizado para realizar a alocação de memória (que no contexto de RAII é um recurso), enquanto o operador delete[]
é utilizada para a desalocação, como no exemplo abaixo:
void exemplo()
{
// Realiza a alocação dinâmica de memória
int* array = new int[10];
// Utiliza a variável
array[5] = 25;
// Realiza a desalocação de memória quando não mais necessária
delete[] array;
}
Caso a desalocação com o operador delete[]
não seja realizada após o uso da variável array
ter sido encerrado, a memória alocada inicialmente com o operador new
permanecerá alocada até o término da execução do programa, pois C++ não contém um coletor de lixo (em inglês: garbage collector) por padrão; este fenômeno é conhecido como vazamento de memória (em inglês: memory leak). Múltiplas ocorrências de vazamento de memória, sobretudo em estruturas de repetição (tais como for
e while
), podem eventualmente esgotar a memória do sistema conforme a aplicação é executada, levando a diminuição do desempenho e a falhas. O fenômeno é demonstrado no exemplo abaixo:
void exemplo()
{
// Realiza a alocação dinâmica de memória
int* array = new int[10];
// Utiliza a variável
array[5] = 25;
} /* A inclusão do operador de desalocação foi acidentalmente esquecida,
a execução da função terminará mas a memória não utilizada permanecerá
alocada para a variável array.
*/
Para solucionar este problema através do RAII, é possível encapsular e abstrair um array alocado dinamicamente através de uma classe RaiiArray
cujo construtor RaiiArray(std::size_t tamanho)
e destrutor ~RaiiArray()
realizem a alocação e desalocação da memória necessária, respectivamente. Similar ao comportamento de um ponteiro inteligente, a alocação é realizada quando a classe é instanciada através do construtor, e a desalocação ocorre automaticamente pelo destrutor quando a execução do programa atinge o fim do escopo onde o objeto se encontra, como demonstrado no exemplo abaixo. O operador []
foi incluído para que o acesso aos índices do array seja feito de maneira similar a forma convencional.
class RaiiArray
{
private:
int* array;
public:
// O construtor realiza a alocação dinâmica de memória
RaiiArray(std::size_t tamanho)
{
this->array = new int[tamanho];
}
// O destrutor realiza a desalocação de memória
~RaiiArray()
{
delete[] this->array;
}
// O operador [] retorna uma referência para o índice solicitado
int & operator [] (std::size_t i)
{
return this->array[i];
}
};
void exemplo()
{
// Instancia a classe e realiza a alocação dinâmica de memória
RaiiArray array = RaiiArray(10);
// Utiliza a variável
array[5] = 25;
} /* O destrutor da instância é automaticamente executado quando ela sai
do escopo da função, desalocando a memória alocada internamente pela
instância e inibindo a ocorrência de vazamento de memória.
*/
Através do uso de templates, a classe acima poderia ser generalizada para um tipo genérico qualquer, como demonstrado abaixo:
#include <string>
template<typename T>
class RaiiArray
{
private:
T* array;
public:
RaiiArray(std::size_t tamanho)
{
this->array = new T[tamanho];
}
~RaiiArray()
{
delete[] this->array;
}
T & operator [] (std::size_t i)
{
return this->array[i];
}
};
void exemplo()
{
RaiiArray<int> int_array = RaiiArray<int>(10);
RaiiArray<float> float_array = RaiiArray<float>(10);
RaiiArray<std::string> string_array = RaiiArray<std::string>(10);
RaiiArray<const char*> const_char_ptr_array = RaiiArray<const char*>(10);
int_array[5] = 25;
float_array[5] = 25.0f;
string_array[5] = std::string("25");
const_char_ptr_array[5] = "25";
}
É importante citar que a biblioteca padrão do C++ moderno fornece alternativas RAII melhores para alocação dinâmica de memória, como std::vector
, std::unique_ptr
, std::shared_ptr
e std::weak_ptr
.
Arquivo
[editar | editar código-fonte]A classe abaixo encapsula chamadas do gerenciamento de arquivos da biblioteca padrão do C usando RAII:
#include <cstdio>
class arquivo {
std::FILE* m_ptrArquivo;
public:
arquivo(const char* nomeArquivo)
: m_ptrArquivo(std::fopen(nomeArquivo, "w+"))
{
if (!m_ptrArquivo)
throw std::runtime_error("Erro ao abrir o arquivo.");
}
// atribuição e cópia não implementados, prevenindo o uso
arquivo(const arquivo&) = delete;
arquivo& operator=(const arquivo&) = delete;
~arquivo() {
if (std::fclose(m_ptrArquivo) != 0) {
// lida-se com erros do sistema de arquivos, fclose() pode falhar
}
}
void escreve(const char* texto) {
if (std::fputs(texto, m_ptrArquivo) == EOF)
throw std::runtime_error("Erro ao escrever no arquivo.");
}
};
Pode-se usar a classe acima da seguinte forma:
void exemplo_de_uso()
{
// abre o arquivo (adquire e inicializa o recurso)
arquivo arquivoLog("arquivoLog.txt");
arquivo.escreve("Olá, log!");
// continua usando arquivoLog...
// lança exceções ou retorna sem se preocupar em fechar o recurso,
// ele é fechado automaticamente quando seu escopo termina
}
A essência do idioma RAII é que a classe arquivo
encapsula o gerenciamento do recurso FILE*
, adquirindo e liberando automaticamente a memória, sem que seja necessário se preocupar com isso.
Uso e alternativas em outras linguagens
[editar | editar código-fonte]Objetos Java são destruídos automaticamente em tempos indeterminados pelo coletor de lixo. Os recursos então devem ser manualmente fechados pelo programador. O exemplo anterior deve ser escrito da seguinte maneira em Java:
void java_example() {
// abre o arquivo (adquire o recurso)
LogFile logfile = new LogFile("logfile.txt") ;
try {
logfile.write("hello logfile!") ;
// continua usado logfile ...
} finally {
// fechamento explícito do recurso
logfile.close();
}
}
A responsabilidade de liberar o recurso recai sobre o programador em cada ponto onde o recurso é usado, e não de forma genérica em um local único.
Referências
- ↑ Bjarne Stroustrup (abril de 2001). «Exception Safety: Concepts and Techniques» (PDF). Consultado em 2 de setembro de 2007
- ↑ Herb Sutter (1999). Exceptional C++. [S.l.]: Addison-Wesley. ISBN 0-201-61562-2
- Bjarne Stroustrup. «resource acquisition is initialization» (em inglês). Glossário de Bjarne Stroustrup