Deep Exploration C ++ object model Reading Notes (3 ).
Test the following code in Visual C ++ 6.0:
#include "iostream"
using namespace std;
class X {};
class Y : public virtual X {};
class Z : public virtual X {};
class A : public Y,public Z {};
int main()
{
cout<<"sizeof(X): "<<sizeof(X)<<endl;
cout<<"sizeof(Y): "<<sizeof(Y)<<endl;
cout<<"sizeof(Z): "<<sizeof(Z)<<endl;
cout<<"sizeof(A): "<<sizeof(A)<<endl;
return 0;
}
The result may give you no clue.
sizeof(X): 1
sizeof(Y): 4
sizeof(Z): 4
sizeof(A): 8
The reasons are explained one by one below:
(1) for an empty class like Class X, the compiler inserts a char into the two objects of this class because they need to configure unique addresses in the memory. therefore, the size of Class X is 1.
(2) Because class y inherits from Class X in virtual mode, in derived class, it contains a pointer (4 bytes) pointing to visual base class subobject ), because different objects of this class need to be distinguished, the 1 bytes of virtual base class X subobject also appears in class Y (1 bytes). In addition, due to alignment restrictions, class y must be filled with 3 bytes (3 bytes). In this way, the size of class Y is 8.
It should be noted that since empty virtual base class has become a special term designed by C ++ Oo, it provides a virtual interface without defining any data. The Visual C ++ 6.0 Compiler regards an empty virtual base class as a part of the beginning of the derived class object. Therefore, the subsequent 1 bytes is saved, and there is no problem with the subsequent alignment, therefore, the actual execution result is 4.
Keywords: malloc wxWidgets OpenGL polymorphism doxygen Deep Exploration C ++ object model Reading Notes (3 ).
(3) No matter how many times it appears in the class inheritance system, a virtual base class subobject will only have one entity in the derived class. Therefore, the size of Class A is determined by the following: (a) the only class X entity shared by everyone (1 byte); (B) the size of base class Y, the size of "configured due to virtual base class X" is 4 bytes. the algorithms of Base Class Z are also the same. (8 bytes) (c) Number of alignment of classs A. The total number is 9 bytes and 3 bytes needs to be filled. The result is 12 bytes.
Considering the processing of empty virtual base class by Visual C ++ 6.0, the 1 byte of the Class X object will be removed, so the extra 3 bytes will not be enough, therefore, the actual execution result is 8.
Whether it is its own class or nonstatic data members inherited from virtual or nonvirtual base class, they are directly stored in each class object. Static data members is placed in a global data segment of the program, which does not affect the size of individual class objects and has only one entity.
* ** Data member binding ***
Early C ++'s two Defensive Programming styles:
(1) Place all data members at the beginning of the class declaration to ensure correct binding:
Class point3d
{
// Put all data member at the beginning of the class declaration
Float x, y, z;
Public:
Float X () const {return X ;}
//...
};
This style is designed to prevent the following phenomena:
typedef int length;
Class point3d
{
Public:
// Length is determined as global
// _ Val is determined as point3d: _ Val
Void mumble (length Val) {_ Val = val ;}
Length mumble ()... {return _ Val ;}
//...
PRIVATE:
// Length must be seen before "the first reference operation of this class on it"
// Such a statement will invalidate the previous reference operation
Typedef float length;
Length _ Val;
//...
};
Keywords: malloc wxWidgets OpenGL polymorphism doxygen Deep Exploration C ++ object model Reading Notes (3 ).
Because the names in the argument list of the member function will be properly resolved during their first encounter, the length type is determined to be global typedef in both member functions. When the nested typedef Statement of length appears later, C ++ standard marks the earlier binding as illegal.
(2) Put all inline functions, regardless of the size, outside the class declaration:
Class point3d
{
Public:
// Move all inlines out of class
Point3d ();
Float X () const;
Void X (float) const;
//...
};
inline float Point3d::X() const
{
return x;
}
The general idea of this style is that "an inline function entity will not be evaluated and evaluated before the entire class declaration is completely invisible." Even if you declare the inline function in the class declaration, the analysis of this member function starts only when the entire class declaration appears.
* ** Data member layout ***
The order of nonstatic data member in the same access section in the class object is the same as the declared order, while data members in multiple access sections can be freely arranged. (Although no compiler will do this currently)
The compiler may also synthesize some internally used data members (for example, vptr, which is inserted into every object containing the class of virtual function) to support the entire object model.
* ** Data member access ***
(1) static data members
Note the following:
(A) Each static data member has only one entity, which is stored in the data segment of the program. Each program uses static member, it is converted to a direct reference operation for the unique extern object internally.
Keywords: malloc wxWidgets OpenGL polymorphism doxygen Deep Exploration C ++ object model Reading Notes (3 ).
Point3d origin, *pt = &origin;
// origin.chunkSize = 250;
Point::chunkSize = 250;
// pt->chunkSize = 250;
Point3d::chunkSize = 250;
(B) If a static data member address is obtained, a pointer pointing to its data type is obtained, instead of a pointer pointing to its class member, because static member is not included in a class object.
& Point3d: chunksize will get the following type of Memory Address: const int *
(C) if there are two classes, each of which declares a static member freelist, the compiler uses name-mangling to encode each static data member to obtain a unique program recognition code.
(2) nonstatic data members uses two methods to access X coordinates, like this:
origin.x = 0.0;
pt->x = 0.0;
Is there a major difference between "access from origin" and "access from PT?
The answer is "When point3d is a derived class, but there is a virtual base class in its inheritance structure, and the member accessed (such as X in this example) is a member inherited from the virtual base class, there will be a major difference ". At this time, we cannot say which class Type PT must point to (so we do not know the true offset position of the member during compilation). Therefore, this access operation must be delayed until the execution period, it can be solved through an additional concise guide. However, if origin is used, there will be no such problems. The type is undoubtedly point3d class, and even if it inherits from virtual base class, the offset position of members will be fixed during the compilation period.
* ** Inheritance and data member ***
(1) As long as the inheritance does not require polymorphism (inheritance without polymorphism)
Let's start with a specific class:
Keywords: malloc wxWidgets OpenGL polymorphism doxygen Deep Exploration C ++ object model Reading Notes (3 ).
class Concrete{
public:
// ...
private:
int val;
char c1;
char c2;
char c3;
};
The size of each concrete class object is 8 bytes. The subdivision is as follows: (a) Val occupies 4 bytes; (B) C1, C2, and C3 each occupy 1 byte; (c) alignment requires 1 byte.
Now, after some analysis, we decided to adopt a more logical expression to split concrete into three layers:
class Concrete {
private:
int val;
char bit1;
};
class Concrete2 : public Concrete1 {
private:
char bit2;
};
class Concrete3 : public Concrete2 {
private:
char bit3;
};
Now the size of concrete3 object is 16 bytes. The subdivision is as follows: (a) concrete1 contains two members: Val and bit1, which add up to 5 bytes and fill 3 bytes, therefore, a concrete1 object actually uses 8 bytes; (B) it should be noted that bit2 of concrete2 is actually placed after the space is filled, so the size of a concrete2 object is changed to 12 bytes; (c) Likewise, the size of a concrete3 object is 16 bytes.
Why not adopt such a layout (INT occupies 4 bytes, bit1, bit2, bit3 each occupies 1 byte, fill 1 byte )?
The following is a simple example:
Concrete2 *pc2;
Concrete1 *pc1_1, *pc1_2;
Pc1_1 = PC2; // point pc1_1 to the concrete2 object
// Derived class subobject is overwritten
// So its bit2 member now has an unexpected Value
* Pc1_2 = * pc1_1;
Pc1_1 actually points to a concrete2 object, and the copied content is limited to its concrete subobject. If the derived class members and concrete1 subobject are bundled together to remove the space to fill the space, the above semantics will not be retained. When pc1_1 copies the content of its concrete1 subobject to pc1_2, it also copies its bit2 value to pc1_1.
Keywords: malloc wxWidgets OpenGL polymorphism doxygen Deep Exploration C ++ object model Reading Notes (3 ).
(2) adding Polymorphism)
To process 2D or 3D coordinate points in a multi-state manner, we need to provide the virtual function interface in the inheritance relationship. The modified class declaration is as follows:
Class point2d {
Public:
Point2d (float x = 0.0, float y = 0.0): _ x (x), _ y (y ){};
Virtual float Z ()... {return 0.0;} // it is reasonable that the Z of the 2D coordinate point is 0.0.
Virtual void operator + = (const point2d & RHs ){
_ X + = RHS. X ();
_ Y + = RHS. Y ();
}
Protected:
Float _ x, _ y;
};
The extra burden of virtual function on point2d:
(A) import a virtual table related to point2d to store the address of each virtual function declared by point2d;
(B) Import a vptr to each class object. (c) enhance constructor and destructor so that they can set and erase vptr.
Class point3d: Public point2d {
Public:
Point3d (float x = 0.0, float y = 0.0, float z = 0.0): point2d (x, y), _ z (z ){};
Float Z () {return _ z ;}
Void Z (float newz) {_ z = newz ;}
Void operator + = (const point2d & RHs) {// note that the parameter is point2d &, not point3d &
Point2d: Operator + = (RHs );
_ Z + = RHS. Z ();
}
Protected:
Float _ z;
};
Since then, you can apply operator + = To A point3d object and a point2d object.
(3) multiple inheritance)
See the following multi-inheritance relationships:
Class point2d {
Public:
//... // Has the Virtual Interface
Protected:
Float _ x, _ y;
};
class Point3d : public Point2d {
public:
// ...
protected:
float _z;
};
Class vertex {
Public:
//... // Has the Virtual Interface
Protected:
Vertex * next;
};
class Vertex3d : public Point3d,public Vertex {
public:
// ...
protected:
float mumble;
};
Keywords: malloc wxWidgets OpenGL polymorphism doxygen Deep Exploration C ++ object model Reading Notes (3 ).
For a multi-inheritance object, the address is assigned to the "pointer of the first base class", which is the same as that of a single inheritance, because both point to the same starting address, the cost is only the specified operation of the address. For the second or subsequent operation to specify the base class address, you must modify the address and add (or subtract, if downcast) The base class subobjects in the middle.
Vertex3d v3d;
Vertex3d *pv3d;
Vertex *pv;
Pv = & v3d;
// The previous row needs to be converted
Pv = (vertex *) (char *) & v3d) + sizeof (point3d ));
Pv = pv3d;
// The previous row needs to be converted
Pv = pv3d? (Vertex *) (char *) pv3d) + sizeof (point3d): 0; // prevent possible 0 values
(4) virtual inheritance)
If the class contains one or more virtual base class subobject, it is divided into two parts: one unchanged part and one shared part. Without changing the data in the local area, no matter how the subsequent evolution, there is always a fixed offset, so this part of data can be directly accessed. As for the shared part, this part of the data is represented by virtual base class subobject. Its location will change with each derivative operation, so they can only be indirectly accessed.
The following program snippets are used as an example:
void Point3d::operator+=(const Point3d& rhs)
{
_x += rhs._x;
_y += rhs._y;
_z += rhs._z;
}
There are three main strategies for indirect access:
(A) install some pointers in each derived class object. Each Pointer Points to a virtual base class. You can use related pointers to indirectly access the inherited virtual base class members.
As the virtual inheritance string chain gets longer, the level of indirect access increases. Ideally, however, we want to have a fixed access time, not because of the depth of virtual evolution. The specific method is to obtain all the nested virtual base class pointers through the copy operation and put them in the derived class object.
Keywords: malloc wxWidgets OpenGL polymorphism doxygen Deep Exploration C ++ object model Reading Notes (3 ).
// Under this policy, this program segment will be converted
Void point3d: Operator + = (const point3d & RHs)
{
_ Vbcpoint2d-> _ x + = RHS. _ vbcpoint2d-> _ x;
_ Vbcpoint2d-> _ y + = RHS. _ vbcpoint2d-> _ y;
_ Z + = RHS. _ z;
}
(B) Based on (a), in order to solve the problem that each object must carry an extra pointer to each virtual base class, the micorsoft compiler introduces the so-called virtual base class table.
If one or more virtual base classes exist for each class object, the compiler inserts a pointer pointing to the virtual base class table. in this way, you can ensure that the class object has a fixed burden, not because of the number of its virtual base classes.
(C) based on (a) and to solve (B) problems, the Foundation project places the offset of the virtual base class in the virtual function table.
The latest sun compiler adopts this index method. If it is positive, it indexes to virtual functions. If it is negative, it indexes to virtual base class offsets.
// Under this policy, this program segment will be converted
Void point3d: Operator + = (const point3d & RHs)
{
(This + _ vptr_point3d [-1])-> _ x + = (& RHS + RHS. _ vptr_point3d [-1])-> _ x;
(This + _ vptr_point3d [-1])-> _ y + = (& RHS + RHS. _ vptr_point3d [-1])-> _ y;
_ Z + = RHS. _ z;
}
Summary: in general, the most effective way to use virtual base class is an abstract virtual base class without any data members.
* ** Object member efficiency ***
Without the optimization switch on, it is difficult to guess the efficiency of a program, because the program code is potentially vulnerable to the odd line quirks related to a specific compiler. Since members is stored continuously in derived class object and its offset is known during the compilation period, a single inheritance will not affect the efficiency. This should be the same for multiple inheritance. The efficiency of virtual inheritance is disappointing.
Keywords: malloc wxWidgets OpenGL polymorphism doxygen Deep Exploration C ++ object model Reading Notes (3 ).
* ** Pointer to data members ***
If you get a data Member Address in the class, the actual offset of data member in the class object is added. 1. Why do you want to do this? It is mainly used to distinguish a pointer that does not point to any data member and a pointer that points to the first data member.
Consider the following example:
Float point3d: * P1 = 0;
Float point3d: * P2 = & point3d: X;
// Point3d: * indicates the pointer type of "pointing to point3d data member ".
if( p1 == p2) {
cout<<" p1 & p2 contain the same value ";
cout<<" they must address the same member "<<endl;
}
To distinguish P1 and P2, each real member offset value is added with 1. therefore, no matter whether the compiler or the user must remember that before using this value to indicate a member, please first subtract 1.
Correct differentiation & point3d: Z and & origin. z: Get the nonstatic data Member Address and get its offset in the class, get the data Member Address bound to the real class object and get the real address of the member in the memory.
Under Multi-inheritance) the combination of the base class pointer and a member bound to the derived class object will be complicated because the offset value needs to be added.
struct Base1 { int val1; };
struct Base2 { int val2; };
struct Derived : Base1, Base2 { ... };
Void func1 (INT derived: * DMP, derived * PD)
{
// The first parameter is expected to get a pointer "pointing to member of derived class"
// What if there is a pointer pointing to the member of the base class?
Pd-> * DMP;
}
Void func2 (derived * PD)
{
// BMP will become 1
Int base2: * BMP = & base2: val2;
// BMP = 1
// But in derived, val2 = 5
Func1 (BMP, PD );
}
That is to say, Pd-> * DMP will access base1: val1. To solve this problem, when BMP is used as the first parameter of func1, its value must be adjusted based on the base1 class involved:
// Internal conversion to prevent BMP = 0
Func1 (BMP? BMP + sizeof (base1): 0, PD );
Series of articles:
Deep Exploration C ++ object model Reading Notes (1)
Deep Exploration C ++ object model Reading Notes (2)
Deep Exploration C ++ object model Reading Notes (4)
Deep Exploration C ++ object model Reading Notes (5)
Deep Exploration C ++ object model Reading Notes (6)
Deep Exploration C ++ object model Reading Notes (7)
Last reading note of Deep Exploration C ++ Object Model