@SebastianRedl ha già dato le risposte semplici e dirette, ma alcune spiegazioni aggiuntive potrebbero essere utili.
TL; DR = c'è una regola di stile per mantenere semplici i costruttori, ci sono ragioni per questo, ma quelle ragioni si riferiscono principalmente a uno stile storico (o semplicemente cattivo) di codifica. La gestione delle eccezioni nei costruttori è ben definita e i distruttori saranno comunque chiamati per variabili e membri locali completamente costruiti, il che significa che non dovrebbero esserci problemi nel codice C ++ idiomatico. La regola di stile persiste comunque, ma normalmente non è un problema - non tutte le inizializzazioni devono essere nel costruttore, e in particolare non necessariamente quel costruttore.
È una regola di stile comune che i costruttori dovrebbero fare il minimo assoluto possibile per impostare uno stato valido definito. Se l'inizializzazione è più complessa, deve essere gestita all'esterno del costruttore. Se non c'è un valore economico da inizializzare che il tuo costruttore può impostare, dovresti indebolire gli invarianti applicati dalla tua classe per aggiungerne uno. Ad esempio, se l'allocazione dello spazio di archiviazione per la classe da gestire è troppo costosa, aggiungi uno stato null non allocato, poiché ovviamente avere stati di casi speciali come null non ha mai causato problemi a nessuno. Ahem.
Sebbene comune, certamente in questa forma estrema è molto lontano dall'assoluto. In particolare, come indica il mio sarcasmo, sono nel campo che dice che indebolire gli invarianti è quasi sempre un prezzo troppo alto. Tuttavia, ci sono ragioni dietro la regola dello stile e ci sono modi per avere sia costruttori minimi che forti invarianti.
Le ragioni si riferiscono alla pulizia automatica del distruttore, in particolare a fronte di eccezioni. Fondamentalmente, ci deve essere un punto ben definito quando il compilatore diventa responsabile della chiamata dei distruttori. Mentre sei ancora in una chiamata del costruttore, l'oggetto non è necessariamente completamente costruito, quindi non è valido chiamare il distruttore per quell'oggetto. Pertanto, la responsabilità della distruzione dell'oggetto viene trasferita al compilatore solo quando il costruttore viene completato correttamente. Questo è noto come RAII (Resource Allocation Is Initialization) che non è proprio il nome migliore.
Se si verifica un'eccezione all'interno del costruttore, tutto ciò che è parzialmente costruito deve essere ripulito esplicitamente, in genere in a try .. catch
.
Tuttavia, i componenti dell'oggetto che sono già stati costruiti correttamente sono già responsabilità dei compilatori. Ciò significa che in pratica non è davvero un grosso problema. per esempio
classname (args) : base1 (args), member2 (args), member3 (args)
{
}
Il corpo di questo costruttore è vuoto. Finché i costruttori base1
, member2
e member3
sono eccezionalmente sicuri, non c'è nulla di cui preoccuparsi. Ad esempio, se il costruttore di member2
lanci, quel costruttore è responsabile della pulizia. La base base1
era già completamente costruita, quindi il suo distruttore verrà automaticamente chiamato. member3
non è mai stato nemmeno parzialmente costruito, quindi non necessita di pulizia.
Anche quando c'è un corpo, le variabili locali che sono state completamente costruite prima che fosse generata l'eccezione verranno automaticamente distrutte, proprio come qualsiasi altra funzione. I corpi di costruttori che manipolano puntatori non elaborati o "possiedono" un qualche tipo di stato implicito (memorizzato altrove) - che in genere significa che una chiamata di funzione di inizio / acquisizione deve essere abbinata a una chiamata di fine / rilascio - possono causare problemi di sicurezza delle eccezioni, ma il vero problema lì non riesce a gestire correttamente una risorsa tramite una classe. Ad esempio, se si sostituiscono i puntatori non elaborati con unique_ptr
nel costruttore, il distruttore per unique_ptr
verrà chiamato automaticamente se necessario.
Ci sono ancora altri motivi per cui le persone preferiscono i costruttori più esigenti. Uno è semplicemente perché esiste la regola di stile, molte persone presumono che le chiamate del costruttore siano economiche. Un modo per ottenerlo, pur avendo ancora forti invarianti, è di avere una classe factory / builder separata che ha invece gli invarianti indeboliti e che imposta il valore iniziale necessario usando (potenzialmente molte) normali chiamate di funzione membro. Una volta che hai lo stato iniziale di cui hai bisogno, passa quell'oggetto come argomento al costruttore per la classe con gli invarianti forti. Ciò può "rubare le viscere" dell'oggetto debole-invariante - spostare la semantica - che è un'operazione economica (e di solito noexcept
).
E ovviamente puoi includerlo in una make_whatever ()
funzione, così i chiamanti di quella funzione non dovranno mai vedere l'istanza di classe degli invarianti indeboliti.