Newsgroup: it.comp.lang.c++
Titolo: dipendenze e transitività di 'include'

Salve ragazzi.
Nel libro di Fowler "UML Distilled" si afferma che l'istruzione include del
C/C++ è transitiva
rispetto alle dipendenze, mentre l'istruzione import di Java, similmente ai
package di UML, no.
Chiaramente questo limita l'impatto dei cambiamenti al momento della
compilazione per Java/UML.
In particolare vorrei essere chiarito in che modo si realizza in C++ questa
transitività (o in che modo
in Java *non* si realizza) e se esistono tecniche per impedire la
transitività delle dipendenze dell'include
in C++, senza alterarne la semantica.
Grazie infinite buon lavoro.
----------
Lello Mele
Via San Sebastiano, 22
84036 Sala Consilina (SA)
P.Tel.:0347/643.35.25
HOMEPAGE:
http://www.lellomele.supereva.it
eMAIL:
v...@libero.it
me...@programmazione.it

Non sono sicuro di aver capito, ma mi ci provo lo stesso:
L'include di C/C++ e' un inclusione testuale, quindi i file che includi
nel tuo sorgente diventano parte della relativa unita' di traduzione,
cosi' come gli header file inclusi da quelli che hai incluso tu.
L'import del Java importa le definizioni contenute direttamente nel
modulo che importi, ma non quelle dei moduli importati dal precedente.
L'assenza del concetto di modulo/package/unit, ovvero l'impossibilita'
di alterare questa situazione, e' secondo me uno dei piu' grossi limiti
del C++.
Nota che la proprieta' di apertura dei namespace li rende grosso modo
ortogonali al problema della modularita'.
Ciao,
Nicola

 in C++, senza alterarne la semantica.
mumble. Non ha importanza in questo contesto ma forse semantica non e' il
termine giusto (almeno non nell'accezione datagli in teoria dei compilatori)
parlando di include.
Comunque se ho capito il concetto la transitivita' in C++ si realizza
semplicemente scrivendo degli include che a loro volta fanno degli include.
La tecnica per impedire la transitivita' in C++ e' semplice:  basta scrivere
degli include che non fanno a loro volta include.
Il problema quindi non nasce dall'include stesso ma dal linguaggio. Ovvero la
domanda non e' come impedisco la transitivita' degli include del C++ ma
piuttosto come posso scrivere dichiarazioni e definizioni C++ che non fanno uso
della transitivita'.
La risposta e' ovviamente quella di non scrivere negli include definizioni di
classi che fanno uso di definizioni di altre classi (ma solo di dichiarazioni).
Questo si ottiene, per esempio, mettendo negli include solo le interfacce e
spostando nei .cpp le implementazioni, nonche' usando solo puntatori e reference
nelle interfacce.
Ovvero spingendo il paradigma OO un tocco oltre a quello che si fa normalmente
in C++.
Esempio esplicativo:
// a.h
class A {
public:
    virtual void metodo1(int );
    int metodo2() const;
    A& metodo3( const A&a );
private:
    int membro;
};
// a.cpp
#include "a.h"
/* definizioni di:
    void A::metodo1(int );
    int A::metodo2() const;
    A& A::metodo3( const A&a );
*/
// b.h
#include "a.h"
class B {
public:
    void met1(int );
    int met2() const;
    B& met3( const A&a );
    B& met4( const B&b );
private:
    A mem;
};
// b.cpp
#include "b.h"
/* definizioni di:
    void B::met1(int );
    int B::met2() const;
    B& B::met3( const A&a );
    B& B::met4( const B&b );
*/
E' evidente come una modifica al file a.h porti a una ricompilazione di tutti i
moduli .cpp che includono il file a.h oppure b.h.
Domanda: come possiamo far si che una modifica a a.h forzi solo la
ricompilazione di a.cpp, b.cpp e dei moduli che includono a.h, ma non quelli che
includono b.h ma non a.h?
Risposta: separiamo le interfacce dalle implementazioni e spostiamo le
implementazioni nei .cpp, poi rimuoviamo la dipendenza degli include. Il
risultato finale sara':
// a.h
class IfcA {
public:
    virtual void metodo1(int )=0;
    virtual int metodo2() const=0;
    virtual IfcA& metodo3( const IfcA&a )=0;
};
// a.cpp
#include "a.h"
class ImplA: public IfcA {
public:
    void metodo1(int );
    int metodo2() const;
    IfcA& metodo3( const IfcA&a );
private:
    int membro;
};
/* definizioni di:
    void ImplA::metodo1(int );
    int ImplA::metodo2() const;
    IfcA& ImplA::metodo3( const IfcA&a );
*/
// b.h
class IfcA;
class IfcB {
public:
    virtual void met1(int )=0;
    virtual int met2() const=0;
    virtual IfcB& met3( const IfcA&a )=0;
    virtual IfcB& met4( const IfcB&b )=0;
};
// b.cpp
#include "b.h"
#include "a.h"
class ImplB: public IfcB {
public:
    void met1(int );
    int met2() const;
    IfcB& met3( const IfcA&a );
    IfcB& met4( const IfcB&b );
private:
    IfcA* mem;
};
/* definizioni di:
    void ImplB::met1(int );
    int ImplB::met2() const;
    IfcB& ImplB::met3( const IfcA&a );
    IfcB& ImplB::met4( const IfcB&b );
*/
Questo e' il concetto generale. Avrai sicuramente notato che cosa ho omesso nel
mio esempio. Ovvero i costruttori.
I costruttori, cosi' come gli operatori, sono un concetto molto piu' C++ che OO
(almeno nel modo in cui vengono supportati in C++). Ci sono due soluzioni.
Quella piu' "pura" consiste nel far si che le istanze di una slasse possano
essere costruite solo partendo da un'istanza di un'altra classe. L'altra e'
quella di immaginarsi i membri statici di una classe come facenti parte di una
specie di "macro classe" di cui esiste una sola istanza (c'e' stato un thread a
proposito, poco fa). In questo caso IfcA non ha costruttori (tra l'altro e'
astratta) ma ha una statica del tipo
IfcA* IfcA::New(/* eventuali parametri*/);
Il nome New non e' a caso. Di fatto esegue una new di una specifica istanza di
IfcA (io ne ho definita una sola: ImpA, ma nulla vieta che ve ne siano tante).
Vi sono svariatissimi modi per costruire un oggetto, la letteratura dei pattern
ne e' piena. Il libro della Gof, capostipite dei libri sui pattern, dedica
un'intera sezione con ben 5 pattern sui 23 conenuti nel libro. E precisamente:
Abstract Factory, Builder, Factory Method, Prototype, Singleton.
Andrea Ferro
---------
Brainbench C++ Master. Scored higher than 97% of previous takers
Scores: Overall 4.46, Conceptual 5.0, Problem-Solving 5.0
More info 
http://www.brainbench.com/transcript.jsp?pid=2522556

Aggiungo un paio di esempi:
In C++ ho:
// a.h
int f() { return 2; }
// b.h
#include "a.h"
int g() {
// qui vedo f
 return 3; }
// c.cpp
#include "b.h"
int main() {
// qui vedo sia f che g
}
In Java, se non ricordo male, ho:
// a.java
package a;
class A {}
// b.java
package b;
import a;
class B {
// qui vedo A
}
// c.java
import b;
class C {
// qui vedo B, ma non A
}
Ciao,
Nicola