L'attività di GotW#8 consiste nell'implementare una struttura di dati di stack generici eccezionalmente neutri in C++, supponendo che solo il distruttore dell'argomento del modello non getti. Il trucco consiste nel gestire potenzialmente le operazioni degli argomenti template di lancio (costruttore, costruttore di copie, assegnazione) in modo da lasciare lo stack in uno stato coerente se lanciano.Una tecnica di classe base per la gestione delle eccezioni
Nella soluzione, Herb Sutter dice
Per mantenere questa soluzione più semplice, ho deciso di non dimostrare la tecnica di classe di base per sicura rispetto alle eccezioni proprietà delle risorse.
Dopo un po 'googling, ho trovato this answer da Dave Abrahams risalente al 1997. Nella sua soluzione si gestisce l'allocazione e la cancellazione della memoria nella classe base, e implementa le operazioni di stack nella sottoclasse. In questo modo, assicura che la copia degli elementi nel costruttore di copie sia separata dall'allocazione della memoria, in modo che se la copia fallisce, viene chiamato il distruttore della classe base, a prescindere da cosa.
Per riferimento, ecco costruttore di copia di Dave con il mio commento ha aggiunto:
// v_ refers to the internal array storing the stack elements
Stack(const Stack& rhs)
: StackBase<T>(rhs.Count()) // constructor allocates enough space
// destructor calls delete[] appropriately
{
while (Count() < rhs.Count())
Push(rhs.v_[ Count() ]); // may throw
}
Se il costruttore di base ha successo, la memoria clean-up nel distruttore di base è garantito anche se il costruttore copia della sottoclasse getta.
Le mie domande sono:
- C'è qualche altro beneficio per l'approccio, ad eccezione di quanto descritto sopra?
sono arrivato fino a questo costruttore di copia quando ho risolto il problema per conto mio:
// v_ refers to the internal array storing the stack elements // vsize_ is the amount of space allocated in v_ // vused_ is the amount of space used so far in v_ Stack (const Stack &rhs) : vsize_ (0), vused_ (0), v_ (0) { Stack temp (rhs.vused_); // constructor calls `new T[num_elements]` // destructor calls `delete[] v_` std::copy (rhs.v_, rhs.v_ + rhs.vused_, temp.v_); // may throw swap (temp); } void swap (Stack &rhs) { std::swap (v_, rhs.v_); std::swap (vused_, rhs.vused_); std::swap (vsize_, rhs.vsize_); }
lo trovo un po 'ingombrante per avere una classe base rispetto a questo approccio. C'è qualche ragione per cui la tecnica della classe base dovrebbe essere preferita rispetto a questo approccio temp-copy-then-swap? Nota che sia Dave che io abbiamo già il membro
swap()
perché lo usiamo nel nostrooperator=()
.- La tecnica di Dave Abrahams non sembra essere molto nota (secondo Google). Ha un nome diverso, è una pratica standard, ho perso qualcosa?
Note:
- assumere Dave
Push()
in un ciclo per essere equivalente a mio uso distd::copy
- teniamoci puntatori intelligenti fuori la risposta, come il loro uso avrebbe tolto il punto di gestione memoria esplicitamente in questo esercizio
La condivisione della base sembra essere l'unico argomento a favore dell'approccio di classe base. Gli darò un giorno o due per vedere se qualcun altro esce con un altro argomento. – Irfy
L'approccio alla classe base vince quando il contenuto dell'oggetto è pesante e lo scambio con 'temp' è costoso. (Anche se il 'move' di C++ 11 potrebbe aiutare) Puoi aggiungere questo bit alla tua risposta, se lo desideri. – Irfy