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
classname (args) : base1 (args), member2 (args), member3 (args)
{
}
O corpo deste construtor está vazio. Enquanto os construtores para base1, member2e member3são seguros exceção, não há nada para se preocupar. Por exemplo, se o construtor de member2arremessos, esse construtor é responsável por se limpar. A base base1já foi completamente construída, então seu destruidor será chamado automaticamente. member3nunca 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_ptrno construtor, o destruidor de unique_ptrserá 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 noexceptoperaçã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.