Friends
What if you want to explicitly grant
access to a function that isn’t a member of the current structure? This is
accomplished by declaring that function a friend
inside the structure declaration. It’s
important that the friend declaration occurs inside the structure
declaration because you (and the compiler) must be able to read the structure
declaration and see every rule about the size and behavior of that data type.
And a very important rule in any relationship is, “Who can access my
private implementation?”
The class controls which code has access
to its members. There’s no magic way to “break in” from the
outside if you aren’t a friend; you can’t declare a new class
and say, “Hi, I’m a friend of Bob!” and expect to see
the private and protected members of Bob.
You can declare a global function as a
friend,
and you can also declare a member function of another
structure,
or even an entire structure, as a friend. Here’s an example
:
//: 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);
} ///:~
struct Y has a member function
f( ) that will modify an object of type X. This is a bit of a
conundrum because the C++ compiler requires you to declare everything before you
can refer to it, so struct Y must be declared before its member
Y::f(X*) can be declared as a friend in struct X. But for
Y::f(X*) to be declared, struct X must be declared
first!
Here’s the solution. Notice that
Y::f(X*) takes the address of an X
object. This is critical because
the compiler always knows how to pass an address, which is of a fixed size
regardless of the object being passed, even if it doesn’t have full
information about the size of the type. If you try to pass the whole object,
however, the compiler must see the entire structure definition of X, to
know the size and how to pass it, before it allows you to declare a function
such as Y::g(X).
By passing the address of an X,
the compiler allows you to make an incomplete type specification
of
X prior to declaring Y::f(X*). This is accomplished in the
declaration:
struct X;
This declaration simply tells the
compiler there’s a struct by that name, so it’s OK to refer
to it as long as you don’t require any more knowledge than the
name.
Now, in struct X, the function
Y::f(X*) can be declared as a friend with no problem. If you tried
to declare it before the compiler had seen the full specification for Y,
it would have given you an error. This is a safety feature to ensure consistency
and eliminate bugs.
Notice the two other friend
functions. The first declares an ordinary global function g( ) as a
friend. But g( ) has not been previously declared at the
global scope! It turns out that friend can be used this way to
simultaneously declare the function and give it friend status.
This extends to entire structures:
friend struct Z;
is an incomplete type specification for
Z, and it gives the entire structure friend
status.