Newsgroup: it.comp.lang.c++
Titolo: Differenza tra costruttorie di copia ed assegnazione

Ciao a tutti,
chi mi da delucidazioni su quanto in subject ?
Grazie, Max

 chi mi da delucidazioni su quanto in subject ?
Il costruttore di copia e` un costruttore e quindi
opera su di un oggetto (*this) ''nuovo''.  Dovra`
chiamare opportunamente i costruttori di basi e
membri, e cosi` via.  Esempio "tipico":
class UnOggetto: public UnAltro {
    UnAltroAncora plop;
public:
    UnOggetto(const UnOggetto& altro):
        UnAltro(altro),
        plop(altro.plop)
    {
    }
};
Nota che il body del copy ctor e` vuoto: caso molto
tipico, visto che i ctor delle basi e dei membri sono
chiamati PRIMA dell'inizio del body stesso.  Se i
ctor stessi sono ben fatti, tipicamente resta poco
o nulla d'altro da fare nel body del ctor.
Naturalmente, se da fare non vi e` proprio nulla,
si puo` evitare di scriverlo:-).  Il compilatore
genera automaticamente un copy ctor che fa proprio
esattamente solo questo -- costruisce ogni base
col copy ctor dell'oggetto-argomento ('altro'),
poi costruisce ogni membro idem idem.  Tipicamente
se puoi lasciar scrivere il copy ctor al compilatore
e` meglio non scriverlo tu (dipende dal compilatore,
ma non ti stupire se e` piu` veloce quello 'implicito'
che si scrive lui se tu non provvedi:-).
L'assignment opera su di un oggetto *this che gia`
vive e opera, magari da un pezzo.  Nulla in questo
caso ti assicura contro l'anomalia:
    x = x;
Tu dirai, ma chi e` il programmatore scemo che
scrive questo... risposta: succede!  Magari da
qualche macro o template o che so.  Tu accertati
(nell'assignment -- nel copy ctor il rischio non
c'e`:-) di NON fare disastri se vieni "assegnato
a te stesso".  Idiomaticamente:
    void operator=(const LaClasse& altro)
    {
        if(this == &altro) return;
        // il resto del copy assignment
    }
Non e` sempre necessario questo controllo, per
carita`.  Ma se non sei sicuro, non guasta:-).
Ci sono alcuni idiomi alternativi, usati da
diverse librerie e framework, fra i quali puoi
dover fare "adattamento d'impedenza" nei tuoi
metodi canonici.  Ad esempio, uno stile prevede
l'inizializzazione "semantica" come parte dei
metodi canonici (default ctor, copy ctor, dtor,
...); un altro invece (e ha i suoi vantaggi)
segue l'idioma "two-phase" -- ad esempio il
ctor e` praticamente un noop e la vera e propria
inizializzazione avviene ad es. con un metodo
.create() da chiamare esplicitamente, idem per
un metodo .close() che fa la finalizzazione vera
e propria mentre il dtor fa poco e nulla, ecc.
Se devi adattare un membro che viene da una
libreria che usa two-phase, ad un framework
che invece prevede un carico semantico sui
metodi canonici, un tipico idioma di adapter
"boilerplate" ti puo` tornare comodo...:
    class Esempio {
        Tipo a;
        Tipo b;
        Tipo c;
        void libera() {
            a.close();
            b.close();
            c.close();
        }
        void inizia() {
            a.create();
            b.create();
            c.create();
        }
        void carica(const Esempio& altro) {
            a.copy(altro.a);
            b.copy(altro.b);
            c.copy(altro.c);
        }
    public:
        Esempio () { this->inizia(); }
        ~Esempio() { this->libera(); }
        Esempio(const Esempio& altro) {
            this->carica(altro);
        }
        void operator=(const Esempio& altro) {
            if(this == &altro) return;
            this->libera();
            this->carica(altro);
        }
    };
Alex (MVP di Brainbench per il C++)



La differenza c'é ed é grossa: mentre il costruttore di copia lavora
su di un oggetto nuovo l'overload dell'operatore di assegnamento opera
invece quando si assegna ad un oggetto giŕ inizializzato il valore di
un altro oggetto.
Un esempio..
class bastone
    {
    private:
        int lunghezza;
        int durezza;
    public:
        bastone(int lunghezza_,int durezza_) :
lunghezza(lunghezza_),durezza(durezza_) {}
        bastone(const bastone &bas1) :
lunghezza(bas1.lunghezza),durezza(bas1.durezza) {}
        void operator=(const bastone &bas1)
            {
            if(this == &bas1)return;
            lunghezza=bas1.lunghezza;
            durezza=bas1.durezza;
            }
    };
int main()
    {
    bastone bastone1(10,5);
    //uso costruttore di copia.    
    bastone bastone2=bastone1;
    bastone bastone3(5,5);
    bastone bastone4(2,2);
    //uso overloading operatore assegnamento.
    bastone4=bastone3;
    return 0;
    }
Da notare che il compilatore fornisce giŕ un costruttore di copia ed
un overload dell'operatore di assegnamento di default, ma se la classe
fa uso di puntatori costruttore e operatore di assegnamento devono
sempre essere definiti dal programmatore(assicurandosi che venga
allocata nuova memoria)in quanto in caso contrario si creerebbero
delle area di memoria condivise da piů istanze che ovviamente
renderebbero il programma inconsistente e lo portebbero sicuramente a
crashare in determinati punti(come ad esempio quando vengono chiamati
i distruttori).
In questi casi é buona regola che il costruttore di copia e l'overload
dell'operatore di assegnamento vadano insieme, se si definisce uno di
essi é doveroso definire anche l'altro.
Il Ghenio(C++, C , PHP and VB programmer)

 essi é doveroso definire anche l'altro.
insieme anche al distruttore...e cosi' abbiamo completato il terzetto!  :-)
--
Ciao by Beppe