📄 mi24.htm
字号:
<P><A NAME="dingp10"></A>
Because you need only one copy of a class's vtbl in your programs, compilers must address a tricky problem: where to put it. Most programs and libraries are created by linking together many object files, but each object file is generated independently of the others. Which object file should contain the vtbl for any given class? You might think to put it in the object file containing <CODE>main</CODE>, but libraries have no <CODE>main</CODE>, and at any rate the source file containing <CODE>main</CODE> may make no mention of many of the classes requiring vtbls. How could compilers then know which vtbls they were supposed to <NOBR>create?<SCRIPT>create_link(10);</SCRIPT>
</NOBR></P><A NAME="62745"></A>
<P><A NAME="dingp11"></A>
A different strategy must be adopted, and compiler vendors tend to fall into two camps. For vendors who provide an integrated environment containing both compiler and linker, a brute-force strategy is to generate a copy of the vtbl in each object file that might need it. The linker then strips out duplicate copies, leaving only a single instance of each vtbl in the final executable or <NOBR>library.<SCRIPT>create_link(11);</SCRIPT>
</NOBR></P><A NAME="62704"></A>
<P><A NAME="dingp12"></A>
A more common design is to employ a heuristic to determine which object file should contain the vtbl for a class. Usually this heuristic is as follows: a class's vtbl is generated in the object file containing the definition (i.e., the body) of the first non-inline non-pure virtual function in that class. Thus, the vtbl for class <CODE>C1</CODE> above would be placed in the object file containing the definition of <CODE>C1</CODE>::<CODE>~C1</CODE> (provided that function wasn't <CODE>inline</CODE>), and the vtbl for class <CODE>C2</CODE> would be placed in the object file containing the definition of <CODE>C2</CODE>::<CODE>~C2</CODE> (again, provided that function wasn't <CODE>inline</CODE>).<SCRIPT>create_link(12);</SCRIPT>
</P><A NAME="62777"></A>
<P><A NAME="dingp13"></A>
In practice, this heuristic works well, but you can get into trouble if you go overboard on declaring virtual functions <CODE>inline</CODE> (see <A HREF="../EC/EI33_FR.HTM#6729" TARGET="_top">Item E33</A>). If all virtual functions in a class are declared <CODE>inline</CODE>, the heuristic fails, and most heuristic-based implementations then generate a copy of the class's vtbl in <I>every object file</I> that uses it. In large systems, this can lead to programs containing hundreds or thousands of copies of a class's vtbl! Most compilers following this heuristic give you some way to control vtbl generation manually, but a better solution to this problem is to avoid declaring virtual functions <CODE>inline</CODE>. As we'll see below, there are <A NAME="p116"></A>good reasons why present compilers typically ignore the <CODE>inline</CODE> directive for virtual functions, <NOBR>anyway.<SCRIPT>create_link(13);</SCRIPT>
</NOBR></P><A NAME="42433"></A>
<P><A NAME="dingp14"></A>
Virtual tables are half the implementation machinery for virtual functions, but by themselves they are useless. They become useful only when there is some way of indicating which vtbl corresponds to each object, and it is the job of the virtual table pointer to establish that <NOBR>correspondence.<SCRIPT>create_link(14);</SCRIPT>
</NOBR></P><A NAME="42437"></A>
<P><A NAME="dingp15"></A>
Each object whose class declares virtual functions carries with it a hidden data member that points to the virtual table for that class. This hidden data member — the <I>vptr</I> — is added by compilers at a location in the object known only to the compilers. Conceptually, we can think of the layout of an object that has virtual functions as looking like <NOBR>this:<SCRIPT>create_link(15);</SCRIPT>
</NOBR></P>
<SPAN ID="Image3of1" STYLE="position: absolute; z-index:1; visibility: hidden"><IMG SRC="./IMAGES/GRAPHICS/DIAGRAMS/I_116A1" BORDER=0></SPAN>
<SPAN ID="Image3of2" STYLE="position: absolute; z-index:1; visibility: hidden"><IMG SRC="./IMAGES/GRAPHICS/DIAGRAMS/I_116A2.GIF" BORDER=0></SPAN>
<SPAN ID="Image3of3" STYLE="position: absolute; z-index:1; visibility: hidden"><IMG SRC="./IMAGES/GRAPHICS/DIAGRAMS/I_116A3.GIF" BORDER=0></SPAN>
<SPAN ID="Image3of4" STYLE="position: absolute; z-index:1; visibility: hidden"><IMG SRC="./IMAGES/GRAPHICS/DIAGRAMS/I_116A4.GIF" BORDER=0></SPAN>
<SPAN ID="Image3of5" STYLE="position: absolute; z-index:1; visibility: hidden"><IMG SRC="./IMAGES/GRAPHICS/DIAGRAMS/I_116A5.GIF" BORDER=0></SPAN>
<SPAN ID="Image3of6" STYLE="position: relative; z-index:1; visibility: hidden"><IMG SRC="./IMAGES/GRAPHICS/DIAGRAMS/I_116A5.GIF" BORDER=0></SPAN>
<A NAME="42458"></A>
<P><A NAME="dingp16"></A>
This picture shows the vptr at the end of the object, but don't be fooled: different compilers put them in different places. In the presence of inheritance, an object's vptr is often surrounded by data members. Multiple inheritance complicates this picture, but we'll deal with that a bit later. At this point, simply note the second cost of virtual functions: you have to pay for an extra pointer inside each object that is of a class containing virtual <NOBR>functions.<SCRIPT>create_link(16);</SCRIPT>
</NOBR></P><A NAME="43372"></A>
<P><A NAME="dingp17"></A>
If your objects are small, this can be a significant cost. If your objects contain, on average, four bytes of member data, for example, the addition of a vptr can <I>double</I> their size (assuming four bytes are devoted to the vptr). On systems with limited memory, this means the number of objects you can create is reduced. Even on systems with unconstrained memory, you may find that the performance of your software decreases, because larger objects mean fewer fit on each cache or virtual memory page, and that means your paging activity will probably <NOBR>increase.<SCRIPT>create_link(17);</SCRIPT>
</NOBR></P><A NAME="42484"></A>
<P><A NAME="dingp18"></A>
Suppose we have a program with several objects of types <CODE>C1</CODE> and <CODE>C2</CODE>. Given the relationships among objects, vptrs, and vtbls that we have <A NAME="p117"></A>just seen, we can envision the objects in our program like <NOBR>this:<SCRIPT>create_link(18);</SCRIPT>
</NOBR></P>
<SPAN ID="Image4of1" STYLE="position: absolute; z-index:1; visibility: hidden"><IMG SRC="./IMAGES/GRAPHICS/DIAGRAMS/I_117A1.GIF" BORDER=0></SPAN>
<SPAN ID="Image4of2" STYLE="position: absolute; z-index:1; visibility: hidden"><IMG SRC="./IMAGES/GRAPHICS/DIAGRAMS/I_117A2.GIF" BORDER=0></SPAN>
<SPAN ID="Image4of3" STYLE="position: absolute; z-index:1; visibility: hidden"><IMG SRC="./IMAGES/GRAPHICS/DIAGRAMS/I_117A3.GIF" BORDER=0></SPAN>
<SPAN ID="Image4of4" STYLE="position: absolute; z-index:1; visibility: hidden"><IMG SRC="./IMAGES/GRAPHICS/DIAGRAMS/I_117A4.GIF" BORDER=0></SPAN>
<SPAN ID="Image4of5" STYLE="position: absolute; z-index:1; visibility: hidden"><IMG SRC="./IMAGES/GRAPHICS/DIAGRAMS/I_117A5.GIF" BORDER=0></SPAN>
<SPAN ID="Image4of6" STYLE="position: relative; z-index:1; visibility: hidden"><IMG SRC="./IMAGES/GRAPHICS/DIAGRAMS/I_117A5.GIF" BORDER=0></SPAN>
<A NAME="42688"></A>
<P><A NAME="dingp19"></A>
Now consider this program <NOBR>fragment:<SCRIPT>create_link(19);</SCRIPT>
</NOBR></P>
<A NAME="42708"></A>
<UL><PRE>void makeACall(C1 *pC1)
{
pC1->f1();
}
</PRE>
</UL>
<A NAME="42712"></A>
<P><A NAME="dingp20"></A>
This is a call to the virtual function <CODE>f1</CODE> through the pointer <CODE>pC1</CODE>. By looking only at this code, there is no way to know which <CODE>f1</CODE> function — <CODE>C1</CODE>::<CODE>f1</CODE> or <CODE>C2</CODE>::<CODE>f1</CODE> — should be invoked, because <CODE>pC1</CODE> might point to a <CODE>C1</CODE> object or to a <CODE>C2</CODE> object. Your compilers must nevertheless generate code for the call to <CODE>f1</CODE> inside <CODE>makeACall</CODE>, and they must ensure that the correct function is called, no matter what <CODE>pC1</CODE> points to. They do this by generating code to do the <NOBR>following:<SCRIPT>create_link(20);</SCRIPT>
</NOBR></P>
<A NAME="42713"></A><OL TYPE="1"><A NAME="dingp21"></A><LI>Follow the object's vptr to its vtbl. This is a simple operation, because the compilers know where to look inside the object for the
vptr. (After all, they put it there.) As a result, this costs only an
offset adjustment (to get to the vptr) and a pointer indirection (to
get to the vtbl).<SCRIPT>create_link(21);</SCRIPT>
<A NAME="42714"></A><A NAME="dingp22"></A><LI>Find the pointer in the vtbl that corresponds to the function being called (<CODE>f1</CODE> in this example). This, too, is simple, because compilers assign each virtual function a unique index within the table. The cost of this step is just an offset into the vtbl array.<SCRIPT>create_link(22);</SCRIPT>
<A NAME="42723"></A><P><A NAME="dingp23"></A><LI>Invoke the function pointed to by the pointer located in step <a href="#42714">2</A>.<SCRIPT>create_link(23);</SCRIPT>
</OL>
<A NAME="42727"></A><A NAME="p118"></A>
<P><A NAME="dingp24"></A>
If we imagine that each object has a hidden member called <CODE>vptr</CODE> and that the vtbl index of function <CODE>f1</CODE> is <CODE>i</CODE>, the code generated for the <NOBR>statement<SCRIPT>create_link(24);</SCRIPT>
</NOBR></P>
<A NAME="42705"></A>
<UL><PRE>pC1->f1();</PRE>
</UL>
<A NAME="42735"></A>
<A NAME="dingp25"></A>
is<SCRIPT>create_link(25);</SCRIPT>
<A NAME="42736"></A>
<UL><PRE>(*pC1->vptr[i])(pC1); // call the function pointed to by the
// i-th entry in the vtbl pointed to
// by pC1->vptr; pC1 is passed to the
// function as the "this" pointer
</PRE>
</UL>
<A NAME="42481"></A>
<P><A NAME="dingp26"></A>
This is almost as efficient as a non-virtual function call: on most machines it executes only a few more instructions. The cost of calling a virtual function is thus basically the same as that of calling a function through a function pointer. Virtual functions <I>per se</I> are not usually a performance <NOBR>bottleneck.<SCRIPT>create_link(26);</SCRIPT>
</NOBR></P><A NAME="42745"></A>
<P><A NAME="dingp27"></A>
The real runtime cost of virtual functions has to do with their interaction with inlining. For all practical purposes, virtual functions aren't inlined. That's because "inline" means "during compilation, replace the call site with the body of the called function," but "virtual" means "wait until runtime to see which function is called." If your compilers don't know which function will be called at a particular call site, you can understand why they won't inline that function call. This is the third cost of virtual functions: you effectively give up inlining. (Virtual functions can be inlined when invoked through <I>objects</I>, but most virtual function calls are made through <I>pointers</I> or <I>references</I> to objects, and such calls are not inlined. Because such calls are the norm, virtual functions are effectively not <NOBR>inlined.)<SCRIPT>create_link(27);</SCRIPT>
</NOBR></P><A NAME="42768"></A>
<P><A NAME="dingp28"></A>
Everything we've seen so far applies to both single and multiple inheritance, but when multiple inheritance enters the picture, things get more complex (see <A HREF="../EC/EI43_FR.HTM#7778" TARGET="_top">Item E43</A>). There is no point in dwelling on details, but with multiple inheritance, offset calculations to find vptrs within objects become more complicated; there are multiple vptrs within a single object (one per base class); and special vtbls must be generated for base classes in addition to the stand-alone vtbls we have discussed. As a result, both the per-class and the per-object space overhead for virtual functions increases, and the runtime invocation cost grows slightly, <NOBR>too.<SCRIPT>create_link(28);</SCRIPT>
</NOBR></P><A NAME="10827"></A>
<P><A NAME="dingp29"></A>
Multiple inheritance often leads to the need for virtual base classes. Without virtual base classes, if a derived class has more than one inheritance path to a base class, the data members of that base class are replicated within each derived class object, one copy for each path between the derived class and the base class. Such replication is almost never what programmers want, and making base classes virtual eliminates the replication. Virtual base classes may incur a cost of their <A NAME="p119"></A>own, however, because implementations of virtual base classes often use pointers to virtual base class parts as the means for avoiding the replication, and one or more of those pointers may be stored inside your <NOBR>objects.<SCRIPT>create_link(29);</SCRIPT>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -