Newsgroup: it.comp.lang.c++
Titolo: Include, template e funzioni inline (discorso generico)

Più vado avanti a scrivere codice e più mi sembra di essere
all'interno di un circolo vizioso. Per migliorare la leggibilità e il
riutilizzo mi capita spesso di scrivere funzioni/metodi da 1 riga, e di
renderle inline. Non di rado poi mi capita di scrivere funzioni/classi
template, con il risultato finale che mi trovo un progetto dove spesso
sono costretto a ricompilare da capo, e dove evitare collisioni fra i
vari include è a volte non banale. Mi rendo conto che risolverei gran
parte dei problemi evitando di usare così massicciamente le funzioni
inline (e i template), il punto è che non vorrei farlo. Voi avvertite
i miei stessi problemi o ci sono tecniche di cui non sono a conoscenza
che permettono di risolvere questo problema?


Allora, per quel che riguarda le collisioni degli include, non capisco a 
che cosa tu ti riferisca... mi viene da pensare che tu abbia dei 
problemi di include inclusi piu' volte, nel qual caso non stai 
utilizzando gli ifndef: il tuo .h dovrebbe avere la forma:
#ifndef _NAMESPACE_NOME_FILE_H
#define _NAMESPACE_NOME_FILE_H
// ... effettivo contenuto
#endif // _NAMESPACE_NOME_FILE_H
Per il resto, e' una cosa di esperienza: bisogna evitare di 
sovraingegnerizzare del codice che e' concettualmente semplice, 
estendendo e generalizzando quando non e' utile/costruttivo.
Se non hai bisogno di un template, non e' obbligatorio farlo. Quante 
volte riutilizzi il codice che hai reso template? Le funzioni da una 
riga identificano sempre un'azione significativa e sensata sulla classe 
a cui si riferiscono?
E' una questione principalmente di esperienza, anche se ci sono delle 
letture specifiche sul tema.
Ciao,
Zeppe

 inline (e i template), il punto è che non vorrei farlo.
 >
Sarebbe anche interessante sapere cosa intendi per "massicciamente".
Quale percentuale del tuo codice (limitatamente al numero di funzioni e 
metodi) e' definito come "inline"?
-- 
Roal Zanazzi

 a cui si riferiscono?
Non e' neanche questo il problema, perche' ogni mia classe template e'
utilizzata da almeno due tipi. Per quanto riguarda le funzioni inline,
oltre ai metodi accessor, utilizzo spesso dei metodi "ponte" per
semplificarmi poi la scrittura (ad esempio dato le classi di sopra, per
avere l'id della room in cui si trova un oggetto utilizzerei un metodo
ponte così:
int getIdRoom() { return rPoint->getId(); }
dove rPoint e':
Room* rPoint

 
Ad occhio e croce direi sul 50%


1) Assicurati che ogni file venga considerato una sola volta in ogni 
unità di compilazione.
2) A volte le macro sono più effettive delle funzioni inline.
3) Includi un intero file solo se strettamente necessario.
4) Rifletti bene prima di includere un header file in un altro header 
file: spesso non è necessario includere le definizioni, ma è sufficiente 
aggiungere le dichiarazionoi (per es. in presenza di puntatori o reference).
5) Sposta nel relativo file x.cpp tutti gli include che non servono al 
file x.h.
6) Se possibile dichiara le funzioni come static: non rendere globale 
ciò che è concettualmente locale.
7) Template. Varie alternative tra cui:
7a) Definisci i template in un file x.h, ma sposta le "implementazioni" 
in un file x.hpp. Includi x.hpp in fondo a x.h. Non usare un file .cpp 
poiché NON deve essere possibile compilare x.hpp indipendentemente da x.h.
7b) Definisci i template in un file x.h, ma sposta le "implementazioni" 
in un file x.cpp che includa x.h (all'inizio del file). In fondo a x.cpp 
aggiungi le "istanziazioni dirette" che ti servono.
Ogni altro file che si serve di x includerà soltanto x. Sarà tuo compito 
istanziare direttamente ciò che serve in x.cpp.
7c) 7b può essere usato per nascondere codice a un eventuale utente 
intermedio.
Kiuhnm

 quello della Oggetto ho un errore di forward declaration.
Riesci a riprodurre il problema in codice semplice semplice (bada bene!) 
da postare su questo ng?
Kiuhnm

 ma devo proprio includere il .h
Se non ho capito male, il problema si puo' esemplificare nel seguente 
programma:
   struct A {
     struct B *pB;
     void f1() { }
     void f2() { pB->g1(); }
   };
   struct B {
     struct A *pA;
     void g1() { }
     void g2() { pA->f1(); }
   };
   int main() {
     A a; a.f2();
     B b; b.g2();
   }
In tal caso, e' possibile risolverlo trasformando il suddetto programma 
nel seguente:
   struct A {
     struct B *pB;
     void f1() { }
     void f2();
   };
   struct B {
     struct A *pA;
     void g1() { }
     void g2() { pA->f1(); }
   };
   inline void A::f2() { pB->g1(); }
   int main() {
     A a; a.f2();
     B b; b.g2();
   }
L'inconveniente e' che le classi A e B devono essere definite nello 
stesso file (header) in quanto la definizione di A::f2 deve essere posta 
dopo la definizione della classe B.
D'altra parte, comunque chiunque abbia bisogno della definizione di A ha 
bisogno anche della definizione di B e viceversa.
-- 
     Carlo Milanesi
 
http://digilander.libero.it/carlmila

 Kiuhnm
-----------------------  File main.cpp --------------------------------
#include "oggetto.h"
#include "room.h"
int main() {
  Room* r    = new Room(1);
  Oggetto* o = new Oggetto(1,r);
  r->addObj(o);
  o->getIdRoom();
  r->getIdObj();
}
-----------------------  File oggetto.h
--------------------------------
#ifndef OGGETTO
#define OGGETTO
#include "room.h"
class Oggetto {
 private:
  int id;
  Room* rPoint;
 public:
  inline Oggetto(int i, Room* r) { id = i; rPoint = r; }
  inline int getIdRoom()  { rPoint->getId();   }
};
#endif
-------------------------  File room.h
---------------------------------
#ifndef ROOM
#define ROOM
#include "oggetto.h"
class Room {
 private:
  int id;
  Oggetto* oPoint;
 public:
  inline Room(int i) { id = i; oPoint = 0; }
  inline int getId() { return id; }
  int getIdObj() { return oPoint ? oPoint->getId() : 0; }
  inline void addObj(Oggetto* o) { oPoint = o; }
};
#endif
In compilazione:
g++ main.cpp -o main
room.h:9: error: ISO C++ forbids declaration of 'Oggetto' with no type
room.h:9: error: expected ';' before '*' token
room.h:15: error: 'Oggetto' has not been declared
room.h: In constructor 'Room::Room(int)':
room.h:12: error: 'oPoint' was not declared in this scope
room.h: In member function 'int Room::getIdObj()':
room.h:14: error: 'oPoint' was not declared in this scope
room.h: In member function 'void Room::addObj(int*)':
room.h:15: error: 'oPoint' was not declared in this scope
main.cpp: In function 'int main()':
main.cpp:8: error: no matching function for call to
'Room::addObj(Oggetto*&)'
room.h:15: note: candidates are: void Room::addObj(int*)
Se in room.h avessi messo
class Oggetto;
al posto di
include "room.h"
avrei ottenuto:
g++ main.cpp -o main
room.h: In member function 'int Room::getIdObj()':
room.h:15: error: invalid use of undefined type 'struct Oggetto'
room.h:5: error: forward declaration of 'struct Oggetto'
A questo punto definendo getIdObj nel file di implementazione
risolverei il problema (ma non e' cosi' che vorrei risolverlo).

Gianni wrote:
[...]
Eccoti una soluzione sistematica e, perché no, elegante:
-------- room_def.h ---------
#ifndef ROOM_DEF
#define ROOM_DEF
class Oggetto;
class Room {
private:
	int id;
	Oggetto* oPoint;
public:
	inline Room(int i) { id = i; oPoint = 0; }
	inline int getId() { return id; }
	int getIdObj();
	inline void addObj(Oggetto* o) { oPoint = o; }
};
#endif
-----------------------------
-------- room_imp.h ---------
#ifndef ROOM_IMP
#define ROOM_IMP
int Room::getIdObj() { return oPoint ? oPoint->getIdRoom() : 0; }
#endif
-----------------------------
---------- room.h -----------
#include "room_def.h"
#include "room_imp.h"
-----------------------------
--------- oggetto.h ---------
#ifndef OGGETTO
#define OGGETTO
#include "room_def.h"
class Oggetto {
private:
	int id;
	Room* rPoint;
public:
	inline Oggetto(int i, Room* r) { id = i; rPoint = r; }
	inline int getIdRoom()  { return rPoint->getId(); }
};
#endif
-----------------------------
---------- main.cpp ---------
#include "oggetto.h"
#include "room.h"
int main() {
	Room* r    = new Room(1);
	Oggetto* o = new Oggetto(1,r);
	r->addObj(o);
	o->getIdRoom();
	r->getIdObj();
}
-----------------------------
Kiuhnm

 #include "room.h"
Se però li inverti non va.
La soluzione completa prevede di fare la stessa cosa anche con "oggetto.h":
oggetto.h e room.h "nascondono" l'infrastruttura ai non addetti ai lavori :)
--- oggetto.h ---
#include "oggetto_def.h"
#include "oggetto_imp.h"
-----------------
--- room.h ---
#include "room_def.h"
#include "room_imp.h"
--------------
Le definizioni sono contenute in oggetto_def.h (se vuoi modifica 
l'estensione anziché il nome) e room_def.h
---- oggetto_def.h ----
#ifndef OGGETTO
#define OGGETTO
class Room;
class Oggetto {
private:
	int id;
	Room* rPoint;
public:
	inline Oggetto(int i, Room* r) { id = i; rPoint = r; }
	inline int getId() { return id; }
	inline int getIdRoom();
};
#endif
----------------
--- room_def.h ---
#ifndef ROOM_DEF
#define ROOM_DEF
class Oggetto;
class Room {
private:
	int id;
	Oggetto* oPoint;
public:
	inline Room(int i) { id = i; oPoint = 0; }
	inline int getId() { return id; }
	int getIdObj();
	inline void addObj(Oggetto* o) { oPoint = o; }
};
#endif
-------------
Il main è come sempre, ma questa volta puoi includere room.h e oggetto.h 
in qualsiasi ordine:
--- main.cpp ---
#include "room.h"
#include "oggetto.h"
int main() {
	Room* r    = new Room(1);
	Oggetto* o = new Oggetto(1,r);
	r->addObj(o);
	o->getIdRoom();
	r->getIdObj();
}
--------------
I due file "chiave" sono oggetto_imp.h e room_imp.h. Direi che sono di 
una chiarezza e semplicità sconvolgente:
--- oggetto_imp.h ---
#ifndef OGGETTO_IMP
#define OGGETTO_IMP
#include "room_def.h"
inline int Oggetto::getIdRoom() { return rPoint->getId(); }
#endif
--------------
--- room_imp.h ---
#ifndef ROOM_IMP
#define ROOM_IMP
#include "oggetto_def.h"
int Room::getIdObj() { return oPoint ? oPoint->getIdRoom() : 0; }
#endif
---------------
Kiuhnm

 bisogno anche della definizione di B e viceversa.
Si potrebbe essere una soluzione, anche se nel caso di classi
abbastanza corpose mi sembra a rimetterci sia l'eleganza.

 La soluzione completa prevede di fare la stessa cosa anche con "oggetto.h":
Soluzione decisamente interessante, devo però ancora capire (e fare le
prove) se può funzionare anche in casistiche differenti (ad esempio se
le classi vengono utilizzate invece che da un main.cpp nell'header di
un'altra classe) e se vale anche in caso di dipendenze intrecciate in
questo modo fra tre classi anzichè due.
Se non ho capito male poi la soluzione mi sembra analoga alla 7a che
citavi prima, mentre devo ancora capire il meccanismo dietro alla 7b.

 mentre devo ancora capire il meccanismo dietro alla 7b.
Un file .cpp che usa un template non deve necessariamente conoscere la 
definizione completa di un template. Infatti il compilatore non segnala 
alcun errore. Sarà il linker a protestare.
Se al file a.cpp serve "int A<int>::f()", basterà istanziare tale 
funzione in un altro file .cpp. Quest'ultimo dovrà ovviamente avere 
accesso all'implementazione completa del template:
--- a.h ---
template <class T>
class A
{
public:
	int f();
};
-----------
----- a.hpp -----
template <class T>
int A<T>::f() { return 1; }
-----------
---- a.cpp ---
#include "a.h"
#include "a.hpp"
// Istanziazione manuale.
int A<int>::f();
----------
--- b.cpp ---
#include "a.h"
....
return a.f();
....
---------
b.cpp usa f(), ma non può istanziarla perché non è stato incluso a.hpp. 
Tuttavia la funzione viene istanziata e inserita in a.obj, quindi il 
linker la troverà ugualmente.
In realtà quando chiami una funzione template, il compilatore esegue 
un'istanziazione automatica.
Kiuhnm

 
E se provassi ad usare una classe Inventario (come ti ho scritto sopra), 
invece di un metodo "ponte"?
Secondo me e' porta a:
- una architettura piu' comprensibile (e' piu' chiaro quali siano i 
compiti delle varie classi
- evita di dare ad alcune classi funzioni che non dovrebbero essere di 
loro competenza
- minore grado di accoppiamento tra le classi
- maggiore grado di riutilizzo potenziale delle classi
- probabilmente anche minor tempo di compilazione (ma questo comincia ad 
interessare veramente solo quando i progetti sono piuttosto grandi.
-- 
Roal Zanazzi

 
Eh stica**i... Ehm, pardon.
Secondo me _questo_ e l'eccessivo accoppiamento tra le classi (vedi 
altra mia risposta) E' il problema.
Se accoppi due classi e molti dei loro metodi (soprattutto quelli in cui 
utilizzi metodi dell'altra classe) sono inline, hai inevitabilmente i 
problemi che tu stesso hai notato.
-- 
Roal Zanazzi

 occuparsi di tenere una std::map tra i due.
Tra l'altro puo' anche essere ragionevole in termini di praticita'
poter risalire dall'Object alla sua Room, quello che invece raramente
lo e' e' il forwarding da Object a Room, come nel caso del metodo
Object::getRoomId()
I casi secondo me sono due: o Object contiene direttamente questa
informazione, ad esempio perche' e' necessaria per ricostruire il
modello da file/database, oppure chi vuole conoscere tale valore puo'
benissimo fare 
obj->getRoom()->getId();
 
Ciao,
Nicola

 invece di un metodo "ponte"?
Vedi sopra, potresti farmi un esempio di classe usata come "ponte"?
Grazie, Gianni

 disaccoppiare le classi. Potresti farmi un esempio semplice a riguardo?
Qualche lettura:
http://en.wikipedia.org/wiki/Observer_pattern
http://www.boost.org/doc/html/signals.html
Ciao,
Nicola

 
A me sembra piu' una _mancanza_ di limitazione.
Sei libero di dichiarare quante funzioni inline vuoi. Ma poi sono affari 
tuoi se cio' ti porta dei problemi perche' non si "incastrano" bene con 
la struttura a file "include" e file di "implementazione" su cui si basa 
il linguaggio.
-- 
Roal Zanazzi