Follow Techotopia on Twitter

On-line Guides
All Guides
eBook Store
iOS / Android
Linux for Beginners
Office Productivity
Linux Installation
Linux Security
Linux Utilities
Linux Virtualization
Linux Kernel
System/Network Admin
Programming
Scripting Languages
Development Tools
Web Development
GUI Toolkits/Desktop
Databases
Mail Systems
openSolaris
Eclipse Documentation
Techotopia.com
Virtuatopia.com
Answertopia.com

How To Guides
Virtualization
General System Admin
Linux Security
Linux Filesystems
Web Servers
Graphics & Desktop
PC Hardware
Windows
Problem Solutions
Privacy Policy

  




 

 

Thinking in C++ Vol 2 - Practical Programming
Prev Home Next

The inner class idiom

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.[145]

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

 
 
   Reproduced courtesy of Bruce Eckel, MindView, Inc. Design by Interspire