C + + object model--object construction in the case of "No Inheritance" (fifth chapter)

Source: Internet
Author: User

5.2 Object construction under the inheritance systemWhen defining an object as follows:
T object;
When what is actually going to happen?If T has a constructor (either provided by the user or synthesized by the compiler), it will be called. It's obvious that the less obvious is that what does the constructor call really have to do with?
Constructor may have a large number of hidden codes, as the compiler expands each constructor, depending on the inheritance system of class T. in general, the compiler does the following expansion operations:
1. The data members initialization operation recorded in the member initialization list is placed in the constructor function itself, in order of the Members ' declaration order.
2. If a member does not appear in the member initialization list, but it has a default constructor, then the default constructor must be called.
3. Prior to that, if class object had virtual table pointers, they had to be set to an initial value, pointing to the appropriate virtual tables.
4 . Until then, all the previous base class constructors must be called in the order of the base class declaration (not associated with the order in the member initialization list):

ifThe base class is listed in the member initialization list, so any explicitly specified arguments should pass through.
ifThe base class is not listed in the member initialization list, and it has the default constructor (or default Memberwise copy constructor), then it is called.
ifBase class is the second or successor base class under multiple inheritance, the this pointer must be adjusted.
5. Until then, all virtual base class constructor must be called, from left to right, from deepest to lightest:
ifClass is listed in the Member initialization list, the past should be passed if there are any explicitly specified parameters. If it is not listed in the list, and the class has a default constructor, it should be called.
also, the offset of each virtual base class Subobject in class must be accessed within the execution period.
ifClass object is the lowest level (most-derived) class, its constructors may be called, and some mechanisms to support this behavior must be put in.

In this section, This paper discusses the necessity of constructors expansion from the perspective of "semantics guaranteed by C + + language to classes" .Take point again as an example and add a copy constructor to it, a copy operator, a
Virtual destructor is as follows: Class Point {public:point (float x = 0.0, float y = 0.0); Point (const point &);//Copy Constructorpoint &operator= (const point &);//Copy Assignment operatorvirtual ~p Oint ();//virtual destructorvirtual float Z () {return 0.0;} Protected:float _x, _y;};
Before you begin to introduce the inheritance system for point, take a look at the Declaration and expansion results of line class, which consists of two points of _begin and _end:
Class Line {point _begin, _end;public:line (float = 0.0, float = 0.0, float = 0.0, float = 0.0); Line (const point &, const point &);d Raw ();};
Each explicit constructor will be expanded to invoke the constructors of its two member class objects. If the definition is constructor as follows:
Line::line (const point &begin, Const point &end): _end (end), _begin (begin) {}
  It will be expanded by the compiler and converted to:
C + + Pseudocode: line Constructor's extended line *line::line (line *this, const point &begin, const point &end) {This->_begin. Point::P oint (begin); This->_end. Point::P oint (end); return this;}
Because point declares copy constructor, a copy operator, and a destructor (this example is virtual), the line class implicit copy constructor,copy Both operator and destructor will have a practical function (nontrivial).
When the programmer writes:
Line A;
When implicit line destructor will be synthesized.(if line is derived from point, the synthesized destructor will be virtual.) But since line is only a point objects rather than a point, So the synthesized destructor is just nontrivial). In which the destructor of its member class objects is called (in the reverse order in which it is constructed):
C + + pseudo code: The synthesized line destructorinline void Line::~line (line *this) {this->_end. Point::~point (); This->_begin. Point::~point ();}
  Of course if Point destructor is an inline function, then each invocation operation is extended at the calling locationNote that although point destructor is virtual, its invocation operation (in containing class destructor) is statically resolved (resolved statically).
Similar to the truth when a programmer writes down:
Line B = A;
When implicit line copy constructor will be synthesized to become an inline public member.
Finally, when the programmer writes:
A = b;
When implicit copy assignment operator will be synthesized to become an inline public member.
when you generate copy operator, you need to filter by using the following conditional statement:
if (this = = &RHS) return *this;
  forgetting to check whether a self-assigned (Assignment) operation failed in a copy operator supplied by a programmer is a mistake the novice is prone to fall into.For example:
User supplied copy assignment operator//forgot to provide a self copy when filtering String &string::operator= (const string &RHS) {//Here need to filter ( Before releasing the resource) delete []str;str = new Char[strlen (RHS.STR) + 1];}
  Such a warning Messageis helpful, "In a copy operator, there is a lack of a filtering operation in the face of self-copying, but there is a delete operator corresponding to a member operation."

Virtual Inheritance (Vsan inheritance)Consider the following virtual inheritance (inherited from Point)
Class Point3d:public virtual point {Public:point3d (float x = 0.0, float y = 0.0, float z = 0.0): Point (x, y), _z (z) {}po Int3d (const Point3D &RHS): Point (RHS), _z (rhs._z) {}~point3d (); Point3D &operator= (const Point3D &); virtual float Z () {return _z;} Protected:float _z;};
  The traditional "constructor expansion phenomenon" is not used because of the "sharing" of virtual base class:
C + + pseudo-code: illegal constructor expansion content Point3D *point3d::P Oint3d (Point3D *this, float x, float y, float z) {this->point::P oint ( x, y); This->__vptr_point3d = __vtbl_point3d;this->__vptr_point3d__point = __vtbl_point3d_point;this->_z = Rhs._z;return this;}
  What's wrong with the Point3D constructor extensions above?
Try to think of the following three kinds of derivation:
Class Vertex:virtual public Point {...}; Class Vertex3d:public Point3D, public Vertex {...}; Class Pvertex:public Vertex3d {...};
  Vertethe constructor of X must also call the constructor of point. However, when Point3D and vertex vertex3d subobjects, they constructor invocation operations must not occur, instead, as a bottom-level class,vertex3d has the responsibility to initialize the point, and further down the inheritance, by Pvertex (no longer vertex3d) to be responsible for the completion of the " The construction of the shared point subobject.
traditional strategy If you want to support "OK, now initialize the virtual base clas ... oh, now do not need ...", will lead to more expanded content in constructor, to indicate the virtual base class Constructors should not be called.The constructor function itself must therefore conditionally test the passed in parameters and then decide to call or not invoke the associated virtual base class constructors. Here's what Point3D constructor to add:
C + + Pseudocode: constructor augmented content in the virtual base class case Point3D *point3d::P Oint3d (Point3D *this, bool __most_derived, float x, Float y, float z) {if (__most_derived! = False) This->point::P oint (x, y); This->__vptr_point3d = __vtbl_point3d;this ->__vptr_point3d_point = __vtbl_point3d__point;this->_z = Rhs._z;return this;}
  In the case of deeper inheritance, such as Vertex3d, when calling the constructor of Point3D and vertex, the __most_derived parameter is always set to false, thus suppressing the call operation to point constructors in two constructor.
C + + Pseudocode: constructor augmented content in the case of virtual base class Vertex3d *vertex3d::vertex3d (Vertex3d *this, bool __most_derived, float x, float y, float z) {if (__most_derived! = False) This->point::P oint (x, y);//Call the previous layer base classes//settings __most_derive D for Falsethis->point3d:::P Oint3d (False, x, Y, z), This->vertex::vertex (False, x, y);//Set vptrs//Insert User Modereturn this;}
  Such a strategy keeps the semantics correct. For example, when defining:
Point3D origin;
, Point3D constructor can call its point virtual base class subobject correctly. And when defined:
VERTEX3D CV;
, Vertex3d constructor correctly calls point constructor. Point3D and Vertex's constructor will do every thing-except the call to point.

VTPR initialization semantics (the semantics of the VPTR initialization)When you define a Pvertex object, the order in which constructors is called is:
Point (x, y); Point3D (x, y, z); Vertex (x, y, z); Vertex3d (x, y, z); Pvertex (x, y, z);
  Assume that each class in this inheritance system defines a virtual function size (). The function is responsible for returning the size of the class, if written:
Pvertex PV; Point3D P3d; Point *pt = &pv;
  Then this call operation:
Pt->size ();
Will returns the size of the Pvertexand
PT = &p3d;pt->size ();
  Will returns the size of the Point3D.
Further, assume that each of the constructors within this inheritance system has a call operation, like this:
Point3D::P Oint3d (float x, float y, float z): _x (x), _y (y), _z (z) {if (Spyon) Cerr < "within Point3D::P Oint3d ()" << ; "Size:" << size () << Endl;}
  When defining Pvertex object, what happens to the five constructors mentioned above? each time the size () call is resolved to Pvertex::size ()? or is the size () function entity of the "class" corresponding to the "currently executing constructor" to be called each time ?
The C + + language rule states that the size () function that is called in Point3D constructor must be more than a resolution of point3d::size () instead of pvertex:size. More generally, In a class constructor or destructor, a virtual function is invoked through the constructed object, and its function entity should be the one that has a role in this class .The above situation is necessary because of the sequence of calls of each constructors.
The order of the constructors is: from the root to the end, from the inside out. When the base class constructor executes, the derived entity is not yet constructed. Before Pvertex constructor is executed, Pvertex is not a complete object; After Point3D constructor is executed, only the POINT3D subobject structure is complete.
This means that when each Pvertex base class constructors is called, the compilation system must ensure that an appropriate size () function entity is called, and how is this guaranteed?
If the call action limit must be called directly in constructor or destructor, then the answer is clear: do not use the virtual mechanism for static resolution of each invocation operation. Point3d::size () is explicitly called as long as it is in the Point3D constructor.
But what happens if a virtual function is called in size ()?In this case, the call must also be resolved as a function entity of Point3D. In other cases, this call is pure virtual and must be determined by the virtual mechanism. That is to say, the virtual mechanism itself must know whether the call originated in a constructor.
Another way to do this is to set up a flag in constructor (or destructor) to indicate a static resolution, and then use the flag value as the basis for judging the conditional invocation operation.
The fundamental solution is that a set of virtual functions candidates must be restricted when executing a constructor.
Think about it, What is the key to deciding on a class virtual functions list?The answer is Virtual Table. How is Virtual table processed?The answer is by Vptr. So, to control a function that functions in a class, the compilation system simply controls the initialization and setting of the vptr.Of course, setting vptr is the compiler's responsibility, and no programmer should worry about it.
what should I do with the VPTR initialization operation?Essentially, this requiresdepending on when the vptr "should be initialized" in constructors. There are three options:
1. Prior to any operation.
2. After the base base class constructors is called, but before the code supplied by the programmer or the members initialization operation listed in the member initialization list.
3. After every thing has happened.
The answer is 2. The other two options are of no value. policy 2 resolves the problem of restricting a set of virtual function lists in class. If each constructor waits until its base class constructors executes, it sets the vptr of its object. Then each time it can invoke the correct virtual function entity.
After each base class constructor sets the vptr of its object so that it points to the associated virtual table, the object in the construct can be strictly and correctly transformed into the object of every class that was created during the construction process. That is to say, A Pvertex object first forms a point object, a Point3D object, a vertex object, a Vertex3d object, but becomes a Pvertex object, in each base class constructor, The object can be compared with the complete object of the constructor's class. For the object, the "individual occurrence" summarizes the "systematics of the system". the execution algorithm for constructor is usually as follows:
1. In derived class constructor, "All virtual base classes" and "previous base class" constructors will be called.
2. After the completion of the above, the object's vptr is initialized, pointing to the associated virtual table.
3. If there is a member initialization list, it will be expanded in the constructors body. This must be done after the vptr is set, lest a virtual member function be called.
4. Finally execute the code provided by the programmer.

For example, this programmer-defined Pvertex constructor is known:
Pvertex::P Vertex (float x, float y, float z): _next (0), Vertex3d (x, Y, z), point (x, y) {if (Spyon) Cerr << "within P Vertex::P Vertex () "<<" Size: "<< size () << Endl;}
  It is likely to be expanded to:
C + + pseudo code: Pvertex constructor Extension results Pvertex *pvertex::P vertex (Pvertex *this, bool __most_derived, float x, float y, float z) {//Conditionally call virtual base Constructorif (__most_derived! = False) This->point::P oint (x, y);//unconditional call to previous layer basethis-> Vertex3d::vertex3d (x, y, z);//Initialize the related vptr This->__vptr_pvertex = __vtbl_pvertex;this->__vptr_point__pvertex = _ _vtbl_point__pvertex;//Programmer wrote the code if (Spyon) Cerr << "within Pvertex::P vertex ()" << "Size:" << (*THIS-&G T;__VPTR_PVERTEX[3].FADDR) (this) << endl;//returns the constructed object return this;
  This perfectly solves the problem of limiting virtual mechanisms, but is this a perfect solution? Suppose that point constructor is defined as:
Point::P oint (Flaot x, float y): _x (x), _y (y) {}
  Point3D constructor is defined as:
Point3D::P Oint3d (float x, float y, float z): Point (x, y), _z (z) {}
  Further, suppose vertex and Vertex3d constructor have similar definitions. Can you see that the solution is not perfect?
Below is vptr Two conditions that must be set:
1. When a complete object is constructed. If you declare a point object, point constructor must set its vptr.
2. When a subobject constructor invokes a virtual function (whether it is a direct call or an indirect call).
When declaring a Pvertex object, and then due to the latest definition of its base class constructors, its vptr will no longer need to be in each base class constructor. The solution is to split the constructor into a complete object entity and a subobject entity. In the Subobject entity, the VPTR setting can be ignored.
Knowing this, you can answer the following questions: is it safe to call a virtual function of the class in the class's constructor member initialization list?In practice, it is always safe to run this function in the initialization action of its class ' data member. This is because vptr guarantees that the compiler can properly set the member before the initialization is extended. But in semantics this may be unsafe, This is not recommended because the function itself may have to rely on members that are not set to the initial value. However, from the overall perspective of vptr, it is safe.
When do I need to supply parameters to a base class constructor? In this case, it is still safe to call the class's virtual function in the "constructor member initialization list of class"? At this point, if the vptr is not yet set, it is set to the wrong class. Further, any class ' data members accessed by the function must not have been initialized.

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

C + + object model--object construction in the case of "No Inheritance" (fifth 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.