I l@ve RuBoard | ![]() ![]() |
Solution![]() Are you wondering why a question like this gets a title like "Name Lookup–Part 3"? If so, you'll soon see why, as we consider an application of the Interface Principle discussed in the previous Item. What Does a Class Depend On?"What's in a class?" isn't just a philosophical question. It's a fundamentally practical question, because without the correct answer, we can't properly analyze class dependencies. To demonstrate this, consider a seemingly unrelated problem: What's the best way to write operator<< for a class? There are two main ways, both of which involve tradeoffs. I'll analyze both. In the end we'll find that we're back to the Interface Principle and that it has given us important guidance to analyze the tradeoffs correctly. Here's the first way: //*** Example 5 (a) -- nonvirtual streaming class X { /*...ostream is never mentioned here...*/ }; ostream& operator<<( ostream& o, const X& x ) { /* code to output an X to a stream */ return o; } Here's the second: //*** Example 5 (b) -- virtual streaming class X { /*...*/ public: virtual ostream& print( ostream& ) const; }; ostream& X::print( ostream& o ) const { /* code to output an X to a stream */ return o; } ostream& operator<<( ostream& o, const X& x ) { return x.print( o ); } Assume that in both cases the class and the function declaration appear in the same header and/or namespace. Which one would you choose? What are the tradeoffs? Historically, experienced C++ programmers have analyzed these options this way:
This is the traditional analysis. Alas, this analysis is flawed. Armed with the Interface Principle, we can see why: The first advantage in Option (a) is a phantom, as indicated by the comments in italics.
So what we've traditionally thought of as Option (a)'s main advantage is not an advantage at all. In both cases, X still in fact depends on ostream anyway. If, as is typical, operator<< and X appear in the same header X.h, then both X's own implementation module and all client modules that use X physically depend on ostream and require at least its forward declaration in order to compile. With Option (a)'s first advantage exposed as a phantom, the choice really boils down to just the virtual function call overhead. Without applying the Interface Principle, though, we would not have been able to as easily analyze the true dependencies (and therefore the true tradeoffs) in this common real-world example. Bottom line, it's not always useful to distinguish between members and nonmembers, especially when it comes to analyzing dependencies, and that's exactly what the Interface Principle implies. Some Interesting (and Even Surprising) ResultsIn general, if A and B are classes and f(A,B) is a free function:
Finally, we get to the really interesting case. In general, if A and B are classes and A::g(B) is a member function of A:
At first, it might seem like a stretch to consider a member function of one class as also part of another class, but this is true only if A and B are also supplied together. Consider: If A and B are supplied together (say, in the same header file) and A mentions B in a member function like this, "gut feel" already usually tells us A and B are probably interdependent. They are certainly strongly coupled and cohesive, and the fact that they are supplied together and interact means that: (a) they are intended to be used together, and (b) changes to one affect the other. The problem is that, until now, it's been hard to prove A and B's interdependence with anything more substantial than gut feel. Now their interdependence can be demonstrated as a direct consequence of the Interface Principle. Note that, unlike classes, namespaces don't need to be declared all at once, and what's "supplied together" depends on what parts of the namespace are visible. //*** Example 6 (a) //---file a.h--- namespace N { class B; }// forward decl namespace N { class A; }// forward decl class N::A { public: void g(B); }; //---file b.h--- namespace N { class B { /*...*/ }; } Clients of A include a.h, so for them A and B are supplied together and are interdependent. Clients of B include b.h, so for them A and B are not supplied together. In summary, I'd like you to take away three thoughts from this miniseries.
![]() |
I l@ve RuBoard | ![]() ![]() |