In genere mi piace il C++ e il modo in cui si sente il C++, ma c’è una stranezza specifica che è causata dalla scelta di strutture dati da parte dei progettisti STL che non possono essere implementate con una semantica copy-on-write (nota anche come COW).
Curioso di sapere quale stranezza? Continua a leggere! COW potrebbe sembrare un dettaglio di implementazione, ma può davvero cambiare il modo in cui progetti le tue API.
Un oggetto con semantica COW (a volte chiamata anche “condivisione implicita”) ha una proprietà interessante: ogni volta che lo copi, non esegue realmente una “copia profonda” ma semplicemente condivide i dati sottostanti tra l’oggetto originale e la sua copia, e incrementa un contatore di riferimento per tracciarlo. Solo nel momento in cui una qualsiasi delle copie viene ulteriormente modificata (scritta), la copia profonda viene effettivamente eseguita.
Ora, è importante rendersi conto che l’overhead imposto dalla semantica COW è solitamente molto piccolo: l’oggetto deve solo tenere un contatore ad ogni copia, e controllarlo prima di qualsiasi modifica. Quest’ultimo è ancora più leggero di quanto sembri perché il compilatore può eliminare molti controlli grazie alle propagazioni costanti.
Ma quali sono i vantaggi dell’utilizzo di COW? Il primo e più importante vantaggio è la possibilità di avere un’API pulita rispetto ai valori restituiti.
Se hai programmato C++ abbastanza a lungo, sai che tutte le funzioni C++ che dovrebbero restituire una struttura dati complessa (qualsiasi cosa che sia più grande di una piccola struttura, come un contenitore, un buffer di memoria e così via) accetteranno invece un riferimento o un puntatore ad un oggetto che il chiamante dovrà fornire e che verrà “compilato” come valore di ritorno. Questo è assolutamente orribile. Se non lo consideri terribile, è giunto il momento di prenderti una pausa dal C++ e passare ad altri linguaggi per un po’. Per esempio:
std::vettore nodi;
cur_node.children(nodi);
printf(“Numero di figli: %u\n”, nodes.size());
La funzione Node::children(), in qualsiasi altro linguaggio, avrebbe restituito un vettore di Nodi. Ma il C++ idiomatico dice che non dovresti imporre il sovraccarico di fare una copia aggiuntiva del vettore al tuo utente, e quindi chiedergli di passarti il contenitore da riempire. Questo è davvero illeggibile perché rompe la sintassi comune delle funzioni mescolando i valori restituiti agli argomenti. Inoltre, impone ulteriori problemi all’API; es: cosa succede se chiami Node::children() con un vettore non vuoto? La funzione aggiungerà semplicemente i suoi dati al vettore? Oppure lo chiarirebbe all’inizio?
Ora, pensa a un mondo in cui std::vettore è una struttura dati COW. Tutti questi problemi scompaiono improvvisamente perché la restituzione del vettore non causerebbe più una copia. Bam, problema risolto! E quando hai un’API chiara, in cui i valori restituiti sono realmente valori restituiti, ne ottieni tutti i vantaggi:
printf(“Numero di figli: %u\n”, cur_node.children().size());
Tre linee fuse in una. Niente più variabili con nome temporaneo. E se Node memorizza internamente i suoi figli come vettore, questo codice è ancora più veloce della versione non COW, perché non viene eseguita alcuna copia: ottieni la stessa velocità come se children() restituisse un riferimento al codice interno vettore, anche se non è così.
Altri vantaggi delle strutture dati COW:
Qt di Nokia ha progettato tutti gli oggetti e le strutture dati con COW. Li hanno persino resi rientranti (per scopi multithreading) utilizzando il conteggio dei riferimenti atomici tramite istruzioni native della CPU. Infine, espongono le viscere di COW attraverso un semplice modello QSharedData che puoi utilizzare per reimplementare i tuoi oggetti COW.
Non potrei essere più d’accordo con i designer di Qt.
Questo è un altro esempio di come il COW possa influenzare positivamente la progettazione dell’API:
QByteArray fileHash(QString fn)
{
QFile f(fn);
QCryptographicHash h(QCryptographicHash::Sha1);
mentre (!f.atEnd())
h.addData(f.read(16*1024));
return h.risultato();
}
Scopri come posso semplicemente passare il buffer restituito da QFile::read() a addData() senza dovermi preoccupare della copia della memoria.
La varietà di colori delle uova, da rosa a blu, riflette la genetica delle galline…
Una startup californiana, Symbrosia, propone l'uso dell'alga Asparagopsis taxiformis nei mangimi per ruminanti per ridurre…
La nave rompighiaccio nucleare "Chukotka", varata a San Pietroburgo, rappresenta un passo cruciale per la…
Scoperta nel Colorado fornisce nuove prove sulla glaciazione sturtiana, rivelando che le calotte di ghiaccio…
Il progetto del Ponte sullo Stretto di Messina avanza con l'approvazione della Commissione Tecnica, introducendo…
Nel 2025, il tasso di rivalutazione del montante contributivo per le pensioni aumenterà dal 2,3%…