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

Implementation inheritance

As we stated earlier, C++ provides only implementation inheritance, meaning that you always inherit everything from your base classes. This can be good because it frees you from having to implement everything in the derived class, as we had to do with the interface inheritance examples earlier. A common use of multiple inheritance involves using mixin classes, which are classes that exist to add capabilities to other classes through inheritance. Mixin classes are not intended to be instantiated by themselves.

As an example, suppose we are clients of a class that supports access to a database. In this scenario, you only have a header file available part of the point here is that you don t have access to the source code for the implementation. For illustration, assume the following implementation of a Database class:

//: C09:Database.h
// A prototypical resource class.
#ifndef DATABASE_H
#define DATABASE_H
#include <iostream>
#include <stdexcept>
#include <string>
 
struct DatabaseError : std::runtime_error {
DatabaseError(const std::string& msg)
: std::runtime_error(msg) {}
};
 
class Database {
std::string dbid;
public:
Database(const std::string& dbStr) : dbid(dbStr) {}
virtual ~Database() {}
void open() throw(DatabaseError) {
std::cout << "Connected to " << dbid << std::endl;
}
void close() {
std::cout << dbid << " closed" << std::endl;
}
// Other database functions...
};
#endif // DATABASE_H ///:~
 

We re leaving out actual database functionality (storing, retrieving, and so on), but that s not important here. Using this class requires a database connection string and that you call Database::open( ) to connect and Database::close( ) to disconnect:

//: C09:UseDatabase.cpp
#include "Database.h"
 
int main() {
Database db("MyDatabase");
db.open();
// Use other db functions...
db.close();
}
/* Output:
connected to MyDatabase
MyDatabase closed
*/ ///:~
 

In a typical client-server situation, a client will have multiple objects sharing a connection to a database. It is important that the database eventually be closed, but only after access to it is no longer required. It is common to encapsulate this behavior through a class that tracks the number of client entities using the database connection and to automatically terminate the connection when that count goes to zero. To add reference counting to the Database class, we use multiple inheritance to mix a class named Countable into the Database class to create a new class, DBConnection. Here s the Countable mixin class:

//: C09:Countable.h
// A "mixin" class.
#ifndef COUNTABLE_H
#define COUNTABLE_H
#include <cassert>
 
class Countable {
long count;
protected:
Countable() { count = 0; }
virtual ~Countable() { assert(count == 0); }
public:
long attach() { return ++count; }
long detach() {
return (--count > 0) ? count : (delete this, 0);
}
long refCount() const { return count; }
};
#endif // COUNTABLE_H ///:~
 

It is evident that this is not a standalone class because its constructor is protected; it requires a friend or a derived class to use it. It is important that the destructor is virtual, because it is called only from the delete this statement in detach( ), and we want derived objects to be properly destroyed.[122]

The DBConnection class inherits both Database and Countable and provides a static create( ) function that initializes its Countable subobject. This is an example of the Factory Method design pattern, discussed in the next chapter:

//: C09:DBConnection.h
// Uses a "mixin" class.
#ifndef DBCONNECTION_H
#define DBCONNECTION_H
#include <cassert>
#include <string>
#include "Countable.h"
#include "Database.h"
using std::string;
 
class DBConnection : public Database, public Countable {
DBConnection(const DBConnection&); // Disallow copy
DBConnection& operator=(const DBConnection&);
protected:
DBConnection(const string& dbStr) throw(DatabaseError)
: Database(dbStr) { open(); }
~DBConnection() { close(); }
public:
static DBConnection*
create(const string& dbStr) throw(DatabaseError) {
DBConnection* con = new DBConnection(dbStr);
con->attach();
assert(con->refCount() == 1);
return con;
}
// Other added functionality as desired...
};
#endif // DBCONNECTION_H ///:~
 

We now have a reference-counted database connection without modifying the Database class, and we can safely assume that it will not be surreptitiously terminated. The opening and closing is done using the Resource Acquisition Is Initialization (RAII) idiom mentioned in Chapter 1 via the DBConnection constructor and destructor. This makes the DBConnection easy to use:

//: C09:UseDatabase2.cpp
// Tests the Countable "mixin" class.
#include <cassert>
#include "DBConnection.h"
 
class DBClient {
DBConnection* db;
public:
DBClient(DBConnection* dbCon) {
db = dbCon;
db->attach();
}
~DBClient() { db->detach(); }
// Other database requests using db
};
 
int main() {
DBConnection* db = DBConnection::create("MyDatabase");
assert(db->refCount() == 1);
DBClient c1(db);
assert(db->refCount() == 2);
DBClient c2(db);
assert(db->refCount() == 3);
// Use database, then release attach from original create
db->detach();
assert(db->refCount() == 2);
} ///:~
 

The call to DBConnection::create( ) calls attach( ), so when we re finished, we must explicitly call detach( ) to release the original hold on the connection. Note that the DBClient class also uses RAII to manage its use of the connection. When the program terminates, the destructors for the two DBClient objects will decrement the reference count (by calling detach( ), which DBConnection inherited from Countable), and the database connection will be closed (because of Countable s virtual destructor) when the count reaches zero after the object c1 is destroyed.

A template approach is commonly used for mixin inheritance, allowing the user to specify at compile time which flavor of mixin is desired. This way you can use different reference-counting approaches without explicitly defining DBConnection twice. Here s how it s done:

//: C09:DBConnection2.h
// A parameterized mixin.
#ifndef DBCONNECTION2_H
#define DBCONNECTION2_H
#include <cassert>
#include <string>
#include "Database.h"
using std::string;
 
template<class Counter>
class DBConnection : public Database, public Counter {
DBConnection(const DBConnection&); // Disallow copy
DBConnection& operator=(const DBConnection&);
protected:
DBConnection(const string& dbStr) throw(DatabaseError)
: Database(dbStr) { open(); }
~DBConnection() { close(); }
public:
static DBConnection* create(const string& dbStr)
throw(DatabaseError) {
DBConnection* con = new DBConnection(dbStr);
con->attach();
assert(con->refCount() == 1);
return con;
}
// Other added functionality as desired...
};
#endif // DBCONNECTION2_H ///:~
 

The only change here is the template prefix to the class definition (and renaming Countable to Counter for clarity). We could also make the database class a template parameter (had we multiple database access classes to choose from), but it is not a mixin since it is a standalone class. The following example uses the original Countable as the Counter mixin type, but we could use any type that implements the appropriate interface (attach( ), detach( ), and so on):

//: C09:UseDatabase3.cpp
// Tests a parameterized "mixin" class.
#include <cassert>
#include "Countable.h"
#include "DBConnection2.h"
 
class DBClient {
DBConnection<Countable>* db;
public:
DBClient(DBConnection<Countable>* dbCon) {
db = dbCon;
db->attach();
}
~DBClient() { db->detach(); }
};
 
int main() {
DBConnection<Countable>* db =
DBConnection<Countable>::create("MyDatabase");
assert(db->refCount() == 1);
DBClient c1(db);
assert(db->refCount() == 2);
DBClient c2(db);
assert(db->refCount() == 3);
db->detach();
assert(db->refCount() == 2);
} ///:~
 

The general pattern for multiple parameterized mixins is simply

template<class Mixin1, class Mixin2, , class MixinK>
class Subject : public Mixin1,
public Mixin2,
public MixinK { };
 
Thinking in C++ Vol 2 - Practical Programming
Prev Home Next

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