PDA

View Full Version : [c++] puntatori a template


ri
14-02-2004, 22:33
ammettiamo di avere un template semplicissimo

template <typename T>
class dato
{
T v;
public:
dato(T v) : v(v) {};

T get() { return v; }
T set(T newv) { return v = newv: }
};

typedef dato<int> datoint;
typedef dato<long> datolong;


spesso e volentieri per strutture di questo tipo serve avere un "posto" dove tenerne memorizzate un numero variabile, spesso a runtime. Il vector sembra l'ideale per fare questo lavoro
però non si può fare un vector di un template
si può però fare una cosa di questo tipo:


class datobase
{
public:
datobase() {};
virtual ~datobase();
}
template <typename T>
class dato : public datobase
{
T v;
public:
dato(T v) : v(v) {};

T get() { return v; }
T set(T newv) { return v = newv: }
};


in modo da poter fare un vettore di datobase


vector<datobase*> vdati;


questo però fa si che il template venga "detipizzato" e, ovviamente, rende impossibile accedere ai metodi della classe dato (come posso sapere che tipo di template era per poter fare il cast?)

idee?

ri
14-02-2004, 23:31
la domanda si può riformulare in un altro modo:
come è possibile definire un'interfaccia alla classe base che però non abbia un tipo di ritorno fisso? (vedi la funzione get...)

ri
15-02-2004, 00:13
Originariamente inviato da ri
Il vector sembra l'ideale per fare questo lavoro
però non si può fare un vector di un template


è tardi e non connetto e sparo cazzate come quella -___-
intendevo dire che non si può fare un vettore di template non ancora definiti

/\/\@®¢Ø
15-02-2004, 01:00
Se ho capito bene la tua domanda non e' difficile fare quello che chiedi. Ad esempio:



template <class T>
void foo()
{
vector< dato<T>*> set;
/* etc... */
}

ri
15-02-2004, 01:15
non voglio un vettore all'interno della classe, non ci sarebbe problema altrimenti, come hai fatto giustamente notare tu
voglio un vettore che contenga puntatori alla classe template, però non dello stesso tipo
questo si fa utilizzando la classe base
il problema è definire un'interfaccia che mi permetta di lavorare coi tipi defiiniti dal template
e questo penso non sia possibile..

verloc
15-02-2004, 06:25
forse,e dico forse con i "traits"

http://www.cantrip.org/traits.html

o forse ancora (se ho capito quello che vuoi) con l"any" della boost:

http://www.boost.org/libs/any/index.html

ri
15-02-2004, 09:05
ottimi link entrambi, grazie mille
le any sembrerebbero la soluzione, devo fare qualche test per capire esattamente cosa comportano
ma già il fatto che una volta definito il tipo non effettua conversioni implicite mi piace ^^

verloc
15-02-2004, 09:17
ah,no,forse ho capito che vuoi.
Si fa con un pointer wrapper.
Cerca "Mumit STL newbie guide".

Vedi se va bene questo:


Templated pointer wrapper that takes a pointer to the base class
The following example shows 2 classes derived from Base, derived1 and derived2 and a templated wrapper Wrapper<T>. The wrapper class assumes that the base class provides a virtual clone facility and does the memory management.
Note: After the new'd Base derivative is passed to the wrapper, it owns it and deletes it in the destructor.



--------------------------------------------------------------------------------

#include <stl.h>
#include <string.h>
#include <iostream.h>

//
// abstract base class
//
class Base {
public:
const char* typename() const { return typename_; }
virtual Base* clone() const = 0;
virtual void identify(ostream& os) const = 0;
virtual ~Base();

public:
static int count;

protected:
Base(const char* typename);
Base(const Base& base);

private:
char* typename_;
};

Base::Base(const char* typename) {
const char* tname = (typename) ? typename : "unknown";
strcpy(typename_ = new char[strlen(tname) 1], tname);
count;
}

Base::Base(const Base& base) {
strcpy(
typename_ = new char[strlen(base.typename_) 1], base.typename_
);
count;
}

Base::~Base() {
delete[] typename_;
--count;
}

//
// First derived class.
//
class Derived1 : public Base {
public:
Derived1(int data) : Base("derived1"), data_(data) { }
Derived1(const Derived1& d) : Base("derived1"), data_(d.data()) { }
virtual ~Derived1() { }
virtual Base* clone() const { return new Derived1(*this); }
virtual void identify(ostream& os) const;
int data() const { return data_; }
private:
int data_;
};

virtual void Derived1::identify(ostream& os) const {
os << "(" << typename() << " " << data() << ")";
}

//
// Second derived class.
//
class Derived2 : public Base {
public:
Derived2(int data) : Base("derived2"), data_(data) { }
Derived2(const Derived2& d) : Base("derived2"), data_(d.data()) { }
virtual ~Derived2() { }
virtual Base* clone() const { return new Derived2(*this); }
virtual void identify(ostream& os) const;
int data() const { return data_; }
private:
int data_;
};

virtual void Derived2::identify(ostream& os) const {
os << "(" << typename() << " " << data() << ")";
}

//
// now define a templated pointer wrapper. The class must support the
// clone() method.
//
template <class T>
class PtrWrapper {
public:
PtrWrapper(T* t_ptr = 0) : t_ptr_(t_ptr) { }
PtrWrapper(const PtrWrapper<T>& w) {
t_ptr_ = w() ? w()->clone() : 0;
}
~PtrWrapper() { delete t_ptr_; }
const T* operator()() const { return t_ptr_; }
T* operator()() { return t_ptr_; }
PtrWrapper<T>& operator= (const PtrWrapper<T>& w) {
delete t_ptr_;
t_ptr_ = w()->clone();
return *this;
}
private:
T* t_ptr_;
};

template <class T>
bool operator== (const PtrWrapper<T>& w1, const PtrWrapper<T>& w2) {
return false;
}

template <class T>
bool operator< (const PtrWrapper<T>& w1, const PtrWrapper<T>& w2) {
return false;
}

//
// end of class defs.
//

// define static members.
int Base::count = 0;

int main(int, char*[]) {
list<PtrWrapper<Base> > list1;
list1.push_back(PtrWrapper<Base> (new Derived1(101)));
list1.push_back(PtrWrapper<Base> (new Derived2(201)));
list1.push_back(PtrWrapper<Base> (new Derived2(202)));
list1.push_back(PtrWrapper<Base> (new Derived1(102)));
list1.push_back(PtrWrapper<Base> (new Derived2(203)));

list<PtrWrapper<Base> >::const_iterator it = list1.begin();
for(; it != list1.end(); it) {
const PtrWrapper<Base>& w = *it;
w()->identify(cerr);
cerr << " ";
}
cerr << endl << endl;

return 0;
}



--------------------------------------------------------------------------------
And here's the output:


--------------------------------------------------------------------------------

(derived1 101) (derived2 201) (derived2 202) (derived1 102) (derived2 203)





vedi un pò tu se fa al caso tuo. :)

ri
15-02-2004, 09:26
è praticamente quello che ho fatto io
il problema è che in quell'esempio l'interfaccia non fornisce metodi che ritornano tipi T

verloc
15-02-2004, 09:37
e infatti...:)
Cmq,se vuoi un contenitore che contenga tipi di dato diversi allora "any" dovrebbe fare al caso tuo.

Dovresti prima di utilizzarlo, tornare indietro e riflettere se l'impostazione logica del tuo problema sia ottimamente risolto da un contenitore di quel genere oppure richieda una diversa soluzione;più efficiente o più pulita.

Dico così,a caso ;)

ri
15-02-2004, 09:39
no, neanche le any fanno al caso mio... :(

a me serve un "qualcosa" che sia una interfaccia comune a tipi diversi
però questo qualcosa, una volta deciso di che tipo è, NON deve accettare tipi diversi
inoltre è per me pressochè fondamentale il poter contenere questo "qualcosa", di qualsiasi tipo sia, all'interno di un singolo container (un vector, un map, etc...)

cionci
15-02-2004, 10:09
Bel problema...in pratica ti servirebbe un vettore di oggetti non specializzati che possa contenere oggetti di qualsiasi tipo... Mica una cosa da nulla...

ri
15-02-2004, 10:14
beh quando mi rivolgo al forum è perchè ho cose stuzzicanti da proporre :p

/\/\@®¢Ø
15-02-2004, 10:21
Originariamente inviato da ri
la domanda si può riformulare in un altro modo:
come è possibile definire un'interfaccia alla classe base che però non abbia un tipo di ritorno fisso? (vedi la funzione get...)

Vediamo se questa volta capisco giusto...
Si, puoi farlo, ad esempio il tipo di ritorno puo' essere a sua volta una classe base; in alternativa fai fare tutto il lavoro alle classi nel vettore e non ritorni mai oggetti. Ma non penso che sia quello che ti serve, visto che poi a runtime devi sempre andare a fare dei dynamic_cast e controllare che hai il tipo che ti serve.
Mi sembra che ti serva il vettore come "buffer temporaneo" per metterci gli oggetti e pescarteli dopo... a questo punto non ti conviene tenerti un contenitore per ogni tipo ?

ri
15-02-2004, 10:27
no il vettore mi serve come descrittore di una struttura dati generica composta da tipi di dato diverse (es, una tabella di un db, con N campi con un max di N tipi diversi per campo), che una volta definita rimane fissa
non voglio fare N vettori per gli N tipi di dato perchè non è detto che i tipi rimangano sempre N...

cionci
15-02-2004, 10:30
Originariamente inviato da /\/\@®¢Ø
Ma non penso che sia quello che ti serve, visto che poi a runtime devi sempre andare a fare dei dynamic_cast e controllare che hai il tipo che ti serve.
Infatti, anche io avevo pensato di aggirare così, ma implicava che dovevi fare un cast...

/\/\@®¢Ø: hai la signature troppo lunga... Può essere al max 3 righe a 1024x768... ;)

cionci
15-02-2004, 10:57
Originariamente inviato da ri
non voglio fare N vettori per gli N tipi di dato perchè non è detto che i tipi rimangano sempre N...
Puoi lasciare che sia il C++ a farli per te... Ora devo pensare come...

/\/\@®¢Ø
15-02-2004, 16:22
Originariamente inviato da cionci
/\/\@®¢Ø: hai la signature troppo lunga... Può essere al max 3 righe a 1024x768... ;)
:mbe:
Io sono a 1024x768 e Konqueror me le fa stare su tre righe :confused:
Vabbe', dammi un attimo di tempo che le modifico

/\/\@®¢Ø
15-02-2004, 16:28
Originariamente inviato da ri
no il vettore mi serve come descrittore di una struttura dati generica composta da tipi di dato diverse (es, una tabella di un db, con N campi con un max di N tipi diversi per campo), che una volta definita rimane fissa
non voglio fare N vettori per gli N tipi di dato perchè non è detto che i tipi rimangano sempre N...
Saranno i bagordi di fine settimana ma ti capisco sempre meno :confused:.
Prima dici che una volta definita rimane fissa, poi dici che non e' detto che rimangano sempre gli stessi... Non puoi farmi un esempio concreto (magari non legale, ma che renda l'idea) ?

ri
15-02-2004, 17:56
i tipi possono non rimanere gli stessi nella "vita" del sorgente.... fra 3 mesi potrebbe esserci bisogno di inserire un nuovo tipo "unsigned long double salto mortale carpiato avvitato" e non vorrei dover andare a toccare il codice

/\/\@®¢Ø
15-02-2004, 22:21
I templates non fanno al caso tuo allora; se cambi il tipo con cui usi un contenitore (o un qualsiasi altro oggetto/funzione parametrico) devi ricompilare il codice. La soluzione piu' pratica resta secondo me quello di una classe base: in ogni caso se non sai a priori con che tipi avrai a che fare non sai neanche che operazioni ci devi fare; se le operazioni sono invece comuni puoi 'spostarle' all'interno delle classi.

cionci
15-02-2004, 23:10
La classe base comunque non gli funziona...se ho capito:

template <class T>
class base
{
public:
base() {};
T get() = 0;
void set(T d) = 0;
};

template <class T>
class derivata: public base
{
T dato;
public:
base(T d):dato(d) {};
T get() { return d; };
void set(T d) { dato = d; };
};

Lui vuole un vettore o una qualsiasi struttura che possa contenere indistintamente e mescolati derivata<int> o derivata<double>...in questo caso la classe usata, ad esempio, con vector non va bene... Nella definizione del vector dobbiamo comunque specificare un tipo...

/\/\@®¢Ø
15-02-2004, 23:29
Io mi riferivo all'esempio che ha fatto lui: classe base non template e classe derivata template.
Visto che lui parla di non toccare il codice le alternative sono due.
Se i parametri del template possono venire scelti a partire da un insieme limitato (magari anche piu' volte, andrebbe bene per l'esempio dei campi del database), allora non deve fare altro che fare che provare a fare un dynamic_cast ai vari tipi e vedere quello che e' (o con qualceh altra alternativa analoga). Se invece vuole poter aggiungere nuovi tipi senza ricompilare, l'unica e' usare un meccanismo che carichi dinamicamente altro codice (una sorta di sistema di plug-ins insomma). In questo caso pero' avra' bisogno di una funzione di inizializzazione comune e questa non potra che ritornare o un void* o una istanza di una qualche classe base, e si ritorna a capo.

ri
16-02-2004, 08:09
non escludevo totalmente un intervento sul codice, è ovviamente impossibile
ma l'aggiungere un vettore per un nuovo tipo di dati significa andare a toccare tutte le procedure che devono accedere ai dati per dirgli dell'esistenza di un nuovo vettore dove guardare
questo, converrete, è parecchio scomodo...
il fatto di avere una classe base con dei metodi predefiniti che lavorano con tipi diversi non mi pare errato concettualmente: su quei dati devo fare operazioni di assegnazione, di confronto e di recupero... perchè devo duplicarmi tutto il codice per ogni singolo tipo quando un template lo fa al posto mio?

comunque concordo che probabilmente la soluzione migliore è riorganizzare il tutto in un altro modo...

ri
16-02-2004, 08:11
Originariamente inviato da cionci
La classe base comunque non gli funziona...se ho capito:

template <class T>
class base
{
public:
base() {};
T get() = 0;
void set(T d) = 0;
};

template <class T>
class derivata: public base
{
T dato;
public:
base(T d):dato(d) {};
T get() { return d; };
void set(T d) { dato = d; };
};

Lui vuole un vettore o una qualsiasi struttura che possa contenere indistintamente e mescolati derivata<int> o derivata<double>...in questo caso la classe usata, ad esempio, con vector non va bene... Nella definizione del vector dobbiamo comunque specificare un tipo...


la classe base non può ovviamente essere una classe template, ma una classe normale senza tipo, il che rende impossibile avere un'interfaccia che preveda un ritorno o un passaggio di un parametro T

/\/\@®¢Ø
16-02-2004, 08:23
Non si puo' fare molto diversamente, a causa della forte tipizazzione del C++:
Se io ho un del codice del tipo

cin >> i;
return v[i];

dove v e' il tuo vettore, come farei a sapere il tipo dell'elemento scelto ?
Con il controllo dei tipi a runtime puoi vare qualcosa del genere:

Base* b = v[i];
Derivata<float>* f = dynamic_cast<Derivata<float>*>(b);
if ( f )
{ ... }

A seconda poi di quel che devi fare con con il tipo contenuto, potrebbe essere piu' semplice (ed elegante ed efficiente) spostare tutto all'interno della classe derivata.

ri
16-02-2004, 08:28
sono in ufficio e ci penso su, giuro che entro pranzo un'idea me la faccio venire :mad:

:muro:

verloc
17-02-2004, 11:20
Originariamente inviato da /\/\@®¢Ø

Base* b = v[i];
Derivata<float>* f = dynamic_cast<Derivata<float>*>(b);
if ( f )
{ ... }



A proposito,Marco dimmi:

A parte i problemi di leggibilità,flessibilità etc

è maggiore l'overhead del controllo di tipo a runtime o
è maggiore quello (sicuramente stilist. + orripilante) di settare un dato membro che "identifichi" il tipo(per esempio con un enum) e poi controllare a runtime che tipo é ?


(ad es. int gettype(if _type==0 return ciccia;...if _type==24 return pappa)

/\/\@®¢Ø
17-02-2004, 13:22
A naso direi che il controllo di tipo a runtime e' un po' piu' pesante, soprattutto perche' 'sale' la gerarchia degli oggetti.
Ad esempio se io ho le classi A,B e C con la seguente parentela

A
/ \
B C

Il seguente codice

C* c = C();
A* a = dynamic_cast<A*>(c);
if ( a )
cout << "si" ;

effettivamente stampa 'si' ad output.
In questi casi penso che il costo aggiuntivo non sia cosi' rilevante, lo potrebbe essere in casi di alberi di derivazione molto profondi, specialmente quando il cast fallisce.
D'altra parte l'enum ti funziona solo quando hai solo una classe base (magari astratta) e classi che derivano direttametne da questa (e, aggiungo, sei sicuro che non verranno aggiunte altre classi da terzi).

/\/\@®¢Ø
17-02-2004, 14:33
uff.. l'esempio non e' chiarificatore, ne faccio un altro piu' simile al problema proposto da ri.

consideriamo la gerarchia

A
\
B
\
C


e il codice

vector<A*> v;
v.push_back( new C() );
/* ... */
B* x = dynamic_cast<B*>( v[0] );

In questo caso B il dynamic_cast viene effettuato con successo, nonostante l'oggetto in questione sia una istanza della classe C. Se usi gli enum non riesci ad ottenere lo stesso effetto o, meglio, per riuscirci devi reinventare il dynamic_cast, con benefici e difetti.

ri
17-02-2004, 14:41
belle ste discussioni
comunque, prestazioni o meno, preferisco il dynamic_cast ad usare gli enum... secondo me un codice con un cast di quel tipo è molto più esplicativo che non andare ad usare un enum

ah per quanto riguarda il mio problema ancora non ho avuto tempo per affrontarlo :(
ma lo scorno, a se lo scorno :mad:

verloc
17-02-2004, 15:59
grazie Marco,
ben gentile :D

cionci
17-02-2004, 17:29
Originariamente inviato da verloc
(ad es. int gettype(if _type==0 return ciccia;...if _type==24 return pappa)
Questo non lo puoi comunque nascondere all'inerno della classe... Qualel tipo gli fai ritornare ?

Si può dichiarare un puntatore a funzione generico ? Nel senso che i parametri sono ben definiti, ma il tipo di ritorno è variaible e può essere definito a runtime con un cast ?

ri
17-02-2004, 19:54
si, usando il void*
però imho diventa pericoloso gestire tutto l'ambaradan

verloc
18-02-2004, 11:57
No,in effetti si dovrebbe gestire con uno static enum :)
oppure gestire tutto con gli interi a "chasson de cain" :D

(roba orripilante) :nonsifa:
meglio adoperare l'ereditarietà.