Вы находитесь на странице: 1из 7

CP pensareprogettareprogrammare n.

139 giugno 2004

Operatore di assegnamento e eccezioni in C++


di Massimiliano Pagani
` Scrivere loperatore di assegnamento di una classe puo sembrare banale...ma quando scendono in campo le eccezioni le sorprese non tardano a mancare

Massimiliano Pagani Si occupa di ingegneria del software e di programmazione di videogiochi. Attualmente lavora in Ubi Studios s.rl. dove ha partecipato alla realizzazione di diversi videogiochi.

pubblicato su WWW.INFOMEDIA.IT stampa digitale da Lulu Enterprises Inc. stores.lulu.com/infomedia


Infomedia
` Infomedia e limpresa editoriale che da quasi venti anni ha raccolto la voce dei programmatori, dei sistemisti, dei professionisti, degli studenti, dei ricercatori e dei professori dinformatica italiani. Sono pi` di 800 gli autori che hanno realizzato per le teu state Computer Programming, Dev, Login, Visual Basic Journal e Java Journal, molte migliaia di articoli tecnici, presentazioni di prodotti, tecnologie, protocolli, strumenti di lavoro, tecniche di sviluppo e semplici trucchi e stratagemmi. Oltre 6 milioni di copie distribuite, trentamila pagine stampate, fanno di questa impresa la pi` grande ed u inuente realt` delleditoria specializzata nel campo della a programmazione e della sistemistica. In tutti questi anni le riviste Infomedia hanno vissuto della passione di quanti vedono nella programmazione non solo la propria professione ma unattivit` vitale e un vero a divertimento. ` Nel 2009, Infomedia e cambiata radicalmente adottando ` un nuovo modello aziendale ed editoriale e si e organizzata attorno ad una idea di Impresa Sociale di Comunit` , a partecipata da programmatori e sistemisti, separando le attivit` di gestione dellinformazione gestite da un board a comunitario professionale e quelle di produzione gesti` te da una impresa strumentale. Questo assetto e in linea con le migliori esperienze internazionali e rende Infomedia ancora di pi` parte della Comunit` nazionale degli u a sviluppatori di software. ` Infomedia e media-partner di manifestazioni ed eventi in ambito informatico, collabora con molti dei pi` imporu tanti editori informatici italiani come partner editoriale e fornitore di servizi di localizzazione in italiano di testi in lingua inglese.

Limpaginazione automatica di questa rivista e realizzata al ` 100% con strumenti Open Source usando OpenOffice, Emacs, BHL, LaTeX, Gimp, Inkscape e i linguaggi Lisp, Python e BASH

For copyright information about the contents of Computer Programming, please see the section Copyright at the end of each article if exists, otherwise ask authors. Infomedia contents is 2004 Infomedia and released as Creative Commons 2.5 BY-NC-ND. Turing Club content is 2004 Turing Club released as Creative Commons 2.5 BY-ND. Le informazioni di copyright sul contenuto di Computer Programming sono riportate nella sezione Copyright alla ne di ciascun articolo o vanno richieste direttamente agli autori. Il contenuto Infomedia e 2004 Infome` dia e rilasciato con Licenza Creative Commons 2.5 BYNC-ND. Il contenuto Turing Club e 2004 Turing Club ` e rilasciato con Licenza Creative Commons 2.5 BY-ND. Si applicano tutte le norme di tutela dei marchi e dei segni distintivi. ` E in ogni caso ammessa la riproduzione parziale o totale dei testi e delle immagini per scopo didattico purch e vengano integralmente citati gli autori e la completa identicazione della testata. Manoscritti e foto originali, anche se non pubblicati, non si restituiscono. Contenuto pubblicitario inferiore al 45%. La biograa dellautore riportata nellarticolo e sul sito www.infomedia.it e di norma quella disponibi` le nella stampa dellarticolo o aggiornata a cura dellautore stesso. Per aggiornarla scrivere a info@infomedia.it o farlo in autonomia allindirizzo http://mags.programmers.net/moduli/biograa

PROGRAMMING

Operatore di assegnamento e eccezioni in C++


Scrivere loperatore di assegnamento di una classe pu sembrare banale... ma quando scendono in campo le eccezioni le sorprese non tardano a mancare
di Massimiliano Pagani

l C++ una brutta bestia. A differenza del suo piccolo e compatto antenato, il C++ grande e complesso. Questo da un lato permette di spaziare verso i terreni dellastrazione spinta, dallaltro, garantendo la possibilit della massima efficienza non pu fare a meno di lasciare che i dettagli di basso livello azzannino lincauto programmatore. Rispetto al C, in un programma C++ ci sono molte pi cose che accadono dietro le quinte (costruttori temporanei, operatori sovraccaricati, funzioni virtuali) di cui fondamentale avere conoscenza o quantomeno sentore per scrivere del buon C++. Finito un progetto C, il mio team si apprestava a tornare sul C++, cos ho preso spunto dalla lettura di un articolo in More C++ Gems [1] per proporre questo simpatico esercizio didattico ai miei colleghi. Il primo obiettivo era quello di rinfrescare il livello dattenzione necessario per il C++, il secondo era quello di rispolverare un po le eccezioni ed il C++ in generale.

Ai fini dellesercizio si consideri che i puntatori abbiano una semantica di possesso e che Peppino sia monomorfica, cio che pp1 e pp2 puntino ad oggetti della classe Peppino e non di sue derivate. Si scriva loperatore di assegnamento per la classe. Ho dato un tempo di unora e mi sono messo in gongolante attesa dei risultati. Dal momento che lesercizio si presentava come innocentemente semplice ero sicuro di raccogliere presto interessanti spunti di discussione. Prima di procedere nella lettura invito il lettore a farsi unidea della soluzione.

Operatore di assegnamento e semantica di possesso


Passano pochi minuti e ricevo la prima frettolosa soluzione. Un rapido esame mi convince che ero stato un po troppo sintetico nel definire il problema e che questo aveva incoraggiato cattive idee:
1: inline operator = ( Pippo& a ) 2: { 3: 4: 5: 6: 7: 8: } a.pp1 = NULL; a.pp2 = NULL; pp1 = (Peppino *)a.pp1; pp2 = (Peppino *)a.pp2;

Il semplice esercizio
Ecco lesercizio che ho proposto ai miei colleghi con la scusa di togliere la ruggine alle proprie conoscenze di C++, promettendo come ricompensa una discussione in pubblico ed il raggiungimento di nuovi livelli di consapevolezza e padronanza dellidioma. Sia data la seguente classe:
class Pippo : public SuperPippo { Peppino* pp1; Peppino* pp2;

La prima di esse modificare largomento, cio dato


x = y;

// altri metodi e definizioni };

Massimiliano Pagani

mpagani@infomedia.it

y viene modificato. Ci sono almeno due buoni motivi per non farlo. Il primo di ordine pratico: se viene modificato il membro destro allora non possibile assegnare dei valori temporanei come in:
x = w + z;

Si occupa di ingegneria del software e di programmazione di videogiochi. Attualmente lavora in Ubi Studios s.rl. dove ha partecipato alla realizzazione di diversi videogiochi.

Infatti in questa espressione viene prodotto un

Computer Programming n. 139 - Ottobre 2004

39

C++
oggetto temporaneo contenente il risultato delloperazione w+z. Il secondo motivo legato al principio di least astonishment (sbigottimento minimo). Loperatore di assegnamento del linguaggio non modifica il membro di destra e quindi leggendo un pezzo di codice, senza conoscere come sono definiti gli operatori, ci si aspetta che il membro di destra non cambi nellespressione. Sarebbe analogo a definire come operator+() una divisione. Questo ci porta a definire loperatore di assegnamento come una funzione membro dalla firma:
Pippo& operator=( const Pippo& rhs );

Loperator=() deve ritornare un riferimento a *this perch cos diventa possibile concatenare gli assegnamenti come nellespressione:
x = y = z;

se SuperPippo in una invocazione esplicita. Purtroppo per questa soluzione destinata a fallire nel caso in cui la classe SuperPippo non dichiari il proprio operatore di assegnamento (anche se una cattiva idea non dichiararlo) e quindi si affidi a quello generato implicitamente dal linguaggio. La seconda soluzione aggira questo problema forzando il compilatore a considerare loggetto Pippo come un riferimento alla propria superclasse. Cos facendo, il simbolo = viene interpretato correttamente sia che la classe SuperPippo abbia definito un operatore di assegnamento o meno. Infine nellinverosimile, ma purtroppo possibile, caso in cui un oggetto venga assegnato a se stesso, capita che vengano persi i riferimenti agli oggetti puntati causando cos un memory leak. Per questo opportuno aggiungere un controllo che eviti questa infausta eventualit.

Seconda soluzione, entrino le eccezioni


Ecco la seconda soluzione che ho ricevuto:

Probabilmente era poco chiara anche la semantica di possesso o come direbbero gli anglofoni (o meglio gli anglografi) owning pointer semantic. Con questo si intende sostanzialmente che in ogni istante esiste un solo puntatore proprietario delloggetto puntato, gli altri riferimenti sono solo alias temporanei. Per esemplificare lowning pointer quello che riceve il riferimento dalla new (o comunque dalla factory) ed quello che viene passato alla delete. Non c nulla di particolare in questo, solo una politica di gestione della memoria dinamica che vuole minimizzare la possibilit di memory leak, esplicitando la responsabilit di deallocare i vari oggetti. Nellesercizio chiaro che se vogliamo preservare la semantica degli owning pointer nelloperatore di assegnamento necessario duplicare gli oggetti Peppino puntati da pp1 e pp2. Tornando al codice della prima soluzione, il cast alle linee 3 e 4 non necessario. A parte che in C++ il cast stile C deprecato, ma in questo caso assegniamo un puntatore a Peppino ad un altro puntatore a Peppino. Inoltre romper le scatole il giorno in cui si decider che pp1 e pp2 puntano ad oggetti non di tipo Peppino. Infine manca il return *this. Questa una dimenticanza abbastanza comune, che comunque ci viene prontamente segnalata dal compilatore, se loperatore di assegnamento stato dichiarato correttamente. Infine c unultima svista, questa volta piuttosto grave: Pippo eredita da SuperPippo! A differenza dei costruttori linvocazione delloperazione sulla classe padre non implicita. Lassegnamento della classe SuperPippo si pu introdurre in due modi.
SuperPippo::operator=( rhs );

1: Pippo& operator=( const Pippo& rhs ) 2: { 3: 4: 5: 6: 7: 8: } if( this == &rhs ) return *this; static_cast<SuperPippo&>(*this) = rhs; pp1 = new Peppino( *rhs.pp1 ); pp2 = new Peppino( *rhs.pp2 ); return *this;

Apparentemente in questa soluzione tutti i problemi della prima sono stati risolti: pp1 e pp2 hanno la semantica di owning pointer; la classe padre viene assegnata; se un oggetto viene assegnato a se stesso non si creano memory leak. Ma... che fine fanno gli oggetti Peppino che erano puntati da pp1 e pp2? Ops. Sarebbe meglio quindi inserire un paio di delete:
1: Pippo& operator=( const Pippo& rhs ) 2: { 3: 4: 5: 6: 7: 8: 9: 10: } if( this == &rhs ) return *this; static_cast<SuperPippo&>(*this) = rhs; delete pp1; delete pp2; pp1 = new Peppino( *rhs.pp1 ); pp2 = new Peppino( *rhs.pp2 ); return *this;

oppure:
static_cast<SuperPippo&>( *this ) = rhs;

La prima espressione pi chiara, loperatore di scope seleziona loperatore di assegnamento della clas-

Tutto bene dunque? Si, se nessuno lancia eccezioni. Prima per di analizzare la seconda soluzione opportuno aprire un po di parentesi sulle eccezioni e su come sia opportuno gestirle. Quando si parla di eccezioni ci sono due propriet rilevanti: exception neutral (neutrale alleccezione) e exception safe (sicuro nelleccezione). Con exception neutral si indica un metodo, una fun-

40

Computer Programming n. 139 - Ottobre 2004

PROGRAMMING
zione o per estensione una classe o una libreria che, in caso di eccezione lanciata dal codice chiamato, sia in grado di propagarla correttamente al codice chiamante. Con exception safe ci si riferisce ad un metodo, una funzione o per estensione ad una classe o libreria che si comporta bene quando si verificano delle eccezioni. Anche se non c un consenso unanime sulla definizione di Comportarsi Bene, lautorevole Guru of the Week dichiara [7]: Exception safe, garanzia base: In caso di eccezione non ci sono leak, gli oggetti rimangono in uno stato tale che il loro distruttore funziona correttamente. Tale stato valido anche se non necessariamente predicibile. Exception safe, garanzia forte: se durante unoperazione A viene lanciata uneccezione, allora lo stato dellintero programma ritorna nello stesso che aveva prima dellinvocazione di A. Per soddisfare questa garanzia necessario implementare una sorta di commit/roll-back transazionale in modo che le operazioni diventino atomiche: o tutto o niente. Per esemplificare si pensi ad un contenitore e alloperazione di inserimento di un oggetto. Soddisfa la garanzia forte se in caso di problemi durante linserimento non solo loggetto non viene inserito, il contenitore ancora buono, ma anche tutti gli iteratori istanziati nel programma continuano ad essere validi. Exception safe, garanzia no-throw: il codice non lancia mai e in nessun caso delle eccezioni (ad esempio gli auto_ptr della libreria standard). Forse superfluo dirlo, ma se non viene soddisfatta la garanzia base, allora meglio ignorare del tutto la gestione delle eccezioni perch solo tempo sprecato. Infatti se in caso di situazione anomala la logica che dovrebbe gestirla rovina lo stato degli oggetti e quindi del programma, non pi possibile recuperare dalleccezione. Con una rapida scorsa, in quanti punti del codice della seconda soluzione, riveduta e corretta, possono essere generate delle eccezioni? 2: guarda meglio (anchio avrei risposto cos prima di alcune sane letture [3][4]). 3: bene. 5: ottimo! 9: giusto, ma paranoico! La linea 3 inoffensiva: confronto tra due puntatori e ritorno per riferimento. La linea 4 ha una possibile eccezione generata nelloperatore di assegnamento della classe SuperPippo. Lo static_cast non lancia eccezioni in quanto non esegue nessun controllo a run-time (come invece fa il dynamic_cast). Le linee 7 e 8 possono lanciare due eccezioni a testa: pu fallire il new, ma pu anche fallire il costruttore di copia di Peppino. Le linee 5 e 6 possono lanciare eccezioni? Si, ma se capita Male. Si pu scrivere un distruttore con dentro un throw, ma se durante la gestione di uneccezione viene lanciata uneccezione, lo standard prevede che il programma sia terminato tramite una chiamata a terminate(). Infatti si pensi a quello che succede quando uneccezione attraversa a ritroso le funzioni chiamate: tutti gli oggetti automatici (cio quelli allocati sullo stack) che erano stati costruiti nella discesa vengono distrutti invocando il loro distruttore. Ma se uno di questi distruttori dovesse a sua volta lanciare uneccezione, ci sarebbero due eccezioni contemporaneamente attive da gestire. E dove andrebbe propagata la seconda eccezione? Verso lalto o verso il basso? Insomma dei grattacapi che nemmeno un linguaggio come il C++, storicamente forte di stomaco, se la sente di affrontare. Per quanto riguarda il delete, lo standard richiede che loperatore predefinito non lanci eccezioni, ma se il programmatore vuole definirlo per le proprie classi e metterci dentro un throw pu farlo (ah, la potenza del C++!). Naturalmente questo equivale a mettere un throw in un distruttore, e se proprio vogliamo farci del male esistono modi senzaltro pi piacevoli. Conclusione: la seconda soluzione non arriva alla garanzia base dellexception safety. ora quindi di passare alla terza soluzione.

Terza soluzione, verso la sicurezza


Ed ecco la terza soluzione:
1: Pippo& Pippo::operator=( const Pippo& rhs ) 2: { 3: 4: 5: 6: 7: 8: 9: 10: if( this != &rhs ) { delete pp1; delete pp2; pp1 = pp2 = NULL; try { static_cast<SuperPippo&>( *this ) = rhs; 11: 12: 13: 14: 15: 16: 17: 18: 18: 19: 20: 21: } } return *this; } } catch( ... ) { delete pp1; delete pp2; throw; pp1 = new Peppino( *rhs.pp1 ); pp2 = new Peppino( *rhs.pp2 );

Come abbiamo visto, inutile preoccuparsi delle possibili eccezioni lanciate dalloperatore delete (cio bene non farlo in questo momento, mentre dobbligo impegnarsi a scrivere distruttori che non lancino eccezioni). Nonostante ci, e nonostante luso di try e catch questa classe non soddisfa ancora la garanzia base. Se fallisce la seconda new (o lanciando un bad_alloc o perch il costruttore di copia fallisce), ci ritroviamo con un

Computer Programming n. 139 - Ottobre 2004

41

C++
oggetto mezzo copiato, infatti il costruttore di SuperPippo stato invocato e pp1 punta gi al valore nuovo, mentre pp2 NULL. A questo punto si entra nel catch e vengono deallocati e distrutti pp1 e pp2. Purtroppo loggetto adesso non pi distruggibile, visto che pp1 punta ad un oggetto che gi stato deallocato. Possiamo arrivare facilmente alla garanzia debole aggiungendo un assegnamento a NULL dei puntatori pp1 e pp2 tra le linee 17 e 18. Ma come si visto la garanzia forte non pu essere raggiunta perch lo stato delloggetto pu cambiare in caso di eccezione.

Semplicit, eleganza e correttezza


In barba allidea che per ogni problema esiste una soluzione semplice, elegante e sbagliata, possibile fare ancora di meglio senza barare nemmeno tanto. Definiamo un metodo swap in questo modo:
void Pippo::swap( Pippo& p ) { SuperPippo::swap( p ); std::swap( pp1, p.pp1 ); std::swap( pp2, p.pp2 ); }

Semplice bello
Le soluzioni sono andate sempre pi complicandosi, cosa tuttaltro che desiderabile per un banale operatore di assegnamento. Si pu sicuramente semplificare e la quarta soluzione procede in questa direzione:
1: Pippo& Pippo::operator=( const Pippo& rhs ) 2: { 3: 4: 5: 6: if( this != &rhs ) { static_cast<SuperPippo&>( *this ) = rhs; std::auto_ptr<Peppino> tmp1( new Peppino ( *rhs.pp1 )); 7: std::auto_ptr<Peppino> tmp2( new Peppino ( *rhs.pp2 )); 8: 9: 10: 11: 12: } } return *this; pp1 = tmp1.release(); pp2 = tmp2.release();

In buona sostanza questo metodo scambia il contenuto di due oggetti Pippo. Anche SuperPippo deve avere tale metodo, altrimenti necessario implementarlo. Per comodit esiste anche una swap nella libreria standard che garantisce di essere neutra alle eccezioni e addirittura di non lanciarne se viene invocata su tipi nativi (interi, float, ecc.) o su puntatori, cio su tutti i tipi per cui non possibile ridefinire loperatore di assegnamento. Dal punto di vista delle eccezioni il metodo Pippo::swap garantisce il no-throw nello stesso modo in cui lo garantisce SuperPippo::swap. Infatti lo scambio dei valori di puntatori (pp1 e pp2 sono puntatori) non pu lanciare eccezioni. In generale dovrebbe essere possibile (anche se magari non sempre semplicissimo) definire un metodo swap che garantisca il nothrow. Supponiamo quindi che SuperPippo::swap garantisca il no-throw. Loperatore di assegnamento potr essere scritto come:
1: Pippo& Pippo::operator=( const Pippo& rhs ) 2: { 3: 4: 5: 6: } Pippo tmp( rhs ); swap( tmp ); return *this;

In breve, un auto_ptr un template della libreria standard che garantisce di avere una sola istanza che punta ad un dato oggetto e che si occupa di distruggere loggetto puntato quando viene distrutto lauto_ptr [5]. In caso di eccezione verranno distrutti tmp1 e/o tmp2 e quindi, per il fatto che sono auto_ptr, il loro eventuale oggetto puntato. Se ad esempio la new Peppino alla linea 7 fallisce, loggetto allocato alla linea precedente non viene perso, perch sar distrutto dal distruttore di tmp1. Inoltre le operazioni con gli auto_ptr hanno garanzia no-throw e quindi alle linee 8 e 9 possiamo stare tranquilli. Per avere la garanzia forte comunque necessario anche in questo caso spostare lassegnamento della classe padre pi avanti ed esattamente tra le linee 7 ed 8. Luso degli auto_ptr ci salva da qualche try/catch per gestire la distruzione degli oggetti Peppino allocati, cos come luso dei temporanei ci permette di implementare una politica di commit/roll-back. Prima di proseguire, mi sembra opportuno fare una considerazione: la gestione delle eccezioni non qualcosa che si risolve aggiungendo artisticamente blocchi try/catch qua e l nellimplementazione o peggio in un secondo momento. Inoltre una cattiva gestione solo tempo perso, perch non darebbe nessun reale vantaggio in caso di situazione anomala [2].

Come funziona? Tramite la costruzione di una copia temporanea di rhs. Se il costruttore di copia di Pippo exception safe, allora loperazione alla linea 3 pu essere considerata atomica: se fallisce, il sistema rimane nello stato precedente allinvocazione delloperatore di assegnamento. La funzione swap non lancia eccezioni per come stata definita e quindi tutto ok. Si noti che anche il controllo di assegnamento sullo stesso oggetto non necessario, perch lassegnamento funziona comunque. Si potrebbe obiettare che il costrutto condizionale eliminerebbe la necessit di costruire inutilmente una copia, ma dal momento che lottimizzazione prematura la radice di tutti i mali [6], bisognerebbe vedere se in effetti risparmiare nelle rare volte che un oggetto viene assegnato a se stesso ripaghi di tutti i controlli fatti invece nel caso pi frequente. possibile semplificare ancora a scapito della leggibilit:

42

Computer Programming n. 139 - Ottobre 2004

PROGRAMMING
1: Pippo& Pippo::operator=( Pippo rhs ) 2: { 3: 4: 5: } swap( rhs ); return *this;

alla linea 9. Succede come per il caso precedente, ma questa volta il delete pp1 della linea 13 ha leffetto di evitare il memory leak. Il delete della linea 14 probabilmente non viene mai utilizzato perch lultima eccezione possibile appunto quella della linea 9 che impedisce la costruzione di *pp2. vero per che saggio metterlo nel caso si vogliano cambiare le cose in seguito.

Infatti non balza allocchio che il passaggio di rhs non avviene per riferimento, ma per copia e quindi il costruttore di copia nascosto nellinvocazione delloperatore. Di fatto rhs un oggetto autonomo che sar distrutto alluscita dalloperatore di assegnamento. A parte la spallata che viene data al principio di least astonishment (e quando mai si passa un oggetto per copia?) la controindicazione potrebbe essere che spostando il costruttore di copia nel codice chiamante si contribuisce allingrassamento del codice. Ho accennato che il costruttore di copia deve essere exception safe e quindi potrebbe essere definito come segue:
1. Pippo::Pippo( const Pippo& copy ) : 2. 3. 4. 5. { 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. } } } catch( ... ) { delete pp1; delete pp2; throw; try { pp1 = new Peppino( *copy.pp1 ); pp2 = new Peppino( *copy.pp2 ); SuperPippo( copy ), pp1( NULL ), pp2( NULL )

Conclusioni
stata una bella fatica scrivere questo operatore di assegnamento. E se loperatore di assegnamento richiede tutta questa fatica, si pu immaginare cosa voglia dire avere a che fare con funzioni pi complesse. Sostanzialmente le eccezioni rompono un po le uova nel paniere, perch rappresentano percorsi di esecuzione alternativi al flusso normale del programma che possono essere generati da tutto il dietro le quinte del C++ (sostanzialmente operatori ridefiniti e costruttori). Luso alla leggera di eccezioni e try/catch di per s non ha nessun vantaggio, perch se il codice non fornisce le garanzie esposte sopra, allora tutto sforzo inutile. Si potrebbe essere tentati anche di ignorare la gestione delle eccezioni, perch se andato male qualcosa allora molto probabilmente il sistema gi compromesso. Questo potrebbe essere vero in alcuni casi, ma non in generale: se, dopo aver finito di scrivere larticolo, comando al word processor di stampare, vorrei comunque non perdere il mio lavoro in un crash dovuto alla stampante non collegata. Dopo varie discussioni con i colleghi ci sembrato pi o meno sensato richiedere neutralit e garanzie forti ai componenti base, quelli che verranno riutilizzati pi spesso, una garanzia debole al resto e al limite ignorare il problema nel codice applicativo di livello pi alto. Desidero ringraziare i miei colleghi ed in particolare Alberto ANSI C++ Barbati che mi ha fornito i comportamenti previsti dallo standard e Stefano Chiappa che mi ha suggerito di trasformare la discussione in questo articolo.

Per come definito ricorda un po le prime soluzioni, ma in questo caso le preoccupazioni sono minori perch basta concentrarsi sullobiettivo di non perdere le risorse allocate senza preoccuparsi di lasciare loggetto in uno stato valido e distruggibile. Infatti se il costruttore fallisce a causa di uneccezione, il linguaggio si premura di non invocare il relativo distruttore e quindi tutta la logica di gestione interna al metodo. Ricapitolando, le eccezioni nel costruttore di copia possono essere sollevate: alla linea 2. In questo caso il costruttore viene abbattuto e nessun oggetto creato. Ovviamente se il costruttore di copia di SuperPippo exception safe allora lo anche questo. alla linea 8. In questo caso leccezione viene intercettata dal catch e i delete alle linee 13 e 14 non hanno effetto perch pp1 e pp2 sono stati inizializzati precedentemente a NULL. Leccezione viene poi rilanciata (linea 15) e quindi il SuperPippo che era stato costruito viene distrutto.

BIBLIOGRAFIA & RIFERIMENTI


[1] AAVV, More C++ Gems, Cambridge University Press/SIGS, 2000 [2] T. Cargill, Exception Handling: A False Sense of Security, C++ Report Nov./Dec. 1994. Disponibile anche su internet a http://www.awl.com/cp/mec++cargill.html [3] H. Sutter, Exceptional C++, Addison Wesley 2000. [4] R. Gillam, The Anatomy of the Assignment Operator, in [1], disponibile anche on-line: http:// www.concentric.net/~rtgillam/pubs/assign.html [5] N. M. Josuttis, The C++ Standard Library, Addison Wesley, 1999 [6] D. Knuth, Literate Programming, Cambridge University Press, 1992 [7] http://www.gotw.ca/gotw/059.htm

Computer Programming n. 139 - Ottobre 2004

43

Вам также может понравиться