PROJETOS E DECLARAÇÕES
152 C++ E FICAZ : 55 MANEIRAS DE APRIMORAR SEUS PROGRAMAS E PROJETOS
de PrettyMenu é garantido pelo fato de pImpl ser privado. Tornar PMImpl uma classe seria, no mínimo, tão bom quanto, torná-lo uma estrutura, mas menos conveniente. (Também ajuda a manter os puristas da orientação a objetos satisfeitos.) Se desejado, PMImpl poderia ser aninhada dentro de PrettyMenu, mas questões de empacotamento como essa não dependem da escrita de código seguro em relação a exceções, que é a nossa preocupa- ção aqui.
A estratégia “copiar e trocar” é uma maneira excelente de fazer mudanças do tipo “tudo ou nada” ao estado de um objeto, mas, em geral, ela não ga- rante que a função como um todo seja fortemente segura em relação a ex- ceções. Para ver por que, considere uma abstração de changeBackground, chamada someFunc (uma função), que usa copiar e trocar, mas que inclui chamadas para duas outras funções, f1 e f2:
void someFunc( ) {
... // faz uma cópia do estado local
f1( ); f2( );
... // troca o estado modificado }
Deve ficar claro que, se f1 ou f2 forem menos do que fortemente seguras em relação a exceções, será difícil para someFunc ser fortemente segura em relação a exceções. Por exemplo, suponhamos que f1 ofereça apenas a garantia básica. Para que someFunc ofereça a garantia forte, ela teria que escrever código para determinar o estado do programa inteiro antes de cha- mar f1, capturar todas as exceções de f1 e, então, restaurar o estado ori- ginal.
As coisas não são realmente nada melhores se f1 e f2 forem fortemente seguras em relação a exceções. Afinal, se f1 for executada até seu término, o estado do programa pode ter mudado arbitrariamente; assim, se f2 lan- çar uma exceção, o estado do programa não será o mesmo que era quando someFunc foi chamada, mesmo que f2 não tenha mudado nada.
O problema são os efeitos colaterais. Enquanto as funções operarem apenas no estado local (ou seja, someFunc afetar apenas o estado do objeto no qual está sendo invocada), é relativamente fácil oferecer a garantia forte. Quando as funções possuem efeitos colaterais em dados não locais, é muito mais difícil. Se um dos efeitos colaterais de chamar f1 for, por exemplo, a mo- dificação de uma base de dados, será difícil fazer someFunc ser fortemente segura em relação a exceções. De modo geral, não é possível desfazer uma modificação em uma base de dados que já tenha sido efetivada (por meio de commit); outros clientes da base de dados já podem ter visto o novo estado da base de dados.
Questões como essa podem impedi-lo de oferecer a garantia forte para uma função, mesmo que você quisesse. Outra questão é a eficiência. O cerne da questão copiar e trocar é a ideia de modificar uma cópia dos dados de um
objeto e depois trocar os dados modificados pelo original em uma operação não lançadora. Isso requer que seja feita uma cópia de cada objeto que será modificado, o que custa tempo e espaço que você pode não ser capaz, ou não desejar, de disponibilizar. A garantia forte é altamente desejável, e você deve oferecê-la quando for praticável, mas ela não é sempre praticável. Quando não for, você precisará oferecer a garantia básica. Na prática, você provavelmente vai descobrir que pode oferecer a garantia forte para algu- mas funções, mas que o custo, em termos de eficiência ou de complexidade, será inviável para muitas outras. Desde que você tenha feito um esforço razoável para oferecer a garantia forte sempre que praticável, ninguém po- derá criticá-lo quando você oferecer apenas a garantia básica. Para muitas funções, a garantia básica é uma escolha perfeitamente razoável.
As coisas são diferentes se você escrever uma função que não oferece ga- rantia alguma de segurança em relação a exceções, porque, nesse caso, é razoável considerar que você seja culpado até que se prove sua inocência. Você deve escrever código seguro em relação a exceções, mas pode ter uma defesa convincente. Considere novamente a implementação de someFunc, que chama as funções f1 e f2. Suponhamos que f2 não ofereça segurança em relação às exceções, nem mesmo a garantia básica. Isso significa que, se f2 emitir uma exceção, é porque o programa pode ter vazado recursos den- tro de f2. Também que f2 pode ter estruturas de dados corrompidas, ou seja, os vetores ordenados podem não estar mais ordenados, os objetos que estavam sendo transferidos de uma estrutura de dados para outra podem ter sido perdidos, etc. Não é possível para someFunc compensar esses pro- blemas. Se as funções que someFunc chama não oferecem garantias sobre a segurança em relação a exceções, someFunc também não pode oferecer garantia alguma.
O que nos traz de volta à gravidez. Uma mulher ou está grávida, ou não está. Não é possível estar meio grávida. De maneira similar, ou um sistema de software é seguro em relação a exceções, ou não é. Não existe um sistema parcialmente seguro em relação às exceções. Se um sistema tem uma só função que não seja segura em relação às exceções, o sistema como um todo não é seguro em relação a exceções, porque as chamadas para essa única função podem vazar recursos e corromper as estruturas de dados. Infeliz- mente, muito código legado em C++ foi escrito sem segurança em relação às exceções em mente, então muitos sistemas atualmente não são seguros em relação a exceções – eles incorporam código que foi escrito de uma ma- neira não segura em relação a exceções.
Não há motivos para perpetuar esse estado das coisas. Quando estiver es- crevendo código novo ou modificando o código existente, pense cuidadosa- mente em como torná-lo seguro em relação a exceções. Inicie usando obje- tos para gerenciar recursos. (Mais uma vez, veja o Item 13.) Isso impedirá vazamentos de recursos. Depois, determine quais das três garantias de se- gurança em relação a exceções é a mais forte que você pode oferecer de ma- neira prática para cada função que escrever, optando por não oferecer ne-