Newsgroup: it.comp.lang.c++
Titolo: Ancora sull'accesso a membri privati

In un precedente messaggio ho mostrato come, ridichiarando una classe senza
includere il relativo header, diventi possibile accedere ai membri privati
di una classe, anche se questa è già compilata. La tecnica è semplice: dato
un header come
class myclass
{
private:
    int x;
public:
    void setx(int);
    int getx(void);
};
E data la relativa implementazione, irrilevante per il contesto, è
sufficiente che io nel mio programma, invece di includere l'header, scriva
class myclass
{
public:
    int x;
    void setx(int);
    int getx(void);
};
E a questo punto, all'interno di quel file, posso modificare myclass::x come
e quanto mi pare.
Questa tecnica non funziona per i metodi privati, ecco cosa succede in un
esempio:
// myclass.h
class myclass
{
private:
    int x;
    void setx(int);
public:
    myclass();
    int getx(void);
};
// myclass.cpp
#include "myclass.h"
myclass::myclass(void)
{
    x = 2;
}
void myclass::setx(int n)
{
    x = n;
}
int myclass::getx(void)
{
    return(x);
}
// main.cpp
#include <iostream>
using namespace std;
class myclass  // ridichiarazione
{
private:
    int x;
public:
    myclass();
    int getx(void);
    void setx(int); // metodo (privato!) che voglio usare
};
int main(int argc,char **argv)
{
    myclass c;
    c.setx(3);
    cout << c.getx() << endl;
    return(0);
}
Questo da un errore in fase di linking con il VC++ 6.0:
main.obj : error LNK2001: unresolved external symbol "public: void
__thiscall myclass::setx(int)"
Dal messaggio si vede bene come il compilatore includa nella signature di
una funzione anche la sua visibilità. Questo impedisce di ridichiarare il
metodo come pubblico, ma credo dipenda dal compilatore e dal linker. Mi
piacerebbe come si comportano altri.
Comunque, questa stessa osservazione fornisce il modo di aggirare il
problema, perché per il linker la funzione privata e quella pubblica sono
due cose diverse, quindi implementandole in files diversi, pur avendo lo
stesso nome e gli stessi parametri, al momento del linking non collideranno.
Esempio:
// nuovo main.cpp, il resto è identico
#include <iostream>
using namespace std;
class myclass
{
private:
    int x;
public:
    myclass();
    int getx(void);
    void setx(int); // metodo (privato!) che voglio usare
};
void myclass::setx(int n) // nuova implementazione
{
    x = n;
}
int main(int argc,char **argv)
{
    myclass c;
    c.setx(3);
    cout << c.getx() << endl;
    return(0);
}
Questo compila, linka correttamente, e funziona pure. Nonostante esistano
due funzioni che per il compilatore sarebbero identiche (se fossero nella
stessa translation unit), per il linker non lo sono.
Si potrebbe obiettare che però così viene perso il metodo originale, di cui
in teoria il sorgente non è disponibile, quindi il guadagno è minimo:
*QUELLO* comunque non si potrà chiamare. Non è così, poter aggiungere un
metodo a una classe permette un accesso completo a tutti i suoi membri e
quindi anche agli altri metodi privati. Un caso eclatante: l'implementazione
di una classe non istanziabile. Eccola qui:
// myclass.h
class myclass
{
private:
    int x;
    myclass();
public:
    int getx(void);
};
// myclass.cpp
#include "myclass.h"
myclass::myclass(void)
{
    x = 2;
}
int myclass::getx(void)
{
    return(x);
}
Così com'è è del tutto inutile, ma ovviamente questo è solo un mini esempio.
Il punto cruciale è che l'istanziazione fallisce per la presenza di un
costruttore privato, quindi posso controllare (con friend, membri protected
o altro) chi e come può istanziare la mia classe. Ecco un main() che non
funziona:
// main.cpp
#include <iostream>
using namespace std;
#include "myclass.h"
int main(int argc,char **argv)
{
    myclass c; // fallisce miseramente
    cout << c.getx() << endl;
    return(0);
}
Ecco una soluzione basata sulla tecnica mostrata sopra:
#include <iostream>
using namespace std;
class myclass
{
private:
    int x;
public:
    myclass();
    int getx(void);
};
myclass::myclass()
{
    x = 4; // notare che è diverso dal costruttore "vero"
}
int main(int argc,char **argv)
{
    myclass c;
    cout << c.getx() << endl; // stampa "4"
    return(0);
}
Qui l'istanziazione avviene per lo stesso motivo per cui prima potevo
chiamare un membro interno. Ma il costruttore chiamato non è quello vero,
quindi non posso utilizzare effettivamente la classe (si suppone che il
costruttore faccia qualcosa di più complessso di inizializzare x a 2 in una
classe reale).
Ma il problema è facilmente evitabile definendo un costruttore con parametri
pubblico che richiami quello reale originariamente privato:
#include <iostream>
using namespace std;
class myclass  // ridichiarazione
{
private:
    int x;
    myclass();
public:
    myclass(int); // nuovo costruttore pubblico
    int getx(void);
};
myclass::myclass(int d)
{
    this->myclass::myclass(); // richiama quello privato
}
int main(int argc,char **argv)
{
    myclass c(0); // istanzia usando il nuovo costruttore
    cout << c.getx() << endl;
    return(0);
}
Inutile aggiungere che il tutto funziona correttamente.
Anche linkando con il modulo oggetto myclass già compilato.
Mi interesserebbero pareri in merito a questi "buchi" nell'access control...
va bene che con un po' di uso intensivo di puntatori io programmatore posso
comunque leggere e scrivere qualcunque cosa sia nel mio spazio di memoria,
ma qui con un lavoro minimo si può scavalcare senza problemi la distinzione
tra pubblico e privato...
Mi interesserebbe anche conoscere il comportamento di altri compilatori.
Massimo

Tutto questo si appoggia a comportamenti non definiti e dipendenti
dall'implementazione. Ad esempio il compilatore e' libero di
riarrangiare l'ordine dei membri appartenenti a diverse sezioni di
visibilita', ovvero ad esempio:
class A {
  public:
    int a;
    int b;
};
class B {
  public:
    int a;
  public:
    int b;
};
Non e' garantito che A e B abbiano lo stesso layout.
Anche reimplementare piu' volte una stessa funzione puo' portare a
errori di comportamento. In generale il meccanismo di protezione del C++
e' pensato per bloccare la sbadataggine e non gli attacchi intenzionali.
Ciao,
Nicola

 scavalcare senza problemi la distinzione tra pubblico e privato...
Una cosa del genere fu discussa, un anno fa circa, anche su 
comp.lang.c++... _non_ includendo il file .h , tu non definisci lo 
scope dei membri, di cui invece definisci il corpo includendo il .cpp o 
l'oggetto. cosa faresti con librerie di terzi compilate (.dll o .so)?
Inoltre l'information hiding deve proteggere dagli stolti, non dai 
machiavelli... che ti devo dire? che funziona? *per adesso* si, piu' in 
la' non credo. Non ci costruirei una libreria da mantenere a lungo, su 
codice come questo... oltre ad essere, per me, pessimo stile di 
programmazione. 

 programmazione.
Ma infatti, mai detto di volerlo usare, intendevo proprio dire che è
pericoloso che ci siano questi buchi...
Massimo

 e' pensato per bloccare la sbadataggine e non gli attacchi intenzionali.
Vero, ma questo non lo rende parecchio pericoloso a livello di
programmazione di sistema ?
Il fatto che debbano essere necessariamente forniti gli header per poter
utilizzare una classe, e che questi debbano contenere non solo l'interfaccia
ma anche tutti i membri interni, rende praticamente una pia illusione l'idea
di incapsulamento...
Massimo

 pericoloso che ci siano questi buchi...
sono buchi "logici" e come tali, vanno evitati dal programmatore, non 
dal compilatore. altrimenti, usa Java o VB :)

unit, il problema è questo.
No... Il C++ dice che facendo roba simile uno si cerca delle
grane. Piu' o meno lo stesso tipo di grane che ti cerchi se
fai un typecast di indirizzo da puntatore a istanza in
puntatore a carattere e poi ti metti a scrivere usando
il risultato della conversione.
Quello che viene detto e' che chi scrive compilatori non e'
tenuto a cercare di evitare errori simili. Il concetto e',
come ti ha gia' detto Nicola, che private/public (e un sacco
di altri controlli) servono a dare una mano a diligenti ma
sbadati programmatori; non a cercare di fermare i pazzi.
Proprio non riesco a credere che qualcuno "per sbaglio"
possa fare cio' che dici tu ("dimenticarsi" di includere
il .h ma "casualmente" ridefinire una classe con lo stesso
nome ma diversi permessi di accesso).
Se un proprio vuole accedere ai membri basta che cambi
file .h ... dove sta il problema ?
Andrea
--
Andrea Griffini, Programmer
http://www.netservers.com/~agriff

di incapsulamento...
Vedi l'esempio della tecnica del "Cheshire cat", illustrata da Bruce
Eckel, in TIC++ vol. I, 5^ capitolo ("Hiding the Implementation"), nel
paragrafo "Reducing recompilation".
--
Andrea Laforgia (ICQ 87230455)
http://www.geocities.com/AndreaLaforgia
"ci sono tre tipi di persone nel mondo : quelli che
 sono bravi in matematica e quelli che non lo sono."

 Mi interesserebbero pareri in merito a questi "buchi" nell'access control...
Io penso che tu abbia dei problemi di paranoia.
Hai scritto 267 righe preoccupandoti che un programmatore possa accedere alla
parte che hai dichiarato privata...
e chi se ne frega se lo fa?
Saranno problemi suoi. Se decide di fare porcate del genere, quando
implementerai la tua nuova classe e cambierai la parte privata, il suo codice
non funzionerà più e dovrà riscriverlo da capo: problemi suoi.

tra pubblico e privato...
quello che dici non è vero. Se tu definisci due classi in due diverse
translation unit e queste non hanno la stessa signature (in pratica,
se sono diverse) violi la regola dell'one definition rule; questo
implica il cadere in un undefined behaviour (comportamento
indefinito); in parole povere, il fatto che con questo compilatore
funzioni non ti assicura che funzioni con altri, che funzioni con una
versione diversa dello stesso e persino che funzioni impostando
opzioni diverse, pur usando lo stesso compilatore. Anzi! Non ti
assicura neppure che il compilatore produca lo stesso codice se
compilato in giorni pari o dispari, oppure a seconda delle fasi
lunari...
Spero di avere reso l'idea :-)
Anto
--
Antonio Cuni, 18 anni, studente
Brainbench Certification: 
Master C++: 4.44, higher than 97% of all previous test takers.
Visual Basic 5: 3.06, higher than 77% of all previous test takers.
http://www.brainbench.com/transcript.jsp?pid=1301688

 e chi se ne frega se lo fa?
Non c'è che dire, un'osservazione decisamente costruttiva.
Se eviti di farne altre è meglio.
Massimo

 file .h ... dove sta il problema ?
La libreria potrebbe non averla scritta lui. Potrebbe essere una DLL già
compilata e non modificabile, di cui sono disponibili solo gli header.
Ma il mio discorso era opposto, comunque: potrebbe esserci qualche fondato
motivo per non dover avere accesso ai membri interni, ad esempio la classe
potrebbe essere un oggetto di sistema. Stavo solo osservando come, in caso
di necessità che l'incapsulamento sia sinonimo di "irraggiungibilità" e di
"integrità dei dati", il C++ non potrebbe soddisfare tale requisito. O
almeno non solo dichiarando un campo private.
Massimo

 paragrafo "Reducing recompilation".
Visto, visto :-)
Proprio da quel capitolo prendevo spunto per l'osservazione...
Massimo

 Spero di avere reso l'idea :-)
Ho ben presente il concetto di "undefined behaviour", ricordo anche quella
proposta del compilatore che in caso di codice indefinito riformattasse
l'hard disk a tradimento ;-)
Proprio per questo chiedevo cosa accadesse con altri diversi dal VC++ che ho
usato io per le prove...
Massimo

 paragrafo "Reducing recompilation".
Sul libro di Carlo Pescio, "C++ Manuale di Stile" pag.262 e succ.(Ediz.
Infomedia, 1995!!), ci sono due paragrafi dedicati a come diminuire le
dipendenze e quindi ridurre anche i tempi di ricompilazione. In particolare
utilizza due particolari implementazioni di classi facciata e quindi
nasconde anche l'implementazione.
Ti allego l'esempio da lui fatto per le "Classi implementazione". Come vedi
il codice cliente non è neppure a conoscenza dell'header della classe
implementazione e di come sono implementate le classi private. Naturalmente
qui per creare una Persona bisogna utilizzare la class factory
CreatePerson() (non credo ci siano particolari problemi ad utilizzarla nel
codice cliente) mentre però non si può ereditare direttamente dalla classe
Person e bisogna utilizzare altre tecniche di design. Ma per l'appunto qui
stiamo andando in una campo molto vasto (il design) di cui spesso non ci si
può occupare a fondo dati problemi derivanti dai tempi di sviluppo, sempre
troppo ridotti, e anche dai molto probabili bug introdotti dalla complessità
di tecniche avanzate di design. A questo punto mi domando se vale la pena
allungare i tempi di svuiluppo e debug, con il management che ti fiata sul
collo, per impedire ad un cliente di utilizzare qualcosa che lui sa di non
dover utilizzare (altrimenti non sarebbero parti private!).
//PERSON.H
Class Person
{
public:
virtual void Store()=0;
virtual void Input()=0;
};
//PERIMPL.H
Class PersonImpl : public Person
{
private:
String firstName;
String lastName;
int height;
int weight;
public:
PersonImpl();
virtual void Store();
virtual void Input();
};
//PERSON.CPP
#include "perimpl.h"
Person *CreatePerson()
{
return ( new PersonImpl() );
}
//CLIENT2.H
#include "person.h"
Class Car
{
privare:
Person owner;
//........
};
//CLIENT2.CPP
//Qui per creare una persona non si usa
//new Person che è illegale ma CreatePerson()
//...........
----------
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
-------------
"Esistono due modi per elaborare il progetto di un software.
 Il primo consiste nel renderlo semplice al punto tale che non
 manifesti lacune evidenti. Il secondo consiste nel renderlo
 complicato al punto tale che non manifesti lacune evidenti.
 Il primo metodo e' di gran lunga piu' difficile." [C.A.R. Hoare]


wrote:

Quella che hai omesso nel quoting della risposta era la
parte importante. Chi vuole accedere a membri privati
di una classe sta facendo male a *se stesso* e non ad
altri. Visto che non si e' ritenuto che questa cosa
potesse accadere *inavvertitamente* non ci si e' posti
il problema che ti poni tu (il problema cioe' non esiste
proprio... non in un linguaggio come il C++).
Dichiarare un membro "private" non significa che vuoi
impedire ad altri di accedere deliberatamente... ma
solamente che vuoi impedire che altri possano pensare
a quel membro come parte integrante dell'interfaccia
ufficiale della classe. Serve come aiuto per errori
*accidentali*; nulla piu' e nulla meno.
Qualcuno puo' aggirare questo aiuto ? ... cavoli suoi.
Qualcuno vuole compilare con tutti i warning disabilitati ?
problemi suoi. Qualcuno vuole andare a fare porcate
con typecasting da equilibrista che funzionano solo con
una specifica versione di uno specifico compilatore ?
ancora una volta sono solo problemi suoi.
Vuoi scrivere dei byte in un array di char e poi eseguire
questi byte come se fossero una funzione ? Libero di
provarci... (e' anche divertente... solo ti prego nel
caso non funzionasse di non rompere le scatole dove si
discute di C++ standard).
La sintassi C++ non serve ne' in questo ne' in altri
aspetti quali la "const-correctness" a salvarti da
maleintenzionati; serve solo ad aiutare i distratti.


almeno non solo dichiarando un campo private.
Nessuno ha mai detto che questo era un utilizzo pensabile
di 'private'. Evidentemente hai frainteso il motivo per
cui sono stati introdotti i controlli di accesso.
Servono ad un programmatore a salvarsi da se stesso.
Punto.
Se poi vogliamo parlare di Sistemi Operativi con le
iniziali maiuscole allora e' abbastanza ingenuo pensare
che un programma in user space possa accedere a strutture
kernel o anche solo di un altro programma utente: la
comunicazione dovrebbe avvenire esclusivamente attraverso
specifiche aree di shared memory (dove pero' si va con
i piedi di piombo) call gates o trap di sistema con
marshalling dei parametri; vivere nello stesso address
space con gli stessi diritti su strutture di sistema
e' improponibile.
Ti sei mai chiesto perche' le chiamate a sistema spesso
richiedono che *tu* fornisca un buffer in cui il sistema
scrive e non viceversa ?
In un sistema operativo che si rispetti tu dovresti poter
tentare di eseguire qualunque sequenza di istruzioni
*assembler* senza creare problemi al sistema. "private"
e "public" sono veramente l'ultimo dei problemi.
No. Nel caso che stessi per domandarlo Windows non e' un
Sistema Operativo serio da questo punto di vista.


 [ MEGA CUT ]
Ti ricordo, cosa che probabilmente già saprai, che in questo modo stai
violando la ODR.
Secondo lo Standard, hai un "undefined behaviour" ed in più il
compilatore non è tenuto a segnalarti il problema.
Saluti Manlio Perillo