C + + object model--virtual Member Functions (virtual member function) (fourth chapter)

Source: Internet
Author: User

4.2 Virtual Member Functions (dummy member function)Have seen the general implementation model of virtual function: Each class has a virtual table containing the address of the function virtual function in the class, then each object has a vptr, pointing to Virtua where table is located.
in order to support the virtual function mechanism, you must first be able to have some form of "runtime type resolution" for polymorphic objects.In other words, the following function call operation will require some information about PTR during the execution period:
Ptr->z ();
This will enable you to find and invoke the appropriate entity for Z ().
Perhaps most straightforward, but the most expensive solution is to add the necessary information to PTR.Under such a strategy, a pointer (or a reference) contains two information:
1. The address of the object to which it is referenced (that is, what it contains now);
2. The address of an object type, or a structure (containing some information that is used to correctly resolution the Z () function instance)
This method brings two questions, first, it significantly increases the space burden, even if the program does not use polymorphism (polymorphism), and second, it interrupts the link compatibility with C programs.
If this additional information cannot be placed with the pointer,The next thing to consider is to put it on the object itself.But which object really needs this information? Should this information be placed on every polymer that might be inherited? But consider the following C struct declaration:
struct Date {int m, d, y;};
Strictly speaking, this conforms to the above specification, but in fact it does not require that information, plus that information will inflate the C struct and break the link compatibility without bringing any obvious compensation benefits.
It is possible to add additional execution period information only to those statements that explicitly use the class keyword, which preserves the compatibility of the language. But it's still not a smart strategy. For example, the following class conforms to the new specification:
Class Date {Public:int m, d, y;};
But in fact it does not need that information, the following class declaration, although not in line with the new specification, but need the information:
struct Geom {public:virtual ~geom ();};
Therefore, a better specification is needed, one that is "based on the use of class, rather than a class or struct (section 1.2)". If the class really needs that information, it will exist; if not, it does not exist. So, When exactly does this information need to be available? It is clear that some form of "runtime polymorphism" must be supported.
In C + +, polymorphic (polymorphism) means "a pointer (or reference) with a public base class addressing the meaning of a derived object". For example, the following declaration:
Point *ptr;
You can specify PTR to address a Point2d object:
ptr = new point2d;
Or a Point3D object:
ptr = new Point3D;
the polymorphic performance of PTR mainly plays a role of transport mechanism (transport mechanism), through which a set of public derived types can be adopted anywhere in the program. This polymorphic form is called negative (passive), The--virtual base class can be completed at compile time, except for the case.
when the indicated object is actually being used, polymorphism becomes positive (active).The following call to virtual function is an example:
Common examples of "active polymorphism (active polymorphism)" ptr->z ();
Before runtime type identification (RTTI) was introduced to the C + + language in 1993, C + + 's only support for active polymorphism (active polymorphism) is for virtual function Call Resolution (resolution) operation. With Rtti, you can query a polymorphic pointer or polymorphic reference during execution.
"Active polymorphic (active polymorphism)" in the second example if (Point3D *p3d = Dynamic_cast<point3d *> (PTR)) return p3d->_z;
So, the problem has been differentiated: to identify which classes exhibit polymorphic characteristics, additional execution period information is required . Keywords class and struct do not help. Because there are no new keywords such as polymorphism, so identifying whether a class supports polymorphism is the only appropriate way to see if it has any virtual function. As long as the class has a virtual function, it needs this additional execution period information.
The next obvious question is, What additional information is needed for storage?In other words, if there is a call like this:
Ptr->z ();
where Z () is a virtual function, then what information will make it possible to invoke the correct z () entity at execution time? Need to know:
The True type of the object that PTR refers to, which enables the selection of the correct z () entity
Z () entity position so that it can be called


On the implementation, First, add two members to each Polymorphic class object:
1. A string or number that represents the type of class.
2. A pointer to a table that contains the program's execution period address for the virtual functions.


How is the virtual functions address in the table constructed? in C + +, virtual functions (which can be called by its class object) can be learned at compile time, in addition, this set of addresses is fixed, The execution period cannot be added or replaced. Since the execution of the program, the size and content of the table will not change, so its construction and access can be fully controlled by the compiler, do not need any intervention in the execution period.
HoweverThe function address is prepared in the execution period, only half of the solution, the other half of the answer is to find those addresses , the following two steps to complete the task:
1. in order to find the table, each class object is inserted into a pointer generated internally by the compiler, pointing to the table.
2. in order to find the function address, each virtual function is assigned a table index value.


All of this work is done by the compiler, and the execution period is done by activating virtual function in a specific virtual table slot (the address that records the virtual function).
A class will have only one virtual table. Each table contains the addresses of all the active virtual functions function entities in its corresponding class object, which are active virtual Functions includes :
This class defines a function entity that overwrites (overriding) a potentially existing base class virtual function entity.
A function entity that inherits from base class, which occurs when derived class decides not to overwrite virtual function
A pure_virtual_called () function entity that can play both the space defender role of the pure virtual function and the execution-time exception handling function


Each virtual function is assigned a fixed index value that remains associated with a specific virtual function throughout the inheritance system. For example, in the Point class system:
Class Point {public:virtual ~point (), Virtual point &mult (float) = 0;float x () const {return _x;} Virtual float y () const {return 0;} Virtual float Z () const {return 0;} Protected:point (float x = 0.0); float _x;};
Virtual destructor is assigned slot 1, and mult () is assigned slot 2,This example does not have a function definition for mult () (because it is a pure virtual function), so the function address of pure_virtual_called () will be placed in slot 2If the function is accidentally called, the usual operation is to end the program, Y () is assigned slot 3 and Z () is assigned slot 4,What is the slot for x ()? The answer is no, because X () is not virtual function.

What happens when a class derives from point? for example, Class POINT2D:
Class Point2d:public Point {public:point2d (float x = 0.0, float y = 0.0): Point (X), _y (y) {}~point2d ();//Overwrite base class Virtual functionspoint2d& mult (float), float y () const {return _y;} Protected:float _y;};
there are altogether three possibilities:
1. It can inherit the function entity of virtual functions declared by base class, correctly, is that the address of the function entity is copied to the slot corresponding to the derived class virtual table.
2. It can use its own function entity, which means that its own function entity address must be placed in the corresponding slot
3. It can add a new virtual function. The size of the virtual table will increase by one slot, and the new function entity address will be put into the slot.

POINT2D's virtual table indicates the destructor in slot 1, while in slot 2 it points to mult () (Supersedes pure virtual function). Its own Y () function entity address is placed in slot 3, The z () function entity address that inherits from point is placed in slot 4.
In a similar case, Point3D derives from Point2d, as follows:
Class Point3d:public point2d {Public:point3d (float x = 0.0, float y = 0.0, float z = 0.0): point2d (x, y), _z (z) {}~poin T3d ();//rewritten base class virtual functionspoint3d& mult (float), float Z () const {return _z;} Protected:float _z;};
Where the slot 1 in virtual table is placed Point3D Destructor,slot 2 Place the Point3d::mult () function address, slot 3 places the Y () function address that inherits from Point2, slot 4 places its own Z () The function address.
As shown in 4.1 (taken from the original inside the C + + Object model):

Figure 4.1 The layout of virtual table: Single inheritance Casenow, there's the equation:
Ptr->z ();
So how do you have enough knowledge to set virtual function calls at compile time?
In general, you do not know the true type of the object that PTR refers to, but know that the virtual table of the object can be accessed via PTR.
Although it is not known which Z () function entity will be called, it is known that each z () function address is placed in slot 4.
This information allows the compiler to convert the call to:
(*ptr->vptr[4]) (PTR);
In this conversion, VPTR represents the pointer inserted by the compiler, pointing to Virtual table;4, which represents the slot number that the z () is assigned to (the virtual tabl associated to the point system). The only thing that can be done in the execution period is: slot What is the Z () function entity that 4 refers to?
In a single inheritance system, the virtual function mechanism behaves well, not only efficiently but also easily modeled, but in multiple inheritance and virtual inheritance, the support for virtual functions is not so good.

Virtual Functions under multiple inheritanceIn virtual functions supported in multiple inheritance, with the complexity of the second and subsequent base classes, and the "this pointer must be adjusted at execution time", take the following class system as an example:
class system to describe the complexity of supporting virtual function time in multiple inheritance (MI) class Base1 {public:base1 (); virtual ~base1 (); virtual void Speakclearly (); virtual Base1 *clone () const;protected:float data_base1;}; Class Base2 {public:base2 (); virtual ~base2 (); virtual void mumble (); virtual Base2 *clone () const;protected:float Data_ Base2;}; Class Derived:public Base1, public Base2 {public:derived (); virtual ~derived (); virtual Derived *clone () const;protected: float data_derived;};
the difficulty of "Derived support virtual Functions" falls on BASE2 subobject, and there are three problems to be solved, in this case, respectively:
(1) virtual destructor
(2) inherited base2::mumble ()
( 3) A set of Clone () function entities

First, the address of a derived object that is configured from the heap is assigned to a BASE2 pointer:
Base2 *pbase2 = new Derived;
The address of the new derived object must be adjusted to point to its Base2 subobject, which produces the following code at compile time:
Transfer to support a second base classderived *temp = new Derived; Base2 *pbase = temp? Temp + sizeof (BASE1): 0;
Without such an adjustment, any "non-polymorphic use" of the pointer will fail:
Even if PBASE2 is assigned a derived object, this should not be a problem pbase2->data_base2;
When the programmer wants to delete the object that Pbase2 refers to:
You must first invoke the correct virtual destructor function entity//And then execute the delete operator//PBASE2 may need to adjust to indicate the starting point of the complete object delete pbase2;
The pointer must be adjusted again to point to the beginning of the derived object again (presumably it also indicates the derived object). However, the above-mentioned offset addition cannot be set directly at compile time, because the real object that Pbase2 points to is determined only during the execution period.
The general rule is to invoke the derived class virtual function by pointing to the second or subsequent base class pointer (or reference).
Base2 *pbase2 = new derived;//... delete pbase2;
The "Necessary this pointer adjustment" operation, which is associated with the call operation, must be completed during the execution period. That is, the size of offset and the small piece of code that adds offset to the top of the this pointer must be inserted somewhere by the compiler. The question is, where is it?
Bjarne The method originally implemented in the Cfront compiler is to enlarge the virtual table so that it accommodates the this pointer required here and adjusts the related things. Each virtual table slot is no longer just a pointer, but a polymer, Contains the possible offset and address, so the call operation of virtual function consists of:
(*pbase2->vptr[1]) (PBASE2);
Change to:
(*PBASE2->VPTR[1].FADDR) (Pbase2 + pbase2->vptr[1].offset);
Where faddr contains the virtual function address, offset contains this pointer adjustment value.
The disadvantage of this approach is that it acts as a joint penalty for all virtual function invocation operations, regardless of whether they require an offset adjustment. So-called penalties, including additional access to offset and its addition, and the size change of each virtual table slot .
The additional burden of adjusting this pointer is due to two different possibilities: (1) called Via derived class (or the first base class), (2) via the second (or its successor) base class, the same function in virtual Multiple pens are required in table slots. For example:
Base1 *pbase1 = new Derived; Base2 *pbase2 = new Derived;delete pbase1;delete pbase2;
Although two delete operations result in the same derived destructor, they require two different virtual table slots.
1. PBASE1 does not need to adjust the this pointer (because BASE1 is the leftmost base class, it already points to the beginning of the derived object), and its virtual table slot needs to place the true destructor address.
2. Pbase2 need to adjust the this pointer, its virtual table slot requires the associated thunk address
Under multiple inheritance, a derived class contains n-1 additional virtual tables,n representing the number of base classes on its previous layer (therefore, there will be no additional virtual tables for single inheritance). For the dervied of this example, There will be two virtual tables generated by the compiler:
1. A primary entity that is shared with BASE1 (the leftmost base class).
2. A secondary entity, which is related to Base2 (the second base class).
For each virtual tables,derived object there is a corresponding vptr, Figure 4.2 (The book of the original 4.2) illustrates this point. As shown below, Vptrs will be set as the initial value in constructor.

Figure 4.2 The layout of virtual table: Multiple Inheritance casesThe traditional way to support "one class with multiple virtual table" is to generate each tables as an external object and give a unique name. For example, the two tables associated with derived may have such a name:
vtbl_derived;//Main Table vtbl_base2_derived//Secondary table
When a Derived object address is assigned to a BASE1 pointer or Derived pointer, the processed virtual table is the primary table vtbl_derived, and when a Derived object address is assigned to a BASE2 pointer, the processed Virtual table is the secondary table vtbl_base2_derived.
Because of the advent of the runtime linkers (which can support dynamic shared libraries), the link to symbolic names can become very slow. To adjust the efficiency of the execution-time linker, the Sun compiler chains multiple virtual tabls into one: A pointer to a secondary table, can be obtained by adding an offset to the primary table name. Under such a strategy, each class has only one named virtual table.
In three cases, the second or subsequent base class will affect the support of virtual functions, the first of which is to invoke the derived class virtual function through a pointer to the second base class. For example:
Base2 *ptr = new derived;//call derived::~derived//ptr must be adjusted backwards for sizeof (BASE1) bytesdelete ptr;
The focus of this invocation operation: ptr points to Base2 subobject in the derived object: to be able to execute correctly, PTR must adjust the start point to the derived object.
The second case is the change of the first case, which invokes an inherited virtual function in the second base class through a pointer to derived class. In this case, the derived class pointer must be adjusted again, To point to a second base subobject. For example:
Derived *pder = new derived;//call base2::mumble ()//Pder must be adjusted forward sizeof (BASE1) bytespder->mumble ();
The third case occurs in a language-augmented nature: Allows a change in the return type of a virtual function, either base type or publicly derived type, which can be obtained by derived clone () function entity to illustrate the derived version of the. Clone () function returns a derived class pointer, silently overwriting its two base class function entities. When Clone () is called with a pointer to a second base class, this The offset problem of the pointer was then born:
Base2 *PB1 = new derived;//call Derived *derived::clone ()//The return value must be adjusted to point to Base2 subobjectBase2 *PB2 = Pb1->clone ();
When Pb1->clone () is performed, the PB1 is adjusted to the starting address of the derived object, and the derived version of Clone () is called, which returns a pointer to a new derived object whose address is assigned to PB2 Must first be adjusted to point to Base2 subobject.
When the function is considered "small enough", the Sun compiler provides a so-called "split functions" technique: Two functions are produced with the same algorithm, with the second returning, the pointer is added with the necessary offset, The return value does not need to be adjusted either through the BASE1 pointer or the derived pointer, and another function is called by the Base2 pointer.
If the function is not small, the Split function policy gives one of the multiple entry points (entry points) in this function. Each entry point requires three instructions, but Mike Ball will find a way to remove this cost, and for OO inexperienced programmers, there may be doubts about this " The application of Split function, however, OO programmers try to "localize" operations using small-scale virtual function, typically the average size of virtual function is 8 rows.
If a function supports multiple entry points, it is not necessary to have a lot of "thunks". As IBM is to cuddle thunk in a truly called virtual function. The function begins with (1) adjusting the This pointer before (2) executing the function code written by the programmer; For function call operations that do not need to be adjusted, go directly to the section (2).
Microsoft replaces the thunk strategy with the so-called "address points", and the function that will be used to rewrite others (that is, overriding functions) expects to get the "class that introduced the virtual function" ( Instead of the derived class) address, which is the "address point" of the function.

Virtual Functions under fictitious inheritanceConsider the following virtual base class derivation system, derived from point2d Point3D:
Class Point2d {public:point2d (float = 0.0, float = 0.0); virtual ~point2d (); virtual void mumble (); virtual float Z ();p rotect Ed:float _x, _y;}; Class Point3d:public virtual point2d {Public:point3d (float = 0.0, float = 0.0, float = 0.0); ~point3d (); float Z ();p rotect Ed:float _z;};
Although Point3D has only one (and leftmost) base class, which is point2d, the starting part of Point3D and POINT2D is not as consistent as the "non-virtual single inheritance" scenario. This is shown in Figure 4.3 (as shown). Because the POINT2D and Point3D objects no longer match, the transition between the two also needs to adjust the this pointer. As for the elimination of thunks in the case of virtual inheritance, it is generally proven to be a highly difficult technology.

Figure 4.3 Virtual table layout: Dummy inheritance caseWhen a virtual base class is derived from another virtual base class, and both support virtual functions and nonstatic data members, the compiler has a virtual base C The support of lass is very complex. Best, do not declare nonstatic data members in a virtual base class.


Copyright NOTICE: This article for Bo Master original article, without Bo Master permission not reproduced.

C + + object model--virtual Member Functions (virtual member function) (fourth chapter)

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.