Newsgroup: it.comp.lang.c++
Titolo: Implementazione del metodo Clone()

Salve.
Ho due classi:
class A {
  private:  
    int i;
};
class B: public A {
  private:
    float f;
};
Come si implementa il metodo B::Clone() per ottenere una copia identica
di un'istanza di B?
Grazie,
Marcello.

 
 B* B::clone() { return new B(*this); }
Ovviamente B deve disporre di un opportuno copy ctor.
Se devi farlo per molte classi, forse ti conviene fare così:
 template< class DERIVED >
 class clonable
 {
   public:
   DERIVED* clone() { 
      return new DERIVED(static_cast<DERIVED&>(*this)); 
   }
 };
Quindi dichiari la tua classe così:
 class B : public A, public clonable<B>
 {
 };
Max

 di un'istanza di B?
Da quello che ne so, non ci dovrebbero essere problemi a istanziare una
seconda o ennesima "b"
Quindi qualcosa del tipo...
void B::Clone()
{
B nuovaistanza = new B;
}
...dovrebbe funzionare.
Sbaglio ?

 Ovviamente B deve disporre di un opportuno copy ctor.
solo B o anche tutti gli antenati?
Grazie,
Ciao.

On Fri, 16 Jun 2000, Marcello Esposito wrote:

Beh, se puoi fare:
  B b1
  B b2( b1 );
il metodo clone() proposto funziona. Se per qualche ragione il copy ctor è
stato reso "private" il metodo clone() continua a funzionare, mentre il
codice qui sopra non si lascerà compilare.
Se lasci che sia il compilatore a sintetizzare il copy ctor per te, ti
troverai un qualcosa che userà implicitamente il copy ctor delle classi
base unitamente ai copy ctor dei membri dati. Diversamente puoi
implementartelo da te invocando esplicitamente il copy ctor delle classi
basi nella initialization-list.
Max

  };
Perché quel cast?
EMG

 Sbaglio ?
Ma cosě la nuova istanza non ha lo stesso stato dell'oggetto corrente.
EMG

 }
Che fine fa nuovaistanza?? (e poi dovresti scrivere B * nuovaistaza.....)


Perchè altrimenti non compila. In quel contesto this è di tipo
clonable<DERIVED>*, sebbene sia garantito che punti di fatto a
un'instanza di DERIVED.
Max

 un'instanza di DERIVED.
Più che giusto.
EMG

 EMG
:)))) Beh, per clone io non avevo capito che doveva avere anche lo stesso
stato. Intendi le propieta' valorizzate allo stesso modo, vero ?
Beh, allora non saprei come fare, se non inserirle un metodo
InizializzaDaPadre(puntatore alla classe padre); che imposta tutte le
proprieta' allo stesso modo della classe che l'ha lanciata.
Risbaglio ?



 Risbaglio ?
Come hanno già detto altri, basta utilizzare un opportuno costruttore di
copia.
EMG

copia.
Ti chiedo scusa. Ho cambiato da poco newsreader, e trovo parecchia difficolta' a
seguire i thread composti da messaggi postati da outlook.
'Windows' e' una antica parola Apache, che significa:
"Uomo bianco che osserva una clessidra attraverso un vetro"

 al momento della clonazione (dimensioni, proprieta' fisiche...)
Va beh, ma qui si parlava di clone in senso informatico.
EMG

};
Come si puo' fare per rendere clonabile un'intera gerarchia?
Cioe', supponiamo di voler dotare di questa caratteristica (l'essere
clonabile) non solo B, ma anche tutti i suoi derivati; o almeno alcuni
di essi.  Immaginiamo una classe C che discenda in qualche modo dalla
gia' nota B.  Cio' a cui mi piacerebbe arrivare e' piu' o meno questo:
B* p1 = new C;
B* p2 = p1->clone();  // ...che p2 punti ad una nuova istanza di C
Al limite, se l'oggetto di classe C non fosse capace di clonarsi, il
metodo clone() potrebbe sollevare un'eccezione oppure ritornare un
sempre-ben-accetto NULL.  Cio' che proprio non dovrebbe fare e' creare
un oggetto crudamente di tipo B senza dire niente a nessuno!
...ehm, ha senso tutto questo ?
Ciao,
Paolo Galassi

 ...ehm, ha senso tutto questo ?
Certo.  Ma l'eredita` di implementazione non ti permette di farlo
nel modo 'ovvio': se B e` una classe concreta, e implementa un
metodo clone, ogni C che deriva da B eredita l'implementazione
di clone dalla B stessa; puo` farne override, ma non puoi IMPORRE
l'override stesso.  Devi dunque trovare un approccio che NON si
erediti, o almeno controllare a runtime che non vi sia stata
questa indesiderata eredita` di implementazione!  (Sarebbe, ovvio,
molto piu` facile adottando la regola d'oro di Scott Meyers: MAI
fare ereditare una classe concreta da un'altra classe concreta;
ereditare SOLO da classi astratte).
Questo puo` essere ad esempio verificato usando per la
"clonazione" una *funzione libera*, NON virtuale, che si
APPOGGIA si` su di un metodo virtuale, ma ne _controlla il
risultato_ a runtime.
Ad esempio...:
class B {
private:
    // gli eredi *DEVONO* fare override...:
    virtual B* _clone() { return new B(*this); }
public:
    // punto di accesso del codice-cliente alla clone:
    B* clone()
        B* result=this->_clone();
        if(typeid(*result)!=typeid(*this))
            throw CloneNotOverriddenException(typeid(*this)->name());
        return result;
    }
};
Sollevare eccezioni a runtime per errori di programmazione
di questo tipo non e` certo soddisfacente: sarebbe assai
meglio ottenere errori a tempo di compilazione.  Purtroppo,
non e` banale avere questo risultato in C++ per questo caso,
e tutto a causa dell'eredita` di implementazione.
Un approccio potrebbe essere quello di rendere B *NON*
"direttamente ereditabile da chiunque".  Ad esempio, una
classe con un distruttore privato non e` direttamente
ereditabile (ci sono anche altri effetti collaterali: non
e` creabile in modo auto, ad esempio).  Con un "friend
template" si potrebbe poi "decorare" B, per renderla
ereditabile, e simultaneamente rendere nuovamente pura
la virtual clone, cosi` da garantire che l'erede la
implementi.  Ma questi approcci hanno sempre qualche
limite: qui, ad esempio, nulla ci garantisce che chi
eredita` renda a propria volta non ereditabile la classe
concreta che sta definendo!-(.  In generale, il "braccio
di ferro" fra chi definisce un framework e vuole obbligare
i programmatori-utenti a rispettare certe regole, e questi
utenti che non ne vogliono sapere, e` una gara senza ne`
vinti ne` vincitori.  Meglio, tutto sommato, stabilire certe
regole e protocolli PER CONVENZIONE, anche se il linguaggio
non permette di IMPORLE ai programmatori-clienti, e la
prima di queste regole dovra` allora certo essere la gia`
citata "regola d'oro di Scott Meyers", "niente eredita` da
classi concrete", che risolve tutte queste iatture e tante
altre ancora.
Alex

 ...ehm, ha senso tutto questo ?
Se la classe C ha ridefinito il metodo clone() allora il tutto dovrebbe
già funzionare, tranne l'eccezione o il NULL. Infatti se p1 è un
puntatore a B ma punta ad un C e se il metodo clone() è virtuale allora
scrivendo
p1 -> clone();
viene chiamato proprio il clone() di C. Se C non ridefinisce clone()
allora viene ovviamente chiamato quello di B che a questo punto non
saprebbe capire se deve clonare un B o lanciare un'eccezione (o almeno
non so farlo io). Al limite si potrebbe ridefinire clone() anche per le
classi che non si sanno clonare e fare in modo che lanci l'eccezione o
restituisca NULL.
EMG

"Uomo bianco che osserva una clessidra attraverso un vetro"
Bellissima ............. Dimmi che e' vera !!

 };
Volevo chiederti se una soluzione come quella che segue presenta qualche
svantaggio rispetto alla tua e se si quali:
#include <iostream>
#include <typeinfo>
using namespace std;
class A
{
    public:
        A( int n ): n( n ) {}
        virtual A *clone()
        {
            if ( typeid( *this ) != typeid( A ) )
                throw "clone() must be overridden!";
            return new A( *this );
        }
        virtual void print() { cout << "A: " << n << endl; }
    private:
        int n;
};
class B: public A
{
    public:
        B( int n ): A( n ), n( 55555 ) {}
//      B *clone() { return new B( *this ); }
        void print() { A::print(); cout << "B: " << n << endl; }
    private:
        int n;
};
int main()
{
    A a( 12345 );
    a.print();
    A *pa = a.clone();
    pa -> print();
    B b( 54321 );
    b.print();
    try
    {
        A *pb = b.clone();
        pb -> print();
    }
    catch ( char *msg )
    {
        cout << msg << endl;
        return 0;
    }
}
EMG

"Uomo bianco che osserva una clessidra attraverso un vetro"
MAGARI!! Secondo me significa:
Uomo bianco che osserva uno schermo blu :-)

                 throw "clone() must be overridden!";
    [snip]
Considera il fatto che l'eredita` puo` avvenire su piu` livelli.
Col mio approccio:
    class X1: public B {    // corretta
        virtual B* _clone() { return new X1(*this); }
        // ecc ecc
    };
    class X2: public X1 {    // errata
        // *NON* fa override di _clone (errore)
        // ecc ecc
    };
B* pB = new X2;
B* pB1 = pB->clone();    // errore correttamente diagnosticato a runtime
Spero che ti sia chiaro come l'analogo caso con il tuo approccio
non permetta invece la corretta diagnostica...:
    class Y1: public A {    // corretta
        virtual A* clone() { return new Y(*this); }
        // ecc ecc
    };
    class Y2: public Y1 {    // errata
        // *NON* fa override di clone (errore)
        // ecc ecc
    };
A* pA = new Y2;
A* pA1 = pA->clone();    // errore NON diagnosticato a runtime
Puo` essere istruttivo capire "il perche`" in modo da evitare
limitazioni del genere in altri simili casi in futuro.
In entrambi gli approcci, il metodo "clone" usato dal codice
cliente svolge *DUE* compiti:
    a -- provvede a clonare questo oggetto, *E*
    b -- verifica che il runtime-type sia corretto (tu lo
            verifichi su *this prima di clonare; io, sul
            clone, dopo avere clonato).
Fra i nostri due approcci ci sono due differenze.  Una,
quella indicata dalla parentesi sub [b], e` che tu verifichi
una pre-condizione (che il tipo runtime di *this sia proprio
A), da cui SUPPONI che consegua la condizione effettivamente
richiesta (che il tipo runtime dell'oggetto ritornato sia
identico al tipo runtime di *this); io, piu` direttamente,
verifico direttamente la post-condizione richiesta.  Quindi
e` piu` agevole "vedere" la correttezza nel mio caso, perche`
non c'e` di mezzo lo scomodo passaggio logico di dimostrare
che la pre-condizione (verificata) implichi davvero la post-
condizione (richiesta) [e in effetti, l'implicazione non vale].
Questo *NON* significa che in generale il controllo delle
pre-condizioni sia un approccio errato, anzi.  Ma il punto
e` che non puo` PRENDERE IL POSTO del controllo delle
post-condizioni... puo` solo AFFIANCARVISI come utile mezzo
per diagnosticare con precisione la RAGIONE di un certo
fallimento in casi piu` complessi!  Nel "modello per
contratto" della programmazione, questo e` un aspetto davvero
cruciale.  Il ruolo di un certo brano di codice e` di
ASSICURARE che, SE le pre-condizioni sono inizialmente
soddisfatte, lo saranno al suo termine le post-condizioni.
NON e` suo ruolo verificare l'antecedente (al massimo e`
un "in piu`" per aiutare a debuggare i clienti); lo e`,
invece, verificare (idealmente, "dimostrare") il
conseguente.
L'altra differenza e` a mio vedere ancora piu` importante.
Dato che devo svolgere due compiti, scrivo due funzioni:
una svolge il solo compito [a]; l'altra DELEGA alla prima
il compito [a] e svolge invece il compito [b].  Tu svolgi
entrambi i compiti nella STESSA funzione.  _Idealmente_,
"una funzione E` una funzione e HA *una* funzione", dove il
termine "funzione" e` usato in due significati diversi,
leggendo "avere una funzione" come "svolgere UN compito".
Raramente si e` "puristi" sino a questo punto (anche il
mio approccio non lo e`, se no la clone() pubblicamente
visibile avrebbe come suo unico compito quello di
"coordinare il flusso" e chiamerebbe DUE altre funzioni,
ciascuna per svolgere uno dei due compiti elementari) ma
e` importante tenere a mente questo "ideale", e questo
soprattutto quando sono in gioco dei possibili 'override',
visto che questi avvengono appunto funzione per funzione.
Ora, in questo caso, ad esempio, noi VOGLIAMO che chi
eredita da questa classe-base faccia l'override di uno
di questi "compiti" (DEVE provvedere a generare il clone
"in proprio", NON contare su eredita`-di-implementazione),
e NON vogliamo che faccia l'override dell'altro (il
controllo della correttezza dell'operazione deve invece
restare proprio come noi l'abbiamo scritto).  Se mettiamo
ambo i compiti nella stessa funzione, questo obbiettivo
non e` chiaramente perseguibile.  "Atomizzando" in modo
corretto, "una funzione per ciascun compito", siamo
invece proprio nella posizione ideale per perseguire il
nostro obbiettivo.
Ancora meglio, in effetti, e` l'atomizzazione completa:
class C {
private:
    // gli eredi *DEVONO* fare override...:
    virtual C* _clone() { return new C(*this); }
protected:
    // gli eredi *POSSONO* fare override...:
    virtual C* check_clone(C* theclone)
         if(typeid(*theclone)!=typeid(*this))
             throw CloneNotOverriddenException(typeid(*this)->name());
         return theclone;
    }
 public:
     // punto di accesso del codice-cliente alla clone:
     // gli eredi NON POSSONO fare override...:
     C* clone()
     {
         C* result=this->_clone();
         return check_clone(result);
     }
     // ecc, ecc
};
Questo PERMETTE, ad un erede che SA cosa sta facendo, di
imporre un ESPLICITO override della funzionalita` di
controllo, che (correttamente atomizzata) e` esposta
appunto come metodo virtuale check_clone.  Notiamo che
_clone e` privata perche` gli eredi NON devono fare
riferimento alla versione della classe-base mentre
check_clone e` protected perche` un simile riferimento
e` invece del tutto ammissibile.  Un'ulteriore passo
di atomizzazione potenzialmente utile sarebbe certo
possibile, realizzando che in un certo senso anche la
check_clone ha DUE compiti:
    -- verificare che il clone sia corretto,
    -- "fare qualcosa" se non lo e` per segnalare il fatto
e anche questi due aspetti sono considerabili in modo
separato: chiaramente, un erede potrebbe avere le sue
condizioni (magari addizionali a quelle controllate
nella classe base) ma accettare il "qualcosa" (cioe` lo
specifico throw) che avviene a condizioni violate, oppure
al contrario accettare esattamente i controlli che fa la
classe base ma volere un comportamento diverso se questi
falliscono (ad es. un return 0 senza throw), o anche puo`
voler operare su entrambi i compiti di controllo (cioe`
sia sulla verifica, sia sulla segnalazione da darsi se e
quando la verifica fallisce).
Ma e` importante anche ricordare che fra le principali
caratteristiche del buon progettista c'e` un "buon gusto",
una capacita` di apprezzare l'estetica complessiva di un
progetto, che include la compresione di "dove fermarsi"
nell'applicazione di certi ottimi principi.  In questo
caso, puo` essere del tutto accettabile che il progettista,
*perfettamente cosciente* della possibilita` di continuare
a spingere avanti la "atomizzazione" del compito, DECIDA
di fermarsi a un certo punto perche` "proprio li`" il suo
progetto ha raggiunto il "giusto" equilibrio, il "ritmo"
(nel senso architettonico del termine) esatto.  L'estremismo
e` un'ottima cosa purche` applicato con moderazione:-).
Notiamo infine quanto naturalmente emerga da casi come
questo la design-pattern "Template": la classe-base che
offre un servizio agli eredi in termini di un metodo NON
virtuale che chiama 'in giusta sequenza' tutta una
serie di metodi virtuali, atomizzati, corrispondenti a
"singoli passi" di una complessiva operazione.  Cfr ad es.
http://exciton.cs.oberlin.edu/javaresources/DesignPatterns/TemplatePattern.h
tm
Alex

Sto maturando la convinzione che se tu raccogliessi una buona parte dei tuoi
mail in un libro e lo facessi pubblicare - ovviamente in italiano - con il
titolo "Lo zen e l'arte del C++ & OO" guadagneresti molti più soldi (e senza
alcuna fatica aggiuntiva) di quelli, molti, che già guadagni ora.
Resto in fiduciosa attesa di una, qualsivoglia, forma di ringraziamento nel
caso l'idea abbia un seguito ;-)
MM

 caso l'idea abbia un seguito ;-)
Grazie sin da ora dell'implicito complimento.  Quanto al "seguito",
dubito che vi sara`, ma se dovesse esserci ti prometto una menzione
prominente sulla pagina degli acknowledgment!-)
Alex

Se l'inglese non ti preoccupa, potresti contattare l'ACCU su
http://www.accu.org
; offrono aiuto su molti degli aspetti collegati.
Ciao,
Nicola

 [...]
Bellissimo articolo. Tutto chiaro. Complimenti.
EMG

tante altre ancora.
Se ho capito bene questo significa che ogniqualvolta si presenti la
necessita' di derivare da una classe concreta bisogna mettere le mani
nella base, cioe' alla stessa classe concreta dalla quale si sta
derivando, per trasformarla in una classe astratta.  Dalla nuova base
astratta si derivano poi due classi: la prima per poter riutilizzare
la base qual era prima, la seconda per soddisfare le esigenze che ci
avevano spinto inizialmente a derivare.
E' cosi' ?
Se mi sono espresso male dite pure, che' provo a chiarire...
Paolo

 alcuna fatica aggiuntiva) di quelli, molti, che già guadagni ora.
Sono perfettamente d'accordo. Ogni messaggio di Alex deve essere oggetto
di studio approfondito. Per quanto riguarda il tipo di libro, mi
verrebbe in mente una impostazione tipo le lezioni di fisica di Richard
Feynman. :-)
EMG

 Se mi sono espresso male dite pure, che' provo a chiarire...
No, sostanzialmente e` cosi`.  La rifattorizzazione puo` fruttuosamente
essere spinta piu` avanti, separando il 'riuso' (per polimorfismo) di
*interfaccia* (da esporre come classe puramente astratta) da quello di
codice (implementazione parziale di quella interfaccia) da esprimersi
in vari possibili modi (l'eredita` di implementazione puo` di solito
essere fruttuosamente sostituita con la "Pattern Without A Name" di
Coplien in qualche variazione; ma visto che questa 'sposa' l'eredita`
con usi avanzati dei template, forse non e` proprio chiarissima per
chi i template non li mastica molto).
Alex