O que significa Aquisição de Recursos é Inicialização (RAII)?
O que significa Aquisição de Recursos é Inicialização (RAII)?
Respostas:
É um nome realmente terrível para um conceito incrivelmente poderoso, e talvez uma das coisas número 1 que os desenvolvedores de C ++ sentem falta quando mudam para outros idiomas. Houve um movimento para tentar renomear esse conceito como Gerenciamento de recursos vinculados a escopo , embora ainda não pareça ter entendido.
Quando dizemos 'Recurso', não queremos dizer apenas memória - podem ser identificadores de arquivo, soquetes de rede, identificadores de banco de dados, objetos GDI ... Em suma, coisas das quais temos um suprimento finito e, portanto, precisamos ser capazes de controlar seu uso. O aspecto 'Limite do escopo' significa que a vida útil do objeto está vinculada ao escopo de uma variável; portanto, quando a variável sai do escopo, o destruidor libera o recurso. Uma propriedade muito útil disso é que contribui para maior segurança de exceção. Por exemplo, compare isso:
RawResourceHandle* handle=createNewResource();
handle->performInvalidOperation(); // Oops, throws exception
...
deleteResource(handle); // oh dear, never gets called so the resource leaks
Com o RAII
class ManagedResourceHandle {
public:
ManagedResourceHandle(RawResourceHandle* rawHandle_) : rawHandle(rawHandle_) {};
~ManagedResourceHandle() {delete rawHandle; }
... // omitted operator*, etc
private:
RawResourceHandle* rawHandle;
};
ManagedResourceHandle handle(createNewResource());
handle->performInvalidOperation();
Neste último caso, quando a exceção é lançada e a pilha é desenrolada, as variáveis locais são destruídas, o que garante que nosso recurso seja limpo e não vaze.
Scope-Bound
é a melhor opção de nome aqui, pois os especificadores de classe de armazenamento, juntamente com o escopo, determinam a duração do armazenamento de uma entidade. Estreitando-lo feito a capa de escopo é talvez uma simplificação útil, no entanto, não é 100% preciso
Este é um idioma de programação que significa brevemente que você
Isso garante que, aconteça o que acontecer enquanto o recurso estiver em uso, ele será liberado (devido ao retorno normal, à destruição do objeto que o contém ou a uma exceção lançada).
É uma boa prática amplamente usada em C ++, porque além de ser uma maneira segura de lidar com recursos, também torna seu código muito mais limpo, pois você não precisa misturar código de tratamento de erros com a funcionalidade principal.
*
Atualização: "local" pode significar uma variável local ou uma variável de membro não estática de uma classe. No último caso, a variável membro é inicializada e destruída com seu objeto proprietário.
**
Atualização2: como o @sbi apontou, o recurso - embora freqüentemente seja alocado dentro do construtor - também pode ser alocado fora e passado como parâmetro.
open()
/ close()
métodos para inicializar e liberar o recurso, apenas o construtor e o destruidor; portanto, a "retenção" do recurso é apenas o tempo de vida do objeto, não importa se esse tempo de vida é manuseado pelo contexto (pilha) ou explicitamente (alloc dinâmico)
"RAII" significa "Aquisição de Recursos é Inicialização" e é realmente um nome impróprio, pois não se trata de aquisição de recursos (e a inicialização de um objeto), mas a liberação do recurso (por meio da destruição de um objeto). )
Mas RAII é o nome que recebemos e permanece.
No seu âmago, o idioma apresenta recursos de encapsulamento (pedaços de memória, arquivos abertos, mutexes desbloqueados, você escolhe) em objetos locais, automáticos , e com o destruidor desse objeto liberando o recurso quando o objeto é destruído no local. final do escopo ao qual pertence:
{
raii obj(acquire_resource());
// ...
} // obj's dtor will call release_resource()
Obviamente, os objetos nem sempre são locais, automáticos. Eles também podem ser membros de uma classe:
class something {
private:
raii obj_; // will live and die with instances of the class
// ...
};
Se esses objetos gerenciam memória, eles são chamados de "ponteiros inteligentes".
Existem muitas variações disso. Por exemplo, no primeiro trecho de código, surge a pergunta sobre o que aconteceria se alguém quisesse copiar obj
. A saída mais fácil seria simplesmente proibir a cópia. std::unique_ptr<>
, um ponteiro inteligente para fazer parte da biblioteca padrão, conforme apresentado no próximo padrão C ++, faz isso.
Outro ponteiro inteligente, std::shared_ptr
apresenta "propriedade compartilhada" do recurso (um objeto alocado dinamicamente) que ele possui. Ou seja, ele pode ser copiado livremente e todas as cópias se referem ao mesmo objeto. O ponteiro inteligente controla quantas cópias se referem ao mesmo objeto e o exclui quando a última estiver sendo destruída.
Uma terceira variante é apresentada porstd::auto_ptr
que implementa um tipo de semântica de movimentação: um objeto pertence a apenas um ponteiro, e a tentativa de copiar um objeto resultará (através da invasão de sintaxe) na transferência da propriedade do objeto para o destino da operação de cópia.
std::auto_ptr
é uma versão obsoleta do std::unique_ptr
. std::auto_ptr
O tipo de semântica de movimentação simulada, tanto quanto possível no C ++ 98, std::unique_ptr
usa a nova semântica de movimentação do C ++ 11. A nova classe foi criada porque a semântica de movimentação do C ++ 11 é mais explícita (requer std::move
exceção do temporário) enquanto foi padronizada para qualquer cópia de não-const no std::auto_ptr
.
A vida útil de um objeto é determinada por seu escopo. No entanto, às vezes precisamos, ou é útil, criar um objeto que viva independentemente do escopo em que foi criado. Em C ++, o operadornew
é usado para criar esse objeto. E para destruir o objeto, o operador delete
pode ser usado. Os objetos criados pelo operador new
são alocados dinamicamente, ou seja, alocados na memória dinâmica (também chamada heap ou free store ). Portanto, um objeto criado por new
continuará existindo até que seja explicitamente destruído usandodelete
.
Alguns erros que podem ocorrer ao usar new
e delete
são:
new
para alocar um objeto e esquecer delete
o objeto.delete
o objeto, e use o outro ponteiro.delete
um objeto duas vezes.Geralmente, as variáveis de escopo são preferidas. No entanto, o RAII pode ser usado como uma alternativa paranew
edelete
tornar um objeto vivo independentemente de seu escopo. Essa técnica consiste em levar o ponteiro para o objeto que foi alocado no heap e colocá-lo em um objeto manipulador / gerenciador . Este último possui um destruidor que cuidará da destruição do objeto. Isso garantirá que o objeto esteja disponível para qualquer função que queira acessá-lo e que o objeto seja destruído quando a vida útil do objeto de manipulação terminar, sem a necessidade de limpeza explícita.
Exemplos da biblioteca padrão C ++ que usam RAII são std::string
e std::vector
.
Considere este pedaço de código:
void fn(const std::string& str)
{
std::vector<char> vec;
for (auto c : str)
vec.push_back(c);
// do something
}
Quando você cria um vetor e envia elementos a ele, não se preocupa em alocar e desalocar esses elementos. O vetor usa new
para alocar espaço para seus elementos no heap edelete
liberar esse espaço. Você, como usuário do vetor, não se importa com os detalhes da implementação e confia no vetor para não vazar. Nesse caso, o vetor é o objeto de manipulação de de seus elementos.
Outros exemplos da biblioteca padrão que usam RAII são std::shared_ptr
, std::unique_ptr
estd::lock_guard
.
Outro nome para essa técnica é SBRM , abreviação de Scope-Bound Resource Management .
O livro C ++ Programming with Design Patterns Revealed descreve RAII como:
Onde
Os recursos são implementados como classes e todos os ponteiros têm wrappers de classe em torno deles (tornando-os ponteiros inteligentes).
Os recursos são adquiridos invocando seus construtores e liberados implicitamente (na ordem inversa de aquisição) invocando seus destruidores.
Existem três partes para uma classe RAII:
RAII significa "Aquisição de recursos é inicialização". A parte "aquisição de recursos" do RAII é onde você inicia algo que deve ser finalizado posteriormente, como:
A parte "is initialization" significa que a aquisição ocorre dentro do construtor de uma classe.
O gerenciamento manual de memória é um pesadelo que os programadores vêm inventando maneiras de evitar desde a invenção do compilador. Linguagens de programação com coletores de lixo facilitam a vida, mas com o custo do desempenho. Neste artigo - Eliminando o coletor de lixo: o caminho da RAII , o engenheiro da Toptal, Peter Goodspeed-Niklaus, nos dá uma espiada na história dos coletores de lixo e explica como as noções de propriedade e empréstimo podem ajudar a eliminar os coletores de lixo sem comprometer suas garantias de segurança.