Newsgroup: it.comp.lang.c++
Titolo: Ottimizzazione di contenitori di oggetti eterogenei tramite incapsulamento

Supponiamo di avere una classe astratta B, da cui derivano due classi
concrete D1 e D2.
Voglio avere un vector (o altra collezione o array) di oggetti che
possono essere di tipo D1 o D2.
La soluzione più semplice è dichiarare la collezione così:
   vector<B *> v;
inserire gli elementi così:
   v.push_back(new D1(parametri));
e, prima di distruggere la collezione, eseguire sull'i-esimo elemento:
   delete v[i];
Implementando tale soluzione in linguaggio C, scriverei invece:
struct eBox {
	enum eTipo { eD1, eD2 } tipo;
	union {
		D1 d1;
		D2 d2;
};
};
struct eBox *v = malloc(...);
In questa soluzione, le entità sono immagazzinate direttamente
nell'array invece di essere allocate singolarmente.
Quest'ultima soluzione mi risulta altrettanto performante nell'accesso
dei dati inseriti, anche se in teoria potrebbe essere migliore in
quanto si ha un livello di indirittezza in meno, un puntatore in meno,
e maggiore località dei dati, purché i due tipi D1 e D2 non abbiano
dimensioni molto diverse.
Il punto interessante è però che questa soluzione mi risulta molto più
performante in fase di allocazione e disallocazione degli elementi.
Infatti, l'unica allocazione necessaria è quella dell'array, mentre
nella soluzione precedente ogni singolo oggetto deve essere allocato e
deallocato.
Ho provato a implementare questa soluzione in C++, ma il compilatore
ha segnalato come errore il fatto che la union contiene membri di
classe con costruttori o distruttori. In realtà risulta illegale
inserire in una union qualunque oggetto che abbia qualche metodo
virtuale.
Sostituendo la union con una struct il programma è corretto, e pur
sempre più efficiente di quello originale, almeno con i due soli
membri d1 e d2. Tuttavia, tale soluzione non è ovviamente ottimale, e
inoltre, man mano che si aggiungono membri alla struct, ogni elemento
occupa sempre più spazio, e quindi diventa rapidamente inefficiente.
Dopo lunghe pensate, ho escogitato la seguente soluzione, che con la
mia piattaforma sembra funzionare, ma usa costrutti dal significato
non definito (in altre parole, potrebbe non funzionare con un'altra
versione del compilatore):
class B {
public:
	virtual void f() const = 0;
};
struct D1_data { long long_value; };
class D1: public B, private D1_data {
public:
	D1(long value) { long_value = value; }
	virtual void f() const { cout << "long_value=" << long_value <<
endl; }
};
struct D2_data { double double_value; };
class D2: public B, private D2_data {
public:
	D2(double value) { double_value = value; }
	virtual void f() const { cout << "double_value=" << double_value <<
endl; }
};
class Box {
public:
	template<typename T2>
		Box(const T2 & d) { new (this) T2(d); }
	void f() const { ((B*)this)->f(); }
private:
	union {
		D1_data d1;
		D2_data d2;
	};
	double filler;
};
int main() {
vector<Box> v;
	v.push_back(D1(123));
	v[0].f();
}
Un oggetto di tipo Box può contenere un oggetto di tipo D1 o un
oggetto di tipo D2, ma non contemporaneamente. Per fare in modo che
Box abbia la dimensione sufficiente, al suo interno c'è una union che
garantisce che ci sia spazio sufficiente per i dati, e un filler che
garantisce che ci sia spazio sufficiente per il vTable pointer.
Dato che non potevo mettere gli oggetti D1 e D2 dentro la union, ho
esternalizzato le variabili membro di tali oggetti, mettendoli
rispettivamente nelle strutture D1_data e D2_data, da cui D1 e D2
ereditano privatamente.
Altri due hack nella classe Box sono il template di costruttore e le
funzioni di forwarding delle funzioni pubbliche di B (in questo caso
c'è solo "f"). Il template del costruttore chiama il copy constructor
dell'oggetto da incapsulare tramite una placement new. Le funzioni di
forwarding chiamano le funzioni corrispondenti di B.
Che cosa ne pensate?
Conoscete soluzioni migliori a questo problema?
Grazie
--
  Carlo Milanesi

 Conoscete soluzioni migliori a questo problema?
Guarda se ti piace questa, che ho scritto al volo:
 
 
http://proxy.maxim.comm2000.it/~maxim/afton/pub/cpp/inplace_wrapper.hxx
E' un wrapper template che può ospitare oggetti di classi qualsiasi
(derivate da una certa classe base). Gli oggetti vengono creati/copiati
tramite placement new.
Per essere "ospitabile" una classe dovrà essere definita usando un semplice
accorgimento. (Andrebbe, comunque, aggiunto il "concept check" che
confronti il sizeof con la dimensione dello storage scelta.)
Esempio:
 #include "inplace_wrapper.hxx"
 #include <vector>
 #include <iostream>
 using std::cerr; using std::endl;
 struct base
 {
        virtual void hello() = 0;
 };
 class A : public CRTP_inplace_clonable_impl< base, A >
                /* qui c'è il solito Curiously Recurring etc.. */
 {
        void hello()
        {
                cerr << "Hi. I'm A" << endl;
        }
 };
 class B : public CRTP_inplace_clonable_impl< base, B >
 {
        void hello()
        {
                cerr << "Hi. I'm B" << endl;
        }
 };
 typedef inplace_wrapper<base,30> wrapper_t; 
 /* gli oggetti deveono avere sizeof <= 30 */
 int main()
 {
        std::vector< wrapper_t > v;
        v.push_back( A() );
        v.push_back( B() );
        v[0]->hello();
        v[1]->hello();
        wrapper_t tmp;
 
        tmp = v[0];
        v[0] = v[1];
        v[1] = tmp;
        v[0]->hello();
        v[1]->hello();
        tmp = A();
        tmp->hello();
 
        tmp = B();
        tmp->hello();
 }
Max

 Conoscete soluzioni migliori a questo problema?
Questo potrebbe interessarti: 
http://boost.org/doc/html/variant.html
Ciao,
Nicola