Newsgroup: it.comp.lang.c++
Titolo: Costruttore di Copia - Fine della Telenovela

Tutto ? partito da Andrea Orlando il quale ha manifestato alcune
perplessit? su un programmino che si avvaleva di una classe pointer.
Analizzando il programma mi sono accorto che non poteva essere compilato
in quanto c'erano alcuni errori sintattici ma anche concettuali.
Le prime risposte al suddetto, non focalizzavano i problemi ma
accusavano indistintamente la mancanza della ridefinizione
dell'assegnazione.
Quasi in maniera PROVOCATORIA ho dimostrato con una mia soluzione (senza
tale ridefinizione) che i problemi erano altri e che una volta corretti
,il programma funzionava tranquillamente anche col solo = di default.
Chiarito tutto ci? adesso passiamo invece al perfezionamento del tutto:
hai perfettamente ragione quando affermi che una volta corretto il
programma, affinch? possa essere compilato, poi diventa rischioso non
ridefinire anche l'operatore =.
In generale ? obbligatorio sempre ridefinirlo quando si usa lo schema
realizzativo "Side - effect senza condivisione di memoria" e si ha a che
fare con allocazione di memoria dinamica nelle classi, per evitare
interferenze.In effetti la delete su memoria gi? deallocata da risultati
imprevedibili e pur sapendo ci? non lo ho sottolineato con la stessa tua
energia, ingannato dal fatto che sia in ambiente Dos che sotto Linux
funzionava bene.
Quando per? ho usato il Borland c++ 4.5 sotto win 98 , il crash era
inevitabile.
Dato che la mia soluzione che aveva lo scopo di portare la discussione
in "carreggiata", ha invece scatenato un putiferio,chiedo ammenda e
completo la classe anche con l'agognata ridefinizione.
Et voil?:
#include <iostream.h>
#include <string.h>
#include <conio.h> //no sotto Linux
#ifndef COSTR_H
#define COSTR_H
class pointer
{ private: char* psStringa;
  public:
  pointer()     //costruttore senza argomenti
  {if (psStringa)
      { this->~pointer();//chiamata al distruttore
         psStringa=0;
      }
  }
  pointer(char * S)         //costruttore unario
  { psStringa = new char[strlen(S)+1];
     for(int k=0;k<=strlen(S);k++)
     psStringa[k]=S[k];
  }
  pointer(const pointer &p)   //costruttore di copia
  { psStringa = new char[strlen(p.psStringa)+1];
     for(int k=0;k<=strlen(p.psStringa);k++)
     psStringa[k]=p.psStringa[k];
     cout<<"chiamato il costruttore di copia"<<endl;
  }
  pointer& operator=(const pointer &p)   //assegnazione
  { char* temp=this->psStringa;
     psStringa = new char[strlen(p.psStringa)+1];
     for(int k=0;k<=strlen(p.psStringa);k++)
     psStringa[k]=p.psStringa[k];
     delete temp;
     cout<<"chiamata assegnazione"<<endl;
     return *this;
  }
  ~pointer()   //distruttore
  {  delete psStringa;
      psStringa=0;
  }
  friend ostream& operator << (ostream& os, const pointer &p)
  { return os <<"Valore della Stringa: "<<p.psStringa;}
};
#endif
void main ()  //per Linux int main()
{ clrscr(); //no sotto Linux
  pointer  pP_Primo("Gino");
  pointer  pP_Secondo(pP_Primo); //Qui chiama il costruttore di copia
  pointer  pP_Terzo("Franco");
  cout <<endl<<"pP_Primo ->\t"<<pP_Primo<<endl
       <<"pP_Secondo ->\t"<<pP_Secondo<<endl
       <<"pP_Terzo ->\t"<<pP_Terzo<<endl;
  cout <<"pP_Secondo = pP_Primo"<<endl;
  pP_Secondo = pP_Terzo; //qui chiama l'assegnazione
                                           //ora secondo e terzo valgono
"Franco"
  pointer pP_Quarto(pP_Primo);//qui chiama il costruttore di copia
  cout <<endl<<"pP_Primo ->\t"<<pP_Primo<<endl
       <<"pP_Secondo ->\t"<<pP_Secondo<<endl
       <<"pP_Terzo ->\t"<<pP_Terzo<<endl
       <<"pP_Quarto ->\t"<<pP_Quarto<<endl;
}
FINE

 void main ()  //per Linux int main()
Per lo standard main deve restituire sempre un int, indipendentemente
dal sistema operativo.
	Mauro

Non serve assegnare valori a un oggetto in distruzione.
ma e' cosa buona e giusta per evitare problemi in caso di bug :)
    mauro

         Mauro
Per quanto riguarda l'osservazione sul void, mi sembra superflua in
quanto i compilatori borland (i pi? diffusi a livello scolastico) lo
usano (e se questo non rispetta lo standard ISO..........).
Anche per il distruttore  mi sembra sia corretto porre a NULL il
puntatore dopo il delete.
Le altre osservazioni invece hanno fondamento.
Riguardando il codice, mi sono accorto che chiamare il distruttore nel
costruttore senza argomenti non serve in una versione che sia definitiva
della classe.Devi sapere che questo codice ? nato per chiarire un dubbio
a un amico del newsgroup e per questo non aveva certo l'ambizione di
gestire anche i casi particolari.
Di caso particolare certo si tratta  in quanto il bug da te segnalato,
salterebbe fuori se si fa:
pointer p1;
pointer p2=p1;
si capisce che solo un programmatore con seri problemi di disadattamento
scriverebbe cos? e non;
pointer p2,p1;
E' questo il motivo per il quale nessuno delle tante persone che hanno
adocchiato la classe (io per primo) si sono posti il problema.
Quello che tu stai facendo, in pratica e di testare la classe,
verificandone la correttezza.
Allora  aggiungo le 2 righe necessarie per la gestione del tutto.
(ma a chi gli importa di una classe cos? inutile ?)
 pointer()     //costruttore senza argomenti
  {psStringa=0;}
  pointer(const pointer &p)   //costruttore di copia
  {  if (p.psStringa)
        { psStringa = new char[strlen(p.psStringa)+1];
           for(int k=0;k<=strlen(p.psStringa);k++)
           psStringa[k]=p.psStringa[k];
         }
      else psStringa=0;
      cout<<"chiamato il costruttore di copia"<<endl;
  }
  pointer& operator=(const pointer &p)   //assegnazione
  { char* temp=this->psStringa;
     if (p.psStringa)
        psStringa = new char[strlen(p.psStringa)+1];
     else psStringa=0;
     for(int k=0;k<=strlen(p.psStringa);k++)
        psStringa[k]=p.psStringa[k];
     delete temp;
     cout<<"chiamata assegnazione"<<endl;
     return *this;
  }
  E' chiaro che si possono realizzare le stesse cose in almeno altri 10
modi diversi (ed alcuni anche pi? sintetici), ma questo fa parte dei
gradi di libert? che ha un programmatore e che rende questa professione
talvolta piacevole.
Giorgio

 puntatore dopo il delete.
Io sono d'accordo con Mauro. Porre a NULL il puntatore dopo il delete č
assolutamente inutile e ogni cosa inutile puņ essere dannosa. Inoltre,
parlando del primo punto, lo standard vuole int main(), poco importa se
il Borland accetta anche void main(). Ricordati che nessun compilatore
fa testo riguardo lo standard ma solo la documentazione ufficiale dello
stesso.
EMG

...

Quello di impostare a NULL un puntatore appena rilasciato č una tecnica che viene usata
spesso in  combinazione con funzioni di copertura o con funzioni che validano i propri
argomenti come prima cosa, tipicamente con asserzioni.  Se, per esempio, passo per errore
un puntatore non inizializzato o gią rilasciato a una di queste funzioni (a chi di voi non
č mai capitato?), questa tecnica genererą il fallimento dell'asserzione, rendendo evidente
l'errore (o almeno il punto di rilevamento dello stesso). Senza tale accorgimento, il
puntatore (ammesso che sia ancora nel suo scope, perché sennņ non compili neppure) č
garbage, quindi qualsiasi cosa. Potrebbe essere comunque un valore che non genera subito
un comportamento anomalo, o lo fa solo random o lo causa da un'altra parte...
E' chiaro che sulla versione di release non serve a nulla, ma in debug puņ essere utile.
--
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Andrea Baruzzo
Via Circonvallazione, 26
Muzzana del Turgnano (UD) - Italy
e-mail: 
andrea....@ud.nettuno.it
~~~~~~~~~~~~~~~~~~~~~~~~~~~~

 EMG
Ogni parere ? rispettabile ma:
- Il null dopo il delete non ? assolutamente dannoso.Spesso anzi molti
errori (ma non ? questo il caso) provengono dal dimenticarsi di farlo
(esempio cancellazioni di liste)
- A mio parere il cambiamento introdotto dalla Borland per quanto riguarda
il void, aumenta     la leggibilit? di un programma (una delle qualit?
interne del software).
-il tentativo di "bloccare" l'evoluzione di un linguaggio mi sembra un
atteggiamento oscurantista.
 Il mio parere ? che se tutti ragionassero cos? probabilmente parleremmo
tutti ancora in latino!
Poi non sono assolutamente daccordo con i "puristi del C".Ogni
programmatore ? libero di programmare come vuole, rispettando per? (se
vuole produrre del buon software)
-CORRETTEZZA
-EFFICIENZA
-PORTABILITA'
-USABILITA'
l'unica qualit? ESTERNA che viene intaccata dall'uso del void ? la
portabilita'.
Ma qui parte una discussione infinita:
secondo te Internet e il protocollo TCP/IP sono da contestare solo perch?
non rispettano i livelli della pila OSI?.
Oppure buttiamo a mare i magnifici compilatori Pascal della Borland (i
primi nella storia a compilare migliaia di righe in pochi secondi quando
esistevano solo i lentissini 8088 ), solo perch? esprimono un Pascal
diversissimo da quello Standard ?
Il progresso non pu? e non deve essere fermato.
Ti faccio un esempio: ? uscito un libro (non ricordo il nome ma posso
rintracciarlo se vuoi - ? in inglese) che finalmente ha detto che il tipo
enum in C++ (per i compilatori Borland) ? praticamente un tipo
intero.Questa cosa ha fatto inferocire i fini linguisti di Oxford
dell'associazione Users C++, gente (con la puzza sotto il naso) che
concepisce un linguaggio di programmazione essenzialmente in termini
formali, fregandosene che poi debba andare in mano ad ingegneri come me,
che lo usano per realizzare programmi concreti e non per giocare di
fioretto.
Alcuni anni fa andava di moda dire che usare un exit in una procedura era
l'equivalente della peste bubbonica.Professori universitari spocchiosi e
potenti (prova ad immaginarti un intera Universit? da informatizzare e
ritrovarsi in 4 gatti), quasi si sono divertiti a rallentare il progresso
inevitabile e cos? sono andati avanti per anni a raccontare scemenze del
genere.
E' un fatto che non si insegnino linguaggi visuali (escluso qualche
intelligente professore) ma magari trovi ancora corsi di Fortran !
Il mio invito ? di non fermarsi alla forma ma di andare alla sostanza, cio?
di capire le metodologie di programmazione sempre meglio, anzich? sentirsi
"fichetti" perch?  siamo riusciti a fare un algoritmo con 2 istruzioni
anzich? 3, oppure (ancora pi? assurdo) perch? abbiamo rispettato lo
standard ISO.
Giorgio

 [...]
Non riesco a capire tutto il discorso che fai. Lo studiare un linguaggio
standardizzato porta solo vantaggi. E nel caso particolare del void
main() questo esiste da prima che lo standard imponesse int main()
quindi è tutto meno che un'evoluzione.
EMG

puntatore dopo il delete.
Puo` avere un senso porre a zero il puntatore associato all'oggetto
appena distrutto se puo` succedere di dover distruggere l'oggetto in piu`
punti del codice, in quanto chiamare delete su un puntatore che contiene
zero non fa nulla e si evita cosi` di incappare in errori di run-time cercando
di deallocare un oggetto gia` deallocato.
Non ha nessun senso farlo nel distruttore.
-- 
Roberto Gerola
Email: 
robert...@penguinpowered.com
WWW: 
http://www.penguinpowered.com/~The_Cpp_Compass

 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Bravo! Esatto.
Ma molti utenti del newsgroup sono alquanto inesperti e uno zero in pi? non fai male.
Non ? dannoso.
Un errore classico ? distruggere una lista e dimenticarsi di rimettere il puntatore iniziale a
NULL.
Vorrei ricordare a tutti che il fine del newgroup ? capire o trasmettere conoscenze non quello
di
sfornare programmi commerciali o come dici tu, release definitive.
Giorgio

 argomenti come prima cosa, tipicamente con asserzioni.
Questo e' utile se il puntatore continua a esistere anche dopo
la deallocazione, ma in questo caso pero' il puntatore sta per
essere distrutto (la deallocazione avviene in un distruttore e
il puntatore e' un membro dell'oggetto).
L'unico effetto che si ottiene e' quello di mettere a 0 un pezzo
non allocato di heap o di stack, sempre che il gestore della 
memoria non ci vada a scrivere sopra.
	Mauro

 portabilita'.
E ti sembra poco?
Ti leghi a un solo compilatore (che gira su un solo S.O.), e cosa 
ottieni in cambio?
E cosa succede se anche il compilatore Borland si adegua allo standard?
	Mauro

...

Certo. L'obiezione che ho fatto era sulla tecnica di porre o meno a NULL un puntatore
appena rilasciato, indipendentemente dal caso particolare. Spesso, molti stili di
programmazione sono influenzati dalle abitudini e si sà, ci sono abitudini buone e
abitudini cattive. Questa è un'abitudine generalmente buona (imho).
Nel caso particolare, il fatto che ci troviamo in un distruttore potrebbe rendere
superflua la precauzione, ma questo solo perché il puntatore è un attributo  dell'oggetto.
Se fosse un puntatore globale o di un oggetto che contiene quello in questione  (o
comunque un puntatore "esterno" rispetto allo scope di quest'ultimo), potrebbe di nuovo
essere rilevante.
Non vedo, invece, alcun evidente rischio correlato a questa tecnica (e nessun vantaggio
nel non farlo). Come spesso accade, nei casi rilevanti, l'uso della tecnica è sinonimo di
abilità; in tutti gli altri casi di stile. Non ti pare?  :-)


standard ISO.
Invito accolto!
Lo standard viene fatto (tra le altre cose) per supportare meglio una determinata
metodologia, e non per fare gli interessi del singolo fornitore di compilatori. E' fatto
da un comitato super-partes. Questo è il motivo per cui nè java, nè VB hanno un vero e
proprio comitato di standardizzazione. Java è di Sun, VB è di M$.
Se lo standard vieta alcune forme di programmazione è perché le ritiene potenzialmente
pericolose. Allora diventa davvero costruttivo chiedersi il perché di un certo divieto.
Dietro a quel perché potrebbero esserci risposte molto interessanti. Finché non ne hai
comprese le ragioni, dire che lo standard è inutile oppure opinabile non ti aiuta molto, a
mio modesto parere.


 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Le tue considerazioni sono molto interessanti e le condivido.
Non volevo pero' dire che lo standard e' inutile: solo che le forme di fanatismo "estremo"
(tipo " ma che sei pazzo che hai messo void?") le trove sinceramente sterili.
Lo standard, come si deduce dai miei ragionamenti del msg precedente, non deve diventare una
"Bibbia" ma solo un modello di riferimento (tipo pila OSI per le reti).
Giorgio

assolutamente inutile e ogni cosa inutile puņ essere dannosa.
Al di la` del fatto che un po' di defensive programming non puo` far
male, in effetti sono sostanzialmente d'accordo con te...
Prendiamo ad esempio il caso di un oggetto allocato staticicamente...
Il distruttore verrebbe invocato sostanzialmente quando si esce dallo 
scope cui l'oggetto appartiene. Ma l'uscita dallo scope rende 
automaticamente non riferibile l'oggetto.
Il problema si porrebbe con gli oggetti dinamici e comunque per via
di puntatori che potrebbero continuare a puntare all'oggetto
distrutto. Si potrebbe pensare che azzerare i membri putatore possa
aiutare a rilevare eventuali anomalie...
Tuttavia secondo me si tratta solo di un palliativo poiche` tale
accorgimento non consentirebbe in generale di "catturare" il tentativo
di riferire all'oggetto tramite p (es p->Op() ) in quanto si potrebbe
eseguire una operazione che non coinvolge il membro di tipo puntatore
(senza considerare che un valore 0 potrebbe essere una situazione
possibile).
Il vero problema e` quello di ricordarsi di "scollegare" l'oggetto da
distruggere dal resto del sistema (ovvero prima azzerare p e poi 
distruggere l'oggetto). Bisogna azzerare quindi non i membri puntatore
dell'oggetto distrutto, ma i puntatori all'oggetto (cosa molto diversa).
Paolo
-----------------------------------------------------------------
  Paolo Marotta
  Computer Science, University of Pisa
  home:   
http://www.cli.di.unipi.it/~marotta
-----------------------------------------------------------------

sfornare programmi commerciali o come dici tu, release definitive.
Infatti e` il puntatore alla lista che va azzerato, non i "puntatori
dell'oggetto lista" (che presumibilmente puntano agli elementi della
lista).
Piccolo esempio ridotto all'osso:
// supponiamo che gli oggetti inseriti nella lista
// siano di proprieta` della lista stessa, ovvero distruggendo
// la lista si distruggono automaticamente gli oggetti in
// essa presenti.
class List {
  public:
    /* ... */
   bool Insert(Obj* Ptr);
    ~List();
  private:
      Obj* Elementi[10];
};
bool List::Insert(Obj* Ptr) {
   /* inserisce Ptr nel primo elemento nullo dell'array */
  /* se l'array e` pieno fallisce */
}
List::~List() {
   for(int i=0; i<10 ++i) 
      if (Elementi[i])  {
                  delete Elementi[i];
                  Elementi[i] = 0;    // operazione inutile
      }
}
Non ha alcuna utilita` eseguire "Elementi[i]=0" in ~List() in quanto
non ci aiuterebbe in
int main() {  
   List* ListaPtr = new List;
   List* Lista2Ptr = ListaPtr;
  /* ... */    
  delete ListaPtr;
  Lista2Ptr->Insert(ObjPtr);
}
I puntatori da annullare sono quelli che puntano all'oggetto
distrutto (ListaPtr e Lista2Ptr) e non i membri puntatore
dell'oggetto distrutto. Anche ammesso che il valore 0 per
tali membri fosse illecito al posto della Insert ci poteva essere
un qualunque messaggio che non coinvolgesse imembri
puntatore e l'errore nuovamente non sarebbe stato rilevabile.
