Newsgroup: it.comp.lang.c++
Titolo: 'magia' dei template?

Considerate il seguente programma
#include <iostream>
class B;
#ifdef __USE_TEMPLATE
template<class T>
#endif
class A
{
  private:
    B* pB;
  public:
    A() {  }
    void f()
    {
      pB->f();
    }
};
#ifdef __USE_TEMPLATE
#define A A<int>
#endif
class B
{
  public:
    B() {  }
    void f()
    {
      std::cout << "B::f() \n";
    }
};
main()
{
  {
    A a;
    a.f();
  }
  {
    B b;
    b.f();
  }
}
Ovviamente se la macro __USE_TEMPLATE non è definita non compila.
Ma se è definita tutto funziona!
E' dovuto alla instantazione ritardata dei template?
Mi sembra comunque strano, infatti uso A::f() prima di usare qualsiasi
metodo di B.
Comunque (se la macro è definita) compila sia con il BCC5.5 che con il
DJGPP.
Saluti  Manlio Perillo

 Ma se è definita tutto funziona!
Scusa, sicuramente mi sfugge qualcosa ma perché non dovrebbe funzionare?
EMG

}
Bye Raffaele, che spera di essere stato chiaro altrimenti ripassa
l'italiano al posto di analisi matematica.
-- 
 (o-  | Un OS per domarli, Un OS per trovarli,
 //\  | Un OS per ghermirli e nel buio incatenarli
 V_/_ | 
http://pinguin.linuxave.net
 r.salmaso(at)
flashnet.it

 wrote in message 8kpu1r$5i9
Intanto int main() e' meglio.
poi, e' errato chiamare un pB->f(), se l'oggetto B non esiste (non l'hai
mai creato, nel tuo codice)
inoltre, se A usa B, devi definire tutte le funz. di B prima di usarle,
percio' sposta la classe B prima di A, o in un altro file da includere.
| Ovviamente se la macro __USE_TEMPLATE non č definita non compila.
| Ma se č definita tutto funziona!
Ti diro' ti piu': se sposti la classe B prima, funziona anche senza. Mi
sembra un baco, e di quelli brutti: la il puntatore alla classe B non e'
_mai_ inizializzato ad un oggetto esistente. Come puo' funzionare? Non ho
sottomano lo STD, ma non mi sembra un dettaglio "implementation defined".
| E' dovuto alla instantazione ritardata dei template?
| Mi sembra comunque strano, infatti uso A::f() prima di usare qualsiasi
| metodo di B.
Probabilmente il fatto che A sia template ne ritarda il link, facendo in
modo che B::f() venga definita prima, e quindi divenga possibile usarla.
Per un parere piu' "professionale", attendiamo Alex M :-)

 wrote in message
[...]
| inoltre, se A usa B, devi definire tutte le funz. di B prima di usarle,
                            ^^^^^^^^
Emmm, ovviamente era dichiarare e non definire, postumi da sbronza.

 sottomano lo STD, ma non mi sembra un dettaglio "implementation defined".
No, e` *undefined*, condizione piu` forte di *implementation defined*: il
compilatore, quando chiami pB->f() con pB non inizializzato, o nullo, puo`
fare QUELLO CHE VUOLE, COME VUOLE, QUANDO VUOLE, e qualunque cosa faccia
non sara` mai considerato un difetto del compilatore a norma di standard,
bensi` sempre un errore da parte del tuo programma.
Comunque, non credo che Manlio volesse concentrarsi su questo, bensi`
sulla differenza di comportamento fra template e non-template.  Poiche`
il parametro di A (quando A e` un template) non entra in gioco nel
determinare cosa sia B, B e` non dipendente, e quindi la sintassi
relativa a B dovrebbe essere risolta quando il template e` definito,
e non quando viene instanziato.  In pratica, il comportamento dei due
compilatori provati da MP pare dimostrare che la loro sequenza di
operazioni non e` questa, bensi` quella, meno chiaramente definita,
tipica del C++ pre-standard (dove non era ben chiaro dove si dovessero
validare sintatticamente le varie parti di un template e quindi in
pratica lo si faceva "quando piu` fa comodo", all'istanziazione).
Se questo specifico caso sia una violazione da parte del compilatore,
non sono qualificato per dirlo.  _Credo_ di si`, ma la presenza di
undefined behavior, ad esempio, potrebbe offrire una via d'uscita ai
'legulei' del compilatore stesso!-).  Piu` seriamente, *non so* cosa
esattamente prevedano le regole relativamente ai *metodi inline* di
una classe template (e piu` in generale alle funzioni template che
sono inline); non essendovi esplicita richiesta d'istanziazione, ma
solo "istanziazione on demand", la situazione e` abbastanza oscura e
intricata.  Puo` essere resa ancora piu` tale usando invece della
esplicita chiamata di metodo una sintassi che si presta a molteplici
interpretazioni, come ad esempio l'uso di un operatore la cui
definizione possa venire variata "in corso d'opera" a seconda del
tipo B, come, che so, pB=new B; -- se piu` avanti B definisce un
suo operator new, il compilatore stara` usando quest'ultimo, o il
globale ::operator new, o abbiamo un caso di errore (e se si` di
quale gravita` -- e` tenuto il compilatore a segnalarlo, o puo` fare
quel che meglio gli aggrada...?).
Il mio atteggiamento personale (e professionale:-) relativo a queste
areee ancora un po' "oscure" del linguaggio puo` riassumersi con la
nota barzelletta:
    "Dottore, mi fa male se faccio cosi`"
    "Beh, allora non faccia cosi`"
In altri termini, "Se lo conosci, lo eviti".  Mi rendo conto che
questo e` vilmente e bassamente pragmatico da parte mia, ma a mia
parziale discolpa tenete conto che sono un ingegnere, quindi il
"dettaglio" relativo al fatto che un particolare costrutto mi
funzioni o meno sulle principali implementazioni ha per me una
discreta importanza (se fossi un puro scienziato, ovviamente,
disdegnerei queste considerazioni da "vil meccanico").
Alex

 wrote in message 8ks062
[...]
| No, e` *undefined*, condizione piu` forte di *implementation defined*: il
| compilatore, quando chiami pB->f() con pB non inizializzato, o nullo,
puo`
| fare QUELLO CHE VUOLE, COME VUOLE, QUANDO VUOLE, e qualunque cosa faccia
| non sara` mai considerato un difetto del compilatore a norma di standard,
| bensi` sempre un errore da parte del tuo programma.
E non e' richiesto nemmeno un warning? Grave mancanza, mi sembra: in fondo
se lo si fa per le variabili mai utilizzate e quelle non inizializzate, un
check sul puntatore non assegnato (tanto piu' membro privato di una classe
senza friend) non e' poi troppo diverso...

 senza friend) non e' poi troppo diverso...
Come puo` lo standard escludere, ad esempio, che quella classe venga
caricata per altre vie, attraverso mezzi non-portatili (cfr ad esempio
il pointer-swizzling ed altri approcci alla persistenza trasparente)?
Decidere per quali aspetti *dubbi* dare messaggi di warning, e per
quali no, e` strettamente una questione di qualita` del compilatore,
cosi` come la e` il fornire mezzi opportuni al programmatore per
controllare i warning emessi, il fatto che i warning siano sensati e
chiari come formulazione, ecc ecc.  Lo standard fa benissimo a non
entrare in questi aspetti, a mio vedere.
Alex

 wrote in message
8ks062$h6q$1...@nslave2.tin.it...
Grazie per la esauriente risposta.
Voglio solo dire che il mio programma è abbastanza stabile.
Un unico problema è che nella classe B ho uno
    static A<T> x
Ed il suo distruttore (a volte) viene chiamato due volte!
Comunque questo 'comportamento' dei template mi permetteva di fare una cosa
molto interessante.
Manlio Perillo

 wrote in message 8kpu1r$5i9$1...@nslave1.tin.it...
Per darvi un'idea del mio 'problema' penso che la soluzione migliore sia
postarvi il codice.
Infatti le due classi che ho implementato sono molto legate l'una all'altra.
Spero che il codice sia chiaro.
Fatemi sapere  Manlio Perillo.
file Handle.hpp
/***************************************************************************
***/
/*
*/
/*            HEADER PER LA DICHIARAZIONE DELLE CLASSI PER GESTIRE
*/
/*                   UN HANDLE ED UNA REFERENCE AD UN OGGETTO
*/
/*
*/
/*        Copyright Manlio Perillo 2000
*/
/*
*/
/*        data inizio:           30/06/2000
*/
/*        data ultima revisione: 16/07/2000
*/
/*
*/
/***************************************************************************
***/
#ifndef __HANDLE_HPP
#define __HANDLE_HPP
#include <list>
#include <algorithm>
#include <function>
#include <cassert>
#include <iostream>
template<class T> class Reference;
//
// --- Classe per gestire un Handle ad un oggetto ---
//
// Un Handle mantiene una ownership semantics (destructive copy semantics)
// sul suo oggetto.
// Dall'esterno si può accedere solo ad una const reference all'oggetto
stesso.
// L'intento è di avere una scatola chiusa ed inaccessibile.
//
// Per provvedere quindi un conveniente accesso all'oggetto, si deve usare
// la classe Reference.
// Ogni handle mantiene una lista di tutte le referenze che fanno capo ad
essa
// in modo da averne il pieno controllo
// -- vedi unlink che annulla tutte le referenze --.
//
// Una cosa importante è che il costruttore di copie copia la lista delle
referenze
// e l'oggetto, annullando poi quelli di provenienza.
// Infine setta come handle a cui fanno capo tutte le referenze della lista
ad esso
// Lo stesso dicasi per l'operatore di assegnamento.
//
// Quindi facendo handle a = b, l'handle b viene completamente annullato
mentre,
// IMPORTANTE, le referenze facenti capo ad esso vengono gestite dal nuovo
handle a.
// Invece facendo c = b, dove c è un handle preesistente, tutte le referenze
a c
// vengono prima annullate, quindi si procede come da sopra
//
// UNA ATTENZIONE particolare va posta nei distruttori in quando le classi
// Handle e Reference sono fortemente legate ed Handle potrebbe accedere ad
una
// Reference già distrutta (e viceversa)
//
template<class T> class Handle
{
  public:
    typedef std::list< Reference<T>* > reference_list;
    // classe per le eccezioni
    class Handle_Error {  };
  private:
    mutable T* ptr;                // puntatore all'oggetto da gestire
    mutable reference_list lRef;   // lista di tutte le referenze a questo
handle
    // cambia l'handle delle referenze
    void set_references_handle() const;
  private:
    friend class Reference<T>;
    //
    // interfaccia per la referenza
    //
    T* get() const;
    void update_reference(Reference<T>*) const;
    void remove_reference(Reference<T>*) const;
  private:
    void handle_error() const;
    bool invariant()    const;     // invariante della classe
  public:
    explicit Handle(T* p = 0)
    : ptr(p), lRef()
    {  }
    Handle(const Handle& ch)
    : ptr(ch.ptr), lRef(ch.lRef)
    {
      // copia in this ch e tutte le sue referenze e annulla l'handle ch
      ch.ptr = 0;
      ch.lRef.resize(0);
      // cambia l'handle delle referenze in this
      set_references_handle();
    }
    Handle& operator=(const Handle& ch)
    {
      if(this != &ch)
      {
        assert(invariant());
        // distrugge il contenuto di this e tutte le sue referenze
        // e vi copia ch
        unlink();
        if(ptr) delete ptr;
        ptr = ch.ptr;
        lRef = ch.lRef;
        // annulla l'handle ch
        ch.ptr = 0;
        ch.lRef.resize(0);
        // cambia l'handle delle referenze in this
        set_references_handle();
      }
      return *this;
    }
    ~Handle()
    {
      // una Reference non può essere legata ad un Handle distrutto
      unlink();
      if(ptr) delete ptr;
    }
    //
    // interfaccia per l'oggetto --- l'handle non modifica l'oggetto ---
    //
    const T& operator*()  const
    {
      if(!ptr) handle_error();
      return *ptr;
    }
    const T* operator->() const
    {
      if(!ptr) handle_error();
      return ptr;
    }
    // operatore per la conversione
    operator const T&() const
    {
      if(!ptr) handle_error();
      return *ptr;
    }
    // numero di referenze a questo handle (all'oggetto di questo handle)
    size_t references() const { return lRef.size(); }
    // annulla tutte le referenze a questo Handle
    void unlink() const;
};
// definizioni funzioni inline
template<class T> inline
void Handle<T>::set_references_handle() const
{
  assert(invariant());
  std::for_each( lRef.begin(), lRef.end(),
                 std::bind2nd(std::mem_fun(&Reference<T>::setHandle),
this) );
}
template<class T> T* Handle<T>::get() const
{
  return ptr;
}
template<class T>
inline void Handle<T>::update_reference(Reference<T>* ref) const
{
  lRef.push_back(ref);
  assert(invariant());
}
template<class T>
inline void Handle<T>::remove_reference(Reference<T>* ref) const
{
  assert( std::find(lRef.begin(), lRef.end(), ref) != lRef.end() );
  lRef.remove(ref);
  assert(invariant());
}
template<class T> void Handle<T>::unlink() const
{
  assert(invariant());
  std::for_each( lRef.begin(), lRef.end(),
std::mem_fun(&Reference<T>::unlink) );
  assert(invariant());
}
// operatori booleani
template<class T>
inline bool operator==(const Handle<T>& sx, const Handle<T>& dx)
{
  return *sx == *dx;
}
template<class T>
inline bool operator!=(const Handle<T>& sx, const Handle<T>& dx)
{
  return *sx != *dx;
}
// stream
template<class T>
inline std::ostream& operator<<(std::ostream& os, const Handle<T>& h)
{
  return os << *h;
}
//
// --- Classe per gestire una Reference ad un oggetto ---
//
// Provvede a fornire un comodo accesso all'oggetto.
// La sicurezza è stabilita dal fatto che la Reference fa riferimento ad un
Handle
// e non all'oggetto direttamente.
//
// Ogni volta che viene costruita una nuova Reference, questa va ad
aggiungersi
// nella lista delle Reference dell'Handle a cui fa capo (ma solo se non è
già nulla).
// Ogni volta che viene distrutta (perchè fuori scope o esplicitamenta
annullata)
// una Reference, questa viene cancellata dalla lista.
//
// Una cosa da precisare è che una Reference non riferita ad un Handle non
ha senso.
// Quindi il costrutore di default dovrebbe essere reso privato e non
definito.
// Tuttavia i containers standard lo richiedono; quindi si è usato il trucco
di
// dichiarare nella classe un membro statico di tipo Handle nullo.
// In tal modo tutte le Reference di default fanno riferimento a questo
Handle
//
template<class T> class Reference
{
  public:
    // classe per le eccezioni
    class Reference_Error {  };
  private:
    T* ptr;                        // puntatore all'oggetto da gestire
    const Handle<T>* pHandle;      // Handle a cui si riferisce questa
referenza
    // Handle nullo usato per il costruttore di default.
    static const Handle<T> null;
  private:
    void reference_error() const;
    bool invariant() const;        // l'invariante della classe
  private:
    friend class Handle<T>;
    //
    // interfaccia per Handle
    //
    void setHandle(const Handle<T>*);
  public:
    explicit Reference()
    {
      //
      // Non dovrebbe essere ammesso;
      // tuttavia i container standard richiedono un costruttore di default.
      //
      //
      // Usando il null membro statico, il suo distruttore viene invocato
due volte!
      //
      static Handle<T> null;
      ptr = null.get();
      pHandle = &null;
      assert(!ptr);
      assert(invariant());
    }
    explicit Reference(const Handle<T>& h)
    : ptr(h.get()), pHandle(&h)
    {
      // L'Handle deve contenere solo References attive
      if(ptr) pHandle->update_reference(this);
    }
    Reference(const Reference& r)
    : ptr(r.ptr), pHandle(r.pHandle)
    {
      assert(invariant());
      // L'Handle deve contenere solo References attive
      if(ptr) pHandle->update_reference(this);
    }
    Reference& operator=(const Reference& r)
    {
      if(this != &r)
      {
        // cancella questa referenza dal prececente Handle
        unlink();
        ptr = r.ptr;
        pHandle = r.pHandle;
        assert(invariant());
        // L'Handle deve contenere solo References attive
        if(ptr) pHandle->update_reference(this);
      }
      return *this;
    }
    ~Reference() { unlink(); }
    //
    // interfaccia per l'oggetto --- la referenza può modificare
l'oggetto ---
    //
    T& operator*() const
    {
      if(!ptr) reference_error();
      return *ptr;
    }
    T* operator->() const
    {
      if(!ptr) reference_error();
      return ptr;
    }
    // operatore per la conversione
    operator T&() const
    {
      if(!ptr) reference_error();
      return *ptr;
    }
    // annulla la referenza
    void unlink();
};
// definizione funzioni inline
template<class T>
inline bool Reference<T>::invariant() const
{
  //
  // Controlla l'invariante della classe.
  // Semplicemente l'Handle deve essere non nullo e l'oggetto (se non nullo)
  // deve coincidere con l'oggetto dell'Handle
  //
  return pHandle != 0 && ( (ptr && ptr == pHandle->ptr) || !ptr);
}
template<class T>
inline void Reference<T>::setHandle(const Handle<T>* pH)
{
  pHandle = pH;
  assert(invariant());
}
template<class T>
inline void Reference<T>::unlink()
{
  assert(invariant());
  if(ptr)
  {
    pHandle->remove_reference(this);
    ptr = 0;
  }
}
// operatori booleani
template<class T>
inline bool operator==(const Reference<T>& sx, const Reference<T>& dx)
{
  return *sx == *dx;
}
template<class T>
inline bool operator!=(const Reference<T>& sx, const Reference<T>& dx)
{
  return *sx != *dx;
}
// stream
template<class T>
inline std::ostream& operator<<(std::ostream& os, const Reference<T>& h)
{
  return os << *h;
}
// operatori booleani misti
template<class T>
inline bool operator==(const Reference<T>& sx, const Handle<T>& dx)
{
  return *sx == *dx;
}
template<class T>
inline bool operator!=(const Reference<T>& sx, const Handle<T>& dx)
{
  return *sx != *dx;
}
template<class T>
inline bool operator==(const Handle<T>& sx, const Reference<T>& dx)
{
  return *sx == *dx;
}
template<class T>
inline bool operator!=(const Handle<T>& sx, const Reference<T>& dx)
{
  return *sx == *dx;
}
#include "Handle.cpp"
#endif
file Handle.cpp
/***************************************************************************
***/
/*
*/
/*            SOURCE PER LA DICHIARAZIONE DELLE CLASSI PER GESTIRE
*/
/*                   UN HANDLE ED UNA REFERENCE AD UN OGGETTO
*/
/*
*/
/*        Copyright Manlio Perillo 2000
*/
/*
*/
/*        data inizio:           30/06/2000
*/
/*        data ultima revisione: 16/07/2000
*/
/*
*/
/***************************************************************************
***/
#ifndef __HANDLE_CPP
#define __HANDLE_CPP
#include "handle.hpp"
// --- definizioni per la classe Handle ---
template<class T> void Handle<T>::handle_error() const
{
  throw Handle_Error();
}
template<class T> bool Handle<T>::invariant() const
{
  //
  // Controlla l'invariante della classe:
  // tutte le referenze facenti capo a questo handle devono essere attive
  // (ossia devono riferirsi ad un oggetto non nullo)
  //
  // Inoltre se l'oggetto è nullo, l'Handle non deve avere referenze attive
  //
  reference_list::const_iterator iterator = lRef.begin();
  for(; iterator != lRef.end(); ++iterator) if(!(*iterator)->ptr) return
false;
  return (!ptr && lRef.size() == 0) || ptr;
}
// --- definizioni per la classe Reference ---
template<class T> const Handle<T> Reference<T>::null;
template<class T> void Reference<T>::reference_error() const
{
  throw Reference_Error();
}
#endif

 sulla differenza di comportamento fra template e non-template.
Temo di aver scelto un esempio infelice.
Eccovene uno che spero sia più chiaro:
#include <function>
#include <algorithm>
#include <list>
// forward declaration for class B
template<class T> class B;
// declaration for class A
template<class T> class A
{
  private:
    friend class B<T>;
    mutable std::list< B<T>* > list;
  public:
    A()
    : list()
    {  }
    size_t linked() const { return list.size(); }
    void unlink();
    // ...
};
template<class T> inline void A<T>::unlink()
{
  std::for_each( list.begin(), list.end(), std::mem_fun(&B<T>::unlink) );
}
// declaration for class B
template<class T> class B
{
  private:
    const A<T>* pA;
  public:
    B(const A<T>& a)
    : pA(&a)
    {
      pA->list.push_back(this);
    }
    void unlink() { pA->list.remove(this); }
    ~B() { unlink(); }
    //...
};
#include <iostream>
int main()
{
  A<int> a;
  std::cout << "B objects linked to a: " << a.linked() << '\n';
  {
    B<int> b(a);
    std::cout << "B objects linked to a: " << a.linked() << '\n';
  }
  std::cout << "B objects linked to a: " << a.linked() << '\n';
}
Manlio Perillo

 }
Qui, nessun problema, poiche` B<T> *e`* chiaramente dependent.
La diagnostica potra` venire dunque solo all'istanziazione e
solo se un dato B<T>::unlink e` inappropriato o assente per
questo uso.
Alex