In the following example, the class YourClass declares that the YourOtherClass class is a friend. This permits member functions of YourOtherClass to directly read or modify the private data of YourClass:
class YourClass
{
friend class YourOtherClass;
private:
int topSecret;
};
class YourOtherClass
{
public:
void change( YourClass yc )
};
void YourOtherClass::change( YourClass yc )
{
yc.topSecret++; // Can access private data
}
The friend declaration is not affected by the public or private keywords; you can place it anywhere in the class's declaration.
Notice that the friend declaration appears in YourClass. When you write YourClass, you specify those classes that you wish to have access to YourClass's private data. Another programmer cannot write a class called HisClass and declare it to be a friend in order to gain access. For example:
class HisClass
{
// Cannot declare itself to be a friend of YourClass
public:
void change( YourClass yc )
};
void HisClass::change( YourClass yc )
{
yc.topSecret++; // Error: can't access private data
}
Thus, you control who has access to the classes you write.
Notice that the friend keyword provides access in one direction only. While YourOtherClass is a friend of YourClass, the reverse is not true. Friendship is not mutual unless explicitly specified as such.
A list class demonstrates the usefulness of friend classes more realistically. Suppose you want to maintain a list of names and phone numbers, and you want to be able to specify someone's name and find his or her phone number. You could write a class like the following:
#include <string.h>
struct Record
{
char name[30];
char number[10];
};
const int MAXLENGTH = 100;
class PhoneList
{
friend class PhoneIter;
public:
PhoneList();
int add( const Record &newRec );
Record *search( char *searchKey );
private:
Record aray[MAXLENGTH];
int firstEmpty; // First unused element
};
PhoneList::PhoneList()
{
firstEmpty = 0;
}
int PhoneList::add( const Record &newRec )
{
if( firstEmpty < MAXLENGTH - 1 )
{
aray[firstEmpty++] = newRec;
return 1; // Indicate success
}
else return 0;
}
Record *PhoneList::search( char *searchKey )
{
for( int i = 0; i < firstEmpty; i++ )
if( !strcmp( aray[i].name, searchKey ) )
return &aray[i];
return 0;
}
Each PhoneList object contains an array of Record structures. You can add new entries and search through the existing entries by specifying a name. You can create as many PhoneList objects as you need for storing separate lists of names.
Now suppose you want to examine each of the entries stored in a PhoneList object, one by one; that is, you want to “iterate” through all the entries. One way to do this is to write an iterator class that is a friend of the PhoneList class.
Here's the friend class PhoneIter:
class PhoneIter
{
public:
PhoneIter( PhoneList &m );
Record *getFirst();
Record *getLast();
Record *getNext();
Record *getPrev();
private:
PhoneList *const mine; // Pointer to a PhoneList object
int currIndex;
};
PhoneIter::PhoneIter( const PhoneList &m )
: mine( &m ) // Initialize the constant member
{
currIndex = 0;
}
Record *PhoneIter::getFirst()
{
currIndex = 0;
return &(mine->aray[currIndex]);
}
Record *PhoneIter::getLast()
{
currIndex = mine->firstEmpty - 1;
return &(mine->aray[currIndex]);
}
Record *PhoneIter::getNext()
{
if( currIndex < mine->firstEmpty - 1 )
{
currIndex++;
return &(mine->aray[currIndex]);
}
else return 0;
}
Record *PhoneIter::getPrev()
{
if( currIndex > 0 )
{
currIndex--;
return &(mine->aray[currIndex]);
}
else return 0;
}
When you declare a PhoneIter object, you initialize it with a PhoneList object. The PhoneIter object stores your current position within the list. Here's a function that demonstrates the use of a PhoneIter object:
void printList( PhoneList aList )
{
Record *each;
PhoneIter anIter( aList );
each = anIter.getFirst();
cout << each->name << ' ' << each->number << '\n';
while( each = anIter.getNext() )
{
cout << each->name << ' ' << each->number << '\n';
}
}
By calling the getNext and getPrev member functions, you can move the current position forward or back, reading the elements in the list at the same time. With the getFirst and getLast functions, you can start at either end of the list.
The PhoneIter class is useful because you can declare several iterator objects for a particular PhoneList class. Thus you can maintain several current positions within the list, like bookmarks, and you can move each one back and forth independently. This type of functionality is cumbersome to implement using only member functions.
An important characteristic of the PhoneList class is that users of the class don't know that it's implemented with an array. You could replace the array with a doubly-linked list without affecting the class's interface. You would have to rewrite the add function to append a new node to the linked list, and rewrite the search function to traverse the list, but the prototypes of those functions would remain the same as they are now. Programs that call the add and search functions don't have to be modified at all.
If you were to rewrite the PhoneList class in this way, you would also have to rewrite the PhoneIter class. Instead of containing the index of the current element, each PhoneIter object would contain a pointer to the current node. However, the available operations would not change; the class's interface would remain the same. (Together, the PhoneList and PhoneIter classes form an “abstract” phone list, which is defined only by its operations, not by its internal workings. Abstraction is discussed in Chapter 9, “Fundamentals of Object-Oriented Design.”)
When you use the friend mechanism in C++, you are no longer writing a class that stands alone; you are writing two or more classes that are always used together. If you rewrite one class, you must also rewrite the other(s). You should therefore use the friend mechanism very sparingly; otherwise you may have to rewrite large amounts of code whenever you change one class.