Newsgroup: it.comp.lang.c++
Titolo: nozioni di base sui template

Salve. 
Sono inesperto nell'utilizzo dei template, ache se credo di conscere
discretamente il resto del C++ standard ANSI.
Ho il seguente problema.
Se io scrivo i seguenti file:
 1 - un file (diciamo pnpla.h) contenente il seguente codice
#ifndef HNPLAH 
#define HNPLA
template <class Tipo> class Npla {
    Tipo *pT;
    int n;
public:
    Npla();
    ~Npla() {delete []pT;}
};
#endif
  2 - un file (diciamo pnpla.cc) contenente il seguente codice
#include "pnpla.h"
 template <class Tipo> Npla<Tipo>::Npla() {
    pT = new Tipo [1];
    n = 1;
}
 3 - un file (diciamo m.cc) contenente il seguente codice
#include "pnpla.h"
void main(){
    int x[]={1,2,3};
    Npla<int> n;
}
e poi provo a compilare con 
 g++ pnpla.cc m.cc -o pnpla
(ho provato con Linux, sia Red Hat 5.0 che Slackware 3.3) 
ottengo il seguente messaggio:
/tmp/cca313072.o: In function `main':
/tmp/cca313072.o(.text+0x20): undefined reference to `Npla<int>::Npla(void)'
Invece se inserisco il codice del costruttore direttamente nella 
dichiarazione del template (quindi nel file npla.h) il tutto sembra
funzionare.
Il tutto sembra funzionare anche se, invece di spostare il codice
del costruttore nel file npla.h, sposto il codice della funzione main
nel file npla.cc.
Questo mi fa pensare che il compilatore, quando istanzia una istanza di
template consideri le funzioni membro come implicitamente inline o
comunque statiche (nel senso di "visibili solo dall'interno del file in
cui si trova il codice della loro definizione").
In questo caso mi viene il sospetto che le funzioni membro di un template
debbano contenere poche istruzioni (come si ritiene in genere per le
funzioni inline, per evitare duplicazioni di codice).
domanda a - C'e' qualcuno che sa dirmi come stanno le cose ? Ho letto vari
            testi,ma non ho trovato molte informazioni a questo proposito.
domanda b - Se io volessi derivare dalla classe Npla altre classi, ad
            esempio coppia, terna e quaterna, sarebbe possibile: 
 
               i)  dichiarare il distruttore di Npla come virtuale ?
               ii) Inserire in Npla delle funzioni membro virtuali ?
Scusate per l'ignoranza e grazie per l'attenzione.
Il mio indirizzo di e-mail preferenziale e'
gu...@sssup1.sssup.it
Marco Guidi

                ii) Inserire in Npla delle funzioni membro virtuali ?
Si.
	Mauro

                ii) Inserire in Npla delle funzioni membro virtuali ?
Risposta positiva ad entrambe le domande.
EMG

, Marco Guidi <g
Scusate se rompo ancora le scatole, ma non riesco a trovare molte informazioni
nemmeno sullo Stroustrup, per cui sto andando un po' per tentativi.
Per quanto riguarda il seguente codice che ho usato per derivare la classe B
"dal template" A, sembra funzionare  (compilando con g++ sotto Linux).
Vorrei sapere se questo codice rispetta lo standard del linguaggio o
se comunque contiene castronerie madornali.
Ogni suggerimento sara' gradito
#include <iostream.h>
template <class T> class A {
    T *p;
    int n;
public:
    A() {
       p = new T [1];
       n = 1;
    }
    A(int i) {
        p = new T [i];
        n = i;
    }
    virtual void metodo() {
        cout << "n vale " << n << endl;
    }
    virtual ~A() {
        delete []p;
    }
};
class B : public A<char> {
public:
   B() : A<char>() { }
   B(int i) : A<char>(i) { }
   virtual ~B( ) { }
};
int  main(int, int *[]) {
    B oggettoB(3);
    oggettoB.metodo();
    return 0;
}
 

 se comunque contiene castronerie madornali.
A me sembra tutto a posto. Molto buona l'accortezza di porre virtuale il
distruttore della classe derivata in previsione di poterla utilizzare
come classe base per ulteriori derivazioni.
EMG

     }
Stessa cosa di prima.
Poi conviene dichiarare "exlpicit" i construttori che prendono un
solo paramentro, soprattutto se questo e' di un tipo base (int, 
char, ...).  Questo evita le conversioni implicite che potrebbero
creare sorprese spiacevoli: per esempio se c'e' un banale errore
di sintassi, il compilatore invece di segnalare l'errore potrebbe
convertire un intero in un A e generare un programma dai risultati
imprevedibili.
Un'altra cosa: la classe ha un membro puntatore (p), ma non ha
il copy constructor e l'operatore = e quindi si becca quelli di
default, che pero' non sono adatti (copiano membro a membro ma
non copiano la roba puntata, che viene condivisa, e di solito
deallocata 2 volte).
Ci sono due possibilita':
- dotare la classe di copy constructor e op. = idonei, che allocano
  un nuovo array e copiano i vari elementi.
- disabilitare quelli di default, dichiarandoli privati e senza
  definirli:
class A {
  ...
private:
  A( const A & );
  A & operator = ( const A & );
public:
  ...
};
In questo modo quando il compilatore avra' bisogno di fare una copia,
invece di farne una sbagliata, segnalera' un errore.
Se usi l'egcs, oltre a -Wall (praticamente obbligatoria), puoi
provare anche -Weffc++, che segnala quando non si rispettano
i consigli di Meyers in "Effective C++". Purtroppo con gli
include standard si ottengono centinaia di warning.
	Mauro

 include standard si ottengono centinaia di warning.
Cosa che rende quell'opzione inutile, almeno secondo me.
EMG

A() : p(new T [1]),  n(1)  {}
   1 - Per quale motivo si ritiene piu' conveniente questo tipo di
       implementazione rispetto ad allocare memoria ed
       eseguire l'assegnamento nel corpo di A ?
   2 - Inizializzando i campi nella member initialization list,
       come posso inserire un controllo sulla allocazione
       di memoria fatta con la new per il costruttore A() come faccio
       invece per il costruttore A(int i). In altre parole come
       puo' essere fatto il codice che intercetta una eventuale eccezione
       generata nella inizializzazione inserita nella  member initialization
       list ?
#include <iostream.h>
template <class T> class A {
    T *p;
    int n;
public:
    A() : p(new T [1]), n(1) { }
    explicit A(int i) {
        try {
            p = new T [i];
        }
        catch(...) {
            cout << "Costruttore di A: errore nella allocazione" << endl;
            exit(0);
        }
        n = i;
    }
    virtual void metodo() const {
        cout << "n vale " << n << endl;
    }
    virtual ~A() {
        delete []p;
    }
};
class B : public A<char> {
public:
    B() : A<char>() { }
    explicit B(int i) : A<char>(i) { }


        list ?
Pippo::Pippo(...)
  try
    : m1(a)
    , m2(b)  // inizializzazione
  {
    // corpo del costruttore
  }
  catch (...) {
    // gestione delle eccezioni
  }
	Mauro

       eseguire l'assegnamento nel corpo di A ?
La costruzione di un oggetto composto avviene costruendo prima i
componenti. Questo implica che al momento in cui viene eseguito il
corpo del costruttore sono gia` stati invocati ed eseguiti i costruttori
degli oggetti contenuti.
Eseguire un assegnamento ai componenti di A nel corpo del costruttore A::A()
significa quindi aggiungere inutile overhead perche` l'inizializzazione era gia`
stata fatta prima. L'uso della lista di inizializzazione e` il mezzo che ci permette di
controllare l'inizializzazione dei sotto oggetti e di evitare quindi inutili overhead.
Questo in linea di principio un motivo piu` che valido per scegliere la lista di
inizializzazione. Altri motivi riguardano sostanzialmente la possibilita` che la
costruzione di un oggetto fallisca...
Per qunato riguarda la seconda domanda, vedo che ti e` gia` stata data una
risposta piuttosto chiara.
Saluti,
Paolo
----------------------------------------------------------
  Paolo Marotta
  Computer Science, University of Pisa
  home:   
http://www.cli.di.unipi.it/~marotta
----------------------------------------------------------