open();
}
~DBConnection() {
close();
}
private:
// Disallow copy
DBConnection(const DBConnection&);
DBConnection& operator=(const DBConnection&);
};
#endif ///:~
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 idiom (RAII) mentioned in Chapter 1 via the DBConnection constructor and destructor. This makes using a DBConnection easy to use, as the following program shows.
//: C09:UseDatabase2.cpp
// Tests the Countable "mixin" class
#include <cassert>
#include "DBConnection.h"
class DBClient {
public:
DBClient(DBConnection* dbCon) {
db = dbCon;
db->attach();
}
~DBClient() {
db->detach();
}
// Other database requests using db…
private:
DBConnection* 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 when the count reaches zero after the object c1 is destroyed. (This is because of Countable’s virtual destructor, as we explained earlier.)
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 DBCONNECTION_H
#define DBCONNECTION_H
#include "Database.h"
#include <cassert>
#include <string>
using std::string;
template<class Counter>
class DBConnection : public Database, public Counter {
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...
protected:
DBConnection(const string& dbStr) throw(DatabaseError)
: Database(dbStr) {
open();
}
~DBConnection() {
close();
}
private:
// Disallow copy
DBConnection(const DBConnection&);
DBConnection& operator=(const DBConnection&);
};
#endif ///:~
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, per se, 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 {
public:
DBClient(DBConnection<Countable>* dbCon) {
db = dbCon;
db->attach();
}
~DBClient() {
db->detach();
}
private:
DBConnection<Countable>* db;
};
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 {..};
Duplicate subobjects
When you inherit from a base class, you get a copy of all the data members of that base class in your derived class. The following program shows how multiple base subobjects might be laid out in memory.[108]
//: C09:Offset.cpp
// Illustrates layout of subobjects with MI
#include <iostream>
using namespace std;
class A {
int x;
};
class B {
int y;
};
class C : public A, public B {
int z;
};
int main() {
cout << "sizeof(A) == " << sizeof(A) << endl;
cout << "sizeof(B) == " << sizeof(B) << endl;
cout << "sizeof(C) == " << sizeof(C) << endl;
C c;
cout << "&c == " << &c << endl;
A* ap = &c;
B* bp = &c;
cout << "ap == " << static_cast<void*>(ap) << endl;
cout << "bp == " << static_cast<void*>(bp) << endl;
C* cp = static_cast<C*>(bp);
cout << "cp == " << static_cast<void*>(cp) << endl;
cout << "bp == cp? " << boolalpha << (bp == cp) << endl;
cp = 0;
bp = cp;
cout << bp << endl;
}
/* Output:
sizeof(A) == 4
sizeof(B) == 4
sizeof(C) == 12
&c == 1245052