Boa pratica das exceções

199 views
Skip to first unread message

Raphael Menezes

unread,
Aug 28, 2007, 12:19:29 PM8/28/07
to ccppb...@googlegroups.com
Alo alo galera cpp,

Tenho escutado, com frequência, que a utilização do throw no tratamento de exceções em C++ não é recomendado. A melhor forma seria com try.
Tá correto isso? Caso sim, porquê?

Grande abraço a todos.

--
"If builders built buildings the way programmers wrote programs, then the first woodpecker that came along would destroy civilization."

Thiago Adams

unread,
Aug 28, 2007, 6:59:23 PM8/28/07
to ccppbrasil

On Aug 28, 5:19 pm, "Raphael Menezes" <rapha...@gmail.com> wrote:
> Alo alo galera cpp,
>
> Tenho escutado, com frequência, que a utilização do throw no tratamento de
> exceções em C++ não é recomendado. A melhor forma seria com try.
> Tá correto isso? Caso sim, porquê?
>

Tem algum mau entendido. Uma coisa não substitui a outra. Elas
trabalham em conjunto.
O throw lança uma exceção, e o block try catch faz o tratamento dela
em algum ponto do código.
Usar exceções é a melhor forma de tratar errors.
No entando as exceções costumam ser mal usadas. (É bem dificil ver um
código correto)

O que é não recomendado:
- usar vários blocos try catch para cada função. (exceções não é para
ser usado locamente)
- propagar erros usando uma mistura de excecoes e codigo de retorno.
Isso é comum mesmo entre programadores com muita experiencia. Mas é
algo errado.
- lançar exceçoes com new. (o lixo da MFC faz isso)
- Gerar memory leaks de todos os tipos e estados invalidos quando
usando exceções. (bem comum)
- lançar excecoes de objetos tipo " " ou int etc.. excecoes sao
lancadas por tipo e nao por valor. Nunca dependa do valor dela.

Eu considero que usar exceções foi a ultima coisa que eu aprendi no C+
+. ( ate o momento :) )


Raphael Menezes

unread,
Aug 28, 2007, 7:14:57 PM8/28/07
to ccppb...@googlegroups.com
Boa Thiago. Mt obrigado por responder!
Acho que entendi!
Soh nao captei o ponto de lancar excecoes com new. Cê diz que dentro do tratamento manda um operador new?

Aliás, to caindo dentro 4hrs por dia com o livro do bjarne traduzido...vale a pena o traduzido ou a traducao distorce os conceitos?

Abraços

Márcio Geovani Jasinski

unread,
Aug 28, 2007, 8:29:20 PM8/28/07
to ccppb...@googlegroups.com
Opa!

Imagino que o  uso do new que deve ser evitado (mencionado pelo Thiago) é uma prática comum de programadores vindos do JAVA que não conhecem C++. Por exemplo:
(...)
throw new string("Mensagem ");
(...)
Isso não é uma boa prática de c++ uma vez que a memória foi aloca e precisa receber um delete para liberar...
Use então:
(...)
throw "Mensagem";
(...)

Isso já evita que a exceção deixe vazamentos de memória.
Outra coisa que acho interessante é definir structs e classes de exceções para evitar coisas como:
throw 59;

Isso é valido em c++, mas veja que é um lixo de código e deve ser evitado.
Outro exemplo que o thiago mencionou que NAO deve ser feito é:
try {
  (...)
} catch (...) {
  return false;
}

Espero que tenha ajudado, um abraço!

--
Márcio Geovani Jasinski

Raphael Menezes

unread,
Aug 28, 2007, 8:50:43 PM8/28/07
to ccppb...@googlegroups.com
Boa marcio, entendi! Brigadão!
Por falar em programadores java....tava acompanhando uma discussão C++ vs Java. Um cara começou a falar que para começar essa discussão deveria ter programadores que pelo menos tivessem conhecimento de coesao x acoplamento, design patterns e etc...

Vocês, experientes em C++ poderia me dizer aonde isto encaixaria numa discussao desse tipo?

Desculpe deturpar o proposito deste "topic" hehehe mas to aproveitando e tirando duvidas :)

abraços a todos

Thiago Adams

unread,
Aug 29, 2007, 6:24:52 AM8/29/07
to ccppbrasil
Este exemplo do
throw 59;

é o que eu quis dizer com:

* Nao lançar excecoes de objetos tipo " " ou int etc.. excecoes sao


lancadas por tipo e nao por valor. Nunca dependa do valor dela.

Também é valido para:
throw "Mensagem";

A motivo é que você trata exceções pelo tipo dela. Entao se você
precisar tratar uma string para pode diferenciar os erros voce
precisaria colocar o
cacth( const char *) { .. } e ainda precisaria colocar uma comparação
de strings para achar o erro que você procura.
Então a moral da história é não lançar valores e sim tipos. O valor
interno, por exemplo a descrição what(), serve para uma informação
extra sobre o problema.


* Outro erro é capturar exceção sem usar referencias.
Sempre que capturar exceções usar const & de preferencia.
Também é possivel usar &, e fazer uma modificação no valor e lançar de
novo. Mas isso parece pedir por problemas.
O motivo mais importante de usar referencias (catch (const X &) é
poder usar as excecoes de forma polimorfica. Caso você não faça isso,
o objeto da exceção é "cortado".

* Quando usar o catch sempre começar com as classes mais
especializadas e deixar as mais genéricas para o final.
Por exemplo

struct ErroMatematico {};
struct DivisaoPorZero : public ErroMatematico {};

catch( const ErroMatematico &) { }
catch( const DivisaoPorZero &) { }

Este exemplo está errado.
Deveria ser:

catch( const DivisaoPorZero &) { }
catch( const ErroMatematico &) { }

O motivo que cada catch funciona como um filtro. (Parecido com um
dynamic_cast) Por isso o catch DivisaoPorZero precisa vir antes. Caso
contrario o codigo nunca seria chamado. Apenas o ErroMatematico que é
menos especializado.

Justamente pelo catch ter este "dynamic_cast" (e por outros motivos
tb) algumas pessoas dizem que exceções são lentas. Ela poderia ser
mais lenta no caso do erro ocorrer. Mas é dificil de comparar porque
de qualquer forma o código em C equivalente deveria ser enorme. (por
exemplo para imitar o stack unwinding)
De modo geral é ótimo usar exceções, o código fica bem mais claro e
limpo. Mas requer um programador que entenda exatamente oq esta
fazendo.

* Nunca lançe exceções no destructor.

Sobre misturar código de retorno e exceções.
Um erro bem comum é:

HRESULT Funcao()
{
vector v;
X & x = dynamic_cast<X&>(asedf);
etc...
}

O que eu quero dizer com isso é o seguinte. Se você usa STL, ou C++
existe várias classes que reportam erros com exceções.
Então se você esta usando STL dentro de um código que reporta erros
com HRESULT por exemplo, automaticamente você está misturando.
A solução que eu uso para isto é a seguinte:
Todo código que eu faço reporta erros com exceções. Toda função da API
(geralmente C) que retorna erro é convertido para um exceção.
( Existem até funçoes próprias para isto na ATL por exemplo)
Quando eu preciso reportar por código de retorno (alguma API requer
isso, por exemplo quando se usa COM) então eu coloco um try catch na
fronteira, ou seja, no ultimo ponto da chamada. A exceção é capturada
e transformada em código de retorno somente neste ponto. E a
informação extra é armazenada para quem quiser saber detalhes do erro
poder "perguntar" exatamente oq aconteceu.


Raphael Menezes

unread,
Aug 29, 2007, 12:09:38 PM8/29/07
to ccppb...@googlegroups.com
Boa Thiago. Claro e objetivo.
Eu estava pesquisando sobre exceptions em C++ e vi que existe uma biblioteca de exceptions em C++, não?
Alguma vez vc ja usou ela ?

Thiago Adams

unread,
Aug 29, 2007, 7:20:19 PM8/29/07
to ccppbrasil

On Aug 29, 5:09 pm, "Raphael Menezes" <rapha...@gmail.com> wrote:
> Boa Thiago. Claro e objetivo.
> Eu estava pesquisando sobre exceptions em C++ e vi que existe uma biblioteca
> de exceptions em C++, não?
> Alguma vez vc ja usou ela ?
>

No c++ existe uma hierarchia de exceções. Todas elas derivam de
std::exception.
Algumas exceções são lançadas pela linguagem, por exemplo
std::bad_alloc, std::bad_cast, bad_typeid .. e outras delas são
lançadas pela biblioteca padrão. Por exemplo out_of_range lançada pelo
vector.at() etc...
Existem classes nesta hieraquia que eu nunca usei, e também que não
são usadas pela STL.
Mas todas as minhas exceções são derivadas de std::exception
justamente para eu poder (se quiser ou precisar) capturar de forma
polimorfica o erro.
Nesta classe exception você pode implementar a funcao virtual what que
retorna a descrição do problema.
Esta descrição não deveria ser para o usário (na minha opniao) mas
conter uma informaçao para o programador. Por exemplo dizendo o nome
da classe, da função e o problema. O usario não quer saber essas
coisas, então a mensagem para ele deve ser uma mensagem feita no ponto
aonde a exceçao é tratada.
Por exemplo, vai carregar um arquivo e o formato é errado. Para o
usario basta a mensagem, "formato de arquivo invalido" não interessa o
ponto do código que recusou o dado. Mas esta informacao poderia estar
no what() como detalhe. A mensagem para o usario eu deixo traduzida,
mas o detalhe não.
Por um certo tempo eu achava que este what da std::exception era ruim
pq não era unicode, ou porque não considerava a tradução. Agora eu me
convenci que a traduçao pode ser e deve ser feita em outro ponto. E
como o detalhe não interessa para o usuário (linha do erro nome da
funcao) não é preciso traduzir.
Eu colocaria em uma informação extra num dialog tipo o estilo
"Detalhes>>" "Detalhes<<"

Existe uma classe de excecao chamada logic_error que tem controversia.
Esta classe representa um erro causado na implementacao. Que poderia
ser evitado antes de rodar o programa. A controversia é que para quer
criar excecoes para um programa que esta errado? Geralmente se tem
asserts para isso.

Wanderley Caloni

unread,
Aug 29, 2007, 11:44:07 PM8/29/07
to ccppbrasil
Olá, Thiago!

Achei um tanto radical sua opinião sobre tratamento de erros em C++.
Contudo, como sei que vocë costuma usar o que tem de melhor na
linguagem e nas bibliotecas que a suportam e, o mais importante, boas
práticas de programação para a linguagem, acredito no que vocë diz,
pois imagino que já tenha usado exceções por um bom tempo.

No entanto, apenas gostaria de adicionar que a linguagem C++ muitas
vezes precisa ser mais do que apenas uma linguagem comportada e
definida por boas práticas. Muitas vezes ela interage com a linguagem
C, o que nos leva ou a encapsular os códigos de retorno em exceções ou
usar um esquema misto.

Outras vezes, também, a plataforma onde ela está sendo usada não
dispões de um compilador completo e/ou 100% de acordo com o padrão. E
nesse caso até que o guia de portabilidade do pessoal de open source
faz algum sentido, apesar de antigo.

Digo isso não para contrariar seus argumentos, que acredito serem
plausíveis, apesar de nunca ter programado em C++ usando exceções por
muito tempo. Apenas estou acrescentando uma argumentação de quem já
mexeu muito com o outro tipo de código, aquele legado ou limitado.
Para esses casos acredito que a abordagem mista funciona e muito bem.
Se melhor ou pior que o uso de exceções, acredito que isso depende da
experiëncia dos programadores que estão usando um ou outro. Usar
código de retorno exige checagem de erro em todas as funções.
Tratamento de exceções exige uma organização melhor para definir quem
vai tratar o quê. Ambos necessitam de bons programadores para
funcionar. Logo, a premissa de que é preciso ótimos programadores para
usar exceções é igualmente válida para o uso de código de retorno.

[]s

Thiago Adams

unread,
Aug 30, 2007, 6:20:33 AM8/30/07
to ccppbrasil
Olá,

Eu lembro que há um tempo atrás eu coloquei um post perguntando várias
opiniões.

Aquela época achava que poderia usar exceção mas não tinha usado.
Agora faz um ano e meio que estou usando somente exceções. (para
código novo)
O código legado que eu tenho que agüentar :), usa exceções de forma
errada e tb codigo de retorno da forma errada. Mas foi bom, pois eu vi
alguns erros comuns.

A dúvida que eu tinha na época era justamente se exceções eram boas so
no "mundo ideal" ou se elas poderiam ser usadas no "mundo real".
Ela pode/deve ser usada no mundo real. Geralmente as libs reportam
erros com código de retorno, também existe as DLLs e as funções do SO.
Geralmente tudo isso é em C.
Mas não tem problema nenhum usar tudo isto ao mesmo tempo (mas não
misturado).
Misturado é colocar uma função que reporta erros ao mesmo tempo por
exceção e código de retorno. Ou matar as exceções que são cheias de
informação convertendo para um bool indicando se funcionou ou nao.

Para criar esta situação errada é simples, é só colocar por exemplo um
container da STL numa função que reporta erros com código de retorno.

bool XXX::Insert(const Argument &value)
{
if (value < 0) return false;

m_vector.push_back( Object(Argument) ); // pronto esta feita a
mistura. se faltar memoria o vector vai reportar com excecao.

return true;
}

Se você pensar que o problema da falta de memoria é raro, então basta
imaginar que você vai perder a liberdade de reportar o problema no
construtor do Object.

Sobre a dificuldade de se usar exceções.
- Você tem que parar de pensar localmente para pode fazer o tratamento
correto do erro.
- É preciso usar RAII em tudo. As pessoas não levam isso a sério e
causam problemas.
- É preciso ter classes adequadas de erros. Um problema de design que
eu vi é criar uma classe de erro para cada classe que contem as
funções. Isso é péssimo porque o erro não pode ser tratado de maneira
genérica. (Ficou cheio de catch (isso) catch(aquilo))

O interessante que exceções você não vê. Por exemplo, cada função que
for chamada por ex. func1 - >func2 - func3 - func4.. Todas elas tem
que estar preparadas para abortar a qualquer momento. Sem deixar o
programa em um estado invalido ou leaks. Usando código de retorno você
vai ver em cada uma delas um código para fazer isso. Usando exceções
você não vê nada. Isso para principiante é difícil. Ele vai olhar um
auto_ptr e vai achar que é enfeite dentro da função :)

Então a política local para as exceções é a seguinte: Cada função tem
que estar pronta para abandonar em qualquer ponto "na região
crítica."
Eu inventei este nome eu quero dizer o seguinte.

Para cada função você pode dividir em regiões.


void funcao()
{
[
// regiao livre de excecoes
]

[
// "regiao critica."
// chamadas, pode haver excecoes em qualquer ponto, em qualquer
uma das funcoes que estou chamando
]

[
// regiao final livre de excecoes
]
}

Geralmente a última região eu deixo para fazer o "commit" e o commit
preferencialmente não lança exceção. Aí é so usar alguma técnica, por
exemplo se tiver que alocar alguma memória fazer isso antes e ter
certeza que vc tem oq precisa na região final.


thoth39

unread,
Aug 30, 2007, 12:51:09 PM8/30/07
to ccppbrasil
On Aug 28, 9:50 pm, "Raphael Menezes" <rapha...@gmail.com> wrote:

> Um cara começou a falar que para começar essa discussão deveria ter
> programadores que pelo menos tivessem conhecimento de coesao x acoplamento,
> design patterns e etc...
>
> Vocês, experientes em C++ poderia me dizer aonde isto encaixaria numa
> discussao desse tipo?
>

Se encaixaria dessa forma.

A maioria das boas práticas de uso de exceções em C++ realiza o
princípio do projeto por contrato; isto é, as exceções disparáveis por
uma função fazem parte da sua especificação.

Em geral, em C++, quase nunca se garante um contrato forte para uma
função em C++, do tipo: esta função _nunca_ dispara exceções, ou esta
função _apenas_ dispara exceções do tipo A, B ou C.

Um contrato da forma "esta função _nunca_ dispara exceções" é viável
em poucos casos.
Por exemplo, por mais que a sua função nunca chame outras funções, se
um dos operadores ali for sobrecarregado, a garantia pode furar; se
ela chama new a garantia pode furar.

Um contrato da forma "esta função dispara _apenas_ exceções do tipo A,
B ou C" sofre do mesmo problema.

Se a sua função for uma função template o problema se acentua
consideravelmente.

Garantir um contrato como esses acabará sendo possível apenas com um
bloco try-catch compreendendo todo o corpo da função.
Se todas as funções da interface pública de um projeto têm contratos
dessa forma todas essas funções terão um bloco try-catch como esses,
derrotando o propósito do mecanismo de exceções, que é evitar a
repetição tediosa de blocos de tratamento de erros.

Além disso, código sensível executado em áreas com requisitos de alta
performance sofrem com a presença de blocos try-catch.

Acredito que a melhor prática nesse sentido é simplesmente documentar
com clareza as pré e pós-condições de uma função, junto com as
invariantes da classe, e listar o melhor que for possível os tipos de
exceção para cada tipo de premissa violada.

Apesar de ser aplicável em geral, o projeto por contrato é uma
extensão ao projeto orientado a objetos, cujos princípios gerais são a
garantia de baixo acoplamento e alta coesão.

--
Pedro Lamarão

Raphael Menezes

unread,
Aug 30, 2007, 1:35:02 PM8/30/07
to ccppb...@googlegroups.com
Esse post foi muito produtivo, mensagens muito esclarecedoras. Mas uma coisa ainda não fixou na minha mente: Utilizamos os tratamentos de erros do C++ com blocos try/catch. Mas não loucamente, como foi dito pelo Thiago.
Entretanto, o thoth, pelo que eu entendi, diz que não devemos ter sempre a total certeza que nossas funções não irão lançar excessões.
Com isso, utilizamos apenas o try/catch usando nosso bom senso, sempre de acordo coma complexidade, tipo de operações e dados que a função irá retornar?

abraços

Márcio Geovani Jasinski

unread,
Aug 30, 2007, 2:33:17 PM8/30/07
to ccppb...@googlegroups.com
Olá!

Então Rafael uma coisa do C++ bastante interessante é que se você usa uma função que dispara exceções, não é obrigatória que você trate isso com um bloco try{}catch{}.
Por exemplo, usando uma lib SQL qualquer, muito provavelmente ela terá algo do tipo:
throw SqlParserException("....");  ou
throw HostUnreachable("....");

Uma função de alto nível não precisa tratar a SqlParserException uma vez que não fará sentido para um cliente um erro do tipo: "Consulta SQL inválida".  Um desenvolvedor pode pensar que o código SQL foi gerado internamente e sempre estará certo pois sem um SQL syntax correto o sistema vai falhar no seu teste inicial. O tratamento de exceção do parser nesse caso é ignorado por ele.
Porém, ele nunca sabe quando o BD está disponível ou não. Nesse caso é ele trata essa exceção.

Assim pode existir  um classes que faz acesso ao BD e nunca verifica o SQL. Nenhum dos métodos dessa classe dispara exceção de parser. Mas usando a lib como um nível mais interno, algum dia, um erro de um desenvolvedor pode fazer surgir um SqlParserException.

Abraços,

--
Márcio Geovani Jasinski

thoth39

unread,
Aug 30, 2007, 3:22:39 PM8/30/07
to ccppbrasil
On Aug 30, 2:35 pm, "Raphael Menezes" <rapha...@gmail.com> wrote:

> Esse post foi muito produtivo, mensagens muito esclarecedoras. Mas uma coisa
> ainda não fixou na minha mente: Utilizamos os tratamentos de erros do C++
> com blocos try/catch. Mas não loucamente, como foi dito pelo Thiago.
> Entretanto, o thoth, pelo que eu entendi, diz que não devemos ter sempre a
> total certeza que nossas funções não irão lançar excessões.
> Com isso, utilizamos apenas o try/catch usando nosso bom senso, sempre de
> acordo coma complexidade, tipo de operações e dados que a função irá
> retornar?

Não.

Você usa blocos try-catch sempre que é importante capturar exceções.
E nunca quando não é importante capturar exceções.

Essa importância é medida de maneira diferente para cada componente de
um sistema.

Saber ou não saber exceções de que tipo _possivelmente_ serão
disparadas por uma determinada função é difícil; por isso, a questão
não é que se deve ou não se deve conhecer esses tipos, mas a
viabilidade de se conhecer esses tipos.

Certamente é possível realizar uma auditoria completa de todos os
caminhos de execução de uma determinada função e computar o conjunto
completo de tipos possíveis. Essa auditoria é tão cara que seu projeto
provavelmente não a incluirá.

Por esse motivo é raro que seja _importante_ capturar exceções em
praticamente todas as funções de um sistema grande.

Observe, por exemplo, a utilidade de uma exceção que vaza para fora de
main() e aborta o processo: essa condição é facilmente observável por
um debbugger, da mesma forma como outros tipos de traps do sistema
operacional (como falhas de segmentação), e um administrador de
sistemas sagaz pode gerar dumps de memória úteis para a equipe de
manutenção.

Capture exceções conscientemente.
Na dúvida, deixe a exceção rolar.
Exceções, ao contrário de "valores de erro", nunca se perdem.
Esse é o principal valor de um mecanismo de exceções.

--
Pedro Lamarão

Thiago Adams

unread,
Aug 30, 2007, 4:15:15 PM8/30/07
to ccppbrasil

Aonde colocar o try catch?

Um caso bem simples. Imagine que você está carregando um arquivo para
fazer a serialização.
Varias coisas podem acontecer erradas, por exemplo arquivo errado,
arquivo deletado, corrompido, versao errada, falta de memoria etc..

O usário vai no menu e seleciona Arquivo->Abrir.
Por exemplo no windows vc vai ter uma mensagem e vai realizar a
operacao de abrir o arquivo no tratamento desta mensagem.
Você não deve deixar excecoes passarem deste ponto. Lembre-se que a
chamada esta vindo do SO vc nao tem o direito de fazer o stack
unwiding no codigo que pode ser em C.
Entao neste caso é simples. Colocar o try catch no comando do menu,
tratando os erros especificos que voce conhece, e deixando o caso mais
generico por ultimo.
No caso generico vc simplesmente coloca "nao foi possivel abrir o
arquivo" e coloca em detalhes o erro.
No caso especifico vc testar por exemplo por versao inválida e coloca
a mensagem apropriada.

Aonde colocar um throw?
Veja o nome da função. Se algo está impedindo que ocorra o desejado
gere uma excecao.
Por exemplo, o nome da funcao é remove. Se nao pode remover (seja lah
o for) lance uma excecao explicando porque.
Mas existem dois tipos de problemas. Algums sao pre-condicoes e sao
tratados com assert. Justamente pela questao de performance.
Por exemplo no vector vc tem o operador [ ] e a funcao at( ) .
É uma pre-condicao do vector [ ] receber o indice correto. Ja no caso
no at() o indice errado é tratado. Entao tudo depende da documentacao
da funcao.
Para frameworks, para classes publicas, eu prefiro usar excecoes tb
para as pre condicoes. Por exemplo indice errado. Caso seja necessario
por uma questao de performance a a funcao tem q explicar direito qual
é a pre condicao e isso é testado em debug.
Mas coisas totalmente internas (funcoes private) a logica do programa
tem q ser tratado com asserts na minha opniao. Ai que vem a
controversia do logic_error da STL. Mas mesmo neste caso é
complicado :), sabe aquele momento que vc coloca um assert e mesmo
assim trata o problema? Vc sabe que nao eh para acontecer mas se por
acaso acontecer.. seria melhor nao explodir tudo e sim lancao uma
excecao.

On Aug 30, 6:35 pm, "Raphael Menezes" <rapha...@gmail.com> wrote:
> Esse post foi muito produtivo, mensagens muito esclarecedoras. Mas uma coisa
> ainda não fixou na minha mente: Utilizamos os tratamentos de erros do C++
> com blocos try/catch. Mas não loucamente, como foi dito pelo Thiago.
> Entretanto, o thoth, pelo que eu entendi, diz que não devemos ter sempre a
> total certeza que nossas funções não irão lançar excessões.
> Com isso, utilizamos apenas o try/catch usando nosso bom senso, sempre de
> acordo coma complexidade, tipo de operações e dados que a função irá
> retornar?
>
> abraços
>

> --
> "If builders built buildings the way programmers wrote programs, then the

> first woodpecker that came along would destroy civilization."- Hide quoted text -
>
> - Show quoted text -

Thiago Adams

unread,
Aug 31, 2007, 9:11:29 AM8/31/07
to ccppbrasil

> Certamente é possível realizar uma auditoria completa de todos os
> caminhos de execução de uma determinada função e computar o conjunto
> completo de tipos possíveis. Essa auditoria é tão cara que seu projeto
> provavelmente não a incluirá.

Eu faço isso (mas não estou ganhando mais por isso :) )
Mas eu deixo uma margem. Isso porque não adianta fazer toda analise e
alguem quiser lancar uma execao diferente no futuro. Entao eu parto do
principio que as excecoes tem uma base comum. Se no futuro alguem
colocar outra funcao, chamada etc.. eu suponho que se lancar excecao
vai ter a base comum std::exception.
E sempre considero que pode lancar excecoes. As funcoes "nothrow" sao
especiais e as vezes so pelo nome eu ja espero que seja nothrow. Por
exemplo, swap, release, delete
Isso nao eh dificil de fazer caso o projeto tenha comecado correto com
excecoes. Se eh algo antigo, saber se pode lancar uma excecao eh beem
mais complicado, porque sempre tem codigo estilo C no meio.
* se alguem gosta do estilo C, não use excecoes nunca * excecoes
precisam de RAII fortemente.

> Observe, por exemplo, a utilidade de uma exceção que vaza para fora de
> main() e aborta o processo: essa condição é facilmente observável por
> um debbugger, da mesma forma como outros tipos de traps do sistema

Eu sempre coloco o main dentro de um try catch. Retorno codigo de erro
dai, se tiver.
Por exemplo, o aplicativo que eu fiz para incrementar o numero da
versao (build) tem um parser que acha isso dentro de um header. Caso
de qualquer problema eh capturado por excecao. E so tenho 1 bloco de
try catch para todo programa.


> Capture exceções conscientemente.
> Na dúvida, deixe a exceção rolar.

Eu entendi o que você quis dizer. Mas eu acho que a dica vale mais
para c#.
No C++, se alguem tem dúvida é melhor não usar. :)
Nem todo código vai estar preparado para capturar a excecao que nao
foi tratada. Um exemplo, é uma calback do windows, ou uma outra thread
etc... Pode terminar o programa, ou pode ter efeitos colaterias..

thoth39

unread,
Aug 31, 2007, 10:50:03 AM8/31/07
to ccppbrasil
On Aug 31, 10:11 am, Thiago Adams <thiago.ad...@gmail.com> wrote:

> > Certamente é possível realizar uma auditoria completa de todos os
> > caminhos de execução de uma determinada função e computar o conjunto
> > completo de tipos possíveis. Essa auditoria é tão cara que seu projeto
> > provavelmente não a incluirá.
>
> Eu faço isso (mas não estou ganhando mais por isso :) )
> Mas eu deixo uma margem.

Então você não faz isso.
Você tem "uma margem", e se baseia em "premissas", porque é econômico.
Mas margens e premissas são garantias fracas -- onde a garantia forte
é a demonstração matemática.

Por exemplo.

Seja a função f a seguir:

int*
f () {
return new int(0);
}

No Unix eu posso garantir que a única exceção disparável de dentro de
f é std::bad_alloc.

Já em win32 um número quase indefinido de exceções podem ser
disparadas de dentro dessa função -- porque win32 mapeia, por exemplo,
"segmentation fault" e "user interrupt" em uma exceção da linguagem.
Se o usuário apertou CTRL+C justo ali você vai receber uma dessas
"win32 exception".

Lá embaixo eu falo sobre o valor de retorno do printf.
Todos que usam printf também se baseiam na "premissa" que printf nunca
retorna erro.
É uma premissa útil, razoavelmente segura, que barateia o custo do
desenvolvimento.

Mas essa premissa oferece uma garantia fraca, não uma garantia forte.
Um sistema pequeno implementado apenas com a STL talvez permita uma
auditoria completa pra aplicar garantias fortes.
Quanto mais componentes externos -- sejam componentes adquiridos no
mercado, ou desenvolvidos internamente por outras equipes -- o seu
sistema usar, mais cara será essa auditoria completa.

Se você usa componentes pré-compilados, cujo código-fonte é de acesso
restrito, essa auditoria pode se tornar impossível.

O macete de "todas as exceções são std::exception" permite a você
única e exclusivamente não abortar o programa com um try-catch que
capture std::exception na camada exterior.
Mas a única coisa que você pode fazer com uma std::exception é chamar
what().
É agradável na tela mas não permite qualquer decisão interna no
sistema.

Conhecer os tipos das exceções é obter semântica do sistema.
Se você sabe que uma foo::file_not_found pode sair de uma função você
pode projetar um fluxo alternativo para "tentar novamente" ou coisa
parecida.
Se a única coisa que você tem é uma std::exception você não pode tomar
essa decisão.

> > Capture exceções conscientemente.
> > Na dúvida, deixe a exceção rolar.
>
> Eu entendi o que você quis dizer. Mas eu acho que a dica vale mais
> para c#.
> No C++, se alguem tem dúvida é melhor não usar. :)
> Nem todo código vai estar preparado para capturar a excecao que nao
> foi tratada. Um exemplo, é uma calback do windows, ou uma outra thread
> etc... Pode terminar o programa, ou pode ter efeitos colaterias..

O efeito colateral _é_ abortar o programa.
Ou melhor: o efeito colateral é uma chamada a std::uncaught_exception
que por sua vez chama std::terminate.
A implementação padrão normativa de std::terminate deve chamar
std::abort.
std::terminate pode ser substituída com std::set_terminate.
Isso está em [exception.terminate] que é seção 18.7.3 do "draft
standard" corrente.

Esse comportamento é ótimo porque é fácil obter um dump de memória de
um programa abortado.
Corrigir bugs com um dump de memória à mão é fácil se você saber
operar o debugger.
Foi o que eu disse na minha mensagem anterior.

Blocos try-catch desesperados não são úteis.
"putz, vou botar um try-catch aqui, sei lá, né, só pra garantir."
Capturar exceções ad-hoc dessa maneira é abandonar a característica
fundamental do mecanismo de exceção -- mais sobre isso lá embaixo.

Capturar uma exceção em um determinado ponto é uma decisão de projeto.
Se não é importante para o projeto, absolutamente não faça.

O mecanismo de exceções é fundamentalmente diferente da prática de
"valores de status" no retorno de uma função.
Quando uma função indica status da operação através de um valor de
retorno esse status precisa ser conferido explicitamente pelo
programador sob risco da informação _desaparecer_ dentro do fluxo do
programa.

A maioria dos programadores não sabe, por exemplo, que printf retorna
_int_.
Um programa executando em um ambiente de altíssimo risco provavelmente
escreveria:

int status = printf("lalala");
if (status < 0) {
return status; // Erro.
}

ou coisa parecida.
O valor em _status_ não pode ser perdido -- essa é a questão.

Acho difícil que alguém nessa lista já tenha escrito um programa tão
pedante.
O status de printf em todos os programas por aí está sendo abandonado.
O custo de considerar todos os casos absolutamente possíveis é muito
alto.

Infelizmente em linguagens projetadas para suportar a programação
estruturada essa é a única maneira de indicar "status de erro".

Exceções funcionam de maneira exatamente oposta.
Para _prender_ uma exceção, e potencialmente fazê-la sumir do fluxo do
programa, é preciso escrever um bloco try-catch explícito.
Se você não fizer _nada_, a exceção vai se propagar pela pilha até o
sistema operacional, que vai reportar este fato ao usuário ou
administrador do sistema do mesmo modo como reportaria outros tipos de
"exceção", como falha de segmentação.

De fato, na plataforma win32, todos os programas que causam uma falha
de segmentação abortam _exatamente_ por uma exceção que não capturada
-- porque em win32 todos essas "exceções" do sistema operacional são
mapeadas em exceções da linguagem.

Escrever:

std::cout.exceptions(ios::failbit | ios::badbit);

(...)

std::cout << "lalala";

nunca perde o status, como no caso de printf.
No raríssimo evento que a saída padrão der merda, essa exceção vai
subir até o usuário, de um jeito ou de outro.

Surgiu na discussão o exemplo da interface de usuário.
Naturalmente, uma interface de usuário não deve abortar porque um
arquivo não foi encontrado.
Mas observe que isso não é simplesmente "uma boa idéia" -- isso é uma
característica fundamental do projeto desta interface gráfica.

Uma exceção como range_error ou logic_error eu _nunca_ capturaria em
lugar algum -- porque esses erros indicam um desastre no meu programa
e quando desastres acontecem crashes devem acontecer.

--
Pedro Lamarão

Thiago Adams

unread,
Aug 31, 2007, 11:59:01 AM8/31/07
to ccppbrasil
Ola, depois eu vou tentar responder todos os items. (estou saindo do
escritorio)
Por enquanto queria comentar este:

>O macete de "todas as exceções são std::exception" permite a você
>única e exclusivamente não abortar o programa com um try-catch que
>capture std::exception na camada exterior.
>Mas a única coisa que você pode fazer com uma std::exception é chamar
>what().
>É agradável na tela mas não permite qualquer decisão interna no
>sistema.

>Conhecer os tipos das exceções é obter semântica do sistema.
>Se você sabe que uma foo::file_not_found pode sair de uma função você
>pode projetar um fluxo alternativo para "tentar novamente" ou coisa
>parecida.
>Se a única coisa que você tem é uma std::exception você não pode tomar
>essa decisão.

Neste exemplo que eu citei de abrir o arquivo você pode tentar de novo
mesmo não sabendo extamente o que aconteceu. Capturando o caso
generico. Mas eu também sugiro os casos especificos.

Nesta discussao de excecoes eu nao tinha em mente em nenhum momento
essa mistura de execoes do SO.
Isso é uma outra historia na minha opniao, mas como sabemos no windows
eh implementado usando o mesmo mecanismo interno eh relevante.
Coisas como ponteiros invalidos , divisao por zero etc...eu nao levo
em consideracao no tratamento de errors. Eu apenas considero as
excecoes que o C++ ou o programa lanca. Se tiver um caso muito
especifico ai sim.

(Tem uma maneira de transformar estes erros do SO (windons) em
std::exception tb.... mas nao usei)


Thiago Adams

unread,
Sep 3, 2007, 6:04:53 AM9/3/07
to ccppbrasil
Sobre a questão de fazer auditoria das exceções nas funções.

Notem que isso não é problema gerado pelo uso de exceções, o problema
também existe com o código de retorno.
Por exemplo:
Se você tem um Lib1 que reporta o Erro1 e a Lib2 que reporta o Erro2 e
junta tudo isso em uma função:

Erro3 Func()
{
Erro1 erro1 = Lib1Class.call();
Erro2 erro2 = Lib2.Class.call();
}
Você pode precisar criar o Tipo Erro3 para retornar os diferentes
problemas. Então caso seja inventado um novo erro na lib1, ou lib2 o
Erro3 precisa ser atualizado. E caso os chamadores da função testem
por erros específicos eles têm que ser revisados.
Se todas as libs reportarem erro com números e o Zero indicar sucesso
os chamadores podem querer testar apenas por isso, ou seja, funcionou
ou não.
Então pode acontecer de não ser preciso modificar nada.
Com exceções eh a mesma coisa, desde que você tenha uma classe base.

Fazer:

Erro erro = funcao();
If (erro != 0) { /*algum erro */}

ou

try { funcao(); } catch(const exception &e) { /*algum erro*/ }

e' a mesma coisa.

Ou:

Erro erro = funcao();
If (erro != 0)
{
if (erro == ERRO_TAL) {/*Especifico*/}
else /*algum erro */
}

ou

try { funcao(); }
catch(const ErroEspecifico &e) {/*Especifico*/}
catch(const exception &e) { /*algum erro*/ }
Também é equivalente.

O que você não pode fazer é deixar de ter uma classe base. Neste
caso um nova exceção pode quebrar a logica dos chamadores de tomar a
decisão independente do tipo do erro. Poderia ser usado o catch(...)
mas não é uma boa idéia porque você perde toda informação.

Sobre a pergunta, se você deve fazer a lista de tudo o que pode dar
errado na função, e manter essa lista atualizada, A resposta eh sim.
Você deve fazer isso. (Eu nao posso escrever que nao)

É como aquele anuncio na latinha de cerveja. "Beba com
responsabilidade".
Mas quem nunca teve uma ressaca? O importante eh ter a ressaca com
responsabilidade.


Reply all
Reply to author
Forward
0 new messages