Thinking in C++ Vol 2 - Practical Programming |
Prev |
Home |
Next |
Here s a situation where we must (in effect) upcast to more
than one type, but in this case we need to provide several different
implementations of the same base type. The solution is something we ve lifted
from Java, which takes C++ s nested class one step further. Java has a built-in
feature called an inner class, which is like a nested class in C++, but
it has access to the nonstatic data of its containing class by implicitly using
the this pointer of the class object it was created within.
To implement the inner class idiom in C++, we must obtain
and use a pointer to the containing object explicitly. Here s an example:
//: C10:InnerClassIdiom.cpp
// Example of the "inner class" idiom.
#include <iostream>
#include <string>
using namespace std;
class Poingable {
public:
virtual void poing() = 0;
};
void callPoing(Poingable& p) {
p.poing();
}
class Bingable {
public:
virtual void bing() = 0;
};
void callBing(Bingable& b) {
b.bing();
}
class Outer {
string name;
// Define one inner class:
class Inner1;
friend class Outer::Inner1;
class Inner1 : public Poingable {
Outer* parent;
public:
Inner1(Outer* p) : parent(p) {}
void poing() {
cout << "poing called for "
<< parent->name << endl;
// Accesses data in the outer class object
}
} inner1;
// Define a second inner class:
class Inner2;
friend class Outer::Inner2;
class Inner2 : public Bingable {
Outer* parent;
public:
Inner2(Outer* p) : parent(p) {}
void bing() {
cout << "bing called for "
<< parent->name << endl;
}
} inner2;
public:
Outer(const string& nm)
: name(nm), inner1(this), inner2(this) {}
// Return reference to interfaces
// implemented by the inner classes:
operator Poingable&() { return inner1; }
operator Bingable&() { return inner2; }
};
int main() {
Outer x("Ping Pong");
// Like upcasting to multiple base types!:
callPoing(x);
callBing(x);
} ///:~
The example (intended to show the simplest syntax for the
idiom; you ll see a real use shortly) begins with the Poingable and Bingable
interfaces, each containing a single member function. The services provided
by callPoing( ) and callBing( ) require that the object
they receive implements the Poingable and Bingable interfaces,
respectively, but they put no other requirements on that object so as to
maximize the flexibility of using callPoing( ) and callBing( ).
Note the lack of virtual destructors in either interface the intent is
that you never perform object destruction via the interface.
The Outer constructor contains some private data (name),
and it wants to provide both a Poingable interface and a Bingable interface
so it can be used with callPoing( ) and callBing( ). (In
this situation we could simply use multiple inheritance, but it is kept simple for clarity.) To provide a Poingable object without deriving Outer
from Poingable, the inner class idiom is used. First, the declaration class
Inner says that, somewhere, there is a nested class of this name. This
allows the friend declaration for the class, which follows. Finally, now
that the nested class has been granted access to all the private elements of Outer,
the class can be defined. Notice that it keeps a pointer to the Outer
which created it, and this pointer must be initialized in the constructor.
Finally, the poing( ) function from Poingable is
implemented. The same process occurs for the second inner class which
implements Bingable. Each inner class has a single private
instance created, which is initialized in the Outer constructor. By
creating the member objects and returning references to them, issues of object
lifetime are eliminated.
Notice that both inner class definitions are private,
and in fact the client code doesn t have any access to details of the
implementation, since the two access functions operator Poingable&( )
and operator Bingable&( ) only return a reference to the upcast
interface, not to the object that implements it. In fact, since the two inner
classes are private, the client code cannot even downcast to the
implementation classes, thus providing complete isolation between interface and
implementation.
We ve taken the extra liberty here of defining the automatic
type conversion functions operator Poingable&( ) and operator
Bingable&( ). In main( ), you can see that these allow
a syntax that looks as if Outer multiply inherits from Poingable
and Bingable. The difference is that the casts in this case are one-way.
You can get the effect of an upcast to Poingable or Bingable, but
you cannot downcast back to an Outer. In the following example of observer,
you ll see the more typical approach: you provide access to the inner class
objects using ordinary member functions, not automatic type conversion
functions.
Thinking in C++ Vol 2 - Practical Programming |
Prev |
Home |
Next |