ATL-style virtual function implementation

WTL for MFC Programmers, Part I - ATL GUI Classes
ATL-style templates

Even if you can read C++ templates without getting a headache, there are two things ATL does that might trip you up at first. Take this class for example:

class CMyWnd : public CWindowImpl<CMyWnd>
{

};

That actually is legal, because the C++ spec says that immediately after the class CMyWnd part, the name CMyWnd is defined and can be used in the inheritance list. The reason for having the class name as a template parameter is so ATL can do the second tricky thing, compile-time virtual function calls.

To see this in action, look at this set of classes:

Collapsetemplate <class T>
class B1
{
public:
void SayHi()
{
T pT = static_cast<T>(this); // HUH?? I’ll explain this below

pT->PrintClassName();
}

void PrintClassName() { cout << "This is B1"; }
};

class D1 : public B1<D1>
{
// No overridden functions at all
};

class D2 : public B1<D2>
{
void PrintClassName() { cout << "This is D2"; }
};

int main()
{
D1 d1;
D2 d2;

d1.SayHi(); // prints "This is B1"
d2.SayHi(); // prints "This is D2"

return 0;
}

The static_cast<T>(this) is the trick here. It casts this, which is of type B1, to either D1 or D2 depending on which specialization is being invoked. Because template code is generated at compile-time, this cast is guaranteed to be safe, as long as the inheritance list is written correctly. (If you wrote class D3 : public B1<D2> you’d be in trouble.) It’s safe because the this object can only be of type D1 or D2 (as appropriate), and nothing else. Notice that this is almost exactly like normal C++ polymorphism, except that the SayHi() method isn’t virtual.

To explain how this works, let’s look at each call to SayHi(). In the first call, the specialization B1<D1> is being used, so the SayHi() code expands to:

void B1<D1>::SayHi()
{
D1 pT = static_cast<D1>(this);

pT->PrintClassName();
}

Since D1 does not override PrintClassName(), D1‘s base classes are searched. B1 has a PrintClassName() method, so that is the one called.

Now, take the second call to SayHi(). This time, it’s using the specialization B1<D2>, and SayHi() expands to:

void B1<D2>::SayHi()
{
D2 pT = static_cast<D2>(this);

pT->PrintClassName();
}

This time, D2 does contain a PrintClassName() method, so that is the one that gets called.

The benefits of this technique are:

  • It doesn’t require using pointers to objects.
  • It saves memory because there is no need for vtbls.
  • It’s impossible to call a virtual function through a null pointer at runtime because of an uninitialized vtbl.
  • All function calls are resolved at compile time, so they can be optimized.