lançar novo std :: exception vs lançar std :: exception

113

enquanto olhava para algum código que encontrei:

throw /*-->*/new std::exception ("//...

e sempre pensei que você não precisa / não deveria usar newaqui.
Qual é a forma correta, ambas estão OK, em caso afirmativo, há alguma diferença?

BTW pelo que eu posso ver enquanto "grepping" com PowerShell boost libs nunca use throw new.

PS também achei algum código CLI que usa throw gcnew. Tudo bem?

NoSenseEtAl
fonte
1
Eu acho throw gcnewque seria útil, por exemplo. se você deseja que o código gerenciado capture sua exceção. Alguém pode me corrigir sobre isso?
jpalecek
1
.Net lida com exceções por ponteiro, então lançar gcnew é a coisa certa a fazer lá.
Sebastian Redl
1
@SebastianRedl .Net "ponteiro" pode ser ambíguo? Embora gcnew certamente não seja. System::Exceptiongeralmente é uma referência a um objeto gerenciado no heap com coleta de lixo. Sempre joguei gcnewe peguei System::Exception ^. É claro que também uso finallyo tempo todo em C ++ / CLI, embora não costumas misturar exceções C ++ no mesmo trybloco, não sei por quê.

Respostas:

89

A maneira convencional de lançar e capturar exceções é lançar um objeto de exceção e capturá-lo por referência (geralmente constreferência). A linguagem C ++ requer que o compilador gere o código apropriado para construir o objeto de exceção e para limpá-lo adequadamente no momento apropriado.

Lançar um ponteiro para um objeto alocado dinamicamente nunca é uma boa idéia. As exceções devem permitir que você escreva um código mais robusto em face das condições de erro. Se você lançar um objeto de exceção da maneira convencional, pode ter certeza de que, se ele for capturado por uma cláusula catch que nomeia o tipo correto, por um catch (...), se for lançado novamente ou não, será destruído corretamente no momento apropriado. (A única exceção é se ele nunca for detectado, mas esta é uma situação irrecuperável de qualquer maneira que você olhe para ela.)

Se você lançar um ponteiro para um objeto alocado dinamicamente, terá que ter certeza de que, seja qual for a aparência da pilha de chamadas no ponto em que deseja lançar sua exceção, há um bloco catch que nomeia o tipo de ponteiro correto e tem a deletechamada apropriada . Sua exceção nunca deve ser capturada por, a catch (...)menos que esse bloco lance novamente a exceção que é então capturada por outro bloco catch que lida corretamente com a exceção.

Efetivamente, isso significa que você pegou o recurso de tratamento de exceções que deve tornar mais fácil escrever código robusto e dificultar a escrita de código correto em todas as situações. Isso é deixar de lado o problema de que será quase impossível agir como código de biblioteca para código cliente que não esteja esperando esse recurso.

CB Bailey
fonte
1
"lançar um objeto de exceção" empilhar ou empilhar meu amigo? Empilhar ou empilhar? (Talvez eu estivesse olhando para um exemplo global ruim em algum lugar) ah, e se empilhar, qual é o escopo apropriado?
@ebyrob: Não tenho certeza do que você está perguntando, mas parece que você deseja saber sobre o armazenamento e / ou a vida útil do objeto de exceção que pode ser respondido aqui . Do contrário, seria melhor fazer uma pergunta separada.
CB Bailey
31

Não há necessidade de usar newao lançar exceção.

Apenas escreva:

throw yourexception(yourmessage);

e pegue como:

catch(yourexception const & e)
{
      //your code (probably logging related code)
}

Observe que yourexceptiondeve derivar std::exceptiondireta ou indiretamente.

Nawaz
fonte
7
Por quê? porque não usar new? por que derivam yourexceptionde std::exception?
Walter
Quando sou preguiçoso (o que é muuuito frequente), por que não throw std::exception;funciona? g ++ não parece compilar ...
7
@ebyrob: std::exceptioné um tipo, e você não pode lançar um tipo , você tem que lançar um objeto . Portanto, a sintaxe deve ser esta: throw std::exception();Isso irá compilar. Agora, quão bom isso é, é uma questão completamente diferente.
Nawaz,
22

O lançamento new std::exceptionestá correto se o site da chamada espera capturar a std::exception*. Mas ninguém espera pegar um ponteiro para uma exceção. Mesmo se você documentar isso é o que sua função faz e as pessoas lerem a documentação, elas ainda podem esquecer e tentar capturar uma referência a um std::exceptionobjeto.


fonte
27
O lançamento new std::exceptionestá correto apenas se o site de chamada está esperando pegar um ponteiro E está esperando assumir o gerenciamento da exceção de alocação E nunca haverá casos em que sua função será chamada por algo que não captura explicitamente o ponteiro correto ( catch(...)ou nenhum manuseio), caso contrário, haverá um vazamento de objeto. Resumindo, isso pode ser aproximado como "nunca".
CB Bailey
É curioso como essa resposta foi aceita, quando na verdade é o comentário de @CharlesBailey que é a resposta correta.
John Dibling
@John: Isso também passou pela minha cabeça. Mas acho que o golpe duplo tem um bom efeito comigo fazendo um resumo seco e Charles explicando de forma divertida as várias maneiras pelas quais as pessoas podem se esquecer de lidar com ele de maneira adequada. É uma pena que você não obtenha reputação de comentários votados positivamente.
Charles não respondeu, e este A (ao contrário do outro) tem explicações tanto no A quanto no comentário.
NoSenseEtAl
9

O C ++ FAQ tem uma boa discussão sobre isso:

  1. https://isocpp.org/wiki/faq/exceptions#what-to-catch
  2. https://isocpp.org/wiki/faq/exceptions#catch-by-ptr-in-mfc

Basicamente, "a menos que haja um bom motivo para não fazer isso, pegue por referência. Evite capturar por valor, pois isso faz com que uma cópia seja feita e a cópia pode ter um comportamento diferente do que foi lançado. Somente em circunstâncias muito especiais você deve capturar por ponteiro. "

user1202136
fonte
2
Como sempre, o FAQ está mal redigido. Você pode pegar por valor ou referência. Um ponteiro passa a ser um valor (que você captura por valor ou referência). Lembre-se de que o tipo Aé diferente do tipo, A*então, se eu fizer throw A()isso, NÃO posso pegar catch(A* e), pois é um tipo completamente diferente.
Martin York
Esses links agora estão quebrados.
stephenspann
1
Corrigi os links @spanndemic
user1202136
1

O operador new não pode garantir que nunca gerará uma exceção. Por esse motivo, usá-lo para lançar uma exceção "válida" (pretendida) produziria um código que não pode ser garantido contra travamento. Uma vez que pode haver apenas uma exceção por vez, e seu programa tenta lançar duas antes que qualquer uma delas possa ser capturada, a melhor coisa que uma implementação pode fazer é abortar imediatamente seu programa, por exemplo, chamando std :: terminate.

zkoza
fonte