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

Preventing template code bloat

Whenever a class template is instantiated, the code from the class definition for the particular specialization is generated, along with all the member functions that are called in the program. Only the member functions that are called are generated. This is good, as you can see in the following program:

//: C05:DelayedInstantiation.cpp
// Member functions of class templates are not
// instantiated until they're needed.
 
class X {
public:
void f() {}
};
 
class Y {
public:
void g() {}
};
 
template<typename T> class Z {
T t;
public:
void a() { t.f(); }
void b() { t.g(); }
};
 
int main() {
Z<X> zx;
zx.a(); // Doesn't create Z<X>::b()
Z<Y> zy;
zy.b(); // Doesn't create Z<Y>::a()
} ///:~
 

Here, even though the template Z purports to use both f( ) and g( ) member functions of T, the fact that the program compiles shows you that it only generates Z<X>::a( ) when it is explicitly called for zx. (If Z<X>::b( ) were also generated at the same time, a compile-time error message would be generated because it would attempt to call X::g( ), which doesn t exist.) Similarly, the call to zy.b( ) doesn t generate Z<Y>::a( ). As a result, the Z template can be used with X and Y, whereas if all the member functions were generated when the class was first instantiated the use of many templates would become significantly limited.

Suppose you have a templatized Stack container and you use specializations for int, int*, and char*. Three versions of Stack code will be generated and linked as part of your program. One of the reasons for using a template in the first place is so you don t need to replicate code by hand; but code still gets replicated it s just the compiler that does it instead of you. You can factor the bulk of the implementation for storing pointer types into a single class by using a combination of full and partial specialization. The key is to fully specialize for void* and then derive all other pointer types from the void* implementation so the common code can be shared. The program below illustrates this technique:

//: C05:Nobloat.h
// Shares code for storing pointers in a Stack.
#ifndef NOBLOAT_H
#define NOBLOAT_H
#include <cassert>
#include <cstddef>
#include <cstring>
 
// The primary template
template<class T> class Stack {
T* data;
std::size_t count;
std::size_t capacity;
enum { INIT = 5 };
public:
Stack() {
count = 0;
capacity = INIT;
data = new T[INIT];
}
void push(const T& t) {
if(count == capacity) {
// Grow array store
std::size_t newCapacity = 2 * capacity;
T* newData = new T[newCapacity];
for(size_t i = 0; i < count; ++i)
newData[i] = data[i];
delete [] data;
data = newData;
capacity = newCapacity;
}
assert(count < capacity);
data[count++] = t;
}
void pop() {
assert(count > 0);
--count;
}
T top() const {
assert(count > 0);
return data[count-1];
}
std::size_t size() const { return count; }
};
 
// Full specialization for void*
template<> class Stack<void *> {
void** data;
std::size_t count;
std::size_t capacity;
enum { INIT = 5 };
public:
Stack() {
count = 0;
capacity = INIT;
data = new void*[INIT];
}
void push(void* const & t) {
if(count == capacity) {
std::size_t newCapacity = 2*capacity;
void** newData = new void*[newCapacity];
std::memcpy(newData, data, count*sizeof(void*));
delete [] data;
data = newData;
capacity = newCapacity;
}
assert(count < capacity);
data[count++] = t;
}
void pop() {
assert(count > 0);
--count;
}
void* top() const {
assert(count > 0);
return data[count-1];
}
std::size_t size() const { return count; }
};
 
// Partial specialization for other pointer types
template<class T> class Stack<T*> : private Stack<void *> {
typedef Stack<void *> Base;
public:
void push(T* const & t) { Base::push(t); }
void pop() {Base::pop();}
T* top() const { return static_cast<T*>(Base::top()); }
std::size_t size() { return Base::size(); }
};
#endif // NOBLOAT_H ///:~
 

This simple stack expands as it fills its capacity. The void* specialization stands out as a full specialization by virtue of the template<> prefix (that is, the template parameter list is empty). As mentioned earlier, it is necessary to implement all member functions in a class template specialization. The savings occurs with all other pointer types. The partial specialization for other pointer types derives from Stack<void*> privately, since we are merely using Stack<void*> for implementation purposes, and do not wish to expose any of its interface directly to the user. The member functions for each pointer instantiation are small forwarding functions to the corresponding functions in Stack<void*>. Hence, whenever a pointer type other than void* is instantiated, it is a fraction of the size it would have been had the primary template alone been used.[63] Here is a driver program:

//: C05:NobloatTest.cpp
#include <iostream>
#include <string>
#include "Nobloat.h"
using namespace std;
 
template<class StackType>
void emptyTheStack(StackType& stk) {
while(stk.size() > 0) {
cout << stk.top() << endl;
stk.pop();
}
}
 
// An overload for emptyTheStack (not a specialization!)
template<class T>
void emptyTheStack(Stack<T*>& stk) {
while(stk.size() > 0) {
cout << *stk.top() << endl;
stk.pop();
}
}
 
int main() {
Stack<int> s1;
s1.push(1);
s1.push(2);
emptyTheStack(s1);
Stack<int *> s2;
int i = 3;
int j = 4;
s2.push(&i);
s2.push(&j);
emptyTheStack(s2);
} ///:~
 

For convenience we include two emptyStack function templates. Since function templates don t support partial specialization, we provide overloaded templates. The second version of emptyStack is more specialized than the first, so it is chosen whenever pointer types are used. Three class templates are instantiated in this program: Stack<int>, Stack<void*>, and Stack<int*>. Stack<void*> is implicitly instantiated because Stack<int*> derives from it. A program using instantiations for many pointer types can produce substantial savings in code size over just using a single Stack template.

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

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