Eu tenho uma classe CPP cujo construtor faz algumas operações. Algumas dessas operações podem falhar. Eu sei que os construtores não retornam nada.
Minhas perguntas são,
É permitido executar algumas operações além da inicialização de membros em um construtor?
É possível dizer à função de chamada que algumas operações no construtor falharam?
Posso
new ClassName()
retornar NULL se ocorrer algum erro no construtor?
object-oriented
c++
MayurK
fonte
fonte
Square
, com um construtor que leva um parâmetro, o comprimento de um lado, você quer verificar se esse valor for maior que 0.Respostas:
Sim, embora alguns padrões de codificação possam proibi-lo.
Sim. A maneira recomendada é lançar uma exceção. Como alternativa, você pode armazenar as informações de erro dentro do objeto e fornecer métodos para acessar essas informações.
Não.
fonte
Você pode criar um método estático que execute o cálculo e retorne um objeto em caso de sucesso ou não em caso de falha.
Dependendo de como essa construção do objeto é feita, talvez seja melhor criar outro objeto que permita a construção de objetos em um método não estático.
Chamar um construtor indiretamente é freqüentemente chamado de "fábrica".
Isso também permitiria retornar um objeto nulo, que pode ser uma solução melhor do que retornar nulo.
fonte
NULL
. Por exemplo,int foo() { return NULL
você retornaria0
(zero) um objeto inteiro. Sestd::string foo() { return NULL; }
você chamar acidentalmentestd::string::string((const char*)NULL)
qual é o comportamento indefinido (NULL não aponta para uma sequência terminada em \ 0).int
. Por exemplo,std::allocator<int>
é uma fábrica perfeitamente sã.O @SebastianRedl já deu respostas simples e diretas, mas alguma explicação extra pode ser útil.
TL; DR = existe uma regra de estilo para manter os construtores simples, há motivos para isso, mas esses motivos se relacionam principalmente a um estilo histórico (ou simplesmente ruim) de codificação. O tratamento de exceções em construtores é bem definido, e os destruidores ainda serão chamados para variáveis e membros locais totalmente construídos, o que significa que não deve haver nenhum problema no código C ++ idiomático. A regra de estilo persiste de qualquer maneira, mas normalmente isso não é um problema - nem toda inicialização precisa estar no construtor e, particularmente, não necessariamente nesse construtor.
É uma regra de estilo comum que os construtores devem fazer o mínimo absoluto possível para configurar um estado válido definido. Se sua inicialização é mais complexa, ela deve ser tratada fora do construtor. Se não houver um valor barato para inicializar que seu construtor possa configurar, você deve enfraquecer os invariantes impostos por sua classe para adicionar um. Por exemplo, se a alocação de armazenamento para o gerenciamento da sua classe for muito cara, adicione um estado nulo ainda não alocado, porque é claro que ter estados de casos especiais como nulo nunca causou problemas a ninguém. Ahem.
Embora comum, certamente nesta forma extrema está muito longe do absoluto. Em particular, como meu sarcasmo indica, estou no campo que diz que o enfraquecimento dos invariantes é quase sempre um preço muito alto. No entanto, existem razões por trás da regra de estilo e existem maneiras de ter construtores mínimos e invariantes fortes.
Os motivos estão relacionados à limpeza automática do destruidor, principalmente diante de exceções. Basicamente, deve haver um ponto bem definido quando o compilador se torna responsável por chamar destruidores. Enquanto você ainda está em uma chamada de construtor, o objeto não é necessariamente totalmente construído, portanto, não é válido chamar o destruidor para esse objeto. Portanto, a responsabilidade de destruir o objeto só é transferida para o compilador quando o construtor é concluído com êxito. Isso é conhecido como RAII (Alocação de Recursos É Inicialização), que não é realmente o melhor nome.
Se ocorrer um lançamento de exceção dentro do construtor, qualquer coisa parcialmente construída precisará ser explicitamente limpa, normalmente em a
try .. catch
.No entanto, os componentes do objeto que já foram construídos com êxito já são de responsabilidade dos compiladores. Isso significa que, na prática, não é realmente um grande problema. por exemplo
O corpo deste construtor está vazio. Enquanto os construtores para
base1
,member2
emember3
são seguros exceção, não há nada para se preocupar. Por exemplo, se o construtor demember2
arremessos, esse construtor é responsável por se limpar. A basebase1
já foi completamente construída, então seu destruidor será chamado automaticamente.member3
nunca foi parcialmente construído, por isso não precisa de limpeza.Mesmo quando há um corpo, as variáveis locais que foram totalmente construídas antes da exceção ser lançada serão automaticamente destruídas, como qualquer outra função. Corpos de construtores que manipulam ponteiros brutos ou "possuem" algum tipo de estado implícito (armazenado em outro local) - normalmente significando que uma chamada de função de início / aquisição deve corresponder a uma chamada de final / liberação - podem causar problemas de segurança de exceção, mas o problema real lá está falhando ao gerenciar um recurso corretamente por meio de uma classe. Por exemplo, se você substituir ponteiros brutos por
unique_ptr
no construtor, o destruidor deunique_ptr
será chamado automaticamente, se necessário.Ainda existem outras razões pelas quais as pessoas dão para optar por construtores que fazem o mínimo. Uma é simplesmente porque a regra de estilo existe, muitas pessoas assumem que as chamadas de construtores são baratas. Uma maneira de obter isso, mas ainda ter fortes invariantes, é ter uma classe de fábrica / construtor separada que, em vez disso, possui os invariantes enfraquecidos e que configura o valor inicial necessário usando (potencialmente muitas) chamadas normais de função de membro. Depois de ter o estado inicial necessário, passe esse objeto como argumento para o construtor da classe com os invariantes fortes. Isso pode "roubar as entranhas" do objeto de invariantes fracos - mover semântica - que é uma
noexcept
operação barata (e geralmente ).E é claro que você pode agrupar isso em uma
make_whatever ()
função, para que os chamadores dessa função nunca precisem ver a instância da classe enfraquecido-invariantes.fonte
main
função ou variáveis estáticas / globais. Um objeto alocado usandonew
, não é propriedade até que você atribui essa responsabilidade, mas ponteiros inteligentes possuir os objetos alocados-heap eles fazem referência, e os recipientes possuem suas estruturas de dados. Os proprietários podem optar por excluir mais cedo, o destruidor dos proprietários é o responsável final.