You can enjoy the study of ATL if you are a black belt in Template. In this episode I will try to explain some of the template techniques used by ATL. I don’t guarantee that you will become black belt in template after reading this episode, but I will try my best to help you feel more comfortable with ATL source code.
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 a template function. It is important that both parameter of the function have the same data type. If you want to pass different data types then you have to tell the compiler 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
You can make the class template too. Here is a simplified version of a 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. The purpose of this program is to show the usage of template, not to make a stack class to be 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 a 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>
Which approach is better? Passing a template parameter is always faster then passing a parameter by constructor. Why? When you pass stack size in template parameter the array of a given data type is created automatic (i.e. created on the stack), however passing parameter by constructor means the constructor has to allocate memory during run time by using the 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 uses 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 you don’t have to pass the data type at the time of creating the object from Stack class.You do 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)
Let’s 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;
}
Round1 and Round2 are classes of different rounds of a game. The Strategy class decides what to do depending 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 a smart pointer without
using a 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 uses 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.
Why should we do this? This is a very good question which has a very good answer. To save the extra bytes of a 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
{
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
I hope to explore some other mysterious of ATL in next article.