Existem excelentes respostas por aí, então eu apenas adiciono algumas coisas esquecidas.
0. RAII é sobre escopos
RAII é sobre ambos:
- adquirir um recurso (não importa qual recurso) no construtor e retirá-lo do destruidor.
- tendo o construtor executado quando a variável é declarada e o destruidor executado automaticamente quando a variável sai do escopo.
Outros já responderam sobre isso, então não vou entrar em detalhes.
1. Ao codificar em Java ou C #, você já usa RAII ...
MONSIEUR JOURDAIN: O quê! Quando eu digo: "Nicole, traga meus chinelos e me dê minha touca de dormir", isso é prosa?
MESTRE DA FILOSOFIA: Sim, senhor.
MONSIEUR JOURDAIN: Há mais de quarenta anos que falo prosa sem saber nada a respeito, e estou muito grato a você por ter me ensinado isso.
- Molière: The Middle Class Gentleman, Act 2, Scene 4
Como Monsieur Jourdain fazia com a prosa, o pessoal de C # e até mesmo de Java já usa RAII, mas de maneiras ocultas. Por exemplo, o seguinte código Java (que é escrito da mesma forma em C #, substituindo synchronized
por lock
):
void foo()
{
// etc.
synchronized(someObject)
{
// if something throws here, the lock on someObject will
// be unlocked
}
// etc.
}
... já está usando RAII: A aquisição mutex é feita na palavra-chave ( synchronized
ou lock
), e a desaquisição será feita ao sair do escopo.
É tão natural em sua notação que quase não requer explicação, mesmo para pessoas que nunca ouviram falar de RAII.
A vantagem de C ++ sobre Java e C # aqui é que qualquer coisa pode ser feita usando RAII. Por exemplo, não há equivalente embutido direto de synchronized
nem lock
em C ++, mas ainda podemos tê-los.
Em C ++, seria escrito:
void foo()
{
// etc.
{
Lock lock(someObject) ; // lock is an object of type Lock whose
// constructor acquires a mutex on
// someObject and whose destructor will
// un-acquire it
// if something throws here, the lock on someObject will
// be unlocked
}
// etc.
}
que pode ser facilmente escrito da maneira Java / C # (usando macros C ++):
void foo()
{
// etc.
LOCK(someObject)
{
// if something throws here, the lock on someObject will
// be unlocked
}
// etc.
}
2. RAII tem usos alternativos
WHITE RABBIT: [cantando] Estou atrasado / Estou atrasado / Para um encontro muito importante. / Não há tempo para dizer "Olá". / Adeus. / Estou atrasado, estou atrasado, estou atrasado.
- Alice no País das Maravilhas (versão Disney, 1951)
Você sabe quando o construtor será chamado (na declaração do objeto) e quando seu destruidor correspondente será chamado (na saída do escopo), então você pode escrever um código quase mágico com apenas uma linha. Bem-vindo ao país das maravilhas do C ++ (pelo menos, do ponto de vista de um desenvolvedor C ++).
Por exemplo, você pode escrever um objeto contador (deixo isso como um exercício) e usá-lo apenas declarando sua variável, como o objeto de bloqueio acima foi usado:
void foo()
{
double timeElapsed = 0 ;
{
Counter counter(timeElapsed) ;
// do something lengthy
}
// now, the timeElapsed variable contain the time elapsed
// from the Counter's declaration till the scope exit
}
que, obviamente, pode ser escrito, novamente, da maneira Java / C # usando uma macro:
void foo()
{
double timeElapsed = 0 ;
COUNTER(timeElapsed)
{
// do something lengthy
}
// now, the timeElapsed variable contain the time elapsed
// from the Counter's declaration till the scope exit
}
3. Por que falta C ++ finally
?
[GRITANDO] É a contagem regressiva final !
- Europa: The Final Countdown (desculpe, eu estava sem aspas, aqui ... :-)
A finally
cláusula é usada em C # / Java para manipular o descarte de recursos em caso de saída do escopo (por meio de uma return
exceção ou lançada).
Leitores de especificações astutos terão notado que C ++ não tem cláusula finally. E isso não é um erro, porque C ++ não precisa, pois o RAII já trata do descarte de recursos. (E acredite em mim, escrever um destruidor em C ++ é muito mais fácil do que escrever a cláusula final do Java certa, ou mesmo o método Dispose correto do C #).
Ainda assim, às vezes, uma finally
cláusula seria legal. Podemos fazer em C ++? Sim, nós podemos! E novamente com um uso alternativo de RAII.
Conclusão: RAII é mais do que filosofia em C ++: é C ++
RAII? ISTO É C ++ !!!
- Comentário indignado do desenvolvedor C ++, descaradamente copiado por um obscuro rei de Esparta e seus 300 amigos
Quando você atinge algum nível de experiência em C ++, começa a pensar em termos de RAII , em termos de execução automatizada de construtores e destruidores .
Você começa a pensar em termos de escopos , e os caracteres {
e }
se tornam os mais importantes em seu código.
E quase tudo se encaixa bem em termos de RAII: segurança de exceção, mutexes, conexões de banco de dados, solicitações de banco de dados, conexão de servidor, relógios, identificadores de sistema operacional, etc. e, por último, mas não menos importante, memória.
A parte do banco de dados não é desprezível, pois, se você aceitar pagar o preço, pode até escrever no estilo " programação transacional ", executando linhas e linhas de código até decidir, no final, se deseja cometer todas as alterações , ou, se não for possível, reverter todas as alterações (desde que cada linha satisfaça pelo menos a Garantia de Exceção Forte). (veja a segunda parte deste artigo do Herb's Sutter para a programação transacional).
E como um quebra-cabeça, tudo se encaixa.
RAII faz parte do C ++, C ++ não poderia ser C ++ sem ele.
Isso explica por que os desenvolvedores C ++ experientes são tão apaixonados por RAII e por que RAII é a primeira coisa que eles procuram quando tentam outra linguagem.
E explica por que o Coletor de Lixo, embora seja uma peça magnífica de tecnologia em si, não é tão impressionante do ponto de vista de um desenvolvedor de C ++:
- RAII já trata a maioria dos casos tratados por um GC
- Um GC lida melhor do que RAII com referências circulares em objetos gerenciados puros (mitigado por usos inteligentes de ponteiros fracos)
- Still A GC é limitado à memória, enquanto RAII pode lidar com qualquer tipo de recurso.
- Conforme descrito acima, RAII pode fazer muito, muito mais ...