Newsgroup: it.comp.lang.c++
Titolo: void* + assegnamento

Premetto che ho iniziato a studiare il C++ da poco..... quindi chiedo a chi
ne sa più di me...
Il mio manuale dice a proposito delle LISTE " Di ciascuna funzione di
inserimento o estrazione ci sono due versioni sovrapposte, una delle quali
ha argomento di tipo Type e l'altra di tipo Void* .... Le funz di tipo Type
allocano memoria con la New .... ma il loro costo è particolarmente gravoso
quando devono essere spostate da una lista all'altra .... In questo caso si
usano le funz di tipo Void* "
Vorrei sapere perchè devo riscrivere il codice due volte, utilizzando prima
Type e poi Void*,  per ogni funz di inserimento o estrazione.
Potreste inoltre spiegarmi (passo-passo!!!!!) che cosa succede nello Heap
durante l'assegnamento membro a membro dei campi dati di due stack e perchè
la Delete di uno dei due può essere così pericolosa?
Ringrazio anticipatamente (soprattutto per la pazienza!!!)
    fry

 usano le funz di tipo Void* "
Forse il tuo dubbio e' riconducibile al fatto che i templete in fase di
compilazione,creano  per ogni template utilizzato con un determinato tipo,
codice differente.
Quindi dato:
 templete<class T> class List {.................};
se utilizzo List<int>,creera' codice apposito per il tipo List<int>
se utilizzo List<string>,creera' codice apposito per il tipo  List<string>
se utilizzo List<int*>,creera' codice apposito per il tipo List<int*>
se utilizzo List<char*>,creera' codice apposito per il tipo List<char*>
NB: gli ultimi 2 esempi sono ottimizzabili.
Il suggerimento che  ti da il libro,e' quello di ottimizzare la dimensione del
codice prodotto,definendo una specializzzione
parziale del telmplete in questo modo:
 templete<class T> class List<T*> : private list<void*> {.................};
in questo modo, ogni volta che userai il templete con puntatori a tipo,il
codice sara' generato una sola volta, nei membri del templete
accederai agli oggetti tramite i membri di list<void*>,e li restituirai
all'utente del templete facendo un cast a T*.
es:
 templete<class T> class List<T*> : private list<void*> {
.....
.....
typedef list<void*> base
public:
   T* elem(i) { return  static_cast<T*>(base::elem(i)); }   //static_cast<> e'
necessario per  verificare la validita' del tipo puntatore in fase di
compilazione.
}; // fac_simile dell ' esempio nel capitolo 13.5 della terza edizione del
libro di Stroustrup.
--
Ivan Molella
__LINUX__ the choice of the __GNU__ generation
icq# 97960484

la Delete di uno dei due può essere così pericolosa?
Qui suppongo che l'esempio mostrato sia qualcosa di simile a:
  class Stack
  {
    private:
      int *stack;
      int stackp;
      int size;
    public:
      Stack(int sz)
        : stack(new int[sz]),stackp(0),size(sz)
      {
      }
      ~Stack()
      {
        delete[] stack;
      }
      void Push(int x)
      {
        stack[stackp++]=x;
      }
      int Pop()
      {
        return stack[--stackp];
      }
   };
In questo caso se un utilizzatore dovesse fare:
   Stack s1(100); // Stack di 100 elementi
   Stack s2(100); // Altro stack di 100 elementi
   s1=s2;
ci sarebbero un po' di problemi...
L'errore e' dovuto al fatto che la classe Stack dichiarata
non ha un operatore di assegnamento o un costruttore di copia:
in questo caso il compilatore genera automaticamente il codice
per queste due funzionalita' mancanti eseguendo una assegnazione
e costruzione di copia membro-a-membro. In altre parole il non
aver scritto queste due funzioni implica che e' come se si
fossero scritte le seguenti versioni:
  Stack& Stack::operator=(const Stack& rhs)
  {
    size=rhs.size;
    stackp=rhs.stackp;
    stack=rhs.stack;       // <- Attenzione!!
    return *this;
  }
  Stack::Stack(const Stack& source)
   : stack(source.stack),  // <- Attenzione!!
     stackp(source.stackp),
     size(source.size)
  { }
Cosa succede infatti nello statement di assegnazione s1=s2 ?
Il codice eseguito copia size e stackp ed effettua una copia
del puntatore stack; viene copiato cioe' un puntatore e non
tutti i dati puntati. Il risultato e' che dopo l'assegnazione
i due stack avranno i dati *IN COMUNE* e invece stackp
indipendenti: gia' questo e' fonte di problemi...
Quando poi si arriva ai distuttori il distruttore di s2
effettuara' "delete[] stack" e quindi liberera' la memoria
a cui pero' s1 sta ancora puntando.
Nel momento in cui viene eliminato s1 ci sara' quindi una
ulteriore chiamata a delete[] per lo stesso puntatore (con
effetti imprevedibili). Nota anche che nel momento in cui
viene eseguito s1=s2 i dati a cui puntava s1 sono "abbandonati"
nello heap senza che sia stata fatta la delete (memory leak).
Per risolvere questo problema esistono due alternative:
far funzionare l'assegnamento in modo che la classe si
comporti correttamente in questi frangenti oppure impedire
che gli utenti della classe possano effettuare queste
operazioni (se decidiamo che per la nostra classe non
hanno senso).
Nel primo caso basta definire delle opportune operazioni
di copy-construction e assegnazione:
  Stack& Stack::operator=(const Stack& rhs)
  {
    size=rhs.size;
    stackp=rhs.stackp;
    delete[] stack;
    stack=new int[size];
    for (int i=0; i<stackp; i++)
      stack[i]=rhs.stack[i];
    return *this;
  }
  Stack::Stack(const Stack& source)
   : stack(new int[source.size]),
     stackp(source.stackp),
     size(source.size)
  {
    for (int i=0; i<stackp; i++)
      stack[i]=source.stack[i];
  }
Nel secondo caso basta dichiarare queste operazioni come
presenti ma "private". In questo modo il compilatore non
le creera al posto nostro e impedira' agli utenti di
utilizzarle:
   class Stack
   {
     private:
       Stack& operator=(const Stack&); // Solo dichiarazione
       Stack(const Stack&);            // Solo dichiarazione
     ...
   };
Con queste dichiarazioni aggiuntive il codice di esempio
di prima genererebbe un errore di compilazione quando si
tenta di fare "s1=s2" (violazione incapsulamento) e un errore
di link se qualcosa del genere venisse fatto per svista
all'interno di metodi di Stack.
Come ho gia' detto altre volte una regola da ricordare
molto semplice ma valida e' che se in una classe e'
presente un distruttore allora molto probabilmente dovrebbero
essere presenti anche un costruttore di copia e un
operatore di assegnazione. Questi tre metodi sono dal punto
di vista concettuale abbastanza legati fra loro e normalmente
se si ha bisogno di uno allora si ha bisogno di tutti e tre.
Spero A) di aver compreso la domanda, B) di essere stato
abbastanza "passo-passo" nella risposta.
HTH
Andrea
--
Andrea Griffini, Programmer
http://www.netservers.com/~agriff