¿Que pasa si explícitamente se quiere dar acceso a una función que
no es miembro de la estructura? Esto se consigue declarando la
función como friend
dentro de la declaración de
la estructura. Es importante que la declaración de una función
friend
se haga dentro de la declaración de la
estructura pues usted (y el compilador) necesita ver la
declaración de la estructura y todas las reglas sobre el tamaño y
comportamiento de ese tipo de dato. Y una regla muy importante en
toda relación es, «¿Quién puede acceder a mi parte
privada?»
La clase controla que código tiene acceso a sus miembros. No hay
ninguna manera mágica de «colarse» desde el
exterior si no eres friend
; no puedes declarar una
nueva clase y decir, «Hola, soy friend
de
Bob
» y esperar ver los miembros
private
y protected
de
Bob
.
Puede declarar una función global como friend
, también
puede declarar un método de otra estructura, o incluso una
estructura completa, como friend
. Aquí hay un ejemplo:
//: C05:Friend.cpp // Friend allows special access // Declaration (incomplete type specification): struct X; struct Y { void f(X*); }; struct X { // Definition private: int i; public: void initialize(); friend void g(X*, int); // Global friend friend void Y::f(X*); // Struct member friend friend struct Z; // Entire struct is a friend friend void h(); }; void X::initialize() { i = 0; } void g(X* x, int i) { x->i = i; } void Y::f(X* x) { x->i = 47; } struct Z { private: int j; public: void initialize(); void g(X* x); }; void Z::initialize() { j = 99; } void Z::g(X* x) { x->i += j; } void h() { X x; x.i = 100; // Direct data manipulation } int main() { X x; Z z; z.g(&x); } ///:~
Listado 5.3. C05/Friend.cpp
struct Y
tiene un método f()
que modifica un objeto de tipo X
. Aquí
hay un poco de lío pues en C++ el compilador necesita que usted
declare todo antes de poder hacer referencia a ello, así
struct Y
debe estar declarado antes de que su
método Y::f(X*)
pueda ser declarado como
friend
en struct X
. Pero para declarar
Y::f(X*)
, struct X
debe estar
declarada antes!
Aquí vemos la solución. Dese cuenta de que
Y::f(X*)
toma como argumento la dirección
de un objeto de tipo X
. Esto es
fundamental pues el compilador siempre sabe cómo pasar una
dirección, que es de un tamaño fijo sin importar el tipo, aunque
no tenga información del tamaño real. Si intenta pasar el objeto
completo, el compilador necesita ver la definición completa de
X
, para saber el tamaño de lo que quiere
pasar y cómo pasarlo, antes de que le permita declarar una
función como Y::g(X)
.
Pasando la dirección de un X
, el compilador le
permite hacer una identificación de tipo
incompleta de X
antes de declarar
Y::f(X*)
. Esto se consigue con la declaración:
struct X;
Esta declaración simplemente le dice al compilador que hay una estructura con ese nombre, así que es correcto referirse a ella siempre que sólo se necesite el nombre.
Ahora, en struct X
, la función
Y::f(X*)
puede ser declarada como
friend
sin problemas. Si intenta declararla antes de
que el compilador haya visto la especificación completa de
Y
, habría dado un error. Esto es una
restricción para asegurar consistencia y eliminar errores.
Fíjese en las otras dos funciones friend
. La
primera declara una función global ordinaria
g()
como friend
. Pero
g()
no ha sido declarada antes como global!. Se
puede usar friend
de esta forma para declarar
la función y darle el estado de friend
simultáneamente. Esto se extiende a estructuras completas:
friend struct Z;
es una especificación incompleta del tipo Z
, y
da a toda la estructura el estado de friend
.
Hacer una estructura anidada no le da acceso a los miembros
privados. Para conseguir esto, se debe: primero, declarar (sin
definir) la estructura anidada, después declararla como
friend
, y finalmente definir la estructura. La
definición de la estructura debe estar separada de su
declaración como friend
, si no el compilador
la vería como no miembro. Aquí hay un ejemplo:
//: C05:NestFriend.cpp // Nested friends #include <iostream> #include <cstring> // memset() using namespace std; const int sz = 20; struct Holder { private: int a[sz]; public: void initialize(); struct Pointer; friend struct Pointer; struct Pointer { private: Holder* h; int* p; public: void initialize(Holder* h); // Move around in the array: void next(); void previous(); void top(); void end(); // Access values: int read(); void set(int i); }; }; void Holder::initialize() { memset(a, 0, sz * sizeof(int)); } void Holder::Pointer::initialize(Holder* rv) { h = rv; p = rv->a; } void Holder::Pointer::next() { if(p < &(h->a[sz - 1])) p++; } void Holder::Pointer::previous() { if(p > &(h->a[0])) p--; } void Holder::Pointer::top() { p = &(h->a[0]); } void Holder::Pointer::end() { p = &(h->a[sz - 1]); } int Holder::Pointer::read() { return *p; } void Holder::Pointer::set(int i) { *p = i; } int main() { Holder h; Holder::Pointer hp, hp2; int i; h.initialize(); hp.initialize(&h); hp2.initialize(&h); for(i = 0; i < sz; i++) { hp.set(i); hp.next(); } hp.top(); hp2.end(); for(i = 0; i < sz; i++) { cout << "hp = " << hp.read() << ", hp2 = " << hp2.read() << endl; hp.next(); hp2.previous(); } } ///:~
Listado 5.4. C05/NestFriend.cpp
Una vez que Pointer
está declarado, se
le da acceso a los miembros privados de
Holder
con la sentencia:
friend Pointer;
La estructura Holder
contiene un array de
enteros y Pointer
le permite acceder a
ellos. Como Pointer
está fuertemente asociada
con Holder
, es comprensible que sea una
estructura miembro de Holder
. Pero como
Pointer
es una clase separada de
Holder
, puede crear más de una instancia en
el main()
y usarlas para seleccionar
diferentes partes del array. Pointer
es una
estructura en vez de un puntero de C, así que puede garantizar
que siempre apuntará dentro de Holder
.
La función de la librería estándar de C
memset()
(en
<cstring
>) se usa en el programa
por conveniencia. Hace que toda la memoria a partir de una
determinada dirección (el primer argumento) se cargue con un
valor particular (el segundo argumento) para
n
bytes a partir de la dirección donde se
empezó (n
es el tercer argumento). Por
supuesto, se podría haber usado un bucle para hacer lo mismo,
pero memset()
está disponible, bien
probada (así que es más factible que produzca menos errores),
y probablemente es más eficiente.