Our Sales_data
class defined three ordinary nonmember functions as friends (§ 7.2.1, p. 269). A class can also make another class its friend or it can declare specific member functions of another (previously defined) class as friends. In addition, a friend function can be defined inside the class body. Such functions are implicitly inline
.
As an example of class friendship, our Window_mgr
class (§ 7.3.1, p. 274) will have members that will need access to the internal data of the Screen
objects it manages. For example, let’s assume that we want to add a member, named clear
to Window_mgr
that will reset the contents of a particular Screen
to all blanks. To do this job, clear
needs to access the private
data members of Screen
. To allow this access, Screen
can designate Window_mgr
as its friend:
class Screen {
// Window_mgr members can access the private parts of class Screen
friend class Window_mgr;
// ... rest of the Screen class
};
The member functions of a friend class can access all the members, including the nonpublic
members, of the class granting friendship. Now that Window_mgr
is a friend of Screen
, we can write the clear
member of Window_mgr
as follows:
class Window_mgr {
public:
// location ID for each screen on the window
using ScreenIndex = std::vector<Screen>::size_type;
// reset the Screen at the given position to all blanks
void clear(ScreenIndex);
private:
std::vector<Screen> screens{Screen(24, 80, ' ')};
};
void Window_mgr::clear(ScreenIndex i)
{
// s is a reference to the Screen we want to clear
Screen &s = screens[i];
// reset the contents of that Screen to all blanks
s.contents = string(s.height * s.width, ' '),
}
We start by defining s
as a reference to the Screen
at position i
in the screens vector
. We then use the height
and width
members of that Screen
to compute anew string
that has the appropriate number of blank characters. We assign that string of blanks to the contents
member.
If clear
were not a friend of Screen
, this code would not compile. The clear
function would not be allowed to use the height width
, or contents
members of Screen
. Because Screen
grants friendship to Window_mgr
, all the members of Screen
are accessible to the functions in Window_mgr
.
It is important to understand that friendship is not transitive. That is, if class Window_mgr
has its own friends, those friends have no special access to Screen
.
Rather than making the entire Window_mgr
class a friend, Screen
can instead specify that only the clear
member is allowed access. When we declare a member function to be a friend, we must specify the class of which that function is a member:
class Screen {
// Window_mgr::clear must have been declared before class Screen
friend void Window_mgr::clear(ScreenIndex);
// ... rest of the Screen class
};
Making a member function a friend requires careful structuring of our programs to accommodate interdependencies among the declarations and definitions. In this example, we must order our program as follows:
• First, define the Window_mgr
class, which declares, but cannot define, clear. Screen
must be declared before clear
can use the members of Screen
.
• Next, define class Screen
, including a friend declaration for clear
.
• Finally, define clear
, which can now refer to the members in Screen
.
Although overloaded functions share a common name, they are still different functions. Therefore, a class must declare as a friend each function in a set of overloaded functions that it wishes to make a friend:
// overloaded storeOn functions
extern std::ostream& storeOn(std::ostream &, Screen &);
extern BitMap& storeOn(BitMap &, Screen &);
class Screen {
// ostream version of storeOn may access the private parts of Screen objects
friend std::ostream& storeOn(std::ostream &, Screen &);
// . . .
};
Class Screen
makes the version of storeOn
that takes an ostream&
its friend. The version that takes a BitMap&
has no special access to Screen
.
Classes and nonmember functions need not have been declared before they are used in a friend declaration. When a name first appears in a friend declaration, that name is implicitly assumed to be part of the surrounding scope. However, the friend itself is not actually declared in that scope (§ 7.2.1, p. 270).
Even if we define the function inside the class, we must still provide a declaration outside of the class itself to make that function visible. A declaration must exist even if we only call the friend from members of the friendship granting class:
struct X {
friend void f() { /* friend function can be defined in the class body */ }
X() { f(); } // error: no declaration for f
void g();
void h();
};
void X::g() { return f(); } // error: f hasn't been declared
void f(); // declares the function defined inside X
void X::h() { return f(); } // ok: declaration for f is now in scope
It is important to understand that a friend declaration affects access but is not a declaration in an ordinary sense.