Don't hesitate to send in feedback: send an e-mail if you like the C++ Annotations; if you think that important material was omitted; if you find errors or typos in the text or the code examples; or if you just feel like e-mailing. Send your e-mail to Frank B. Brokken.Please state the document version you're referring to, as found in the title (in this document: 6.5.0) and please state chapter and paragraph name or number you're referring to.
All received mail is processed conscientiously, and received suggestions for improvements will usually have been processed by the time a new version of the Annotations is released. Except for the incidental case I will normally not acknowledge the receipt of suggestions for improvements. Please don't interpret this as me not appreciating your efforts.
In all examples we've discussed up to now, we've seen that private
members are only accessible by the members of their class. This is good, as
it enforces the principles of
encapsulation and
data hiding: By
encapsulating the data in an object we can prevent that code external to
classes becomes
implementation dependent on the data in a class, and by
hiding the data from external code we can control modifications of the data,
helping us to maintain
data integrity.
In this short chapter we will introduce the
friend
keyword as a means to
allow external functions to access the private
members of a class. In this chapter the subject of
friendship among classes is not discussed. Situations in which it is
natural to use friendship among classes are discussed in chapters
16 and 18.
Friendship (i.e., using the friend
keyword) is a complex and dangerous
topic for various reasons:
friend
keyword can be
used quite safely and naturally. It is the purpose of this chapter to
introduce the required syntax and to develop principles allowing us to
recognize cases where the friend
keyword can be used with very little
danger.
Let's consider a situation where it would be nice for an existing class to have access to another class. Such a situation might occur when we would like to give a class developed earlier in history access to a class developed later in history.
Unfortunately, while developing the older class, it was not yet known that the newer class would be developed. Consequently, no provisions were offered in the older class to access the information in the newer class.
Consider the following situation. The
insertion operator may be used to
insert information into a stream. This operator can be given data of several
types: int, double, char *
, etc.. Earlier (chapter 7), we
introduced the class Person
. The class Person
has members to retrieve
the data stored in the Person
object, like char const
*Person::name()
. These members could be used to `insert' a Person
object into a stream, as shown in section 9.2.
With the Person
class the implementation of the insertion
and extraction operators is fairly optimal. The insertion operator uses
accessor members which can be implemented as inline members,
effectively making the private data members directly available for
inspection. The extraction operator requires the use of
modifier members
that could hardly be implemented differently: the old memory will always have
to be deleted, and the new value will always have to be copied to newly
allocated memory.
But let's once more take a look at the class PersonData
, introduced in
section 9.4. It seems likely that this class has at least the
following (private) data members:
class PersonData { Person *d_person; size_t d_n; };When constructing an overloaded insertion operator for a
PersonData
object, e.g., inserting the information of all its persons
into a stream, the overloaded insertion operator is implemented rather
inefficiently when the individual persons must be accessed using the
index operator.
In cases like these, where the
accessor and
modifier members tend to
become rather complex, direct access to the private data members might improve
efficiency. So, in the context of insertion and extraction, we are looking for
overloaded member functions implementing the insertion and extraction
operations and having access to the private data members of the objects to be
inserted or extracted. In order to implement such functions non-member
functions must be given access to the private data members of a class. The
friend
keyword is used to realize this.
PersonData
class, our initial implementation of
the insertion operator is:
ostream &operator<<(ostream &str, PersonData const &pd) { for (size_t idx = 0; idx < pd.nPersons(); idx++) str << pd[idx] << endl; }This implementation will perform its task as expected: using the (overloaded) insertion operator of the class
Person
, the information about
every Person
stored in the PersonData
object will be written on a
separate line.
However, repeatedly calling the
index operator might reduce the
efficiency of the implementation. Instead, directly using the array Person
*d_person
might improve the efficiency of the above function.
At this point we should ask ourselves if we consider the above operator
<<()
primarily an extension of the globally available operator
<<()
function, or in
fact a member function of the class PersonData
. Stated otherwise: assume
we would be able to make operator
<<()
into a true member function of the class
PersonData
, would we object? Probably not, as the function's task is very
closely tied to the class PersonData
. In that case, the function can
sensibly be made a
friend of the class PersonData
, thereby allowing
the function access to the private data members of the class PersonData
.
Friend functions must be declared as friends in the class interface. These
friend declarations refer neither to private
nor to public
functions, so the friend declaration may be placed anywhere in the
class interface. Convention dictates that friend declaractions are listed
directly at the top of the class interface. So, for the class PersonData
we get:
class PersonData { friend ostream &operator<<(ostream &stream, PersonData &pd); friend istream &operator>>(istream &stream, PersonData &pd); public: // rest of the interface };The implementation of the insertion operator can now be altered so as to allow the insertion operator direct access to the private data members of the provided
PersonData
object:
ostream &operator<<(ostream &str, PersonData const &pd) { for (size_t idx = 0; idx < pd.d_n; idx++) str << pd.d_person[idx] << endl; }Once again, whether
friend
functions are considered acceptable or not
remains a matter of taste: if the function is in fact considered a member
function, but it cannot be defined as a member function due to the nature of
the C++ grammar, then it is defensible to use the friend
keyword. In
other cases, the friend
keyword should rather be avoided, thereby
respecting the principles of
encapsulation and
data hiding.
Explicitly note that if we want to be able to insert PersonData
objects into ostream
objects without using the friend
keyword, the
insertion operator cannot be placed inside the
PersonData
class. In this case operator
<<()
is a normal
overloaded variant of the insertion operator, which must therefore
be declared and defined outside of the PersonData
class. This situation
applies, e.g., to the example at the beginning of this section.
friends
can be considered
member functions of a class, albeit that the characteristics of the
function prevents us from actually defining the function as a member
function. In this section we will extend this line of reasoning a little
further.
If we conceptually consider friend functions to be member functions, we should
be able to design a true member function that performs the same tasks as our
friend
function. For example, we could construct a function that inserts a
PersonData
object into an ostream
:
ostream &PersonData::insertor(ostream &str) const { for (size_t idx = 0; idx < d_n; idx++) str << d_person[idx] << endl; return str; }This member function can be used by a
PersonData
object to insert
that object into the ostream str
:
PersonData pd; cout << "The Person-information in the PersonData object is:\n"; pd.insertor(str); cout << "========\n";Realizing that
insertor()
does the same thing as the overloaded
insertion operator, earlier defined as a friend
, we could simply call the
insertor()
member in the code of the friend
operator
<<()
function. Now
this operator
<<()
function needs only one statement: it calls
insertor()
. Consequently:
insertor()
function may be hidden in the class by making it
private
, as there is not need for it to be called elsewhere
operator
<<()
may be constructed as inline member, as it
contains but one statement. However, this is deprecated since it contaminates
class interfaces with implementations. The overloaded operator
<<()
member should
be implemented below the class interface:
PersonData
becomes:
class PersonData { friend ostream &operator<<(ostream &str, PersonData const &pd); private: ostream &insertor(ostream &str) const; }; inline std::ostream &operator<<(std::ostream &str, PersonData const &pd) { return pd.insertor(str); }The above example illustrates the final step in the development of
friend
functions. It allows us to formulate the following principle:
AlthoughUsing this principle, we ascertain that all code that has access to the private data of a class remains confined to the class itself. This even holds true forfriend
functions have access to private members of a class, this characteristic should not be used indiscriminately, as it results in a severe breach of the principle of encapsulation, thereby making non-class functions dependent on the implementation of the data in a class.Instead, if the task a
friend
function performs, can be implemented by a true member function, it can be argued that afriend
is merely a syntactical synonym or alias for this member function.The interpretation of a
friend
function as a synonym for a member function is made concrete by constructing thefriend
function as an inline function.As a principle we therefore state that
friend
functions should be avoided, unless they can be constructed as inline functions, having only one statement, in which an appropriate private member function is called.
friend
functions, as they are defined as simple inline functions.