Newsgroup: it.comp.lang.c++
Titolo: differenze tra assegnazione e costruttore di copia

Salve. 
Mi spiegate qual'č la differenza tra l'assegnazione e il costruttore
di copia?
Entrambi operano ( se non "sovraccaricati" ) un assegnazione campo a
campo di due oggetti, ed entrambi ( se "sovraccaricati" ) operano
secondo le esigenze del programmatore. L'unica cosa che posso notare
che il costruttore di copia puň essere usato solo durante la creazione
di un oggetto, mentre l'assegnazione in qualunque parte del programma.
Ma se č cosě, mi spiegate quando sono utili i costruttori di copia?
Grazie.
Giacomo


Il costruttore di copia interviene nei tre seguenti casi:
1. Costruzione con copia (ovviamente). Es.:
   T a;
   T b( a );
2. Passaggio di parametri per valore. Es.:
   T a;
   pippo( a );
3. Restituzione di valore. Es.:
   T pluto()
   {
       T a;
       return a;
   }
Sono utili sostanzialmente quando non tutti i membri di una classe
possono essere copiati per valore (es. un puntatore per il quale si deve
copiare anche la memoria a cui punta).
EMG

 Ma se č cosě, mi spiegate quando sono utili i costruttori di copia?
Come tutti i costruttori, sono utili per costruire oggetti che
prima NON erano stati costruiti.  Spiacente se sembra lapalissiano,
ma e` proprio cosi`, tutto cosi`, e solo cosi`.
Proviamo a fare un esempio.  Diciamo che ci sono oggetti ausiliari,
di classe Pippo, che mantengono reference count (gestito con
metodi AddRef e Release).  Vogliamo scrivere una classe Pluto:
ogni Pluto punta ad un Pippo, e vari Pluto possono condividere
lo stesso Pippo grazie al reference count di quest'ultimo.
class Pippo {
    long rc;
public:
    Pippo(): rc(1) {}
    void AddRef() { ++ rc; }
    void Release() { if(0==--rc) delete this; }
    // ecc ecc
};
class Pluto {
    Pippo* p;
public:
    // il default ctor:
    Pluto(): p(new Pippo) {}
    // il copy ctor:
    Pluto(const Pluto&pp): p(pp.p) { p->AddRef(); }
    // il dtor:
    ~Pluto() { p->Release(); }
    // l'assegnazione:
    void operator=(const Pluto&pp) {
        if(p!=pp.p) {
            p->Release();
            p=pp.p;
            p->AddRef();
        }
    }
    // ecc ecc
};
Come vedi, il copy ctor si occupa di costruire un NUOVO
oggetto Pluto dove prima c'erano solo 'bit spazzatura'.
Il campo p del nuovo oggetto non e` inizializzato quando
il copy ctor (o qualsiasi altro ctor) comincia ad eseguire
(con l'initializer-list).  Bisogna dunque dargli un valore
e NON leggere il precedente, poiche` non ESISTE nessun
"precedente".
L'assignment invece si occupa di modificare un oggetto
Pluto gia` esistente.  Il suo campo p dunque gia` esisteva
e puntava ad un certo Pippo: e` importante, se questo p
deve cambiare, che sia chiamata la Release del p vecchio
prima di sovrascrivere il cambio.
Nota, incidentalmente, che orrendo, sciocco, assurdo spreco
e` scrivere, ad esempio:
    void plip1(const Pluto& q1) {
        Pluto q2;    // default ctor
        q2 = q1;     // assignment a seguire
        // ecc ecc
    }
invece di:
    void plip2(const Pluto& q1) {
        Pluto q2(q1);    // copy ctor
        // ecc ecc
    }
La plip1 crea un nuovo Pippo (nel default ctor di q2)
solo per buttarlo via subito dopo (nell'assegnazione),
quando e` ovviamente tanto piu` semplice riusare subito
il Pippo gia` presente in q1 (come fa la plip2).
Perche` un programmatore, che sceglie di usare un
linguaggio cosi` tremendamente complesso come il C++
(che ha dalla sua l'efficienza raggiungibile), butta
poi alle ortiche l'efficienza con disastri come la
plip1...?  Misteri.  Secondo me il problema e` che il
copy ctor, invece che nel modo usato in plip2, si puo`
anche invocare con la sintassi, forse piu` 'bella'...:
        Pluto q2 = q1;    // sempre copy ctor e`!
che CONFONDE LE IDEE poiche` ASSOMIGLIA alla sintassi
dell'assegnazione -- quel dannato 'equale' in mezzo!-)
Alex