In-depth exploration of the C ++ object model: 4. Function semantics

Source: Internet
Author: User

Chapter 4: Function semantics
Nonstatic Member Functions (non-static Member function)

Point3d obj;

Point3d * ptr = & obj;

Point3d Point3d: normalize () const {

Register float mag = magniterator ();

Point3d normal;

Normal. _ x = _ x/mag;

Normal. _ y = _ y/mag;

Normal. _ z = _ z/mag;

Return normal;

}

Point3d: magnbench () const {

Return sqrt (_ x * _ x + _ y * _ y + _ z * _ z );

}
One of the design principles of C ++ is that nonstaticmember functions must have at least the same efficiency as general nonmember functions. This is because the compiler has converted the "member function instance" into a peering "nonmember function instance ".

For example, here is a nonmember definition of magnbench:

Float magnitude3d (constPoint3d * _ this) const {

Return sqrt (_ this-> _ x * _ this-> _ x +

_ This-> _ y * _ this-> _ y +

_ This-> _ z * _ this-> _ z );

}

Our member function version will be converted as follows:

1. Rewrite the signature of the function to insert an additional parameter (this pointer) to the member function. The result is as follows:

Point3d Point3d: magnitude (const Point3d * constthis)

There are two const because the first const indicates that the magnitude function has the const property, and the second const indicates that this is the const pointer.

2. Change the access to nonstatic data member to this pointer. The result is as follows:

{

Return sqrt (this-> _ x * this-> _ x +

This-> _ y * this-> _ y +

This-> _ z * this-> _ z );

}

3. rewrite the member function as an external function and process the function Name through magling (the so-called Name Mangling refers to adding the class Name and the signature of the member function to the member Name, make it a unique vocabulary in the program:

Extern magnitude_7Point3dFv (

Register Point3d * constthis );

Now this function has been replaced, and every call to it must be converted, as shown below:

Obj. magnitude () is changed to magnitude_7Point3dFv (& obj );

Ptr-> magnitude () becomes magnitude_7Point3dFv (ptr );

The normalize () function will be converted to the following form, assuming that NRV is feasible:

Void normalize_7Point3dFv (registerconst Point3d * constthis, Point3d * _ result ){

Register float mag = this-> magnister ();

_ Result. Point3d: Point3d ();

_ Result. _ x = this-> _ x/mag;

_ Result. _ y = this-> _ y/mag;

_ Result. _ z = this-> _ z/mag;

Return;

}

UE ue Member Function (Virtual Member Function)

If normalize () is a virtual member function, the following calls

Ptr-> normalize ();

Will be converted

(* Ptr-> vptr [1]) (ptr );

Where:

Vptr indicates the pointer generated together on the right, pointing to the virtual table.

1 is the index value of the virtual table slot and is associated with normalize ();

The second ptr indicates the this pointer.

But for the following calls:

Obj. normalize ();

The above called function instance can only be Point3d: normalize (), so the compiler will convert it as follows:

Normalize_7Point3dFv (& obj );

Like calling a virtual function through a class object above, such operations should always be determined by the compiler like nonstaticmember function.

Static Member Function)

If Point3d: normalize () is a static memberfunction, call the following two methods:

Obj. normalize ();

Ptr-> normalize ();

Will be converted into a general nonmember function call, like this:

Normalize_7Point3dSFv ();

Normalize_7Point3dSFv ();

There are two main methods to access static data member operations of class object independent of class object:

1. The solution in the program is to forcibly convert 0 to a class pointer, so a this pointer instance is provided, as shown in the following example:

Class Point3d {

Public:

Int object_count (){

Cout <x <endl;

Return x;

}

Private:

Static intconst x = 5;

};

Int main (){

(Point3d *) 0)-> object_count ();

Return 0;

}

Result output 5.

2. Introduce static member function to solve the problem at the language level. The main feature of Static member function is that it does not have the this pointer. The following secondary features are all rooted in the main features:

1) He cannot directly access nonstaticmembers in his class.

2) He cannot be declared as const, volatile, or virtual

3) it does not need to be called through class object-although it is called in most cases like this

Take the address of a static member function and obtain its location in the memory, that is, its address. Because static member function does not have the this pointer, its address type is a nonmember function pointer, not a pointer to class member function.

The Static member function lacks the this pointer, so it is almost equivalent to the nonmember function.

4.2 virtual member functions

General implementation model of Virtual function (non-inheritance): Each class has a virtual table containing the address of the virtual function in the class, and then each object has a vptr, indicates the existence of a virtual table. This section discusses three cases: single inheritance, multi-inheritance, and virtual inheritance.

Single inheritance:

During the compilation period: the address values of the virtual function will be prepared and stored in the virtual function table. The addresses in the table are fixed and the table size will not change; to locate the table, a pointer is inserted to the table for each class object.

During the execution period, you must activate the virtual function in a specific virtual table slot.

In a single inheritance, a class only has one virtual table. Each table contains the address of all the activate virtual function instances of its corresponding class object. These activevirtual functions include:

1) function instances defined by this class. It will rewrite a possible baseclass virtual function instance.

2) function instances inherited from base class

3) A pure_virtual_called () function instance.

Example:

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;

};

Shows its memory layout and virtual table:


<喎?http: www.bkjia.com kf ware vc " target="_blank" class="keylink"> VcD4KPHA + yOe5 + 9PQUG9pbnQyZMXJyfrX1FBvaW50yOfPwqO6PC9wPgo8cCBhbGlnbj0 = "left"> class Point2d: public Point {

Public:

Point2d (float x = 0.0, floaty = 0.0)

: Point (x), _ y (y ){}

~ Point2d ();

Point2d & mult (float );

Float y () const {return _ y ;}

Protected:

Float _ y;

};

There are three possibilities:

1) It may inherit the virtualfunction Instance declared by the base class, then the address of the function instance will be copied to the corresponding slot of the virtual table of the derived class.

2) It may use its own function instance, indicating that its own function instance must be placed in the corresponding slot.

3) it may add a new virtual function. At this time, the size of the virtual table will increase by a slot, and the new function instance will be put into the slot.

Shows the memory layout and virtual table of Point2d.

In a similar case, Point3d is derived from Point2d.

Class Point3d: public Point2d {

Public:

Point3d (float x = 0.0, floaty = 0.0, float z = 0.0)

: Point2d (x, y), _ z (z ){}

~ Point3d ();

Point3d & mult (float );

Float z () const {return _ z ;}

Protected:

Float _ z;

};

Shows the memory layout and virtual table of Point3d.

If the following formula is available:

Ptr-> z ();

Generally, every time you call z (), you do not know the real type of the object indicated by ptr, but you can know that the virtual table of the object can be accessed through ptr. you can know that every z () the function address is stored in slot 4. Therefore, the compiler can convert the call:

(* Ptr-> vptr [4]) (ptr );

In this conversion, the only thing that can be known during the execution period is: Which z () function instance slot 4 refers.

Virtual functions under multiple inheritance

The following multi-inheritance is known:

Class Base1 {

Public:

Base1 ();

Virtual voidspeakClearly (){};

Virtual Base1 * clone () const {};

Protected:

Float data_Base1;

};

Class Base2 {

Public:

Base2 ();

Virtual void mumble (){};

Virtual Base2 * clone () const {};

Protected:

Float data_Base2;

};

Class Derived: public Base1, public Base2 {

Public:

Derived (){};

Virtual Derived * clone () const {};

Protected:

Float data_Derived;

};

The difficulty of "Derived supports virtual functions" falls on Base2 subobject. Three problems need to be solved.

1) virtual destructor

2) inherited Base2: mumble ()

3) A group of clone () function instances

First, we specify the address of a configured Derived object from heap to a Base2 pointer:

Base2 * pbase2 = new Derived;

The address of the new Derived object must be adjusted to point to its Base2 subobject. The following code is generated during compilation:

Derived * temp = new Derived;

Base2 * pbase2 = temp? Temp + sizeof (Base1): 0;

Without such adjustments, any "non-polymorphism Application" (as shown below) of the pointer will fail:

// Even if pbase2 is specified with a Derived object, this should be fine.

Pbase2-> data_Base2;

When the programmer wants to delete the objects referred to by pbase2:

// The correct virtual destructor function entity must be called first

// Then execute the delete Operator

// Pbase2 may need to be adjusted 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 (presumably it also points to the Derived object ). However, the preceding offset addition cannot be set directly during the compilation period, because the real object pointed to by pbase2 can be determined only during the execution period.

The general rule is to call the derived class virtual function by pointing to the second or subsequent base class pointer (or reference.

// The example below is the same

Base2 * pbase2 = new Derived;

...

Delete pbase2;

The "necessary this Pointer Adjustment" Operation associated with this call operation must be completed at the executor. That is to say, the size of the offset and the code that adds the offset to the header of this pointer must be inserted by the compiler somewhere. The problem is, where is it?

In the original implementation of Bjame and the cfront compiler, the method is to increase virtualtable so that it can accommodate the this pointer required here and adjust related things. Each virtual table slot is no longer just a pointer, but a collection, containing possible offset and address. Therefore, the call operation of virtual function is as follows:

(* Pbase2-> vptr [1]) (pbase2 );

Changed:

(* Pbase2-> vptr [1]. faddr)

(Pbase2 + pbase2-> vptr [1]. offset );

Faddr contains the virtualfunction address, and offset contains the this governing adjustment value.

The disadvantage of this practice is that it is equivalent to penalizing all virtual function call operations, whether or not they need offset adjustment.

A more effective method is to use the so-called thunk.

The so-called thunk is a small assembly code, used to (1) adjust this pointer with an appropriate offset value, (2) Jump to the virtual function. For example, if a Base2 rule calls Derived destructor, the related thunk may look like this:

Pbase2_dtor_thunk:

This + = sizeof (base1); // It is always this-= sizeof (base1 )???

Derived ::~ Derived (this );

Thunk technology allows the virtual table slot to contain a simple pointer, so multi-inheritance does not require any extra space. The address in the Slots can direct directly to the virtual function, or to a related thunk (if you need to adjust this pointer ). Therefore, virtual functions that do not need to adjust this pointer do not need to carry extra load on the efficiency.

The second extra burden of adjusting this pointer is because of two different possibilities: (1) calling through derived class (or the first base class), (2) by calling the second (or its successor) base class, the same function may need more slots in the virtual table. For example:

Base1 * pbase1 = new Derived;

Base2 * pbase2 = new Derived;

Delete pbase1;

Delete pbase2;

Although two delete operations lead to the same Derived destructor, they need two different virtual tableslots:

1. pbase1 does not need to adjust this pointer (because Base1 is the leftmost base class, it has already pointed to the starting position of the Derived object ). Its virtual table slot must be placed with the real destructor address.

2. The this pointer needs to be adjusted for Pbase2. Its virtual table slot requires the corresponding thunk address.

Under Multi-inheritance, a derived class contains n-1 additional virtual tables, and n indicates the number of base classes on the previous layer (therefore, there will be no additional virtual tables for a single inheritance ). For Derived in this example, two virtual tables will be generated by the compiler:

1. A major entity, shared with Base1.

2. A secondary entity is related to Base2.

For each virtual tables, the Derived object has the corresponding vptr. Vptrs will be set to an initial value in constructor (s) (the code generated by the compiler ).

The traditional method to support "one class has multiple vritualtables" is to generate an object other than tables and give it a unique name. For example, the two tables associated with Derived may have the following names:

Vtbl _ Derived:

Vtbl _ Base2 _ Derived;

When you specify a Derived object address to a Base1 pointer or Derived pointer, the processed virtual table is the main table vtbl _ Derived. When you specify a Derived object address to a Base2 pointer, the processed virtual table is the secondary table vtbl_Base2_Derived.


Before the opening, we mentioned that there are three situations that will affect the support for virtual functions.

The first case is to call derived class virtualfunction through a pointer pointing to the second base class. For example:

Base2 * ptr = new Derived;

// Call Derived ::~ Derived

// Ptr must be adjusted backward by sizeof (Base1) bytes

Delete ptr;

From Figure 4.2, you can see the focus of this call operation: ptr points to the Base2 subobject In the Derived object; in order to be able to correctly execute, ptr must adjust the starting position of the Derived object.

The second case is the change in the first case. A pointer pointing to the derived class calls an inherited virtual function in the second base class. In this case, the derived class pointer must be adjusted again to point to the second base subobject. For example:

Derived * pder = new Derived;

// Call Base2: mumble ()

// Pder must be adjusted forward to the sizeof (Base1) bytes

Pder-> mmble ();

The third case occurs in the extended nature of a language. The type of the returned value of a virtual function may be base type or public derived type. This can be explained through the Derived: clone () function entity. The Clone function's Derived version returns a Derived class pointer, silently rewriting its two base class function entities. When we call clone () through the pointer pointing to the second base class, the offset problem of this pointer is born:

Base2 * pb1 = new Derived;

// Call Derived * Derived: clone ()

// The return value must be adjusted to point to Base2 subobject

Base2 * PBS = pb1-> clone ();

When pb1-> clone () is performed, pb1 is adjusted to the starting address of the Derived object, so the Derived version of clone () is called. It returns a pointer, point to a new Derived object. The address of this object must be adjusted before it is assigned to the master node of the master node.

Virtual functions under virtual inheritance

Consider the following virtual base class derivation system to derive Point3d from Point2d:

Class Point2d {

Public:

Point2d (float = 0.0, float = 0.0 );

Virtual ~ Point2d ();

Virtual void mumble ();

Virtual float z ();

//...

Protected:

Float _ x, _ y;

};

Class Point3d: publicvirtual Point2d {

Public:

Point3d (float = 0.0, float = 0.0, float = 0.0 );

~ Point3d ();

Float z ();

Protected:

Float _ z;

};


Although Point3d has the only baseclass, that is, Point2d, the starting part of Point3d and Point2d is not as consistent as that of "non-virtual single inheritance. Since the objects of Point2d and Point3d are no longer consistent, the conversion between the two needs to adjust the this pointer. Thunks should be cleared in the case of virtual inheritance, which has proved to be a difficult technique.

Pointer to Member Function

Take the address of a nonstatic memberfunction. If the function is nonvirtual, the result is the real address in the memory. However, this value is not complete. It also needs to be bound to the address of a class object to call functions through it. All nonstaticmember functions require the object address.

A pointer to member function. Its declaration syntax is as follows:

Double // function return type

(Point: * // class name,: symbol, and * symbol

Pmf) // pointer name

(); // List of function parameters

Then we can define and initialize the pointer as follows:

Double (Point: * coord) () = & Point: x;

You can also assign values:

Coord = & Point: y;

To call it, you can do this:

(Orgin. * coord )();

Ptr-> (* coord )();

These operations will be converted by the compiler as follows:

(Coord) (& origin );

(Coord) (ptr );

Supports pointer to virtual memberfunction

The polymorphism is still running, for example:

Float (Point: * pmf) () = & Point: z;

Point * ptr = new Point3d;

(Ptr-> * pmf) (); // The call is Point3d: z ()

However, for a virtual function, its address is unknown during the compilation period. All you can know is the index value of virtualfunction in its related virtual table. That is to say, getting the address of a virtual table function is just an index value. For example, suppose we have the following Point declaration

Class Point {

Public:

Virtual ~ Point ();

Float x ();

Float y ();

Virtual float z ();

}

Get the destructor address: & Point ::~ Point returns 1. Get the address of x () or y () to get the address of the function in the memory, because they are not virtual. The address of z () is 2. Calling z () through pmf will be converted as follows:

(* Ptr-> vptr [(int) pmf]) (ptr );

Therefore, the internal definition of pmf must be able to determine two different types, because the nonvirtual function pointer is the memory address and the virtual function pointer is the index value.

Under multiple inheritance: pointer to MemberFunction

To allow pointers to member functions to support multiple inheritance and virtual inheritance, stroustup designs the following struct:

Struct _ mptr {

Int delta;

Int index;

Union {

Ptrtofunc faddr;

Int v_offset;

};

};

Index and faddr hold virtualtable indexes and nonvirtual member function addresses respectively. In this model, call operations like this:

(Ptr-> * pmf )();

Will become:

(Pmf. index <0 )?

(* Pmf. faddr) (ptr ):

(* Ptr-> vptr [pmf. index] (ptr ));

Microsoft imports the so-called vcall thunk. In this policy, either the real member function address or the vcall thunk address is specified by faddr. Therefore, the call operations of virtual or nonvirtual functions are transparent. vcall thunk selects and calls the appropriate slots in the related virtual table.

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.