Newsgroup: it.comp.lang.c++
Titolo: Classe virtuale usata come POD

Salve, mi piacerebbe sapere se il codice postato sotto è una bomba ad 
orologeria o è accettato dallo standard.
-----------------
class Base
{
   public:
     Base() {}
     virtual ~Base() {}
};
class Container
{
   public:
     ...
     bool add(Base* b);
     bool search(Base* b);
     ...
};
class Derived : public Base
{
   public:
     ...
};
-----------------
Container cont;
......
Derived d;
...
if(cont.search(&d)) {
   ...
   }
else {
   Derived* pd = (Derived*)new char[sizeof(Derived)];
   *pd = d;
   ...
   cont.add(pd);
   }
----------------
La classe Base è solo un segnaposto per l'ereditarietà.
Allocando un oggetto Derived come un POD (in effetti, se non fosse che 
eredita da Base, è un POD) vado incontro a problemi?
Non ho implementato nessun costruttore di copia quindi la copia dovrebbe 
avvenire anche per l'eventuale tabella virtuale (anche se probabilmente 
in questo caso non viene generata).
Mi conviene invece usare la Derived come un POD puro e fare il cast 
stile C a Base?
Grazie

 stile C a Base?
Secondo me ti conviene definire un costruttore di copia, e non
preoccuparti dell'ottimizzazione, almeno finche' non sei certo che e'
li' il problema della tua architettura.
--
Marco
 
-- 
I'm using an evaluation license of nemo since 175 days.
You should really try it!
http://www.malcom-mac.com/nemo

 
Premetto che avevo già compilato ed utilizzato il codice postato 
(diciamo il codice completo, non l'esempio) sia in modalità debug che 
release senza avere problemi.
Il mio dubbio riguardava l'utilizzo della tabella delle funzioni 
virtuali e di cosa definisse lo standard riguardo le classi aventi, come 
virtuale, il solo distruttore il quale non esegue alcuna azione.
Probabilmente la classe Derived morirà così senza nessuna estensione 
futura comunque ho aggiunto:
     void* operator new(size_t s, void* p) {  return p;  }
     void operator delete(void* p, void* p2) {   }
nella classe Derived e ho utilizzato il placement new.
Naturalmente l'allocazione non è in linea, ma avviene tramite una lista 
libera. Probabilmente, dovesse servirmi ancora un approccio non POD, 
implementerò un'altra lista libera, non come un blocco di byte, ma 
templatizzata sul tipo.
Grazie ancora

  nella classe Derived e ho utilizzato il placement new.
Bene. Non son certo servisse l'aggiunta degli operatori che hai
indicato, per quanto mi riguarda bastava questo per ottenere un
oggetto valido dalla tua allocazione. Rimango dell'idea che non sia
garantito il funzionamento della memcpy, in generale (secondo me
continuano a valere le considerazioni della mail precedente).
Ciao,
Marco
 
-- 
I'm using an evaluation license of nemo since 176 days.


    }
Cosi' come e' scritto questo frammento di codice funziona, e funziona
anche se Derived non e' un POD. Questo perche' lo standard garantisce
che la memoria ritornata da
new char[sizeof(Derived)]
e' allineata correttamente per qualsiasi tipo di dato di dimensioni
minori o uguali a sizeof(Derived) . Pero' quello che hai scritto e'
equivalente a
Derived *pd = new Derived(d);
Il problema che ti si pone davanti se come sospetto in realta'
vorresti preallocare lo spazio per piu' Derived in un colpo solo e'
che dato
Derived* pd = (Derived*)new char[sizeof(Derived) * n];
non hai nessuna garanzia che pd + (sizeof(Derived) * m) sia ancora
allineato correttamente.
Ti consiglio di andare a sbirciare come e' implementato std::vector
nella libreria standard che usi; sospetto che faccia molto di quello
che ti interessa. Altre funzioni/classi che ti potrebbero interessare
si trovano in <memory>.
Ciao,
Nicola

 anche se Derived non e' un POD.
Scusa, ma non capisco come questo frammento possa avere un comportamento
definito se Derived non č un POD. Mi pare che invocare l'operatore
assegnamento senza che sia stato eseguito il costruttore non sia legale, a
meno che l'oggetto non sia -appunto- di un tipo POD.
Mi sfugge qualcosa?
Max

 Mi sfugge qualcosa?
No, hai ragione tu. In realta' avrebbe dovuto fare qualcosa come
char* pc = new char[sizeof(Derived)];
Derived* pd = new (pc) Derived(d);
Ciao,
Nicola

Nicola Musatti ha scritto:

Ringrazio per le risposte :-)
Alla fine ho usato il placement new.
Comunque anche nel caso dell'esempio che ho postato il codice era 
funzionante.
La classe Derived ha solo alcuni tipi nativi e nessun metodo virtuale e 
probabilmente il compilatore elimina la tabella delle funzioni virtuali 
perché inutile.
L'ho utilizzata anche in modalità debug, quindi nessuna ottimizzazione e 
non ho avuto problemi.
Come avevo già postato, l'allocazione non è in linea (quello era solo un 
esempio), ma mi proviene da una lista libera in cui alloco a blocchi di 
byte ed utilizzo, nel rilascio del blocco, i primi sizeof(void*) come 
puntatore al successivo libero.
Per inciso, ho rifatto una lista libera templatizzata. Purtroppo c'è un 
overhead del puntatore al successivo, visto che non posso riutilizzare 
la memoria allocata per l'oggetto e devo poi utilizzare anche la lista 
libera a byte per mantenere la struttura contenente i puntatori al 
successivo e all'oggetto.
Non l'ho ancora messa in prova, comunque la classe è così fatta:
//----------------------------------------------------------------------
template <typename T>
class P_FreePacket
{
   private:
     struct packet
     {
       packet* next;
       T* pack;
     };
   public:
     P_FreePacket() : FreeList(sizeof(packet)), First(0) {}
     ~P_FreePacket();
     T* get();
     void release(T* f);
   private:
     packet* First;
     P_freeList FreeList;
     NO_COPY_COSTR_OPER(P_FreePacket)
};
//----------------------------------------------------------------------
mentre la freelist in uso (e utilizzata anche sopra) è:
//----------------------------------------------------------------------
class P_freeList
{
   public:
     P_freeList(size_t size) : firstFree(0),
		Size(max(size, MIN_SIZE_FREELIST)) { }
     ~P_freeList();
     LPVOID getFree();
     void addToFree(LPVOID data);
   private:
     LPVOID firstFree;
     size_t Size;
     NO_COPY_COSTR_OPER(P_freeList)
};
//----------------------------------------------------------------------
con una semplice classe per templatizzarne il tipo:
//----------------------------------------------------------------------
template <typename T, size_t tsize = sizeof(T)>
class P_SimpleFreePacket
{
   public:
     P_SimpleFreePacket() : FreeList(tsize) {}
     ~P_SimpleFreePacket() {}
     T* get();
     void release(T* f);
   private:
     P_freeList FreeList;
     NO_COPY_COSTR_OPER(P_SimpleFreePacket)
};
//----------------------------------------------------------------------

 la memoria allocata per l'oggetto
Non ho guardato gran che il tuo codice, ma per questo potresti forse
usare una union, tipo
template <typename T>
union free_list_node {
  char buf[sizeof(T)];
  free_list_node * next;
};
Ciao,
Nicola

 
Non so come ottimizza poi il compilatore, ma se mi trovo dieci oggetti 
diversi ho una moltiplicazione del codice dove l'unica cosa che cambia è 
la dimensione del buffer.
D'accordo che il codice è banale, ma con un po' di cast stile C ottengo 
la stessa cosa e posso usare dimensioni del buffer a piacere senza 
essere legato ad una struct/class.
Ma forse ho frainteso e intendevi la seconda lista libera.
Ma facendo così non vado a sovrascrivere la tabella?
Me lo ricordo ancora (alcuni anni fa) quando feci un azzeramento di this 
su oggetto con funzioni virtuali (a mia discolpa posso dire che prima 
era un POD con abbastanza proprietà e quindi era più comoda una 
fresatura completa :-) poi, passato a virtuale, mi sono dimenticato di 
aggiustare il costruttore).
Grazie e ciao

 Ma facendo così non vado a sovrascrivere la tabella?
Torno a dire, non ho guardato il tuo codice. Comunque,
indipendentemente dal dettaglio implementativo, il riutilizzare una
parte del blocco libero per memorizzare il puntatore al blocco
successivo (e la dimensione di quello attuale) e' una tecnica standard
negli allocatori che mantengono una lista dei blocchi liberi, che
evita la doppia allocazione del blocco e del nodo della lista.
Ciao,
Nicola


Ciao,
non ho capito qual e' il problema che stai cercando di risolvere...
perche' non fai semplicemente
cont.Add(new Derived(d));
senza bisogno di alcun cast.
Saluti,
Fabio

Fabio ha scritto:

Salve,
avevo già detto che l'allocazione in linea era solo un esempio e che la 
memoria viene da una lista libera.
In pratica lo scenario è questo:
- c'è un miniscript con una serie di espressioni parserizzate in alberi 
(con l'aggiunta di alcune espressioni di controllo, if, salto, ciclo, ecc.)
- ad ogni ciclo vengono eseguite queste espressioni che richiedono l'uso 
di variabili prese in vario modo
poiché è ininfluente aggiornare le variabili (inviare il loro contenuto) 
direttamente all'occorrenza o al termine del ciclo, ho pensato di 
implementare una cache per mantenerle in locale ed inviare poi, al 
termine del ciclo, solo quelle modificate in una unica soluzione, il che 
riduce l'overhead di più invii serializzati.
Quindi, all'inizio del ciclo ho la cache vuota, quando reperisco le 
variabili le inserisco in cache, eventualmente ci lavoro più volte 
all'interno del ciclo, al termine del ciclo invio le variabili e vuoto 
la cache.
Considerando che questo ciclo avviene, più o meno, ogni 300msec e che 
può restare in vita per vari mesi, allocare e deallocare, oltre ad 
essere poco performante, rischia di frammentare troppo la memoria.
Saluti