C++ Q&A;: ATL Virtual Functions and vtables

We were unable to locate this content in de-de.

Here is the same content in en-us.

This article may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist. To maintain the flow of the article, we've left these URLs in the text, but disabled the links.
MIND

ATL Virtual Functions and vtables

Paul DiLascia

Code for this article: Mar00CQ&A.exe

Q I've noticed that many of the classes in the ActiveX® Template Library (ATL) are declared with ATL_NO_VTABLE, which expands to __declspec(novtable).

template <class T>
class ATL_NO_VTABLE IPersistImpl : public IPersist
{
���
};
The documentation for __declspec(novtable) says that this prevents the vtable pointer from being initialized in the class's constructor and destructor, but I don't really understand what that means and why it's important. Can you explain?
Raman Bhagwati

A Well, I can try. As with anything, to understand what __declspec(novtable) is all about, you have to understand the problem it was invented to solve. For that you need to review how virtual functions work. Figure 1 shows a demented C++ program with a class A that has a constructor, destructor, and three virtual functions: func1, func2, and func3. Figure 2 shows the assembly code generated when you compile something like foo1 using -FAs. I've annotated the code to highlight the important parts. (I know, you hate assembly code. Me too.)
      As you all know, virtual functions work through something called the virtual function tableâ€"vtable for short, or vtbl if you're feeling really terse. The vtable is just a table of pointers to functions. In this case, the compiler generates the vtable as the pronunciation-defying variable ??_7A@@6B@.

; This is the vtable for class A.
; It has three members: func1, func2, func3.
; Isn't name mangling fun?
CONST SEGMENT
??_7A@@6B@
   DD  FLAT:?func1@A@@UAEXXZ
   DD  FLAT:?func2@A@@UAEXXZ
   DD  FLAT:?func3@A@@UAEXXZ
CONST ENDS
      Each C++ object stores a pointer to its vtable as the first data item in the objectâ€"that is, at offset zero within the storage representing an instance of A. So when your code calls something like pa->func3, the compiler generates code that calls through the vtable:
; A* pa;
; pa->func3();
mov eax, DWORD PTR _pa$[ebp]
mov edx, DWORD PTR [eax]
mov ecx, DWORD PTR _pa$[ebp]
call  DWORD PTR [edx+8]
      You don't have to be an assembler jock to get the basic idea that this code calls the function whose address is at offset 8 in the vtable. (Each pointer is 4 bytes, so the third pointer, func3, is at offset 8.) This is what makes virtual functions virtual: the call is indirect. C++ jocks say the function is bound at runtime, or late-bound (as a opposed to compile time, which is early-bound). If some other A-derived object has a different vtable, the previous code calls the function in that vtable, as my next example shows.
      In foo2.cpp (see Figure 3), I've added class A1, derived from A. It has overrides for each of the three virtual functions (func1, func2, and func3). Now when main calls pa->func3, the vtable points to the functions for A1 so they're the ones called. The calling code is identical in both cases (foo1.cpp and foo2.cpp); the difference is the vtable. But where does the vtable get initialized? That's the million-dollar question. And the final answer is... as you'd expect. It happens in the constructor. If you look at the assembly code for foo2 (the same as that shown in Figure 2), you'll see that the constructor for A initializes the vtable to A's vtable.
??0A@@QAE@XZ PROC NEAR ; A::A
push  ebp
mov ebp, esp
push  ecx
mov DWORD PTR _this$[ebp], ecx
mov eax, DWORD PTR _this$[ebp]
;;; *** here it is: initialize A's vtable:
mov DWORD PTR [eax], OFFSET FLAT:??_7A@@6B@
���
??0A@@QAE@XZ ENDP ; A::A
The first few lines are standard prolog stuff. The last line sets the vtable ([eax] = offset zero of "this") to ??_7A@@6B@, the vtable for A. In pseudo C++, it looks like this:
A:A()
{
    *this = ??_7A@@6B@;
    // your code
}
      Likewise, A1's constructor initializes the vtable to its vtable. But it does this only after calling A::A! Don't forget that the first thing each derived class constructor does is call the constructor for its base class. That's how all the base classes get initialized. So first A::A sets the vtable to A's vtable, then A1::A1 sets it to A1's vtable. Here's the entire sequence of events when you construct an instance of class A1:
  1. A1::A1 calls A::A
  2. A::A sets vtable to A's vtable
  3. A::A executes and returns
  4. A1::A1 sets vtable to A1's vtable
  5. A1::A1 executes and returns
      If your constructor calls a virtual function (in step 3), A's functions will be called, not A1's, because A is "not an A1 yet." This makes sense; you wouldn't want to call an A1 function before A1 is initializedâ€"that is, before A1's constructor has executedâ€"would you? But while it's logical, this behavior can cause bugs. As a general rule, you should never call a virtual function from a constructor or destructor unless you intend the call to be non-virtual.
      A similar thing happens with the vtables during destruction. Before your destruction code executes, the compiler-generated code sets the vtable to the vtable for the class to which the destructor belongs (that is, A::~A sets the vtable to A's vtable and A1::~A1 sets the vtable to A1's vtable). Your code executes, then the compiler-generated code calls the base class destructor. Once again, virtual function calls inside a destructor behave as if they were static. Again, this makes sense because once a destructor has finished, that object doesn't exist any longer, and you wouldn't want to call a derived virtual function after that object has been destroyed. So the first thing each destructor does is clobber one level of derivedness by setting the vtable to its own.
      The upshot is that construction and destruction are not instantaneous, but take place in time. As an object is constructed, it passes through each of its base class stages like a person passing from infancy to adulthood. When the object is destroyed, it regresses the other way, like going from adulthood to senility.
      What does all this have to do with __declspec(novtable)? Well, take a look at Figure 4. Now I've made the virtual functions in A pure by adding =0 in the class declaration. A is now an abstract class. It has no concrete virtual functions; it only declares them. Since A is abstract, you can't create instances of A; it can only be used to derive other classes.
      Nevertheless, if you compile foo3.cpp with -FAs (I'll spare you the code), you'll see that the compiler still generates a vtableâ€"all of whose entries are NULLâ€"and still generates code to initialize the vtable in the constructor or destructor for A. Unlike the compiler, a human looking at this situation is smart enough to realize that since no instance of A can ever be created, and since no virtual function of A can ever be called (attempting to call a NULL function is guaranteed to produce a serious booboo), there's no need for all this code for the vtable itself or the code to initialize it.
      Big deal. What's a few extra assembler lines in an era of 100MB apps? I assure you the bloat from demented programming practices in a typical Windows-based application dwarfs by orders of magnitude the waste of an extra six lines of assembly code. And besides, this abstract class situation is pretty rare and contrived.
      Or is it?
      It may have once been rare, but these days abstract classes are not just common, they're ubiquitous. Think: where in Windows® do abstract classes appear over and over again? That's right, in COM! A COM interface is an abstract class with only pure virtual functions. As everything in Windows migrates to COM land, Windows-based programs have COM interfaces up the wazoo. A typical COM class might implement a dozen or more interfaces, each with several functions.
      Even outside COM, the notion of an interface is quite powerful and useful, as in the Java language. Each interface implementation might use several layers of classes, none intended to be used by themselves, but only as base classes for yet more classes. ATL provides many such classes using templates, another source of class proliferation. All of this adds up to lots of initialization code and useless vtables with NULL entries. The total bloat can become significant, especially when you're developing small objects that must load over a slow medium like the Internet.
      So __declspec(novtable) was invented to solve the problem. It's a Microsoft-specific optimization hint that tells the compiler: this class is never used by itself, but only as a base class for other classes, so don't bother with all that vtable stuff, thank you. In my next foo program (see Figure 5), I've added __declspec(novtable) to the declaration for A. If you compile foo4.cpp with -FAs and look at the assembly code (go ahead, I dare you), you'll see that the compiler generates no vtable and no vtable initialization code. Amazing.
      Both ATL and MFC use __declspec(novtable), as does Windows itself. The COM interfaces themselvesâ€"IPersist, IMoniker, and IWhatHaveYouâ€"are all declared with __declspec(novtable), though it's a little hard to see it because the declaration is buried in a MIDL_INTERFACE macro. Since __declspec(novtable) is an innovation of Visual C++® 5.0, macros are typically used to employ __declspec(novtable) only when _MSC_VER >= 1100.
      COM interfaces are abstract classes that have no concrete virtual functions, but you can use __declspec(novtable) for abstract classes that do have some virtual functions. You can even use it for nonabstract classes! If you know your class is never used except as a base class, and if you don't call any virtual functions from your constructor or destructor (which you should never do anyway), you can use __declspec(novtable) to save space. I would encourage you, however, to make such a class abstract by giving it at least one pure virtual function, or else give it a protected constructor so programmers will get a compiler warning if they attempt to create instances of your class.
      In my final foo program, foo5.cpp (see Figure 6), I changed things yet again so all the virtual functions are implemented in A:
class __declspec(novtable) A {
   virtual void func1() { cout<<1; }
   virtual void func2() { cout<<2; }
   virtual void func3() { cout<<3; }
 };

class A1 : public A {
    // no func1, func2, or func3
};
But I've still declared A with __declspec(novtable), so A has no vtable and no vtable initialization code. Everything works because I never create an A, and I don't call any virtual functions from A's constructor or destructor. The compiler initializes A1's vtable with pointers to A's functions (see Figure 7), as well as the normal constructor/destructor code to initialize the vtable pointer.

Paul DiLascia is the author of Windows++: Writing Reusable Windows Code in C++ (Addison-Wesley, 1992) and a freelance consultant and writer-at-large. He can be reached at askpd@pobox.com or http://www.dilascia.com.

From the March 2000 issue of MSDN Magazine.

Page view tracker