Newsgroup: it.comp.lang.c++
Titolo: Funzioni friend

Ho un problema con una dichiarazione friend in ambiente Visual C++ 5.0.
Mi sembra infatti che non ci sia modo di dichiarare, all'interno di una
classe generica A contenuta nel file "File A.h", una funzione friend
dichiarata e definita poi in una classe generica B contenuta nel file "File
B.h", se il file "File B.h" deve poi includere "File A.h" (e nel mio caso
deve farlo).
Il problema nasce dal fatto che per poter scrivere una dichiarazione come
    friend void Class B::Func();
occorre già aver definito la classe B, altrimenti il qualificatore "B::"
risulta non definito.
Ma se la definizione della classe B dipende dall'inclusione del file "File
A.h", non posso includere "File B.h" nel file "File A.h", quindi "B::"
rimarrà sempre indefinito e non potrò dichiarare la mia funzione friend.
C'è un modo per sbloccare questa situazione?
Ecco sotto la schematizzazione del problema:
/* File A.h */
#if !defined(FILE_A)
#define FILE_A
#include "File B.h"                    /* Per non lasciare indefinito "B::"
nella dichiarazione
                                                    della funzione friend,
devo includere "File B.h", e quindi
                                                    si genera il circolo
vizioso
Class A
{
    /* Dichiarazioni varie */
    friend void Class B::Func();
}
#endif
/* File B.h */
#if !defined(FILE_B)
#define FILE_B
#include "File A.h"                    /* questa direttiva è indispensabile,
"File B.h" dipende
                                                    da "File A.h" */
Class B
{
    /* Dichiarazioni varie */
    void Func();
}
#endif
Insomma, la reciproca inclusione causa il problema, ma d'altra parte sembra
che non se ne possa fare a meno.
Qualche suggerimento?
Alessandro Pulpito

Al momento non ho il tempo di studiare la cosa, ma ti
rispondo con un'altra domanda (su cui ti consiglio
di riflettere):
Se la classe A ha bisogno di B che senso ha usare 2
file header separati se quello di B ha poi bisogno di
includere A.
Ovvero se le due classi sono mutualmente dipendenti
perche`  tenerle fisicamente separate in header
diversi?
Saluti,
Paolo
-----------------------------------------------------------------
  Paolo Marotta
  Computer Science, University of Pisa
  home:   
http://www.cli.di.unipi.it/~marotta
-----------------------------------------------------------------

diversi?
La classe A non ha alcun bisogno di includere la dichiarazione della classe
B.
E' B che ha bisogno di includere A (infatti uno dei suoi membri è un oggetto
di classe A) indipendentemente dalla propria dichiarazione di funzione
friend. Quindi le classi non sono state pensate per essere mutualmente
dipendenti.
E tuttavia, solo per poter dichiarare in A una funzione friend appartenente
a B, pare che sia costretto ad includere B (cosa che appunto non posso fare,
visto che poi B deve includere A di nuovo). Ecco il motivo per cui sembrano
mutualmente dipendenti, ma in realtà, prima della necessità di inserire la
dichiarazione friend in A, solo B includeva la dichiarazione di A.
Mi stupisce poi che il problema sia solo, se ho capito bene, di non avere le
dichiarazioni in file separati ma in uno solo. Vuol dire che se metto la
dichiarazione della classe B dopo quella di A, nello stesso file, e prima di
A metto una forward declaration di B, il meccanismo funziona? Credevo che ci
fosse in ballo un discorso più generale sull'ordine in cui vengono compilate
le classi, indipendentemente dal fatto che fossero nello stesso header o in
header separati, ma adesso proverò comunque come dici tu (sempre se ho
capito bene), anche se, lo ripeto, la dipendenza di A e di B non è
reciproca, escluse le dichiarazioni friend.
Alessandro

reciproca, escluse le dichiarazioni friend.
Vedi sopra.
Paolo
P.s.
   Se non ci fossero state le dichiarazioni friend, avresti potuto scrivere:
class B;
class A {
  /* ... */
  B* Obj;     // nota il puntatore
};
tenere le classi in file separati e includere l'header di B nel solo file
di implementazione di A (non nell'header e salvo funzioni inline che
fanno riferimento a membri di B).


risolti dal linker.
Il compilatore non dimentica alcun identificatore nel caso delle inclusioni.
Le inclusioni appartengono tutte alla stessa translation unit, quindi la
tavola degli identificatori non viene azzerata.
Comunque non ci interessa parlare delle inclusioni, in quanto hanno poco a
che vedere con il problema: come ti ho detto non cambia nulla nel caso del
singolo file sorgente. Così come non ci deve interessare il fatto che le
classi siano o meno funzionalmente legate e quindi debbano o meno essere
collocate in file header e di implementazione differenti. Vorrei risolvere
il problema teorico, al di là di trovare motivi per non fare ciò che sto
cercando di fare.
Ti dirò, io non ho problemi pratici da risolvere avendo posto questa mia
difficoltà, sono interessato esclusivamente all'aspetto teorico della
questione, che è: cosa si deve aggiungere per compilare il codice che segue?
class A
{
     void func1() {;}
     friend void B::func2();
};
class B
{
    void func2() {;}
    friend void A::func1();
};
Io credo che il discorso verta su questo punto: il compilatore che uso
(Microsoft Visual C++ 5.0) pare essere a singola passata.
Non so se questo rappresenti l'orientamento comune del mercato attuale dei
compilatori, ma, io che provengo dal linguaggio assembly (per la famiglia di
CPU 680x0), ricordo che usavo un assemblatore a due passate: durante la
prima
scansione del codice, l'assemblatore non faceva null'altro che creare una
tavola contenente tutti gli identificatori introdotti nel sorgente; poi,
quando nella seconda passata cominciava a tradurre i riferimenti, aveva già
tutti i simboli definiti e quindi non incorreva mai in un problema analogo a
quello da me sollevato.
Secondo me una soluzione c'è anche per il C++. Rimango in ascolto.
Alessandro



class B;







    class A;
è una dichiarazione,
    class A
    {
        void func1();
    };

   A oggettoA;
è un istanziamento.









Ora vorrei fare qualche altra considerazione.
Ti dirò, io non ho problemi pratici da risolvere avendo posto questa mia
difficoltà, sono interessato esclusivamente all'aspetto teorico della
questione che è: come si deve aggiungere per compilare il semplice frammento
che segue?
class A
{
     void func1() {;}
     friend void B::func2();
};
class B
{
    void func2() {;}
    friend void A::func1();
};
Io credo che il discorso verta su questo punto: il compilatore che uso
(Visual C++ 5.0) pare essere a singola passata.
Non so se questo rappresenti la scelta comune del mercato attuale dei
compilatori, ma, io che provengo dal linguaggio assembly (per la famiglia


risolti dal linker.
L'unita' di traduzione non e' il file (vedi sopra).
Quello che conta e' l'ordine di definizione delle classi nell'unita'.
Non centra niente la suddivisione in piu' file.
Per dichiarare due classi una friend dell'altra e necessario dichiarare
la seconda interamente friend della prima.
La seconda potra' invece dichiarare friend selettivamente i membri della
prima.
class A;
class B
{
 int f(int);
 friend class A;
};
class A
{
 ...
 friend int B::f(int);
};

risolti dal linker.
Il compilatore non dimentica alcun identificatore nel caso delle inclusioni.






class B;







    class A;
è una dichiarazione,
    class A
    {
        void func1();
    };

   A oggettoA;
è un istanziamento.









Ti dirò, io non ho problemi pratici da risolvere avendo posto questa mia
difficoltà, sono interessato esclusivamente all'aspetto teorico della
questione, che è: cosa si deve aggiungere per compilare il codice che segue?
class A
{
     void func1() {;}
     friend void B::func2();
};
class B
{
    void func2() {;}
    friend void A::func1();
};
Io credo che il discorso verta su questo punto: il compilatore che uso
(Microsoft Visual C++ 5.0) pare essere a singola passata.
Non so se questo rappresenti l'orientamento comune del mercato attuale dei
compilatori, ma, io che provengo dal linguaggio assembly (per la famiglia di
CPU 680x0), ricordo che usavo un assemblatore a due passate: durante la




class B;







    class A;
è una dichiarazione,
    class A
    {
        void func1();
    };

   A oggettoA;
è un istanziamento.









Ora vorrei fare qualche altra considerazione.
Ti dirò, io non ho problemi pratici da risolvere avendo posto questa mia
difficoltà, sono interessato esclusivamente all'aspetto teorico della
questione che è: come si deve aggiungere per compilare il semplice frammento
che segue?
class A
{
     void func1() {;}
     friend void B::func2();
};
class B
{
    void func2() {;}
    friend void A::func1();
};
Io credo che il discorso verta su questo punto: il compilatore che uso
(Visual C++ 5.0) pare essere a singola passata.
Non so se questo rappresenti la scelta comune del mercato attuale dei
compilatori, ma, io che provengo dal linguaggio assembly (per la famiglia




Il C++ non permette forward declaration di membri di una classe.
D'altronde, non sarebbe implementabile.
in C++, una volta dichiarata una funzione, la si puo' richiamare
Supponiamo di avere una situazione di questo tipo:
class C : public class B, public A
{
...
};
come si potrebbe comportare se, per assurdo, dichiarassimo una
funzione membro in questo modo:
class C;
void C::f(void);
e poi la usassismo nel seguente:
void funzione(C* pC)
{
    pC->f();
}
il compilatore non avrebbe modo di calcolare il puntatore *this*
da passare come parametro nascosto. void f (void) a chi appartiene?
Appartiene a C? Allora il puntatore può essere passato cosi' com'e'.
Lo ha ereditato da B? idem come sopra.
Lo ha ereditato da A? deve ricalcolare il this in questo modo
    this = pC + size_of(C) - size_of(A);
Sarebbe , inoltre,  evidentemente impossibile per le funzioni virtuali.
La questione piu' importante e' che potendo dichiarare
funzioni membro all'esterno della definizione di una classe,
la si potrebbe estendere a piacere senza toccare la classe
originale, e allora addio incapsulamento e tutto il resto.
esempio:
class A
{
    int dati_privatissimi;
    void fa_solo_questo(void);
};
class A;
void A::fagli_fare_questo_e_quello(void);
...se la dichiarazione fosse valida potrei fare una cosa del genere:
void A::fagli_fare_questo_e_quello(void);
{
    int e_chi_se_ne_frega = 0;
    dati_privatissimi = e_chi_se_ne_frega;
}
Oreste Notelli

tavola degli identificatori non viene azzerata.
Vedi sopra.
Paolo
P.s.
  Controlla il tuo newsreader, manda il post piu` volte...
  Scommetto un penny che si tratta di outlook...


si ripresenta identico a prima.
Ok, mi era sfuggito il fatto che le dichiarazioni friend erano entrambe
relative a membri funzione...
Paolo


 E' proprio di un meccanismo del genere ciň di cui sono tuttora in cerca. [...]
Ma che aspetti a buttare via quel cesso di Outlook? Ti sei accorto di
quante copie del messaggio hai inviato? Scusa lo sfogo...
EMG


Comincio a sospettare che "quel cesso di Outlook" mostri una sola copia delle
migliaia che genera, cosi' gli utenti ignari non si rendono conto di inondare
i newsgroups di messaggi identici...
*Fabio*
-- 
Your education begins where what is called your education is over.

soluzione.
No, nel senso che ho dimenticato che essendo entrambe dichiarazioni
friend relative a funzioni membro, la prima non puo` essere risolta dal
compilatore. E` necessario che la prima delle due classi dichiari friend
tutta l'altra classe.
Comunque il mettere le due classi nello stesso file e` secondo me la
cosa migliore dato che comunque non e` possibile includere uno
dei due header senza includere anche l'altro (o piu` precisamente
dato che le due classi sono strettamente dipendenti l'una dall'altra).
Paolo.


*Fabio*
Io da Outlook vedo di aver postato il mio messaggio cinque volte, non
migliaia.
La cosa ha riscontro, perché per problemi di connessione col mio provider mi
sono connesso cinque volte prima di riuscire a mandare il post, salvo poi
scoprire alla fine che me lo aveva preso tutte le volte.
Chiedo scusa per il disordine generato, ma Outlook non c'entra proprio
nulla. E' ben configurato (per rispondere a Paolo Marotta), non mi ha mai
dato problemi e questo inconveniente č unicamente da addebitarsi alla
comunicazione tra me ed il mio provider.
Se poi i post identici mandati non sono cinque ma migliaia come dice Fabio,
allora vi do ragione perché io ne vedo solo cinque copie.
E' stato un problema circoscritto, insomma, avvenuto per circostanze che
ricordo bene e che so a cos'altro imputare, non certo ad Outlook. E' la
prima volta che mi capita da quando, da un anno e mezzo, uso Outlook come
newsreader, quindi nel mio caso non č un problema funzionale di Outlook.
Alessandro Pulpito

Scusate, correzione al mio post precedente: ne vedo otto di miei post
identici da Outlook.
Ve lo segnalo io, se no magari pensate ancora che ne veda meno di quanti ce
ne siano veramente.
Alessandro Pulpito

 quindi nel mio caso non è un problema funzionale di Outlook.
Forse nel tuo caso no, ma io, da un po' di tempo, per sport, ogni volta
che vedo messaggi ripetuti vado a vedere con quale programma sono stati
inviati e immancabilmente si tratta dell'Outlook.
EMG

 Alessandro Pulpito
Ciao++
*Fabio*
-- 
A journey of a thousand miles begins with a cash advance.

La classe B contiene un oggetto di tipo A (quindi dipendenza).
La classe B deve accedere tramite una sua funzione ad attributi privati
della classe A (quindi l'utilizzo della friend e conseguente altra
dipendenza).
A questo punto le classi dipendono una dall'altra e secondo questo
disegno non e' possibile fare altrimenti.
Quindi dovunque vada una classe deve per forza andare anche l'altra.
Dovresti provare a ragionare sulla possibilita' di mettere insieme le
due classi e definirne cosi una unica contenente le funzionalita' di
entrambe, dato che non avrai mai la possibilita' di utilizzare una
delle due classi da sola.
O anche, da qualche parte ho letto (;-)), che quando un metodo di una
classe utilizza molti (a volte troppi) attributi di un'altra classe
(attraverso un'amicizia o attraverso metodi Set/Get) e' opportuno
rivedere il disegno delle classi cercando di trovare una soluzione che
permetta di rendere tali classi indipendenti tra loro.
Se descrivi con maggiori dettagli le due classi forse e' possibile che
qualcuno possa fornirti indicazioni per modificarne lo schema ed
eliminare il problema.
MM
--== Sent via Deja.com 
http://www.deja.com/
 ==--
---Share what you know. Learn what you don't.---

eliminare il problema.
Io, come spiegato in un post precedente, non ho alcun problema pratico di
programmazione da risolvere: il mio era solo un discorso puramente
teorico-accademico. Per questo non voglio descrivere le mie classi: non è un
problema specifico che mi riguarda, è un dubbio che avrebbe potuto sorgere a
chiunque e che si illustra molto meglio con le schematizzazioni generali che
riporto negli altri miei post che con un complesso esempio concreto.
Alessandro Pulpito

linguaggio stesso.
Una dichiarazione friend nasconde sempre un profondo legame 
strutturale tra due classi.
Chiaramente dichiarare friend tutta una classe, significa che tale
legame puo` essere riscontrato in piu` punti della classe friend.
Una dichiarazione friend di un solo membro invece pone un limite locale 
a tale accoppiamento (solo in quella funzione membro).
A livello sintattico comunque c'e` un'altra differenza dovuta al modo
in cui opera il compilatore.
In sostanza se A dichiara friend B::f(), il linguaggio ci impone di
dichiarare prima B e poi A affinche` il compilatore possa verificare
che la classe B abbia un metodo f(); se invece A dichiara friend class B
allora possiamo anche utilizzare una forward declaration e dichiarare
prima A e poi B perche al compilatore interessa sapere solo che B e`
un identificatore di classe.
Paolo


 Alessandro Pulpito
Se le classi non sono cosi' dipendenti puoi non includere A.h in B.h e risolvere.
esempio:
----------
FILE "b.h"
----------
class A; // forward declaration, non occorre includere "a.h"
class B
{
   // varie cose che non usano A
   void f ( A &a );
};
----------
FILE "a.h"
----------
#include "b.h"
class A
{
   // roba varia
   friend void B :: f ( A &a );
};
------------
FILE "b.cpp"
------------
#include "b.h"
#include "a.h"
void B :: f ( A &a )
{
   a.metodo_privato();	
}
il metodo B :: f() in qualche modo  deve agire su un istanza della classe A,
suppongo che gli venga passata come parametro. 
Se invece A deve essere un membro della classe B, c'e' da rivedere qualcosa nel
design...
*Fabio*

#endif
mi pareva di aver capito che la dipendenza era presente.
Se il file header della classe A e' incluso nel file header della
classe B e', di solito, perche' nell'interfaccia della classe B e'
definito un oggetto della classe A. Altrimenti non mi spego la
necessita' dell'inclusione, in quanto, come certo saprai, e'
sufficiente includere la classe A in nome in modo da eliminare la
dipendenza.
Ho l'impressione che ci sia un misunderstanding (sicuramente da parte
mia ;-)).



Chiaro. Non avevo capito la natura teorica del problema, pensavo si
riferisse ad un caso pratico. Mi scuso.


dall'altra...qual'e' il motivo che porta a questo tipo di disegno ?
Posso, posso...uso tranquillamente class A senza tirare in ballo
automaticamente e per forza class B (lo faccio come classe friend solo
quando mi occorre).
Suggerisco a chiunque voglia intervenire ancora su questo problema (che
personalmente considero chiuso e risolto col sistema suggerito da Oreste
Notelli) di partire, per le proprie considerazioni, dal post dello stesso
Notelli datato martedì 11 maggio più che dai miei stessi post precedenti, in
quanto tirano in ballo inclusioni che non incidono sul problema e che
aggiungono solo inutile complessità allo stesso.
Alessandro Pulpito

design...
class A non deve assolutamente essere membro di class B. Ripeto ancora che
le due classi, a parte le funzioni friend, non dipendono una dall'altra.
Alessandro Pulpito

MM
Hai chiaramente ragione rispetto al mio codice che riporti sopra ma, come ti
ho scritto nel mio post precedente suggerendoti di leggere tutto il thread
dei messaggi, da allora il codice in discussione è stato cambiato per
semplificarlo e per concentrarsi sul nocciolo della questione che, come
vedrai, ha ben poco a che vedere con le inclusioni.
Non è più questo il codice in discussione, insomma.
Alessandro Pulpito