Looking at the code for the last example, you may notice that most of the Remote
methods are implemented by using the public interface for the Tv
class. This means that those methods don’t really need friend status. Indeed, the only Remote
method that accesses a private Tv
member directly is Remote::set_chan()
, so that’s the only method that needs to be a friend. You do have the option of making just selected class members friends to another class rather than making the entire class a friend, but that’s a bit more awkward. You need to be careful about the order in which you arrange the various declarations and definitions. Let’s look at why.
The way to make Remote::set_chan()
a friend to the Tv
class is to declare it as a friend in the Tv
class declaration:
class Tv
{
friend void Remote::set_chan(Tv & t, int c);
...
};
However, for the compiler to process this statement, it needs to have already seen the Remote
definition. Otherwise, it won’t know that Remote
is a class and that set_chan()
is a method of that class. This suggests putting the Remote
definition above the Tv
definition. But the fact that Remote
methods mention Tv
objects means that the Tv
definition should appear above the Remote
definition. Part of the way around the circular dependence is to use a forward declaration. To do so, you insert the following statement above the Remote
definition:
class Tv; // forward declaration
This provides the following arrangement:
class Tv; // forward declaration
class Remote { ... };
class Tv { ... };
Could you use the following arrangement instead?
class Remote; // forward declaration
class Tv { ... };
class Remote { ... };
The answer is no. The reason, as mentioned earlier, is that when the compiler sees that a Remote
method is declared as a friend in the Tv
class declaration, the compiler needs to have already viewed the declaration of the Remote
class in general and of the set_chan()
method in particular.
Another difficulty remains. In Listing 15.1, the Remote
declaration contains inline code such as the following:
void onoff(Tv & t) { t.onoff(); }
Because this calls a Tv
method, the compiler needs to have seen the Tv
class declaration at this point so that it knows what methods Tv
has. But as you’ve seen, that declaration necessarily follows the Remote
declaration. The solution to this problem is to restrict Remote
to method declarations and to place the actual definitions after the Tv
class. This leads to the following ordering:
class Tv; // forward declaration
class Remote { ... }; // Tv-using methods as prototypes only
class Tv { ... };
// put Remote method definitions here
The Remote
prototypes look like this:
void onoff(Tv & t);
All the compiler needs to know when inspecting this prototype is that Tv
is a class, and the forward declaration supplies that information. By the time the compiler reaches the actual method definitions, it has already read the Tv
class declaration and has the added information needed to compile those methods. By using the inline
keyword in the method definitions, you can still make the methods inline methods. Listing 15.4 shows the revised header file.
// tvfm.h -- Tv and Remote classes using a friend member
#ifndef TVFM_H_
#define TVFM_H_
class Tv; // forward declaration
class Remote
{
public:
enum State{Off, On};
enum {MinVal,MaxVal = 20};
enum {Antenna, Cable};
enum {TV, DVD};
private:
int mode;
public:
Remote(int m = TV) : mode(m) {}
bool volup(Tv & t); // prototype only
bool voldown(Tv & t);
void onoff(Tv & t) ;
void chanup(Tv & t) ;
void chandown(Tv & t) ;
void set_mode(Tv & t) ;
void set_input(Tv & t);
void set_chan(Tv & t, int c);
};
class Tv
{
public:
friend void Remote::set_chan(Tv & t, int c);
enum State{Off, On};
enum {MinVal,MaxVal = 20};
enum {Antenna, Cable};
enum {TV, DVD};
Tv(int s = Off, int mc = 125) : state(s), volume(5),
maxchannel(mc), channel(2), mode(Cable), input(TV) {}
void onoff() {state = (state == On)? Off : On;}
bool ison() const {return state == On;}
bool volup();
bool voldown();
void chanup();
void chandown();
void set_mode() {mode = (mode == Antenna)? Cable : Antenna;}
void set_input() {input = (input == TV)? DVD : TV;}
void settings() const;
private:
int state;
int volume;
int maxchannel;
int channel;
int mode;
int input;
};
// Remote methods as inline functions
inline bool Remote::volup(Tv & t) { return t.volup();}
inline bool Remote::voldown(Tv & t) { return t.voldown();}
inline void Remote::onoff(Tv & t) { t.onoff(); }
inline void Remote::chanup(Tv & t) {t.chanup();}
inline void Remote::chandown(Tv & t) {t.chandown();}
inline void Remote::set_mode(Tv & t) {t.set_mode();}
inline void Remote::set_input(Tv & t) {t.set_input();}
inline void Remote::set_chan(Tv & t, int c) {t.channel = c;}
#endif
If you include tvfm.h
instead of tv.h
in tv.cpp
and use_tv.cpp
, the resulting program behaves the same as the original. The difference is that just one Remote
method—instead of all the Remote
methods—is a friend to the Tv
class. Figure 15.1 illustrates this difference.
Recall that inline functions have internal linkage, which means the function definition must be in the file that uses the function. Here, the inline definitions are in the header file, so including the header file in the file that uses the definitions places the definition in the right place. You could place the definitions in the implementation file instead, provided that you remove the inline
keyword, thus giving the functions external linkage.
By the way, making the entire Remote
class a friend doesn’t need a forward declaration because the friend statement itself identifies Remote
as a class:
friend class Remote;
3.135.198.174