Newsgroup: it.comp.lang.c++
Titolo: Elementi protected

]

int getI()?
Gli accessors pubblici sono la via giusta. Ricorda che derivando una classe hai 
(intuitivamente parlando) un'istanza della classe base nella tua classe 
derivata - e private, protected e public controllano l'accesso a *questa* zona 
di memoria. Di un altro oggetto, (cioe' - altra zona di memoria) puoi comunque 
accedere solo alle parti pubbliche.
-- 
God is Real, unless declared Integer.
ObjectZone - 
http://space.tin.it/computer/csadun/

accedere solo alle parti pubbliche.
Da un punto di vista del linguaggio sono d'accordo. In questo caso
pero' l'accesso ad 'i' vorrei che restasse un dettaglio
implementativo, non visibile nell'interfaccia esterna della classe. La
cosa piu' logica in questo caso forse e' dichiarare B friend di A, ma
e' una soluzione che non mi piace molto...


 e' una soluzione che non mi piace molto...
Mh, capisco. Pero', riguarda il tuo design: come mai ti trovi in quella
situazione? Spesso (non sempre, ma di solito :) quel tipo di situazione e'
dovuto ad un uso dell'ereditarieta' piu' orientato al riuso di codice che
all'idea di avere un sottotipo proprio. Una rifattorizzazione opportuna di
solito aiuta. Cosa vuoi ottenere prescisamente?


E' un tipo di albero un po' particalore (da spiegare e' un casino, ma
in pratica serve come indice e come struttura d'appoggio per calcoli
su punti in uno spazio multidimensionale con 20<dim<100). A e' la
classe Nodo, B e' la classe Radice, che e' una specializzazione di
Nodo. Radice ha tutti i servizi per gestire dall'esterno l'albero, ma
vorrei potesse fare anche delle manutenzioni al'interno dei nodi,
senza dover per forza rendere pubblici certi metodi dei nodi stessi.


e' una soluzione che non mi piace molto...
Premetto che a me non piace proprio l'esistenza di attributi public e/o
protected, perche` il friend non ti soddisfa? Se usato bene
e` uno strumento che puo` rafforzare l'incapsulamento (almeno
in alcuni contesti).
Paolo
-----------------------------------------------
  Paolo Marotta
  Computer Science, University of Pisa
  home:   
http://www.cli.di.unipi.it/~marotta
-----------------------------------------------

senza dover per forza rendere pubblici certi metodi dei nodi stessi.
Ok. Una pattern utile nel caso e' il seguente: 
class A { 
 protected: int property; 
 public: int getProperty(void) const { return property; }
};
class A_impl: virtual public A { 
 public: void setProperty(int value) { property=value; } 
};
class B : virtual public A, virtual protected A_impl {
 public: void methodOfB(void) { setProperty(5); } };
void foo() {
	B b;
    	b.methodOfB();
	cout << b.getProperty() << endl;
	// ...ma questa linea da' (giustamente) errore d'accesso
	b.setProperty(5);
}
In pratica, costruisci una classe implementativa che determina 
l'interfaccia 
"interna" (quello che in java chiamersti "package level") mentre la tua 
classe 
deriva in modo pubblico solo dalla classe base "principale".
La derivazione virtual e' necessaria per evitare ambiguita'. 
In questo modo i metodi protetti possono essere acceduti dal codice di B 
(dato 
che eredita in modo protetto o privato da A_impl ma non da codice esterno 
(che 
vede solo la derivazione pubblica da A).
Questa fattorizzazione separa nettamente l'interfaccia esterna da quella 
'manutentiva' - senza usare le funzioni friend, che quasi sempre (imho, non 
me 
ne voglia Paolo ;-) sono sintomo di design cattivo o frettoloso.


 me ne voglia Paolo ;-) sono sintomo di design cattivo o frettoloso.
Non sono d'accordo; se vedi dei "friend" (fra classi) nel mio codice (o
in quello di qualsiasi altro buon programmatore C++), e` sintomo di
una "partizione" che ho fatto, dividendo in piu` classi quella che avrebbe
potuto essere una classe sola (ma sarebbe stata poco coesa se non
l'avessi cosi` spartita).  Se vedi dei "friend" di funzioni, e` una pura
scelta sintattica -- ho voluto nascondere al codice cliente che vi sia
uno speciale rapporto fra la funzione f e la classe C, rapporto che
invece sarebbe stato sintatticamente evidenziato se avessi scelto di
dichiarare f come membro statico di C; come qualsiasi altra scelta di
separazione fra caratteristiche visibili all'interfaccia, e caratteristiche
presenti nell'implementazione, la considero tutt'altro che tipica dei
progetti "cattivi o frettolosi".
Alex

 int getI()?
Certo, si chiama "friend".
Alex

 all'interno" di nodi che non sono di classe Radice, come richiesto.
Uh? Costruisci una classe implementativa associata a Nodo che espone
l'intefaccia "interna", e derivi Radice *anche* da quella. In quel modo puoi
accedere ai sui membri protetti senza problemi (che e' il tipo di
manutenzione accennata, vedendo l'esempio), mentre il codice "client"
esterno non puo'. Che altro?


Come dicevo, punti di vista. ;-)

 esterno non puo'. Che altro?
Sembra in gioco il solito fraintendimento sul concetto di "protected".
Se Radice deriva da ImplementoNodo, puo` accedere ai membri protetti
di ImplementoNodo ***PER UN OGGETTO DI TIPO RADICE***,  ***NON***
per altri oggetti-nodo (altre classi derivate da ImplementoNodo) che Radice
non sono.
Se, dunque, come richiesto, i metodi della classe Radice devono potere
fare elaborazioni speciali su oggetti di ogni tipo Nodo, lo schema che tu
proponi (e che avra` magari mille altri vantaggi) non soddisfa le
specifiche.
"friend" esiste nel linguaggio C++ esattamente per soddisfare questa
importantissima esigenza.  Chi non capisce "friend", e il suo uso nella
fattorizzazione, non ha ben compreso il linguaggio C++.
Alex

fattorizzazione, non ha ben compreso il linguaggio C++.
LOL sono del tutto d'accordo. Chi lo usa a sproposito, pero', non ha ben 
compreso l'approccio object oriented. 


 
Sghignazzi a parte, siamo dunque perfettamente d'accordo sulle
linee guida generali.  E adesso torniamo allo specifico "caso
particolare", sul quale pare tu stia facendo, spero involontariamente,
della vera e propria disinformazione.
Gli "accessori pubblici", da te originalmente proposti, NON sono
"la via giusta", come da te asserito nel primo tuo messaggio su
questo thread, nel senso che permettono a qualunque altro codice
di avere esattamente gli stessi accessi (a generici oggetti di tipo
Nodo, che non sono di tipo Radice) che potrebbe avere qualsiasi
altro codice del programma.
Si richiede un metodo di permettere al codice delle funzioni-membro
di Radice un accesso superiore (a generici oggetti di tipo Nodo)
a quello goduto da "qualsiasi altro codice del programma".  Non
vogliamo, dunque, accessori _pubblici_ -- chiamabili da qualsiasi
altra zona del codice; vogliamo invece restringere le manipolazioni
a certi aspetti di un generico oggetto di una qualsiasi classe che
eredita da Nodo ad avvenire:
	a) o entro metodi della classe Nodo stessa, ovviamente,
	b) o entro metodi della classe Radice
Le tue proposte non soddisfano queste specifiche.  "friend", invece,
si`.
C'e` inoltre un sottile problema, forse di espressione o di chiarezza,
nella seguente parte del tuo primo post su questo thread:
private, protected e public controllano l'accesso a *questa* zona 
di memoria. Di un altro oggetto, (cioe' - altra zona di memoria) puoi
comunque 
accedere solo alle parti pubbliche.
La granularita` di protezione NON e` per-oggetto, bensi` per-classe.
Di un _altro_ oggetto, cioe` altra zona di memoria, puoi benissimo
accedere anche alle parti private e protette, *purche`* l'altro oggetto
in questione sia della stessa classe del tuo.  Non e` dunque questione
di "*questa* zona di memoria" rispetto ad "altra zona di memoria",
bensi` di appartenenza-a-classe.
"Sciasbat", autore del primo post su questo thread, ha chiarito le
sue esigenze solo dopo qualche ulteriore scambio, specificando:
A e' la
classe Nodo, B e' la classe Radice, che e' una specializzazione di
Nodo. Radice ha tutti i servizi per gestire dall'esterno l'albero, ma
vorrei potesse fare anche delle manutenzioni al'interno dei nodi,
senza dover per forza rendere pubblici certi metodi dei nodi stessi.
A questo tu rispondesti proponendo:
class A { 
 protected: int property; 
 public: int getProperty(void) const { return property; }
};
class A_impl: virtual public A { 
 public: void setProperty(int value) { property=value; } 
};
class B : virtual public A, virtual protected A_impl {
 public: void methodOfB(void) { setProperty(5); } };
eccellente ricetta, di per se`, ma tale e` anche, nel
giusto contesto, quella della Sacher Torte -- ne` essa,
ne` la tua, soddisfano le specifiche richieste, e anzi
quella della Sacher ha rispetto alla tua il vantaggio di
essere ampiamente consolatoria rispetto a qualsiasi
genere di problemi (per utenti sufficientemente golosi).
Per permettere ai metodi di B (Radice) di "fare delle 
manutenzioni all'interno dei nodi" (oggetti A, non-B), come
Sciasbat richiede, senza "rendere pubblici certi metodi
dei nodi stessi", questo tuo schema *non funziona*,
cosi` come non funzionerebbe un altro meno raffinato.
Tu ribadisci il suggerimento nell'ulteriore post dove dici:
Uh? Costruisci una classe implementativa associata a Nodo che espone
l'intefaccia "interna", e derivi Radice *anche* da quella. In quel modo
puoi
accedere ai sui membri protetti senza problemi (che e' il tipo di
manutenzione accennata, vedendo l'esempio), mentre il codice "client"
esterno non puo'. Che altro?
E questo, con tutti i suoi "Uh" e i suoi "Che altro", continua
ad essere errato.  Nulla di male (anzi!) nella divisione fra
interfaccia vera e propria, e "classe implementativa", ma
questo raffinato schema, senza un "friend" da qualche
parte, *non soddisfa le specifiche*.
Veniamo ad un esempietto semi-concreto.  Diciamo che
un Nodo ha un Parent e due Sibling accedibili read-only
da parte del codice cliente, e un Payload che e` ad es. un
int pure accedibile read-only, cioe` l'interfaccia astratta:
	class Nodo {
	public:
		virtual Nodo* Get_Parent() = 0;
		virtual Nodo* Get_Left_Sibling() = 0;
		virtual Nodo* Get_Right_Sibling() = 0;
		virtual int Get_Payload() = 0;
	};
(naturalmente avremo anche un First_Child, ecc, ecc,
ma questo qui non e` importante).
Esiste poi anche un'altra interfaccia, che non deve essere
accedibile "da cani e porci", per modificare ciascun
aspetto:
	class Nodo_Modificabile {
	public:
		virtual void Set_Parent(Nodo*) = 0;
		virtual void Set_Left_Sibling(Nodo*) = 0;
		virtual void Set_Right_Sibling(Nodo*) = 0;
		virtual void Set_Payload(int) = 0;
	};
con l'ovvia implementazione del tipo (aggiungi pure tutti
i virtual che desideri:-):
	class Nodo_Impl: public Nodo, protected Nodo_Modificabile
	{
		Nodo *Parent, *Left, *Right;
		int Payload;
	public:
		virtual Nodo* Get_Parent() { return Parent; }
		virtual Nodo* Get_Left_Sibling() { return Left; }
		virtual Nodo* Get_Right_Sibling() { return Right; }
		virtual int Get_Payload() { return Payload; }
	protected:
		virtual void Set_Parent(Nodo*p) { Parent=p; }
		virtual void Set_Left_Sibling(Nodo*p) { Left=p; }
		virtual void Set_Right_Sibling(Nodo*p) { Right=p; }
		virtual void Set_Payload(int i) { Payload=i; }
	// presumibilmente anche ctor e dtor, ecc
	// e metodi-fabbrica come ad esempio:
		static Nodo* New_Nodo() {
			Nodo_Impl* result = new Nodo_Impl;
			result->Set_Parent(0);
			result->Set_Left_Sibling(0);
			result->Set_Right_Sibling(0);
			result->Set_Payload(0);
			return result;
		}
	}
Una Radice e` un particolare Nodo che mette a disposizione
dell'utente anche altri metodi:
	class Radice: public Nodo_Impl {
	public:
		void Aggiungi_Nodo_Foglia(int Nuovo_Payload);
		void Azzera_Tutti_I_Payload();
		// ecc ecc
	};
Come puo` Radice implementare Aggiungi_Nodo_Foglia?
Non puo`; non ha accesso all'interfaccia Nodo_Modificabile
del Nodo* tornato dalla Nodo_Impl::New_Nodo, allo scopo di
settarne il Payload (e settarne Parent e Sibling per incastrarlo
nell'albero).
Per rimediare a questo, bisognerebbe anzitutto che
New_Nodo tornasse, non un Nodo*, bensi` un Nodo_Impl*.
E ancora non basta!  Perche` Radice, anche se ha un
Nodo_Impl*, non ha accesso ai suoi metodi Set_...
(Oppure, naturalmente, basterebbe un "friend"!-).
Come puo` Radice implementare Azzera_Tutti_I_Payload?
Se tiene i propri nodi interni come Nodo*, non puo` --
non ha accesso all'interfaccia Nodo_Modificabile dei
propri nodi.  Per aggirare questo, bisognera` anzitutto
che Radice mantenga riferimento a tutti i propri nodi
come Nodo_Impl*.
E ancora non basta!  Perche` Radice, anche se ha un
Nodo_Impl*, non ha accesso ai suoi metodi Set_...
(Oppure, naturalmente, basterebbe un "friend"!-).
E se l'utente ha ottenuto un riferimento a un Nodo,
e vuole passarlo a Radice per chiedere una qualsiasi
operazione di manutenzione su di esso?  Se, cioe`,
Radice ha un altro paio di metodi:
	Nodo* Trova_Dato_Payload(int Payload_cercato);
	void Raddoppia_Payload(Nodo* Da_Elaborare);
La prima e` facile (Radice puo` iterare sugli oggetti di
tipo Nodo che essa "possiede", "cercando" il Payload),
ma, come puo` Radice implementare la seconda?  Se
anche tiene i propri nodi come puntatori a Nodo_impl,
non puo` semplicemente risalire dal Nodo*, che e` tutto
quel che vede l'utente, al Nodo_impl corrispondente;
puo` essere necessaria una fruga estremamente
inefficiente che da ciascun Nodo_impl* ottiene il
Nodo* e ne verifica l'eguaglianza con l'argomento
Da_Elaborare, o una complessa struttura-dati, come
una mappa da Nodo* a Nodo_Impl*, e tutte le varie
operazioni di manutenzione di questa mappa... e il
tutto per cosa?  Per evitare UN "friend"?!
La situazione diviene sempre piu` insostenibile se gli
oggetti di tipo Nodo devono poter essere generati in
modo indipendente da Radice, ad esempio se
la classe Nodo offre un metodo-fabbrica, in grado
magari di accedere ad altre implementazioni della
stessa interfaccia (dopo tutto, l'uso principe della
separazione interfaccia-implementazione e` quello
di permettere diverse implementazioni della stessa
interfaccia).  In questo caso, ad un metodo di Radice
potrebbe essere fornito un Nodo* che non si trova
gia` fra quelli noti a Radice.  E allora come puo`
Radice risalire all'interfaccia Nodo_Modificabile,
come deve fare per potere apportare modifiche,
in un modo che non sia accessibile ad altre zone
del codice?  Che facciamo, aggiungiamo una
funzione static e protected alla classe Nodo, che
da un Nodo* sa risalire al relativo Nodo_Modificabile?
E come la implementiamo?  E se ci riusciamo, ancora
non abbiamo rispettato le specifiche, perche` a questo
punto staremmo permettendo a qualsiasi altra
sottoclasse di Nodo di sfrucugliare indegnamente
altri oggetti Nodo, mentre le specifiche dicono che
questo deve poterlo fare solo Radice...!
***E tutto questo per evitare un "friend"?!***
Il problema proposto da Sciasbat *GRIDA*, a chiunque
capisca bene il C++: "qui ci vuole un friend".  Paolo
Marotta ha colto perfettamente l'essenza del problema
nel suo unico, conciso post a questo thread:
Premetto che a me non piace proprio l'esistenza di attributi public e/o
protected, perche` il friend non ti soddisfa? Se usato bene
e` uno strumento che puo` rafforzare l'incapsulamento (almeno
in alcuni contesti).
L'unica ragione per cui il thread continua, Cristiano, e` la
tua ostinazione a proporre sedicenti "alternative" a questa
limpida idea -- che, e basta pensarci un attimo e capire
bene i significati di "protected", eredita` (anche virtuale),
eccetera, zoppicano da tutte le parti, richiedono potenzialmente
grossi "castelli" di sovrastruttura complessa e dubbia assai,
e alla fin fine anche con tutto questo sangue, sudore, e
lacrime, *non arrivano a soddisfare le specifiche* come
"friend" farebbe semplicissimamente, in modo pulito,
indolore, chiaro, manutenibile, e via discorrendo.
Alex