Compreender o significado do termo e do conceito - RAII (Aquisição de recursos é inicialização)

110

Vocês, desenvolvedores de C ++, poderiam nos dar uma boa descrição do que é RAII, por que é importante e se pode ou não ter alguma relevância para outras linguagens?

I fazer saber um pouco. Eu acredito que significa "Aquisição de recursos é inicialização". No entanto, esse nome não combina com meu entendimento (possivelmente incorreto) do que é RAII: tenho a impressão de que RAII é uma forma de inicializar objetos na pilha de forma que, quando essas variáveis ​​saem do escopo, os destruidores irão automaticamente ser chamado fazendo com que os recursos sejam limpos.

Então, por que isso não é chamado de "usar a pilha para acionar a limpeza" (UTSTTC :)? Como você vai de lá para "RAII"?

E como você pode fazer algo na pilha que causará a limpeza de algo que vive na pilha? Além disso, há casos em que você não pode usar RAII? Você já se pegou desejando a coleta de lixo? Pelo menos um coletor de lixo que você poderia usar para alguns objetos enquanto permite que outros sejam gerenciados?

Obrigado.

Charlie Flowers
fonte
27
UTSTTC? Eu gosto disso! É muito mais intuitivo do que RAII. RAII tem um nome ruim, duvido que qualquer programador C ++ questione isso. Mas não é fácil mudar. ;)
jalf
10
Esta é a opinião de Stroustrup sobre o assunto: groups.google.com/group/comp.lang.c++.moderated/msg/…
sbi
3
@sbi: Enfim, +1 em seu comentário apenas para a pesquisa histórica. Acredito que ter o ponto de vista do autor (B. Stroustrup) sobre o nome de um conceito (RAII) é interessante o suficiente para ter sua própria resposta.
paercebal
1
@paercebal: Pesquisa histórica? Agora você me fez sentir muito velha. :(Eu estava lendo o tópico inteiro, naquela época, e nem me considerava um novato em C ++!
sbi
3
+1, eu estava prestes a fazer a mesma pergunta, feliz por não ser o único que entende o conceito, mas não dá sentido ao nome. Parece que deveria ser chamado de RAOI - Aquisição de recursos na inicialização.
Laurent

Respostas:

132

Então, por que isso não é chamado de "usar a pilha para acionar a limpeza" (UTSTTC :)?

RAII está lhe dizendo o que fazer: Adquira seu recurso em um construtor! Eu acrescentaria: um recurso, um construtor. UTSTTC é apenas uma aplicação disso, RAII é muito mais.

Gerenciamento de recursos é uma merda. Aqui, recurso é tudo o que precisa de limpeza após o uso. Estudos de projetos em muitas plataformas mostram que a maioria dos bugs está relacionada ao gerenciamento de recursos - e isso é particularmente ruim no Windows (devido aos muitos tipos de objetos e alocadores).

Em C ++, o gerenciamento de recursos é particularmente complicado devido à combinação de exceções e modelos (estilo C ++). Para uma espiada sob o capô, consulte GOTW8 ).


C ++ garante que o destruidor seja chamado se, e somente se, o construtor for bem-sucedido. Contando com isso, o RAII pode resolver muitos problemas desagradáveis ​​dos quais o programador médio pode nem estar ciente. Aqui estão alguns exemplos além de "minhas variáveis ​​locais serão destruídas sempre que eu retornar".

Vamos começar com uma FileHandleclasse excessivamente simplista que emprega RAII:

class FileHandle
{
    FILE* file;

public:

    explicit FileHandle(const char* name)
    {
        file = fopen(name);
        if (!file)
        {
            throw "MAYDAY! MAYDAY";
        }
    }

    ~FileHandle()
    {
        // The only reason we are checking the file pointer for validity
        // is because it might have been moved (see below).
        // It is NOT needed to check against a failed constructor,
        // because the destructor is NEVER executed when the constructor fails!
        if (file)
        {
            fclose(file);
        }
    }

    // The following technicalities can be skipped on the first read.
    // They are not crucial to understanding the basic idea of RAII.
    // However, if you plan to implement your own RAII classes,
    // it is absolutely essential that you read on :)



    // It does not make sense to copy a file handle,
    // hence we disallow the otherwise implicitly generated copy operations.

    FileHandle(const FileHandle&) = delete;
    FileHandle& operator=(const FileHandle&) = delete;



    // The following operations enable transfer of ownership
    // and require compiler support for rvalue references, a C++0x feature.
    // Essentially, a resource is "moved" from one object to another.

    FileHandle(FileHandle&& that)
    {
        file = that.file;
        that.file = 0;
    }

    FileHandle& operator=(FileHandle&& that)
    {
        file = that.file;
        that.file = 0;
        return *this;
    }
}

Se a construção falhar (com uma exceção), nenhuma outra função de membro - nem mesmo o destruidor - será chamada.

RAII evita usar objetos em um estado inválido. já torna a vida mais fácil antes mesmo de usarmos o objeto.

Agora, vamos dar uma olhada nos objetos temporários:

void CopyFileData(FileHandle source, FileHandle dest);

void Foo()
{
    CopyFileData(FileHandle("C:\\source"), FileHandle("C:\\dest"));
}

Existem três casos de erro a tratar: nenhum arquivo pode ser aberto, apenas um arquivo pode ser aberto, ambos os arquivos podem ser abertos, mas a cópia dos arquivos falhou. Em uma implementação não RAII, Footeria que tratar todos os três casos explicitamente.

RAII libera recursos que foram adquiridos, mesmo quando vários recursos são adquiridos em uma instrução.

Agora, vamos agregar alguns objetos:

class Logger
{
    FileHandle original, duplex;   // this logger can write to two files at once!

public:

    Logger(const char* filename1, const char* filename2)
    : original(filename1), duplex(filename2)
    {
        if (!filewrite_duplex(original, duplex, "New Session"))
            throw "Ugh damn!";
    }
}

O construtor de Loggerfalhará se originalo construtor de falhar (porque filename1não pôde ser aberto), duplexo construtor de falhar (porque filename2não pôde ser aberto) ou gravar nos arquivos dentro Loggerdo corpo do construtor falhar. Em qualquer um desses casos, Loggero destruidor de não será chamado - portanto, não podemos contar com Loggero destruidor de para liberar os arquivos. Mas se originalfoi construído, seu destruidor será chamado durante a limpeza do Loggerconstrutor.

RAII simplifica a limpeza após a construção parcial.


Pontos negativos:

Pontos negativos? Todos os problemas podem ser resolvidos com RAII e smart pointers ;-)

O RAII às vezes é difícil de controlar quando você precisa de aquisição atrasada, colocando objetos agregados no heap.
Imagine que o Logger precisa de um SetTargetFile(const char* target). Nesse caso, o identificador, que ainda precisa ser um membro Logger, precisa residir na pilha (por exemplo, em um ponteiro inteligente, para acionar a destruição do identificador de forma adequada).

Nunca desejei realmente a coleta de lixo. Quando faço C #, às vezes sinto um momento de felicidade que simplesmente não preciso me importar, mas muito mais sinto falta de todos os brinquedos legais que podem ser criados por meio da destruição determinística. (usar IDisposablesimplesmente não resolve.)

Eu tive uma estrutura particularmente complexa que pode ter se beneficiado do GC, onde ponteiros inteligentes "simples" causariam referências circulares em várias classes. Fizemos confusão equilibrando cuidadosamente os indicadores fortes e fracos, mas sempre que quisermos mudar algo, temos que estudar um grande gráfico de relacionamento. O GC pode ter sido melhor, mas alguns dos componentes continham recursos que deveriam ser liberados o mais rápido possível.


Uma observação sobre o exemplo do FileHandle: Não era para ser completo, apenas um exemplo - mas acabou incorreto. Obrigado Johannes Schaub por apontar e FredOverflow por transformá-lo em uma solução C ++ 0x correta. Com o tempo, me conformei com a abordagem documentada aqui .

Peterchen
fonte
1
+1 Por apontar que GC e ASAP não combinam. Não dói com frequência, mas quando isso acontece não é fácil de diagnosticar: /
Matthieu M.
10
Uma frase em particular que esqueci em leituras anteriores. Você disse que "RAII" está lhe dizendo: "Adquira seus recursos dentro dos construtores." Isso faz sentido e é quase uma paráfrase palavra por palavra de "RAII". Agora eu entendi ainda melhor (eu votaria em você novamente se pudesse :)
Charlie Flowers
2
Uma grande vantagem do GC é que uma estrutura de alocação de memória pode evitar a criação de referências pendentes na ausência de código "inseguro" (se o código "inseguro" for permitido, é claro, a estrutura não pode impedir nada). GC também é frequentemente superior ao RAII ao lidar com objetos imutáveis compartilhados , como strings, que geralmente não têm um proprietário claro e não requerem limpeza. É uma pena que mais frameworks não procurem combinar GC e RAII, já que a maioria dos aplicativos terá uma mistura de objetos imutáveis ​​(onde GC seria o melhor) e objetos que precisam de limpeza (onde RAII é melhor).
supercat
@supercat: Eu geralmente gosto de GC - mas funciona apenas para recursos que o GC "entende". Por exemplo, o .NET GC não conhece o custo dos objetos COM. Ao simplesmente criá-los e destruí-los em um loop, ele felizmente permitirá que o aplicativo seja executado em relação ao espaço de endereço ou memória virtual - o que quer que venha primeiro - sem nem mesmo pensar em fazer um GC. --- além disso, mesmo em um ambiente perfeitamente GC, ainda sinto falta do poder da destruição determinística: você pode aplicar o mesmo padrão a outros artifícios, por exemplo, mostrando elementos de interface do usuário em condições certian.
Peterchen
@peterchen: Uma coisa que eu acho que está ausente em muito do pensamento relacionado à POO é o conceito de propriedade de objeto. Manter o controle da propriedade geralmente é claramente necessário para objetos com recursos, mas também é frequentemente necessário para objetos mutáveis ​​sem recursos. Em geral, os objetos devem encapsular seu estado mutável em referências a objetos imutáveis ​​possivelmente compartilhados ou em objetos mutáveis ​​dos quais são proprietários exclusivos. Essa propriedade exclusiva não implica necessariamente acesso exclusivo de gravação, mas se for o Fooproprietário Bare Bozsofrer mutação, ...
supercat
42

Existem excelentes respostas por aí, então eu apenas adiciono algumas coisas esquecidas.

0. RAII é sobre escopos

RAII é sobre ambos:

  1. adquirir um recurso (não importa qual recurso) no construtor e retirá-lo do destruidor.
  2. 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 synchronizedpor 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 ( synchronizedou 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 synchronizednem lockem 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 finallycláusula é usada em C # / Java para manipular o descarte de recursos em caso de saída do escopo (por meio de uma returnexceçã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 finallyclá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 ...
paercebal
fonte
Um fã de Java: eu diria que GC é muito mais útil do que RAII, pois lida com toda a memória e o livra de muitos bugs em potencial. Com GC, você pode criar referências circulares, retornar e armazenar referências e é difícil errar (armazenar uma referência a um objeto supostamente de vida curta prolonga seu tempo de vida, o que é uma espécie de vazamento de memória, mas esse é o único problema) . Manipular recursos com GC não funciona, mas a maioria dos recursos em um aplicativo tem um ciclo de vida trivial e os poucos restantes não são grande coisa. Eu gostaria que pudéssemos ter GC e RAII, mas isso parece impossível.
maaartinus
16

Por favor, veja:

Os programadores de outras linguagens, além de C ++, usam, conhecem ou entendem RAII?

RAII e ponteiros inteligentes em C ++

C ++ oferece suporte a blocos 'finalmente'? (E o que é esse 'RAII' que eu sempre ouço falar?)

RAII vs. exceções

etc ..

Trigo mitch
fonte
1
Algumas delas estão de acordo com minha pergunta, mas uma pesquisa não as revelou, nem a lista de "perguntas relacionadas" que aparece depois que você insere uma nova pergunta. Obrigado pelos links.
Charlie Flowers,
1
@Charlie: A construção na pesquisa é muito fraca em alguns aspectos. Usar a sintaxe da tag ("[tópico]") é muito útil, e muitas pessoas usam o google ...
dmckee --- ex-moderador gatinho
10

RAII está usando a semântica de destruidores C ++ para gerenciar recursos. Por exemplo, considere um ponteiro inteligente. Você tem um construtor parametrizado do ponteiro que inicializa este ponteiro com o endereço do objeto. Você aloca um ponteiro na pilha:

SmartPointer pointer( new ObjectClass() );

Quando o ponteiro inteligente sai do escopo, o destruidor da classe do ponteiro exclui o objeto conectado. O ponteiro é alocado na pilha e o objeto - alocado na pilha.

Existem certos casos em que RAII não ajuda. Por exemplo, se você usar ponteiros inteligentes de contagem de referência (como boost :: shared_ptr) e criar uma estrutura semelhante a um gráfico com um ciclo, você corre o risco de enfrentar um vazamento de memória porque os objetos em um ciclo evitarão que um ao outro seja liberado. A coleta de lixo ajudaria contra isso.

dente afiado
fonte
2
Deve se chamar UCDSTMR :)
Daniel Daranas 16/09/09
Pensando bem, acho que UDSTMR é mais apropriado. O idioma (C ++) é fornecido, portanto, a letra "C" não é necessária na sigla. UDSTMR significa Usar semântica do destruidor para gerenciar recursos.
Daniel Daranas,
9

Eu gostaria de colocar um pouco mais forte do que as respostas anteriores.

RAII, Resource Acquisition Is Initialization significa que todos os recursos adquiridos devem ser adquiridos no contexto da inicialização de um objeto. Isso proíbe a aquisição de recursos "nua". A justificativa é que a limpeza em C ++ funciona com base no objeto, não com base na chamada de função. Portanto, toda a limpeza deve ser feita por objetos, não por chamadas de função. Nesse sentido, C ++ é mais orientado a objetos do que, por exemplo, Java. A limpeza do Java é baseada em chamadas de função em finallycláusulas.

MSalters
fonte
Ótima resposta. E "inicialização de um objeto" significa "construtores", certo?
Charlie Flowers,
@Charlie: sim, especialmente neste caso.
MSalters,
8

Eu concordo com cpitis. Mas gostaria de acrescentar que os recursos podem ser qualquer coisa, não apenas memória. O recurso pode ser um arquivo, uma seção crítica, um thread ou uma conexão de banco de dados.

É chamado de Aquisição de Recursos é Inicialização porque o recurso é adquirido quando o objeto que controla o recurso é construído. Se o construtor falhou (isto é, devido a uma exceção), o recurso não é adquirido. Então, quando o objeto sai do escopo, o recurso é liberado. c ++ garante que todos os objetos na pilha que foram construídos com sucesso serão destruídos (isso inclui construtores de classes base e membros, mesmo se o construtor de superclasse falhar).

O raciocínio por trás do RAII é tornar segura a exceção de aquisição de recursos. Que todos os recursos adquiridos sejam devidamente liberados, independentemente de onde ocorra uma exceção. No entanto, isso depende da qualidade da classe que adquire o recurso (isso deve ser excepcionalmente seguro e isso é difícil).

iain
fonte
Excelente, obrigado por explicar a razão por trás do nome. Pelo que entendi, você pode parafrasear RAII como: "Nunca adquira nenhum recurso por meio de qualquer outro mecanismo que não seja a inicialização (baseada no construtor)". Sim?
Charlie Flowers,
Sim, esta é minha política, no entanto, tenho muito medo de escrever minhas próprias classes RAII, pois elas devem ser seguras com exceção. Quando os escrevo, tento garantir a segurança da exceção reutilizando outras classes RAII escritas por especialistas.
ia em
Não os achei difíceis de escrever. Se suas classes forem pequenas o suficiente, elas não serão difíceis.
Rob K,
7

O problema com a coleta de lixo é que você perde a destruição determinística que é crucial para RAII. Quando uma variável sai do escopo, cabe ao coletor de lixo quando o objeto será recuperado. O recurso mantido pelo objeto continuará sendo mantido até que o destruidor seja chamado.

Mark Ransom
fonte
4
O problema não é apenas o determinismo. O verdadeiro problema é que os finalizadores (nomenclatura java) atrapalham o GC. GC é eficiente porque não lembra os objetos mortos, mas os ignora no esquecimento. Os GCs devem rastrear objetos com finalizadores de uma maneira diferente para garantir que eles sejam chamados
David Rodríguez - dribeas
1
exceto em java / c #, você provavelmente limparia em um bloco finally em vez de em um finalizador.
jk.
4

RAII vem de Alocação de recursos é inicialização. Basicamente, significa que quando um construtor termina a execução, o objeto construído está totalmente inicializado e pronto para uso. Também implica que o destruidor irá liberar quaisquer recursos (por exemplo, memória, recursos do sistema operacional) pertencentes ao objeto.

Comparado com linguagens / tecnologias de coleta de lixo (por exemplo, Java, .NET), C ++ permite o controle total da vida de um objeto. Para um objeto alocado na pilha, você saberá quando o destruidor do objeto será chamado (quando a execução sai do escopo), coisa que realmente não é controlada no caso de coleta de lixo. Mesmo usando ponteiros inteligentes em C ++ (por exemplo, boost :: shared_ptr), você saberá que, quando não houver referência ao objeto apontado, o destruidor desse objeto será chamado.

Cătălin Pitiș
fonte
3

E como você pode fazer algo na pilha que causará a limpeza de algo que vive na pilha?

class int_buffer
{
   size_t m_size;
   int *  m_buf;

   public:
   int_buffer( size_t size )
     : m_size( size ), m_buf( 0 )
   {
       if( m_size > 0 )
           m_buf = new int[m_size]; // will throw on failure by default
   }
   ~int_buffer()
   {
       delete[] m_buf;
   }
   /* ...rest of class implementation...*/

};


void foo() 
{
    int_buffer ib(20); // creates a buffer of 20 bytes
    std::cout << ib.size() << std::endl;
} // here the destructor is called automatically even if an exception is thrown and the memory ib held is freed.

Quando uma instância de int_buffer passa a existir, ela deve ter um tamanho e alocará a memória necessária. Quando sai do escopo, seu destruidor é chamado. Isso é muito útil para coisas como objetos de sincronização. Considerar

class mutex
{
   // ...
   take();
   release();

   class mutex::sentry
   {
      mutex & mm;
      public:
      sentry( mutex & m ) : mm(m) 
      {
          mm.take();
      }
      ~sentry()
      {
          mm.release();
      }
   }; // mutex::sentry;
};
mutex m;

int getSomeValue()
{
    mutex::sentry ms( m ); // blocks here until the mutex is taken
    return 0;  
} // the mutex is released in the destructor call here.

Além disso, há casos em que você não pode usar RAII?

Não, na verdade não.

Você já se pegou desejando a coleta de lixo? Pelo menos um coletor de lixo que você poderia usar para alguns objetos enquanto permite que outros sejam gerenciados?

Nunca. A coleta de lixo resolve apenas um subconjunto muito pequeno de gerenciamento dinâmico de recursos.

Rob K
fonte
Eu usei Java e C # muito pouco, então nunca tive que perder, mas GC certamente limitou meu estilo quando se tratava de gerenciamento de recursos quando tive que usá-los, porque eu não podia usar RAII.
Rob K,
1
Usei muito o C # e concordo 100% com você. Na verdade, considero um GC não determinístico uma desvantagem em uma linguagem.
Nemanja Trifunovic,
2

Já existem muitas respostas boas aqui, mas gostaria apenas de acrescentar:
Uma explicação simples de RAII é que, em C ++, um objeto alocado na pilha é destruído sempre que sai do escopo. Isso significa que um destruidor de objetos será chamado e poderá fazer toda a limpeza necessária.
Isso significa que, se um objeto for criado sem "novo", não será necessário "excluir". E essa também é a ideia por trás dos "ponteiros inteligentes" - eles residem na pilha e, essencialmente, envolvem um objeto baseado em heap.

E Dominique
fonte
1
Não, eles não querem. Mas você tem um bom motivo para criar um ponteiro inteligente no heap? A propósito, o ponteiro inteligente foi apenas um exemplo de onde o RAII pode ser útil.
E Dominique,
1
Talvez meu uso de "pilha" vs "heap" seja um pouco desleixado - por um objeto na "pilha", eu quis dizer qualquer objeto local. Pode ser naturalmente parte de um objeto, por exemplo, na pilha. Por "criar um ponteiro inteligente no heap", quis dizer usar new / delete no próprio ponteiro inteligente.
E Dominique,
1

RAII é um acrônimo para Resource Acquisition Is Initialization.

Esta técnica é muito única para C ++ por causa de seu suporte para Construtores e Destruidores e quase automaticamente os construtores que correspondem aos argumentos sendo passados ​​ou, no pior caso, o construtor padrão é chamado de & destruidores se explicitamente fornecido for chamado de outra forma, o padrão que é adicionado pelo compilador C ++ é chamado se você não escreveu um destruidor explicitamente para uma classe C ++. Isso acontece apenas para objetos C ++ que são gerenciados automaticamente - o que significa que não estão usando o armazenamento gratuito (memória alocada / desalocada usando novos, novos [] / delete, delete [] operadores C ++).

A técnica RAII faz uso deste recurso de objeto autogerenciado para manipular os objetos que são criados no heap / free-store solicitando explicitamente por mais memória usando new / new [], que deve ser destruído explicitamente chamando delete / delete [] . A classe do objeto autogerenciado envolverá este outro objeto criado na memória heap / free-store. Portanto, quando o construtor do objeto autogerenciado é executado, o objeto empacotado é criado no heap / memória de armazenamento livre e quando o identificador do objeto autogerenciado sai do escopo, o destruidor desse objeto autogerenciado é chamado automaticamente no qual o objeto empacotado objeto é destruído usando delete. Com os conceitos OOP, se você agrupar tais objetos dentro de outra classe em escopo privado, você não terá acesso aos membros e métodos das classes agrupadas e esta é a razão pela qual os ponteiros inteligentes (também conhecidos como classes de identificador) são projetados. Esses ponteiros inteligentes expõem o objeto empacotado como objeto digitado para o mundo externo e lá, permitindo invocar quaisquer membros / métodos dos quais o objeto de memória exposto é feito. Observe que os ponteiros inteligentes têm vários sabores com base em necessidades diferentes. Você deve consultar a programação C ++ moderna de Andrei Alexandrescu ou a implementação / documentação da biblioteca boost (www.boostorg) shared_ptr.hpp para aprender mais sobre ela. Espero que isso ajude você a entender RAII. Você deve consultar a programação C ++ moderna de Andrei Alexandrescu ou a implementação / documentação da biblioteca boost (www.boostorg) shared_ptr.hpp para aprender mais sobre ela. Espero que isso ajude você a entender RAII. Você deve consultar a programação C ++ moderna de Andrei Alexandrescu ou a implementação / documentação da biblioteca boost (www.boostorg) shared_ptr.hpp para aprender mais sobre ela. Espero que isso ajude você a entender RAII.

techcraver
fonte