Quem elimina a memory alocada durante uma operação “nova” que tem exceção no construtor?

Eu realmente não acredito que não consegui encontrar uma resposta clara para isso …

Como você libera a memory alocada depois que um construtor de class C ++ lança uma exceção, no caso em que é inicializado usando o new operador. Por exemplo:

 class Blah { public: Blah() { throw "oops"; } }; void main() { Blah* b = NULL; try { b = new Blah(); } catch (...) { // What now? } } 

Quando eu tentei isso, b é NULL no bloco catch (o que faz sentido).

Ao depurar, notei que o conrol entra na rotina de alocação de memory ANTES de atingir o construtor.

Isso no site da MSDN parece confirmar isso :

Quando new é usado para alocar memory para um object de class C ++, o construtor do object é chamado depois que a memory é alocada.

Portanto, tendo em mente que a variável local b nunca é atribuída (ou seja, é NULL no bloco catch), como você exclui a memory alocada?

Também seria bom obter uma resposta multiplataforma sobre isso. ou seja, o que a especificação C ++ diz?

ESCLARECIMENTO: Não estou falando sobre o caso em que a class alocou a própria memory no c’tor e depois lança. Eu aprecio que nesses casos o dont não será chamado. Eu estou falando sobre a memory usada para alocar o object ( Blah no meu caso).

    Você deve se referir às perguntas semelhantes aqui e aqui . Basicamente, se o construtor lançar uma exceção, você estará seguro de que a memory do object em si será liberada novamente. Embora, se outra memory foi reivindicada durante o construtor, você está sozinho para liberá-lo antes de deixar o construtor com a exceção.

    Para sua pergunta, WHO elimina a memory, a resposta é o código por trás do novo operador (que é gerado pelo compilador). Se reconhecer uma exceção deixando o construtor, ele deve chamar todos os destruidores dos membros de classs (como aqueles que já foram construídos com sucesso antes de chamar o código do construtor) e liberar sua memory (pode ser feito recursivamente junto com a chamada de destruidor, provavelmente chamando uma exclusão apropriada neles), bem como liberar a memory alocada para essa class em si. Em seguida, ele tem que retroceder a exceção capturada do construtor para o chamador de novo . É claro que pode haver mais trabalho a ser feito, mas não consigo extrair todos os detalhes da minha cabeça, porque eles estão sujeitos à implementação de cada compilador.

    Se um object não pode completar a destruição porque o construtor lança uma exceção, a primeira coisa a acontecer (isso acontece como parte da manipulação especial do construtor) é que todas as variables ​​de membro a serem construídas são destruídas – se uma exceção é lançada na lista inicializadora Isso significa que apenas os elementos para os quais o inicializador foi concluído são destruídos.

    Então, se o object estava sendo alocado com new , a function de desalocação apropriada ( operator delete ) é chamada com os mesmos argumentos adicionais que foram passados ​​para o operator new . Por exemplo, new (std::nothrow) SomethingThatThrows() irá alocar memory com operator new (size_of_ob, nothrow) , tentar construir SomethingThatThrows , destruir quaisquer membros que foram construídos com sucesso, então chamar operator delete (ptr_to_obj, nothrow) quando uma exceção é propagado – não vazará memory.

    O que você tem que ter cuidado é alocar vários objects em sucessão – se um dos posteriores for lançado, os anteriores não serão automaticamente desalocados. A melhor maneira de contornar isso é com pointers inteligentes, porque como objects locais seus destrutores serão chamados durante o desenrolamento da pilha, e seus destruidores desalocarão corretamente a memory.

    Do C ++ 2003 Standard 5.3.4 / 17 – Novo:

    Se qualquer parte da boot de object descrita acima for lançada uma exceção e uma function de desalocação adequada pode ser encontrada, a function de desalocação é chamada para liberar a memory na qual o object estava sendo construído, após o qual a exceção continua a se propagar no contexto da nova expressão. Se nenhuma function de desalinhamento de correspondência não ambíguo puder ser encontrada, a propagação da exceção não fará com que a memory do object seja liberada. [Nota: Isso é apropriado quando a function de alocação chamada não aloca memory; caso contrário, é provável que resulte em um memory leaks. ]

    Portanto, pode ou não haver um vazamento – depende se um desalocador apropriado pode ser encontrado (o que normalmente é o caso, a menos que o operador new / delete tenha sido substituído) .No caso em que há um desalocador adequado, o compilador é responsável para fiação em uma chamada para ele, se o construtor lança.

    Observe que isso é mais ou menos não relacionado ao que acontece com os resources adquiridos no construtor, que é o que discuti minha primeira tentativa de resposta – e é uma questão que é discutida em muitas perguntas frequentes, artigos e postagens.

    Se o Construtor lança a memory alocada para o object é retornado automaticamente para o sistema.

    Observe o destruidor da class que jogou não será chamado.
    Mas o destruidor de qualquer class base (onde o construtor base foi concluído) também será chamado.

    Nota:
    Como a maioria das outras pessoas observou, os membros podem precisar de alguma limpeza.

    Os membros que foram totalmente inicializados terão seus destrutores chamados, mas se você tiver algum membro de ponteiro RAW que você possui (isto é, apague no destruidor) você terá que limpar antes de fazer o lançamento (outra razão para não usar o dono) Ponteiros RAW na sua class).

     #include  class Base { public: Base() {std::cout < < "Create Base\n";} ~Base() {std::cout << "Destroy Base\n";} }; class Deriv: public Base { public: Deriv(int x) {std::cout << "Create Deriv\n";if (x > 0) throw int(x);} ~Deriv() {std::cout < < "Destroy Deriv\n";} }; int main() { try { { Deriv d0(0); // All constructors/Destructors called. } { Deriv d1(1); // Base constructor and destructor called. // Derived constructor called (not destructor) } } catch(...) { throw; // Also note here. // If an exception escapes main it is implementation defined // whether the stack is unwound. By catching in main() you force // the stack to unwind to this point. If you can't handle re-throw // so the system exception handling can provide the appropriate // error handling (such as user messages). } } 

    O longo e curto é que, se você não fez quaisquer alocações de outras entidades em seu object (como no seu exemplo), a memory que foi alocada será excluída automaticamente. No entanto, quaisquer novas instruções (ou qualquer outra coisa que gerencie diretamente a memory) precisam ser manipuladas em uma instrução catch no construtor. Caso contrário, o object será excluído sem excluir as alocações subsequentes e você, meu amigo, terá um vazamento.

    Citado de C ++ FAQ ( parashift.com ):

    [17.4] Como devo lidar com resources se meus construtores podem lançar exceções?

    Todo membro de dados dentro do seu object deve limpar sua própria bagunça.

    Se um construtor lança uma exceção, o destruidor do object não é executado. Se o seu object já tiver feito algo que precisa ser desfeito (como alocar memory, abrir um arquivo ou bloquear um semáforo), essa “coisa que precisa ser desfeita” deve ser lembrada por um membro de dados dentro do object.

    Por exemplo, em vez de alocar memory em um membro de dados Fred* bruto, coloque a memory alocada em um object de membro “ponteiro inteligente” e o destruidor desse ponteiro inteligente delete o object Fred quando o ponteiro inteligente for executado. O modelo std::auto_ptr é um exemplo de “ponteiro inteligente”. Você também pode escrever seu próprio ponteiro inteligente de contagem de referência . Você também pode usar pointers inteligentes para “apontar” para registros ou objects de disco em outras máquinas .

    A propósito, se você acha que sua class Fred vai ser alocada em um ponteiro inteligente, seja gentil com seus usuários e crie um typedef dentro de sua class Fred :

      #include  class Fred { public: typedef std::auto_ptr Ptr; ... }; 

    Esse typedef simplifica a syntax de todo o código que usa seus objects: seus usuários podem dizer Fred::Ptr invés de std::auto_ptr :

      #include "Fred.h" void f(std::auto_ptr p); // explicit but verbose void f(Fred::Ptr p); // simpler void g() { std::auto_ptr p1( new Fred() ); // explicit but verbose Fred::Ptr p2( new Fred() ); // simpler ... } 

    O problema descrito é tão antigo quanto o caminho para Roma, para usar um ditado holandês. Eu tenho trabalhado o problema e uma alocação de memory para um object que pode lançar uma exceção parece da seguinte maneira:

     try { std::string *l_string = (_heap_cleanup_tpl(&l_string), new std::string(0xf0000000, ' ')); delete l_string; } catch(std::exception &) { } 

    Antes da chamada real para o new operador, um object sem nome (temporário) é criado, que recebe o endereço da memory alocada por meio de um operador novo definido pelo usuário (veja o restante desta resposta). No caso de execução normal do programa, o object temporário passa o resultado do operador new (o object recém-criado e totalmente construído, no nosso caso, uma string muito, muito, muito longa) para a variável l_string . No caso de uma exceção, o valor não é transmitido, mas o destruidor do object temporário exclui a memory (sem chamar, é claro, o destruidor do object principal).

    É uma maneira pouco confusa de lidar com o problema, mas funciona. Podem surgir problemas porque esta solução requer um operador novo definido pelo usuário e um operador de exclusão definido pelo usuário para ir all-in com ele. Os operadores new / delete-user-defined teriam que chamar a implementação da biblioteca padrão C ++ de new / delete-operators, mas deixei isso de lado e usei malloc() e free() .

    Não é a resposta final, mas acho que vale a pena trabalhar nisso.

    PS: Havia um recurso “não documentado” no código abaixo, então fiz uma melhoria.

    O código para o object temporário é o seguinte:

     class _heap_cleanup_helper { public: _heap_cleanup_helper(void **p_heap_block) : m_heap_block(p_heap_block), m_previous(m_last), m_guard_block(NULL) { *m_heap_block = NULL; m_last = this; } ~_heap_cleanup_helper() { if (*m_heap_block == NULL) operator delete(m_guard_block); m_last = m_previous; } void **m_heap_block, *m_guard_block; _heap_cleanup_helper *m_previous; static _heap_cleanup_helper *m_last; }; _heap_cleanup_helper *_heap_cleanup_helper::m_last; template  class _heap_cleanup_tpl : public _heap_cleanup_helper { public: _heap_cleanup_tpl(p_alloc_type **p_heap_block) : _heap_cleanup_helper((void **)p_heap_block) { } }; 

    O operador novo definido pelo usuário é o seguinte:

     void *operator new (size_t p_cbytes) { void *l_retval = malloc(p_cbytes); if ( l_retval != NULL && *_heap_cleanup_helper::m_last->m_heap_block == NULL && _heap_cleanup_helper::m_last->m_guard_block == NULL ) { _heap_cleanup_helper::m_last->m_guard_block = l_retval; } if (p_cbytes != 0 && l_retval == NULL) throw std::bad_alloc(); return l_retval; } void operator delete(void *p_buffer) { if (p_buffer != NULL) free(p_buffer); } 

    Eu acho que é meio estranho para um construtor levantar uma exceção. Você poderia ter um valor de retorno e testá-lo no seu principal?

     class Blah { public: Blah() { if Error { this.Error = "oops"; } } }; void main() { Blah* b = NULL; b = new Blah(); if (b.Error == "oops") { delete (b); b = NULL; }