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:
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:
{
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:
{
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.