Introduction
One can enjoy the study of ATL if he/she is black belt in Templates. In this episode I will
try to explain some of the template techniques which ATL uses. I don't guarantee that you
will become a black belt in templates after reading this episode, but I am trying my best that
you will feel more comfortable with ATL source code after reading this.
Program 35
#include <iostream>
using namespace std;
template <typename T>
T Maximum(const T& a, const T& b) {
return a > b ? a : b;
}
int main() {
cout << Maximum(5, 10) << endl;
cout << Maximum('A', 'B') << endl;
return 0;
}
The output of this program is
10
B
Here we don't need to overload the function for int and char data type due to template
function. Here one thing is important that both parameter of the function should have the
same data type. But if we want to pass different data type then we have to tell the compiler
that which data type will be considered.
Program 36
#include <iostream>
using namespace std;
template <typename T>
T Maximum(const T& a, const T& b) {
return a > b ? a : b;
}
int main() {
cout << Maximum<int>(5, 'B') << endl;
cout << Maximum<char>(5, 'B') << endl;
return 0;
}
The output of this program is
66
B
We can make the class template too. Here is simplified version of template stack class.
Program 37
#include <iostream>
using namespace std;
template <typename T>
class Stack {
private:
T* m_pData;
int m_iTop;
public:
Stack(int p_iSize = 0) : m_iTop(0) {
m_pData = new T[p_iSize];
}
void Push(T p_iData) {
m_pData[m_iTop++] = p_iData;
}
T Pop() {
return m_pData[--m_iTop];
}
T Top() {
return m_pData[m_iTop];
}
~Stack() {
if (m_pData) {
delete [] m_pData;
}
}
private:
Stack(const Stack<T>&);
Stack<T>& operator = (const Stack<T>&);
};
int main() {
Stack<int> a(10);
a.Push(10);
a.Push(20);
a.Push(30);
cout << a.Pop() << endl;
cout << a.Pop() << endl;
cout << a.Pop() << endl;
return 0;
}
There isn't any robust error checking in this program, but the purpose of this program is
to show the usage of template not making a stack class used professionally.
The output of this program is
30
20
10
We can also pass the data type as a template argument and assign a default value to it.
Let's change program 36 little bit and pass the size of stack as a template parameter rather
than constructor parameter.
Program 38
#include <iostream>
using namespace std;
template <typename T, int iSize = 10>
class Stack {
private:
T m_pData[iSize];
int m_iTop;
public:
Stack() : m_iTop(0) {
}
void Push(T p_iData) {
m_pData[m_iTop++] = p_iData;
}
T Pop() {
return m_pData[--m_iTop];
}
T Top() {
return m_pData[m_iTop];
}
private:
Stack(const Stack<T>&);
Stack<T>& operator = (const Stack<T>&);
};
int main() {
Stack<int, 10> a;
a.Push(10);
a.Push(20);
a.Push(30);
cout << a.Pop() << endl;
cout << a.Pop() << endl;
cout << a.Pop() << endl;
return 0;
}
The output of this program is same as pervious program. The important part of this program is.
template <typename T, int iSize = 10>
Now there is a question which approach is better? Passing template parameter is always
faster then passing parameter by constructor. Why? Because when you pass stack size in
template parameter then the array of given data type is created automatic (i.e. created on
stack), however passing parameter by constructor means constructor has to allocate memory
during run time by new or malloc family of functions. If we are sure that we are not going
to change the size of stack once we made this, as we have done in above program by making
copy constructor and assignment operator private, then use template parameter is better
approach.
You can also pass user define class as a parameter in place of type parameter, but be sure
that class have all the overloaded operators which is used in that template function or
template class.
E.g. take a look at Maximum function of program 35. This program use one operator >,
so if we pass our own class then that class must have overloaded > operator. Here is a
program which shows this.
Program 39
#include <iostream>
using namespace std;
template <typename T>
T Maximum(const T& a, const T& b) {
return a > b ? a : b;
}
class Point {
private:
int m_x, m_y;
public:
Point(int p_x = 0, int p_y = 0) : m_x(p_x), m_y(p_y) {
}
bool friend operator > (const Point& lhs, const Point& rhs) {
return lhs.m_x > rhs.m_x && lhs.m_y > rhs.m_y;
}
friend ostream& operator << (ostream& os, const Point& p) {
return os << "(" << p.m_x << ", " << p.m_y << ")";
}
};
int main() {
Point a(5, 10), b(15, 20);
cout << Maximum(a, b) << endl;
return 0;
}
The output of this program is
(15, 20)
We can also pass the template class as a template parameter. Let's make this Point class
template and pass it as a template parameter to Stack template class.
Program 40
#include <iostream>
using namespace std;
template <typename T>
class Point {
private:
T m_x, m_y;
public:
Point(T p_x = 0, T p_y = 0) : m_x(p_x), m_y(p_y) {
}
bool friend operator > (const Point<T>& lhs, const Point<T>& rhs) {
return lhs.m_x > rhs.m_x && lhs.m_y > rhs.m_y;
}
friend ostream& operator << (ostream& os, const Point<T>& p) {
return os << "(" << p.m_x << ", " << p.m_y << ")";
}
};
template <typename T, int iSize = 10>
class Stack {
private:
T m_pData[iSize];
int m_iTop;
public:
Stack() : m_iTop(0) {
}
void Push(T p_iData) {
m_pData[m_iTop++] = p_iData;
}
T Pop() {
return m_pData[--m_iTop];
}
T Top() {
return m_pData[m_iTop];
}
private:
Stack(const Stack<T>&);
Stack<T>& operator = (const Stack<T>&);
};
int main() {
Stack<Point<int> > st;
st.Push(Point<int>(5, 10));
st.Push(Point<int>(15, 20));
cout << st.Pop() << endl;
cout << st.Pop() << endl;
return 0;
}
The output of this program is
(15, 20)
(5, 10)
The most important part of this program is
Stack<Point<int> > st;
Here you have to pass the space between the two angle brackets, other wise compiler treat
it >> (shift right operator) and generate error.
There is one more thing we can do with this program. We can pass the default type value
of template parameter too. We can change
template <typename T, int iSize = 10>
to
template <typename T = int, int iSize = 10>
Now we don't have to pass the data type at the time of creating the object from Stack
class. But you have to write the blank angle brackets at the time of creating the object to
order the compiler to use default data type. You will create object something like this.
Stack<> st;
When you declare template class member function outside the class then you have to give
the complete name of template class with its Template parameter.
Program 41
#include <iostream>
using namespace std;
template <typename T>
class Point {
private:
T m_x, m_y;
public:
Point(T p_x = 0, T p_y = 0);
void Setxy(T p_x, T p_y);
T getX() const;
T getY() const;
friend ostream& operator << (ostream& os, const Point<T>& p) {
return os << "(" << p.m_x << ", " << p.m_y << ")";
}
};
template <typename T>
Point<T>::Point(T p_x, T p_y) : m_x(p_x), m_y(p_y) {
}
template <typename T>
void Point<T>::Setxy(T p_x, T p_y) {
m_x = p_x;
m_y = p_y;
}
template <typename T>
T Point<T>::getX() const {
return m_x;
}
template <typename T>
T Point<T>::getY() const {
return m_y;
}
int main() {
Point<int> p;
p.Setxy(20, 30);
cout << p << endl;
return 0;
}
The output of the program is
(20, 30)
Lets change program 35 little bit and pass string value rather than int or float and see
the result.
Program 42
#include <iostream>
using namespace std;
template <typename T>
T Maximum(T a, T b) {
return a > b ? a : b;
}
int main() {
cout << Maximum("Pakistan", "Karachi") << endl;
return 0;
}
The output of this program is Karachi. Why? Because here char* is passed as a template
parameter. Karachi is stored at higher memory location so the > operator just compare the
value of address rather then the string itself.
What should we do if we want the comparison on the basis of length of string not on the
basis of their address?
The solution is to specialized the template on char* data type. Here is an example
of template specialization.
Program 43
#include <iostream>
using namespace std;
template <typename T>
T Maximum(T a, T b) {
return a > b ? a : b;
}
template <>
char* Maximum(char* a, char* b) {
return strlen(a) > strlen(b) ? a : b;
}
int main() {
cout << Maximum("Pakistan", "Karachi") << endl;
return 0;
}
Classes can be specialized in the same way.
.
Program 44
#include <iostream>
using namespace std;
template <typename T>
class TestClass {
public:
void F(T pT) {
cout << "T version" << '\t';
cout << pT << endl;
}
};
template <>
class TestClass<int> {
public:
void F(int pT) {
cout << "int version" << '\t';
cout << pT << endl;
}
};
int main() {
TestClass<char> obj1;
TestClass<int> obj2;
obj1.F('A');
obj2.F(10);
return 0;
}
The output of this program is
T version A
int version 10
ATL has several classes which have specialized version like this, such as CComQIPtr
define in ATLBASE.H
Template can also be used in different design patter. E.g. Strategy design pattern can be implemented
by using template.
Program 45
#include <iostream>
using namespace std;
class Round1 {
public:
void Play() {
cout << "Round1::Play" << endl;
}
};
class Round2 {
public:
void Play() {
cout << "Round2::Play" << endl;
}
};
template <typename T>
class Strategy {
private:
T objT;
public:
void Play() {
objT.Play();
}
};
int main() {
Strategy<Round1> obj1;
Strategy<Round2> obj2;
obj1.Play();
obj2.Play();
return 0;
}
Here Round1 and Round2 are classes of different round of a game and Strategy class decide
what do to do depend on the template parameter pass to this class.
The output of the program is
Round1::Play
Round2::Play
ATL implement threading using Strategy design pattern.
Proxy design pattern can also be implemented using template. Smart pointer is an example
of proxy design pattern. Here is an example of simplified version of smart pointer without
using template.
Program 46
#include <iostream>
using namespace std;
class Inner {
public:
void Fun() {
cout << "Inner::Fun" << endl;
}
};
class Outer {
private:
Inner* m_pInner;
public:
Outer(Inner* p_pInner) : m_pInner(p_pInner) {
}
Inner* operator -> () {
return m_pInner;
}
};
int main() {
Inner objInner;
Outer objOuter(&objInner);
objOuter->Fun();
return 0;
}
The output of the program is
Inner::Fun()
For simplicity, we just overload the -> operator, but in real smart pointer all the
necessary operators such as =, ==, !, &, * are overloaded. There is one big problem with
this smart pointer; this can only contain pointer to Inner object. We can remove this
restriction by making OuterClass template. Let's change program little bit.
Program 47
#include <iostream>
using namespace std;
class Inner {
public:
void Fun() {
cout << "Inner::Fun" << endl;
}
};
template <typename T>
class Outer {
private:
T* m_pInner;
public:
Outer(T* p_pInner) : m_pInner(p_pInner) {
}
T* operator -> () {
return m_pInner;
}
};
int main() {
Inner objInner;
Outer<Inner> objOuter(&objInner);
objOuter->Fun();
return 0;
}
The output of the program is same as previous one but now OuterClass can contain any class
whose type is passed as a template parameter.
ATL has two smart pointer classes. CComPtr
and CComQIPtr
.
You can do some interesting work with the help of template. E.g. your class can be child of
different base class depend on different situation.
Program 48
#include <iostream>
using namespace std;
class Base1 {
public:
Base1() {
cout << "Base1::Base1" << endl;
}
};
class Base2 {
public:
Base2() {
cout << "Base2::Base2" << endl;
}
};
template <typename T>
class Drive : public T {
public:
Drive() {
cout << "Drive::Drive" << endl;
}
};
int main() {
Drive<Base1> obj1;
Drive<Base2> obj2;
return 0;
}
The output of this program is
Base1::Base1
Drive::Drive
Base2::Base2
Drive::Drive
Here the Drive class is inherited from Base1 and Base2 depend on the parameter passed to
the template at the time of creation of object.
ATL use this technique. When you make a COM component using ATL then CComObject
is inherit
from your class. Here ATL take advantage of template, because ATL doesn't know in advance
the name of the class which you create to make COM component. CComObject class is define in
ATLCOM.h file
We can simulate virtual functions too with the help of template. Let's recall virtual
function once again. Here is a simple program to recall the virtual function.
Program 49
#include <iostream>
using namespace std;
class Base {
public:
virtual void fun() {
cout << "Base::fun" << endl;
}
void doSomething() {
fun();
}
};
class Drive : public Base {
public:
void fun() {
cout << "Drive::fun" << endl;
}
};
int main() {
Drive obj;
obj.doSomething();
return 0;
}
The output of the program is
Drive::fun
We can get the same behavior with the help of template.
Program 50
#include <iostream>
using namespace std;
template <typename T>
class Base {
public:
void fun() {
cout << "Base::fun" << endl;
}
void doSomething() {
T* pT = static_cast<T*>(this);
pT->fun();
}
};
class Drive : public Base<Drive> {
public:
void fun() {
cout << "Drive::fun" << endl;
}
};
int main() {
Drive obj;
obj.doSomething();
return 0;
}
The output of the program is same as previous one. So we can simulate the behavior of
virtual function with the help of template.
The interesting parts of this program are
class Drive : public Base<Drive> {
This shows that we can pass the Drive class as a template parameter. The other interesting
part of the program is the doSomething
function of base class.
T* pT = static_cast<T*>(this);
pT->fun();
Here the base class pointer is converted into drive class pointer, because drive class is
passed as a template parameter of Base class. Then function is executed from that pointer,
Now that pointer points to drive class object, so the drive class object is called.
But there is very good question, why should we do this? And there is very good answer of
this question so save the extra byes of virtual pointer, virtual table and save the extra
time to call virtual function. This is the main philosophy of ATL to make component as small
as possible and as fast as possible.
Now there is one more question may arise in the mind. If due to this technique you can
simulate virtual function with less memory and faster then original virtual function then
why should we call virtual function? Shouldn't we replace all virtual function with this
technique? The short answer of this question is no, we can not replace all virtual function
with this technique.
There are some problems with this technique. First you can not further inherit any class
from Drive
class. If you try to do so then that function will no more act as a virtual
function. It doesn't happen in the case of virtual function; once you declare function
virtual then it becomes virtual in all of drive class no matter how deep in inheritance
chain. Let's take a look at a program what happen when we inherit one more class from
Drive.
Program 51
#include <iostream>
using namespace std;
template <typename T>
class Base {
public:
void fun() {
cout << "Base::fun" << endl;
}
void doSomething() {
T* pT = static_cast<T*>(this);
pT->fun();
}
};
class Drive : public Base<Drive> {
public:
void fun() {
cout << "Drive::fun" << endl;
}
};
class MostDrive : public Drive {
public:
void fun() {
cout << "MostDrive::fun" << endl;
}
};
int main() {
MostDrive obj;
obj.doSomething();
return 0;
}
The output of this is program as same as previous one. In case of virtual function the
output should be
MostDrive::fun
There is one more problem with this technique, when we want to take pointer of
Base
class
and want to store address of drive class.
Program 52
#include <iostream>
using namespace std;
template <typename T>
class Base {
public:
void fun() {
cout << "Base::fun" << endl;
}
void doSomething() {
T* pT = static_cast<T*>(this);
pT->fun();
}
};
class Drive : public Base<Drive> {
public:
void fun() {
cout << "Drive::fun" << endl;
}
};
int main() {
Base* pBase = NULL;
pBase = new Drive;
return 0;
}
This program gives an error, because we couldn't pass the template parameter of the base
class. Now change program little bit and pass the template parameter.
Program 53
#include <iostream>
using namespace std;
template <typename T>
class Base {
public:
void fun() {
cout << "Base::fun" << endl;
}
void doSomething() {
T* pT = static_cast<T*>(this);
pT->fun();
}
};
class Drive : public Base<Drive> {
public:
void fun() {
cout << "Drive::fun" << endl;
}
};
int main() {
Base<Drive>* pBase = NULL;
pBase = new Drive;
pBase->doSomething();
return 0;
}
Now this program works fine and gives the same output as we expected i.e.
Drive::fun
But there is a problem when you inherit more then one class from Base
class. To better
understand it, take a look at the following program.
Program 54
#include <iostream>
using namespace std;
template <typename T>
class Base {
public:
void fun() {
cout << "Base::fun" << endl;
}
void doSomething() {
T* pT = static_cast<T*>(this);
pT->fun();
}
};
class Drive1 : public Base<Drive1> {
public:
void fun() {
cout << "Drive1::fun" << endl;
}
};
class Drive2 : public Base<Drive2> {
public:
void fun() {
cout << "Drive2::fun" << endl;
}
};
int main() {
Base<Drive1>* pBase = NULL;
pBase = new Drive1;
pBase->doSomething();
delete pBase;
pBase = new Drive2;
pBase->doSomething();
return 0;
}
This program gives error at
pBase = new Drive2;
Because pBase
is a pointer to Base<Drive1>
not Base<Drive2>
. In short you can't make
pointer of Base
class and store address of different Drive class in it. In other words you
cant make an array of Base pointer and store address of different drive class in it, which
you can do in case of virtual function.
Hope to explore some other mysterious of ATL in next article.