Thinking in C++ Vol 2 - Practical Programming |
Prev |
Home |
Next |
Any novice C++ programmer can figure out how to modify a class to keep track of the number of objects of that class that currently exist. All
you have to do is to add static members, and modify constructor and destructor
logic, as follows:
//: C05:CountedClass.cpp
// Object counting via static members.
#include <iostream>
using namespace std;
class CountedClass {
static int count;
public:
CountedClass() { ++count; }
CountedClass(const CountedClass&) { ++count; }
~CountedClass() { --count; }
static int getCount() { return count; }
};
int CountedClass::count = 0;
int main() {
CountedClass a;
cout << CountedClass::getCount() <<
endl; // 1
CountedClass b;
cout << CountedClass::getCount() <<
endl; // 2
{ // An arbitrary scope:
CountedClass c(b);
cout << CountedClass::getCount() <<
endl; // 3
a = c;
cout << CountedClass::getCount() <<
endl; // 3
}
cout << CountedClass::getCount() <<
endl; // 2
} ///:~
All constructors of CountedClass increment the static
data member count, and the destructor decrements it. The static member
function getCount( ) yields the current number of objects.
It would be tedious to manually add these members every time
you wanted to add object counting to a class. The usual object-oriented device used
to repeat or share code is inheritance, which is only half a solution in this
case. Observe what happens when we collect the counting logic into a base
class.
//: C05:CountedClass2.cpp
// Erroneous attempt to count objects.
#include <iostream>
using namespace std;
class Counted {
static int count;
public:
Counted() { ++count; }
Counted(const Counted&) { ++count; }
~Counted() { --count; }
static int getCount() { return count; }
};
int Counted::count = 0;
class CountedClass : public Counted {};
class CountedClass2 : public Counted {};
int main() {
CountedClass a;
cout << CountedClass::getCount() <<
endl; // 1
CountedClass b;
cout << CountedClass::getCount() <<
endl; // 2
CountedClass2 c;
cout << CountedClass2::getCount() <<
endl; // 3 (Error)
} ///:~
All classes that derive from Counted share the same,
single static data member, so the number of objects is tracked collectively
across all classes in the Counted hierarchy. What is needed is a way to
automatically generate a different base class for each derived class.
This is accomplished by the curious template construct illustrated below:
//: C05:CountedClass3.cpp
#include <iostream>
using namespace std;
template<class T> class Counted {
static int count;
public:
Counted() { ++count; }
Counted(const Counted<T>&) { ++count; }
~Counted() { --count; }
static int getCount() { return count; }
};
template<class T> int Counted<T>::count =
0;
// Curious class definitions
class CountedClass : public Counted<CountedClass>
{};
class CountedClass2 : public
Counted<CountedClass2> {};
int main() {
CountedClass a;
cout << CountedClass::getCount() <<
endl; // 1
CountedClass b;
cout << CountedClass::getCount() <<
endl; // 2
CountedClass2 c;
cout << CountedClass2::getCount() <<
endl; // 1 (!)
} ///:~
Each derived class derives from a unique base class that is
determined by using itself (the derived class) as a template parameter! This
may seem like a circular definition, and it would be, had any base class
members used the template argument in a computation. Since no data members of Counted
are dependent on T, the size of Counted (which is zero!) is known
when the template is parsed. So it doesn t matter which argument is used to
instantiate Counted because the size is always the same. Any derivation
from an instance of Counted can be completed when it is parsed, and
there is no recursion. Since each base class is unique, it has its own static
data, thus constituting a handy technique for adding counting to any class
whatsoever. Jim Coplien was the first to mention this interesting derivation
idiom in print, which he cited in an article, entitled Curiously Recurring
Template Patterns.
Thinking in C++ Vol 2 - Practical Programming |
Prev |
Home |
Next |