Newsgroup: it.comp.lang.c++
Titolo: E' giusto quello che ho capito?

Se ho una classe i cui metodi usano come parametro oggetti di un certo tipo,
che è rappresentato da un'altra classe, classe non implementata sullo stesso
file .cpp corrente bensi in un altro file .cpp, il modo per fare capire alla
classe "incriminata" che i tipi , da lei al momento sconosciuti, sono
definiti altrove è far uso dei file headers?
Più precisamente, se la mia classe A ha bisogno di oggetti il cui tipo è
definito nelle classi B e C, creo dei file header A.h, B.h e C.h, in ognuno
dei quali inserico il prototipo della classe (in A.h quello della classe A,
in B.h quello della classe omonima e cosi via).
Nei file A.cpp, B.cpp, C.cpp definisco i metodi della classe omonima (credo
senza ripetere il prototipo dei metodi, perchè credo di aver fatto quello
che occorre nel relativo file .h),
cioè cose simili a queste:
Es.
------------A.cpp-------------
int A:: Metodo_numero_1(int var_1,int var_2){
//......
// Definizione del metodo
}
void A:: Metodo_numero_2(double timestamp){
//......
// Definizione del metodo
}
ecc. ecc col resto dei metodi di questa classe
-------------End_definition_class_A---------------
-----------B.cpp--------------
Definizione dei metodi della classe B, ecc. ecc.
------------End_definition_class_B----------------
Visto che ciascuno dei metodi delle classi A, B e C avrà bisogno di
"conoscere" le altre classi, prima della dichiarazione della classe, es.
della classe A, inserisco le dichiarazioni delle classi mancanti:
-------------A.h----------
class B;
class C;
class A{
// prototipi dei metodi della classe A
int A:: Metodo_numero_1(int var_1,int var_2);
void A:: Metodo_numero_2(double timestamp);
/............./
}
------------End_of_A.h--------------
ed alla stessa maniera per le altre classi B e C
Alla fine, inoltre, nei relativi files .cpp scrivo:
-----------A.cpp--------
#include "A.h"
#include "B.h"
#include "C.h"
-x-x-x-x-x-x-x-x-x-x-
Definizione dei metodi di una specifica classe
-x-x-x-x-x-x-x-x-x-x-
-------End_file_A.cpp------------
Quello che ho scritto finora è la maniera giusta di implementare?
C'è una discussione su questo news del 18/07/2000 : subject <inclusione di
header>
a cui risponde, tra i quali Alex Martelli.
Non so , però, se ho afferrato bene la loro discussione.
Grazie davvero tanto a chi mi da una mano.
Bye,
Sebastiano

 wrote in message 8n993o$8if$1...@serv1.albacom.net...
[...]
Non sono sicuro di aver capito quello che vuoi sapere, comunque eccoti
un esempio che spero ti sia chiaro.
Nota: tra <> č scritto il nome del file
<B.h>
// qui č buona norma inserire solo la dichiarazione della classe
class B
{
  //...
   public:
      B();
      int f();
      // ...
};
----------------
<B.cpp>
// qui va l'implementazione della classe B
#include "B.h"
B::B() { // ... }
int B::f() {  //... }
---------------
Lo stesso dicasi per classe C.
Se ora hai una classe A che usa le classi B e C, l'unica cosa che devi fare č
includere gli header in cui queste classi sono definite.
<A.h>
#include "B.h"
#include "C.h"
class A
{
   private:
     B l;
     //...
  public:
    A(B b);
    C g();
    // ...
};
---------------
<A.cpp>
#include "A.h"
A::A(B b) { // ... }
C A::g() { // ... }
----------------
Era questo che intendevi?
Se la classe A usa le classi B e C, deve conoscere come queste sono fatte.
Nel mio esempio la classe A ha un membro di tipo B.
Quando il compilatore analizza A, deve sapere la grandezza
della classe B (sizeof(B)) per poter determinare la grandezza della classe A.
La cosa č diversa se usi SOLO dei puntatori:
class A
{
   private:
     B* pB;
   // ...
};
In questo caso basta pre-dichiarare che B č una classe.
Basta cioč scrivere prima della classe A:
class B;
Tieni presente che in questo modo non puoi usare i metodi di B
(ad ex pB->f())
in quanto il compilatore non sa come č fatta la classe B.
Supponiamo ora di avere un programma che usa la classe A
<main.cpp>
#include "A.h"
int main()
{ 
   A a;
   a.f();
   // ...
}
Se compili il codice cosě come č otterrai un "Unresolved reference ..."
Infatti il compilatore non vede le implementazione di A::A() e A::f() etc.
Supponendo di usare il BCC 5.5 quello che devi fare č:
BCC32 -c B.cpp
BCC32 -c C.cpp
BCC32 -c A.cpp
(l'opzione -c i
ndica che vuoi solo compilare)
In questo modi ottieni dei file oggetto  B.obj C.obj A.obj
Per compilare il programma main.cpp non devi fare altro che:
BCC32 main.cpp B.obj C.obj A.obj
Il compilatore per prima cosa compila il file main.cpp ottenendo main.obj,
quindi linka tutti i file oggetto.
Un modo alternativo č fare direttamente
BCC32 main.cpp B.cpp C.cpp A.cpp
Spero di essere stato chiaro   Manlio Perillo

 manipolare oggetti che non conosce.
No.  In A.cpp, includerai sia A.h sia B.h, quindi la manipolazione
di oggetti di classe B sara` possibile ai metodi di A, e viceversa
poiche` gli stessi include li farai in B.cpp.
Alex
-------------End_Re:Inclusione header ----------------------
------------Re:Inclusione header------------------
Eh, già, questo normalmente è un problema. Però puoi sempre fare così,
all'inizio di A.h metti:
class B;
in questo modo dici che esiste senza dire cos'è. Il contrario in B.h:
class A;
In questo modo ognuna delle due classi sa che l'altra esiste, anche se non
sa come è fatta.
------------------------END----------------------
Alla fine di questo, cosa mi suggerisci?

 definiti altrove è far uso dei file headers?
Esatto.
EMG

Alla fine di questo, cosa mi suggerisci?
Quando sono presenti forti coupling (A e B non hanno nessun
senso se non vengono usati assieme) io normalmente uso un
solo .H per le due classi.
Tutti i tipi "base" come UL32 e simili andrebbero definiti
in un .H per conto loro (incluso da chi ne ha bisogno).
Andrea
--
Andrea Griffini, Programmer
http://www.netservers.com/~agriff

 multipla della classe A?
Non credo, non hai nessuna dichiarazione multipla della classe A.
Comunque per avere codice più lineare, ti consiglio di utilizzare un header
con tutte le dichiarazioni dei tipi in comune.
Ad esempio
<common.h>
// predichiarazioni per le classi A e B
class A;
class B;
//...
---------------
Tieni conto che comunque potrei aver detto delle cose non corrette;
tuttavia compilando gli esempi che ti ho fornito con il BCC 5.5,
non ho avuto problemi.
Anche per l'ultimo esempio (quello con Buffer) non ci sono stati problemi,
eccoti il programmino per il test:
<common.hpp>
#ifndef __COMMON_HPP
#define __COMMON_HPP
class A;
struct Buffer
{
  A* pA;
  int i;
};
#endif
-------------------
<A.hpp>
#ifndef __A_HPP
#define __A_HPP
#include "common.hpp"
class A
{
  private:
    Buffer buf;
  public:
    A();
    void f();
};
#endif
----------------------
<A.cpp>
#include "A.hpp"
A::A()
{
  buf.pA = this;
  buf.i = 0;
}
void A::f()
{
  //...
}
-----------------------
Ciao  Manlio Perillo

 wrote:
[...]
Vorrei approfittare della tua competenza per un dubbio che mi
č ancora rimasto.
Ecco un esempio:
#include <iostream>
template<class T> class B;       // forward declaration per B
template<class T> class A
{
  private:
    B<T>* pB;
  public:
    A(T val)
    : pB(new B<T>(val)) {  }
    void f() { pB->f(); }
};
template<class T> class B
{
  private:
    T i;
  public:
    B(T val)
    : i(val) {  }
    void f() { std::cout << "called B::f()  with B::i = " << i << '\n'; }
};
int main()
{
  A<float> a(5.6);
  a.f();
}
Questo codice funziona con il BCC 5.5 (e con il DJGPP) ma č legale secondo lo Standard?.
In effetti per la compilazione la situazione mi č chiara, essendo template ed essendo
pB->f() dipendente dal contesto del template, il tutto viene risolto al momento
della instantazione.
Ma i miei dubbi cominciano qui;
in che modo avviene l'instantazione?
Lo Standard cosa dice in proposito?
Ecco come penso vadano le cose:
Quando chiamo A<float> a(5.6) il compilatore instanzia A per il tipo float.
A questo punto passa ad analizzare i membri di A e nota che vi č presente
pB<float>.
Quindi occorre instanziare B<float> e procede tutto bene.
Le cose vanno veramente cosě?
Ma il mio cervello va in confusione totale con questo esempio
(che compila con i suddetti compilatori):
#include <iostream>
template<class T> class B;
template<class T> class A
{
  private:
    B<T>* pB;
  public:
    A()
    : pB(0) {  }
    A(T val)
    : pB(new B<T>(val)) {  }
    void f() { pB->f(); }
    void g() { std::cout << "called A::g()  with A::pB = " << pB << '\n'; }
};
template<class T> class B
{
  private:
    A<T>* pA;
  public:
    B(T val)
    : pA(new A<T>()) {  }      // se chiamo A<T>(val) entra in un circolo vizioso
    void f()
    {
      std::cout << "called B::f()  with B::pA = " << pA << '\n';
      pA->g();
    }
};
int main()
{
  A<float> a(5.6);
  a.f();
}
Questo codice č legale?
Come procedono le cose?
E' possibile trovare esempi, tipo quelli da me proposti, in cui si ottenga un errore?
Grazie  Manlio Perillo

E' possibile trovare esempi, tipo quelli da me proposti, in cui si ottenga un errore?
Se ti interessa vedere un errore di istanziazione template
per ricorsione infinita del compilatore c'e' un esempio
nel draft:
  #include <iostream>
  template<int i> int fac() { return i>1 ? i*fac<i-1>() : 1; }
  template<> int fac<0>() { return 1; }
  int main()
  {
    std::cout << fac<10>() << std::endl;
  }
Questo programma crea 10 funzioni per istanziazione ricorsiva
del template. Se a 10 sostituisci 100 o 1000 il compilatore
sara' felicissimo di esplodere (e non sempre si tratta di
esplosioni "gestite"). Il draft prevede che esista un limite
al numero di istanziazioni ricorsive, ma non dice quale sia
il limite minimo garantito (14.3.2[11])
(immagino quindi che chi scrive un compilatore possa fermare
la cosa a 1 senza perdere la conformita' ANSI...)
Curiosita': dichiarando le funzioni inline il codice generato
da BCC55 calcola direttamente il risultato compile time
(ho controllato guardando il codice assembler generato).
Sui template bisogna andarci cauti... ho l'impressione
che esclusi casi semplici I compilatori non siano ancora
coerenti con lo standard (e io non sono nemmeno cosi'
sicuro che lo standard sia coerente di suo: ho scritto
in passato dei compilatori e ti assicuro che la sintassi
C++ in generale e template in particolare e' veramente
una bestiaccia orribile)
Ad esempio nel codice mostrato nel draft il "template<>"
prima della specializzazione non e' presente... senza quello
pero' BCC55 non accetta la specializzazione... e procede
con ricorsione infinita sino ad esplodere.
Io ho poi incontrato forti incompatibilita' fra g++, BCC55
e VisualC++ quando ho iniziato a usare le specializzazioni
o le classi nidificate. Anche su altre parti che centrano
poco con i template ho avuto problemi di portabilita'
(vedi conversioni di tipo che entrano in gioco
nell'operatore ternario "?:" ... ho controllato il draft e
secondo me sull'argomento non fa affatto chiarezza).
La mia opinione spassionata e' che se si vuole giocare
allora e' ok pasticciare con queste cose ... per roba seria
meglio volare basso e schivare i sassi almeno per qualche
tempo ancora...
Io ogni tanto ricompilo su tutti e tre i compilatori che ho
a disposizione per verificare se ho usato cose su cui sono
tutti d'accordo e mi capita spesso di dover fare qualche
aggiustamento per restare nell'intersezione delle
interpretazioni...
Per non parlare dei bug dei compilatori in cui si incappa
quando si gironzola per la palude dei template: su BCC55
non appena si usano template con reference ed ereditarieta'
il sistema di controllo dei const impazzisce e genera
warning a sproposito (tipo: "variabile temporanea passata
a non-const reference" quando invece il reference e' const)
Oppure il VisualC++ che mi ha compilato questo template:
  template<class T>
  MyVector<T>::clear()
  {
    while(len_)
      v_[len_--].~T();
  }
dimenticandosi di decerementare "len_" se il template
viene istanziato con tipi nativi come int (ok... la
chiamata ~int() sparisce giustamente nel nulla... ma
gli effetti collaterali su len_ dovrebbero restare!!)


  }
whoooops... intendevo ovviamente v_[--len_].~T();


 wrote :
| <A.hpp>
| #ifndef __A_HPP
| #define __A_HPP
giusto un appunto :
Secondo lo standard i nomi che iniziano con un double under score, oppure
underscore e lettera maiuscola, sono riservati per usi interni del
compilatore. Meglio
#ifndef A_HPP__
e non usare i nome _Key, _Alloc per i nomi dei parametri template

 wrote in message 8ndcab$n6m$1...@lacerta.tiscalinet.it...

Non lo sapevo  GRAZIE
Manlio Perillo


Su questa cosa si base la Template Metaprogramming e l' Expression Template.
Sono tecniche recenti che usano i template in modo molto interessante.
Si può trovare qualcosa a riguardo alla home page di Bliz++.
Ad esempio:
template<int N> struct Factorial
{
  static const value = N * Factorial<N - 1>::value;    // static const obbliga il compilatore a calcolarlo a
compile time
};
template<> struct Factorial<1>
{
  static const value = 1;
};
int main()
{
  const int fact5 = Factorial<5>::value;
}
E questo è solo una delle applicazioni più semplici.
Questa tecnica è usata massicciamente nella libreria Bliz++ per fare in modo che, ad
esempio
Vector a, b, c, d:
// ...
a = b + c + d;
Nell'ultima riga di codice, grazie a queste tecniche, il tutto viene espanso a compile time
in una espressione che calcola la somma direttamente
a[i] = b[i] + c[i] + d[i].
Saluti  Manlio Perillo

 > compilatore.
Però, un momento...
Che cosa intende lo standard per 'riservati per usi interni del compitaore'?
La mia macro serve (solo) per evitare inclusioni multiple, non è quindi
un uso interno al compilatore?
Manlio Perillo

 un uso interno al compilatore?
No. Per uso interno si intende utilizzato nell'implementazione del
linguaggio specificato dallo standard, quindi nel compilatore o nelle
librerie che lo accompagnano.
Ciao,
Nicola

 Che cosa intende lo standard per 'riservati per usi interni del compitaore'?
Credo si riferisca agli implementatori delle librerie.
EMG