ATL Under the Hood Part 3

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 not Base. 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.

I hope to explore some other mysterious of ATL in next article.



About the Author

Zeeshan Amjad

C++ Developer at Bechtel Corporation. zamjad.wordpress.com

Comments

  • the difference between template technique and virtual function

    Posted by Legacy on 12/24/2002 12:00am

    Originally posted by: fgao8241

    zeeshan amjad,

    I like your attack on the difference between template technique and virtual function. I'd discussed it with my colleagues before and see something here, but still not clear yet. Here I have something to contribute and something to ask again.
    We know structure wise, template is more macro-like (based on symbolic substitution), and class is more function-like (stack, heap, etc). This may somehow explain why template can only inherit one level deep. I also think it is the VTable that plays a key role for virtual function inheritage to go as deep as will, in the cost of memory and speed. You can also see the difference between these two from another angle: from different OO programming languages, from different compilers, you can see pretty consistency for class implementation, but you don't see the beauty of consistency for template implementation. Even for the widely used STL, different vendors implement it differently in various C++ compilers. Different compilation models may cause confusion on when and how to instantiate template object if you need to reference a template class pointer.
    My understanding of template may be wrong. I wish you or anyone can correct me if I am wrong.

    Reply
  • Excellent!

    Posted by Legacy on 11/11/2002 12:00am

    Originally posted by: Ken

    Reply
  • ATL Under The Hood Part 3

    Posted by Legacy on 07/16/2002 12:00am

    Originally posted by: Adrian

    Anyone else having problems building example 53?
    
    Keep getting the following (VC6 / SP5):
    error C2955: 'Base' : use of class template requires template argument list. See declaration of 'Base'

    Reply
  • Splendid

    Posted by Legacy on 04/19/2002 12:00am

    Originally posted by: Liviu Morosan

    When will be the next!

    Reply
  • classic examples

    Posted by Legacy on 03/26/2002 12:00am

    Originally posted by: Ashok

    All Your examples are classic in Part 1,2,3

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: October 29, 2014 @ 11:00 a.m. ET / 8:00 a.m. PT Are you interested in building a cognitive application using the power of IBM Watson? Need a platform that provides speed and ease for rapidly deploying this application? Join Chris Madison, Watson Solution Architect, as he walks through the process of building a Watson powered application on IBM Bluemix. Chris will talk about the new Watson Services just released on IBM bluemix, but more importantly he will do a step by step cognitive …

  • Businesses are moving more and more of their customer transactions to the web. Security is understandably a top concern as online transactions increase, so it is important to make sure your electronic signature provider meets the highest security standards. That means more than simply passing a security audit or obtaining a certification. This white paper provides recommendations for taking a broader view of e-signature security, and answers key questions that help identify the security requirements against …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds