We're always interested in getting feedback. E-mail us if you like this guide, if you think that important material is omitted, if you encounter errors in the code examples or in the documentation, if you find any typos, or generally just if you feel like e-mailing. Send your email to Frank Brokken.Please state the document version you're referring to, as found in the title (in this document: 4.4.2).
Let's return to friend
s once more. In section 5.6 the
possibility of declaring a function or class as a friend of a class was
discussed. At the end of that section, we mentioned
A
changes, all its
friends must be recompiled (and possibly modified) as well.
In our opinion, there are indeed very few reasons for using the friend
keyword. It violates the principle of data hiding, and makes the maintenance
of a class dependent on another class.
Nonetheless, it might be worthwhile to look at some examples in which the
friend
keyword can be used profitably. Having seen such examples,
the decision about whether or not to use friend
s might be based on a
somewhat more solid foundation than on a plain rule of thumb.
At the onset, we remark that in our programming projects we never found
any convincing reason to resort to friend
s. Having thus made our position
clear, 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 an old class access to a class developed later in history.
However, while developing the older class, it was not yet known that the newer class would be developed later in time. E.g., the older class is distributed in the runtime-library of a compiler, and the newer class is a class developed by us.
Consequently, no provisions were offered in the older class to access the information in the newer class.
Consider the following situation. Within the C++
I/O-library the
extraction >>
and insertion <<
operators
may be used to extract from and to insert into a stream.
These operators can be given data of several
types: int, double, char *
, etc.. Now assume that we develop a class
String
. Objects of the class String
can be
given a string, and String
objects can also produce other
String
objects.
While it is possible to use the insertion operator to write the string that is stored in the object to a stream, it is not possible to use the extraction operator, as illustrated by the following piece of code:
#include <iostream> class String { public: // ... void set(char const *s); char const *get() const; private: char *str; }; void f() { String str; str.set("hello world"); // Assign a value. Can't use // cin >> str.set() or // a similar construction cout << str.get() << endl; // this is ok. }Actually, the use of the insertion operator in combination with the
String
class is also a bit of a kludge: it isn't the String
object that is inserted into the stream, but rather a string produced by
one of its members.
Below we'll discuss a method to allow the insertion and extraction of
String
objects, based on the use of the friend
keyword.
String
objects into
streams, rather than derivatives of String
objects, like
char const *
's. If we would be able to write String
objects into
streams, we could be using code comparable to
int main() { String str("Hello world"); cout << "The string is: '" << str << "'" << endl; return (0); }
Analogously, with the extraction operator, we would like to be able to write code comparable to the next example:
int main() { String str; cout << "Enter your string: "; cin >> str; cout << "Got: '" << str << "'" << endl; return (0); }
In this situation we would not have to rely on the availability of a
particular member (like char const *String::get()
), and we would be able to
fill a String
object directly via the extraction operator, rather than
via an intermediate variable of a type understood by the cin
stream.
Even more central to the concept of object oriented programming: we would be
able to ignore the functionality of the String
class in combination
with iostream
objects: our objective is, after all, to insert the
information in the String
object into the cout
stream, and not
to call a particular function to do so.
Once we're able to focus our attention on the object, rather than on its
member functions, the above piece of code remains valid, no matter what
internal organization the String
object has.
>>
, to be used as an extraction
operator with a String
object:
istream &String::operator>>(istream &is) { char buffer[500]; // assume this buffer to be // large enough. is >> buffer; // extraction delete str; // free this->str // memory // assign new value str = strdupnew(buffer); return (is); // return is-reference }The extraction operator can now be used with
String
objects. Unfortunately, this implementation produces awkward code.
The extraction operator is
part of the String
class, so its left operand must be a
String
object.
As the left operand must be a String
object, we're now forced to
use weird-looking code like the following, which can only partially be
compiled. The numbered statements are annotated next.
void fun() { String s; s >> cin; // (1) int x; s >> (cin >> x); // (2) cin >> x >> s; // (3) }
s
is the left-hand operator, and cin
the right-hand, consequently, this statement represents
extraction from a cin
object into a String
object.
cin >> x
is executed,
producing an istream &
, which is then used as a right-hand operand
with the extraction
to s
.
istream
's overloaded operator >>
doesn't know how to extract
information into String
objects.
A solution is to overload the global >>
operator to accept a
left-operand of the istream &
type, and a right operand of the
String &
type, returning an istream &
. Its
prototype is, therefore:
istream &operator>>(istream &is, String &destination);
To implement this function, the implementation given for the overloaded
extraction operator of the String
class can't simply be copied, since
the private datamember str
is accessed there. A small (and perfectly legal)
modification would be to access the String
's information via a
char const *String::get() const
member, but this would again generate
a dependency on the String::get()
function, which we would like to
avoid.
However, the need for overloading the extraction operator arose strictly in
the context of the String
class, and is in fact depending on the
existence of that class. In this situation the overloading of the operator
could be considered an extension to the String
class, rather than to
the iostream
class.
Next, since we consider the overloading of the >>
operator in the context
of the String
class an extension of the String
class, we feel safe
to allow that function access to the private members of a String
object,
instead of forcing the operator>>()
function to assign the data members
of the String
object through the String
's member functions.
Access to the private data members of the String
object is granted by
declaring the operator>>()
function to be a friend
of the String
class:
#include <iostream> class String { friend istream &operator>>(istream &is, String &destination); public: // ... private: char *str; }; istream &operator>>(istream &is, String &destination) { char buffer[500]; is >> buffer; // extraction delete destination.str; // free old 'str' memory destination.str = strdupnew(buffer); // assign new value return (is); // return istream-reference } void fun() { String s; cin >> s; // application int x; cin >> x >> s; // extraction order is now // as expected }
Note that nothing in the implementation of the operator>>()
function
suggests that it's a friend of the String
class. The compiler detects
this only from the String
interface, where the operator>>()
function
is declared as a friend.
operator>>()
function for the String
class, it's hopefully clear that there is only
very little reason to declare it as a friend of the class String
, assuming
that the proper member functions of the class are available.
On the other hand, declaring the operator>>()
as a friend function isn't
that much of a problem, as the operator>>()
function can very well be
interpreted as a true member function of the class String
, although, due
to a syntactical peculiarity, it cannot be defined as such.
To illustrate the possibility of overloading the >>
operator for the
istream
and String
combination, we present here the version which does
not have to be declared as a friend
in the String
class interface.
This implementation assumes that the class String
has
an overloaded operator =
, accepting as r-value a char const *
:
istream &operator>>(istream &lvalue, String &rvalue) { char buffer[500]; lvalue >> buffer; // extraction rvalue = buffer; // assignment return (lvalue); // return istream-reference }No big deal, isn't it? After all, whether or not to use
friend
functions might purely be a matter of taste. As yet, we haven't come across a
situation where friend
functions are truly needed.
For example, a window application can define a class Window
to contain the information of a particular window, and a class Screen
shadowing the Window
objects for those windows that are actually
visible on the screen.
Assuming that the window-contents of a Window
or Screen
object are accessible through a char *win
pointer, of unsigned size
characters, an overloaded operator
!=
can be defined in one (or both) classes to compare the contents of
a Screen
and Window
object immediately. Objects of the two
classes may then be compared directly, as in the following code fragment:
void fun() { Screen s; Window w; // ... actions on s and w ... if (w != s) // refresh the screen w.refresh(s); // if w != s }
It is likely that the overloaded operator !=
and other member
functions of w
(like refresh()
) will benefit from direct access to
the data of a Screen
object. In this case the class Screen
may declare the class Window
as a friend class, thus allowing
Window
's member functions to access the private members of its objects.
A (partial) implementation of this situation is:
class Window; // forward declaration class Screen { friend class Window; // Window's object may // access Screen's // private members public: // ... private: // ... char *win; unsigned size; }; // ============================================= // now in Window's context: int Window::operator!=(Screen const &s) { return ( s.size != size // accessing Screen's || // private members !memcmp(win, s.win, size) ); };
It is also possible to declare classes to be each other's friends, or to declare a global function to be a friend in multiple classes. While there may be situations where this is a useful thing to do, it is important to realize that these multiple friendships actually violate the principle of encapsulation.
In the example we've been giving earlier for single friend
functions,
the implementation of such functions
can be placed in the same directory as the actual member
functions of the class declaring the function to be its friend. Such functions
can very well be considered part of the class implementation, being
somewhat `eccentric` member functions.
Those functions will normally be inspected automatically
when the implementation of the data of the class is changed.
However, when a class itself is declared as a
friend of another class, things become a little more complex. If the sources
of classes are kept and maintained in different directories, it is not clear
where the code of Window::operator!=()
should be stored, as this
function accesses private members of both the class Window
and
Screen
. Consequently caution should be exercized when these
situations arise.
In our opinion it's probably best to avoid friend classes, as they violate of the central principle of encapsulation.