@SebastianRedl ha già fornito risposte semplici e dirette, ma potrebbe essere utile qualche spiegazione aggiuntiva.
TL; DR = c'è una regola di stile per mantenere semplici i costruttori, ci sono delle ragioni, ma queste ragioni si riferiscono principalmente a uno stile di codifica storico (o semplicemente cattivo). La gestione delle eccezioni nei costruttori è ben definita e i distruttori saranno ancora chiamati per variabili e membri locali completamente costruiti, il che significa che non dovrebbero esserci problemi nel codice C ++ idiomatico. La regola dello 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 possibile per impostare uno stato valido definito. Se la tua inizializzazione è più complessa, dovrebbe essere gestita al di fuori del costruttore. Se non è disponibile un valore di inizializzazione economico che il costruttore può impostare, è necessario indebolire gli invarianti applicati dalla classe per aggiungerne uno. Ad esempio, se l'allocazione dello spazio di archiviazione per la classe da gestire è troppo costosa, aggiungi uno stato non assegnato ma negativo, perché ovviamente gli stati del caso speciale come null non hanno mai causato problemi a nessuno. Ahem.
Anche se comune, certamente in questa forma estrema è molto lontano dall'assoluto. In particolare, come indica il mio sarcasmo, sono nel campo che dice che gli invarianti indeboliti sono quasi sempre un prezzo troppo alto. Tuttavia, ci sono delle ragioni alla base della regola di stile e ci sono modi per avere invarianti forti sia per i costruttori che per minimi.
I motivi si riferiscono alla pulizia automatica del distruttore, in particolare in presenza di eccezioni. Fondamentalmente, ci deve essere un punto ben definito quando il compilatore diventa responsabile della chiamata ai 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à per la distruzione dell'oggetto si trasferisce al compilatore solo quando il costruttore si completa correttamente. Questo è noto come RAII (l'allocazione delle risorse è inizializzazione), che in realtà non è il nome migliore.
Se si verifica un'eccezione all'interno del costruttore, qualsiasi elemento costruito in parte deve essere ripulito in modo esplicito, in genere in try .. catch
.
Tuttavia, i componenti dell'oggetto già costruiti con successo sono già responsabilità dei compilatori. Ciò significa che, in pratica, non è davvero un grosso problema. per es.
classname (args) : base1 (args), member2 (args), member3 (args)
{
}
Il corpo di questo costruttore è vuoto. Fintanto che i costruttori di base1
, member2
e member3
sono eccezionalmente sicuri, non c'è nulla di cui preoccuparsi. Ad esempio, se il costruttore di member2
genera, il costruttore è responsabile della pulizia di se stesso. La base base1
era già completamente costruita, quindi il suo distruttore verrà automaticamente chiamato. member3
non è mai stato nemmeno parzialmente costruito, quindi non ha bisogno di pulizia.
Anche quando c'è un corpo, le variabili locali che sono state completamente costruite prima che l'eccezione venga lanciata verranno automaticamente distrutte, proprio come qualsiasi altra funzione. I corpi dei costruttori che manipolano i puntatori grezzi o "possiedono" una sorta di stato implicito (archiviato 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 di eccezione, ma il vero problema non riesce a gestire correttamente una risorsa tramite una classe. Ad esempio, se sostituisci i puntatori grezzi con unique_ptr
nel costruttore, il distruttore per unique_ptr
verrà chiamato automaticamente se necessario.
Ci sono ancora altre ragioni che le persone danno per preferire i costruttori del minimo. Uno è semplicemente perché esiste la regola di stile, molte persone assumono che le chiamate del costruttore siano economiche. Un modo per avere questo, ma avere ancora invarianti forti, è avere una classe factory / builder separata che abbia gli invarianti indeboliti e che configuri il valore iniziale necessario usando (potenzialmente molti) normali chiamate di membri. Una volta ottenuto lo stato iniziale necessario, passare quell'oggetto come argomento al costruttore per la classe con gli invarianti forti. Questo può "rubare il coraggio" dell'oggetto debole-invariante - spostare la semantica - che è un'operazione a buon mercato (e solitamente noexcept
).
Ovviamente puoi racchiuderlo in una funzione make_whatever ()
, quindi i chiamanti di quella funzione non avranno mai bisogno di vedere l'istanza della classe indebolita-invariante.