c++--class inheritance and class initialization order

Source: Internet
Author: User

For classes and class inheritance, there are several main issues:
1) Inheritance mode: Public/protected/private inheritance.
This is a C + +, in fact, inheritance is a kind of permission to allow sub-class control ideas. The subclass inherits from public, can restore the base class real, and the private inheritance completely masks the base class.
This masking is relative to the object layer, meaning that the object of the subclass is completely invisible to the base class, and if the inheritance is private, even if the method is public in the base class.
However, inheritance does not affect the vertical access attribute, that is, the subclass of the function of the base class access is not affected by the way the inheritance.

Comparison (Java): Java is simplified and can actually be thought of as a public inheritance in C + +. There is really no need to private/protected inheritance, because if you want to control, you can directly control the base class.


2) object Initialization Order: C + + has a member initialization list, and definitely distinguishes between the initial and the assignment. The order in which C + + objects are initialized is:
(a) base class initialization
(b) Initial time of the object member
(c) Assignment statements for constructors

Example:
Suppose class C:public A, public B {
D d;//
}
The order of initialization is the constructor of a, B, D, C.

The order in which the base classes are initialized is in the order in which they are declared, and the member objects are in the order in which they are declared. So c (int i, int j): B (i), A (j) {}//the order in which members initialize the list is not working;
The order of destructors is exactly the same, and the structure/destructor order can be regarded as a stack order.

Comparison (Java): initialization assignment in Java is one thing. And the constructor call to the base class must show the declaration in the order you wrote it yourself.
To the member object, also called by you to initialize. There is no systematic arrangement of order problems that make you feel comfortable;


3) Multiple Inheritance issues: C + + supports multiple inheritance, which causes "root" to be not unique. And Java does not have the problem;
In addition, C + + does not have a unified root object, all Java objects exist in the object class makes a lot of things very convenient. such as the public seriall, persistent and so on.


4) Overloads in inheritance: in C + +, derived classes inherit the member functions of all base classes, except constructors, destructors.
This means that if B inherits a, a (int i) is a base class constructor, then b b (i) cannot define the object. Unless b also defines the same constructor.
The reason for C + + is that if a derived class defines a new member, the base class initialization function cannot initialize all new members of the derived class.

Comparison (Java): In Java, regardless, even if there is a new object base class function is not considered, the big deal is null, or you have the default value. is also reasonable.


5) Overwrite and ambiguity with the same name in the inheritance: This means that when a derived class has exactly the same member variable or function as the base class, the derived class overrides the base class.
Similar to local variables with the same name overrides global variables. However, the overridden base class members can still be accessed. If B inherits A, A, B has a member variable A, b b, B.A is Access B's A, b.a::a accesses a in the base class. This is also true for member functions.
However, it is important to note that functions of the same name must be identical to be overwritten. the int func (int j) and int func (long j) are actually different. If the base class has these two functions, the derived class will not overwrite with the same name.
Most importantly, neither of them constitutes an overloaded function. So if a has a function int func (int j), B has the function int func (long j). The object B.func (int) of B is called an error. Because the Func in B does not constitute an overload at all.

The problem caused by overwriting with the same name is ambiguity. If C->b=>a, here C Inherits B, B inherits A. If A and B have the same member fun, then the object of C C.fun exists ambiguity. Does it mean A's or B's fun?
The workaround is to qualify the symbol C with a field. A::fun to quote A's fun.

Another one that leads to two semantics is multiple inheritance. Assuming B1, B2 inherit from B, D inherits B1, B2. Then D has two B and produces a ambiguity.
The solution to this situation is to use the virtual base class. Class B1:virtual public B, Class B2:virtual public B, D is class D:public B1, public B2. In this way, the members of D only contain a member of B so that no ambiguity is generated.

Comparison (Java). In Java, it is directly overwritten. Do not give the opportunity so complicated, but also to save the base class with the same name. The same name is directly covered, without the same name is directly inherited.

The addition of virtual base classes also affects the initialization order of classes. The principle is that each derived class's membership initialization list must contain initialization of the virtual base class.
When it is finally initialized, only the invocation of the class that actually instantiates the object will work. Calls to virtual base classes by other classes are ignored. This guarantees that the virtual base class will only be initialized once.










C + + does not have an explicit interface concept, I think the C + + language of the defeat. This is also causing the C + + to support component-level reuse very cumbersome. Although there is no explicit interface, pure virtual functions in C + + and the support of abstract classes are, in fact, equivalent to interface facilities. When a class, all member functions are pure virtual functions, then the class is actually an interface.
Java C + +
Interface class (all member functions are pure virtual functions)
Abstract class Class (partial function is virtual function)
Object class object class











C + + constructor call order
1. If there is a member class inside the class, the constructor of the member class is called first;
2. Create the object of the derived class, the constructor of the base class is called first (also precedence over the member class in the derived class);
3. base class constructors If there are multiple base classes, the order in which the constructors are called is the order in which a class appears in the class-derived table rather than the order in which they are in the Member initialization table;
4. Member class object constructors If there are multiple member class objects, the order in which the constructors are called is the order in which the objects are declared in the class, not the order in which they appear in the Member initialization table;
5. Derived class constructors, as a general rule derived class constructors should not be able to assign values directly to a base class data member but to pass values to the appropriate base class constructor, otherwise the implementation of the two classes becomes tightly coupled (tightly coupled) will make it more difficult to correctly modify or extend the implementation of the base class. (The responsibility of the base class Designer is to provide a set of appropriate base class constructors)

Example:
#include <iostream>
#include <string>
Class A {
Public:a{...}
~a{...}
};
Class B {
Public:b{...}
~b{...}
};
Class D {
Public:d{...}
~d{...}
};
Class E {
Public:e{...}
~e{...}
};
Class C:p ublic a,public B {
Public:c{...}
Private
D Objd_;
E Obje_;
~c{...}
}

int main (void)
{
C test;
return 0;
}
The operating result is:
A{...} Order in a derived table
B{...}
D{...} The constructor of the member class is called first
E{...}
C{...}
~c{...}
~e{...}
~d{...}
~b{...}
~a{...}






Conceptually, the execution of a constructor can be divided into two phases, the initialization phase and the calculation phase, and the initialization phase precedes the computational phase:

Initialization phase:
All members of the class type are initialized during the initialization phase, even if the member does not appear in the initialization list of the constructor;

Calculation phase:
Typically used to perform assignment operations within the constructor body.
The following code defines two structs, where Test1 has constructors, copy constructors, and assignment operators to make it easier to see the results, Test2 is a test class that takes Test1 objects as members, and we look at how Test2 constructors are executed.

Class Test1
{
Test1 ()//No parameter constructor
{
cout << "Construct Test1" << Endl;
}

Test1 (const test1& T1)//copy constructor
{
cout << "Copy constructor for Test1" << Endl;
This->a = t1.a;
}

test1& operator = (const test1& t1)//assignment operator
{
cout << "Assignment for Test1" << Endl;
This->a = t1.a;
return *this;
}

int A;
};
struct TEST2
{
Test1 test1;
Test2 (Test1 &t1)
{
Test1 = T1;
}
};
Calling code:
Test1 T1;
Test2 T2 (T1);
Output:
Construct Test1
Construct Test1
Assignment for Test1
Explain:
The first line of output corresponds to the first line in the calling code, constructing a Test1 object;
The second line outputs the code in the Test2 constructor, initializes the object with the default constructor test1//This is the so-called initialization phase;
The third line outputs the assignment operator for the Test2, and performs the assignment on the Test1//This is the so-called computational phase;


Why use an initialization list?
There are two ways to initialize a member of a class, one is to use an initialization list, and the other is to assign a value in the constructor body.
Mainly performance issues, for built-in types such as int, float, etc., using the initialization class table and initialization in the constructor body is not very different, but for the class type, it is best to use the initialization list, why?
The following test shows that the process of invoking the default constructor one less time with the initialization list is very efficient for data-intensive classes. Also see the example above, we use the initialization list to implement the Test2 constructor.
struct TEST2
{
Test1 test1;
Test2 (Test1 &t1): test1 (t1) {}
}
Using the same calling code, the output is as follows:
Construct Test1
Copy Constructor for Test1
The first line of output corresponds to the first line of the calling code
The second line outputs the initialization list of the corresponding Test2, invoking the copy constructor directly to initialize the Test1, eliminating the process of invoking the default constructor.
Therefore, a good principle is that you can use the initialization list when possible to use the initialization list;


In addition to performance issues, it is necessary to initialize the list in some situations where the initialization list must be used:
1. Constant members, because constants can only initialize can not be assigned value, so must be placed in the initialization list;
2. Reference type, the reference must be initialized at the time of definition, and cannot be re-assigned, so it is also written in the initialization list;
3. The class type does not have a default constructor, because using an initialization list can be initialized without calling the default constructor, but instead invoking the copy constructor directly;


struct Test1 {
Test1 (int a): I (a) {}
int i;
};
struct TEST2 {
Test1 test1;
};
The above code cannot be compiled because the Test2 constructor test1 = T1 is actually divided into two steps:
1. Call Test1 's default constructor to initialize the Test1;
Because Test1 does not have a default constructor, 1 cannot execute, and therefore compiles errors. The correct code is as follows, using an initialization list instead of an assignment operation,
struct TEST2 {
Test1 test1;
Test2 (int x): test1 (x) {}
}


Initialization order of member variables: first the defined member variables are initialized first
Members are initialized in the order in which they appear in the class, rather than in the order in which they appear in the initialization list, see Code:
struct Foo {
int i; Int J;
Foo (int x): I (x), J (i) {}; OK, initialize I first, post-initialize J
};
Then look at the following code:
struct Foo {
int i; Int J;
Foo (int x): j (x), I (j) {}//I value not defined
};
The value of I here is undefined because although J appears in the initialization list in front of I, but I is defined before J, so I is initialized first, and I is initialized by J, at this time J is not initialized, so the value of I is undefined.
A good habit is to initialize in the order in which the members are defined.









For global objects, VC is first defined first, but the C + + standard does not provide.
Global objects are static by default, and global static objects must be constructed before the main () function, telling the compiler to store variables in the program's static store, implemented by the C + + compiler startup code.
The startup code is code that executes earlier than the program entry point (main or WinMain), which can do things like function library initialization, process information setup, I/O stream generation, and initialization actions on static objects (that is, calling their constructors);
The destructor that is called after the main () function ends.

----------------initialization constructs for derived class objects
#include <iostream>
using namespace Std;

Class A {
Private
int A;
Public
A (int x): A (x) {cout <<a << "";}
};
Class B:a {
Private
int B, C;
const int D;
A x, y;
Public
b (int v): B (v), Y (b+2), X (b+1), d (b), A (v) {
C=v;
cout <<b << "<<c <<" "<<d;
}
};
int main (void)
{
B Z (1);
return 0;
}
/*
1. Define a derived class object, first initialize its base class member (the base class part), that is, the constructor that calls the base class (if multiple inheritance, the constructor of the base class is called in the Order of inheritance)

2. After the base class part is initialized, the derived class part is initialized, the member of the derived class initializes the declaration order that relies on it, does not rely on its initialization list to initialize the derived class member, and, in summary, is the initialization of the derived class member, relying on its declaration order rather than on the order of the initialization list.

3. Call the constructor of the derived class, which is understood to be the function body that executes the derived class constructor

4. Special note: However, note that the above two-point call constructor or other parameter passing is a reference to the parameters given by the initialization list.


Detailed Explanation:
First: B z (1); The constructor of the base class is called according to 1, but it is not known which constructor of the base class is called, because the base class has a default constructor (that is, no arguments) and you define a (int x) constructor, so the compiler chooses.
According to 4, refer to initialization list B (v), Y (b+2), X (b+1), d (b), A (v) with a (v), so the compiler chooses to call the constructor a (int x) you define, so print out the value of a, Output 1, and then, according to 2, The part of the derived class itself is initialized in the order in which it is defined,
That is, in this order, b,c,d,x,y.
int B, C;
const int D;
A x, y;
Therefore, according to 4, respectively, refer to the initialization List B (v), Y (b+2), X (b+1), d (b), A (v) Given the parameter information, you can know initialization B, using B (v), B is initialized to 1. Then, initialize C, because the initialization list does not specify the initialization of C, so temporarily c is not initialized, and then initialize D, according to the initialization list of D (b), D is initialized to the value of B, that is, 1.
Then initialize Class A objects x and Y, initialize x based on the X (b+1) in the initialization list, because B has a value of 1, so that is equivalent to X (2), given a parameter 2, call the constructor a (int x) you define, print the value of a in the X object of the output Class A, that is, Output 2, in the same vein, by Y (b + 2) initialize Y, print output 3.
Finally, based on 3, the derived class constructor is called, i.e.
B (int v)
{
C=v;
cout <<b << "<<c <<" "<<d;
}
At this point, directly ignoring the initialization list, execute the constructor of the derived class, then execute the function body c=v; then the uninitialized C is assigned a value of V, that is, the value of C is 1. Finally print out the values for the output B and C, so then output two 1.

Summary: Output 1 2 3 1 1 1

First, C + + member variable initialization

1, ordinary variables: generally do not consider what the efficiency of the case can be assigned in the constructor. Consider the efficiency that can be done in the initialization list of the re-constructor function

2 Static variables (localized data and code scope):
The static variable belongs to the class and not to the object of the class, so there is only one variable regardless of how many objects the class is instantiated. Understanding in this nature is somewhat analogous to the uniqueness of global variables.
The function body, unlike the auto variable, is scoped to the function body, and the variable memory is allocated only once, so its value is maintained the last value on the next call.
The static global variable within the module can be accessed by all functions within the module, but not by other functions outside the module.
The static function within the module can only be called by other functions within the module, and the application scope of the function is limited to the module in which it is declared.
Static member variables in a class are owned by the entire class and have only one copy of all objects of the class.
The static member function in a class belongs to the entire class, which does not accept the this pointer, and therefore can only access the static member variables of the class.

3, const constant Volume:
Const constants need to be initialized at the time of declaration. It is therefore necessary to initialize the variable when it is created. Generally used in the initialization list of constructors.

4. Reference Reference variable:
The reference variable is similar to the const variable. It needs to be initialized at the time of creation. is also done in the initialization list. But you need to be careful with the reference type.

5. String initialization
Char str[10] = "HELLO";
The end will be automatically added to the end of the compiler "/0", when compiled can see it finally ", the ASC code value is 0;
"HELLO" only 5 characters, plus the compiler automatically added '/0 ', that will initialize the first 6 elements of the array, the remaining elements will be all initialized to '/0 ', this should pay attention to OH;

Char str[] = "HELLO";
The compiler automatically assigns a size to the subsequent string and adds '/0 ';

Char str[] = {' H ', ' E ', ' l ', ' l ', ' O ', '/0 '};
The compiler allocates space based on the size of the string, but does not automatically allocate '/0 ', so add '/0 ' to the end;

Char *str = "HELLO";
A pointer to a string is given to a defined character pointer;

1) Ensure initialization with constructor function
For an empty class, the compiler automatically declares 4 default functions: Constructors, copy constructors, assignment functions, destructors (which should be explicitly rejected if you do not want to use auto-generation), and the resulting functions are public and inline.

2) Why the constructor cannot have a return value
(1) There is a Class C, which is defined as follows:
The invocation of the constructor does not set the return value because of the particularity of the constructor. From the basic semantic point of view, the constructor should return the constructed object. Otherwise, we will not be able to use temporary objects:
void f (int a) {...}//(1)
void f (const c& a) {...}//(2)
F (C ()); (3), who actually calls?
for (3), we want to call the (2), but if C::C () has the return value of type int, then whether it is a tune (1) Good or call (2) good. As a result, our overloaded systems, and even the entire grammatical system, will collapse.
The core here is the type of the expression. Currently, the type of expression C () is Class C. However, if C::C () has a return type R, then the type of the expression C () should be r, not C, and the above type problem will be raised.
(2) just the C + + standard specifies that a construct/destructor/custom type converter cannot specify a return type. But you can't just say that they don't have a return type.
(3) My opinion is that the constructor has a return value, which returns the newly constructed object itself, but cannot specify the return type, because you use the constructor of this class to indicate that you are returning an object of this class, it is not necessary to specify the return type, even if the specified must be the return type of the specified class itself. That's a lot of time.

3) Why the constructor cannot be a virtual function
The mechanism of a virtual function call is a function that knows the interface without knowing its exact object type, but creating an object must know the exact type of the object, and one of the first things it does when a constructor is called is to initialize its vptr to point to vtable.

#include <iostream>
using namespace Std;

Class Base {
Private
int i;
Public
Base (int x) {
i = x;
}
};

Class Derived:public Base {
Private
int i;
Public
Derived (int x, int y) {
i = x;
}
void print () {
cout << i + base::i << Endl;
}
};

int main ()
{
Derived A (2,3);
A.print ();
return 0;
}

First of all, it is the access rights issue, the subclass directly access base::i is not allowed, should change the parent class to protected or public (preferably with protected)
Second, the addition of the parent class and subclass I is counted, but the parent class variable is not initialized by the subclass constructor; The constructor is not found here because the subclass call constructor will first look up the parent class constructor, but there are no 2 arguments, so you can call the parent class constructor in the initialization list
The last problem is the single-parameter constructor, which may have an implicit conversion problem, because the single-argument constructor, like the copy constructor, is likely to be implicitly converted when invoked, plus the explicit keyword
#include <iostream>
using namespace Std;

Class Base {
Protected
int i;
Public
explicit Base (int x) {
i = x;
}
};

Class Derived:public Base {
Private
int i;
Public
Derived (int x, int y): Base (x) {
i = y;
}
void print () {
cout << i + base::i << Endl;
}
};

int main ()
{
Derived A (2,3);
A.print ();
return 0;
}


There are two ways to initialize a member of a class, one is to use an initialization list, and the other is to assign a value in the constructor body.
Mainly performance issues, for built-in types such as int, float, etc., using the initialization class table and initialization in the constructor body is not very different, but for the class type, it is best to use the initialization list, why?
The following test shows that the process of invoking the default constructor one less time with the initialization list is very efficient for data-intensive classes.


Initialize List
1) Improve efficiency with initialization lists
Class Student {
Public
Student (string in_name, int in_age) {
name = In_name;
age = In_age;
}
Private:
String name;
int age;
};

In the constructor, the name is assigned, not initialized, and the string object calls its default constructor before invoking the assignment constructor of the string class (seemingly the Basic_string class);
Class Student {
Public
Student (string in_name, int in_age): Name (In_name), age (In_age) {}
Private:
String name;
int age;
};
In the initialization of the call is a copy of the constructor of the string, and the previous call two constructors, from the performance will be a small increase;


In some cases, it must be initialized with an initialization list: const object, Reference object
Initialize List initial order
#include <iostream>
using namespace Std;

Class Base {
Public
Base (int i): M_j (i), m_i (M_j) {}
Base (): M_j (0), m_i (M_j) {}
int get_i () const {
return m_i;
}
int Get_j () const {
return m_j;
}

Private
int m_i;
int m_j;
};

int main ()
{
Base obj (98);
cout << obj.get_i () << Endl << obj.get_j () << Endl;
return 0;
}
The output is a random number and 98, why?
Because the initialization of the member variables for the initialization list is strictly based on the order in which they are declared, rather than in the initialization list, this problem does not occur if the assignment is initialized instead.
Of course, in order to use the initialization list, still pay attention to the declaration order, such as declaring the array size first, then declare the array.


C + + constructor initialization is called in the following order:
First, the constructors of any virtual base class are constructed in the order in which they are inherited;
Second, the constructors of any non-virtual base class are constructed in the order in which they are inherited;
Again, the constructors of any member object are called in the order in which they are declared;
Finally, the class is its own constructor.

#include <iostream>
using namespace std;
Class obj1{
Public:
OBJ1 () {cout<< "obj1\n";}
};
Class obj2{
Public:
OBJ2 () {cout<< "obj2\n";}
}
Class base1{
Public:
Base1 () {cout<< "base1\n";}
}
Class base2{
Public:
Base2 () {cout << "base2\n";}
};
Class base3{
Public:
Base3 () {cout << "base3\n";}
};
Class base4{
Public:
Base4 () {cout << "base4\n";}
};
Class Derived:p ublic Base1, virtual public base2,public Base3, virtual public base4//inheritance order {
Public:
Derived () : Base4 (), Base3 (), Base2 (), Base1 (), Obj2 (), obj1 () {//Initialize list
cout << "Derived ok.\n";
}
Protected:
OBJ1 obj1;//Declaration order
OBJ2 obj2;
};

int main ()
{
Derived aa;//Initialization
cout << "This is ok.\n";
return 0;
}

Results:
BASE2//Virtual base classes are initialized in the order in which they are inherited
BASE4//virtual base classes in the order in which they are inherited
BASE1//non-virtual base classes are initialized in the order in which they are inherited
BASE3//non-virtual base classes in the order in which they are inherited
The OBJ1//member functions are initialized in the order in which they are declared
OBJ2//member functions in the order in which they are declared
Derived OK.
This is OK.

Duplicate inheritance (repeated inheritance): A derived class inherits the same base class more than once.
But C + + does not allow a derived class to inherit directly from the same base class two or more times.

Two kinds of duplicate inheritance: Copy inheritance and shared inheritance

Shared inheritance in duplicate inheritance: Enables duplicate base classes to store only one copy in a derived object instance by using a virtual base class.

Initialization order rules for derived class objects that involve sharing inheritance
① first calls the constructor of the virtual base class.
② second calls the constructors of the ordinary base class, and multiple base classes are listed in the order from left to right as they are declared by the derived class.
③ calls the constructor of the object member again, in the order in which the object members appear in the class declaration.
④ finally executes the constructor of the derived class.

Destructors are executed in the opposite order of their initialization.

Cases:
/*
program:repeated inheritance, virtual base class test
Author:ideal
Date:2006/3/28
*/

#include <iostream.h>

Class Basea
{
Public
Basea ()
{
cout << "Basea class." << Endl;
}
};

Class Baseb
{
Public
Baseb ()
{
cout << "Baseb class." << Endl;
}
};

Class Deriveda:public Baseb, virtual public Basea
{
Public
Deriveda ()
{
cout << "Deriveda class." << Endl;
}
};

Class Derivedb:public Baseb, virtual public Basea
{
Public
Derivedb ()
{
cout << "Derivedb class." << Endl;
}
};

Class Derived:public Deriveda, virtual public derivedb
{
Public
Derived ()
{
cout << "Derived class." << Endl;
}
};

void Main ()
{
Derived obj;
cout << Endl;
}

Result
=========
Basea class.
Baseb class.
Derivedb class.
Baseb class.
Deriveda class.
Derived class.

————————————————————————————————————————
Analysis: Class hierarchy relationships of various types are
①derived derived from Deriveda and virtual base class Derivedb
②deriveda derived from Baseb and virtual base class Basea, Derivedb derived from Baseb and virtual base class Basea

Execution order (constructor)
By the ① layer, according to the rules can be ordered as derivedb,deriveda,derived.

Then, for Derivedb, the same order in which the rules are further analyzed is basea,baseb,derivedb.

For Deriveda, it is important to note that both Deriveda and Derivedb are derived from the virtual base class Basea, so based on the processing method that only stores one copy,

Since Basea has already been initialized in Derivedb, Deriveda will not have to be initialized again, so the execution is Baseb, Deriveda.

The last is derived.

The corresponding constructor order can be synthesized: basea (), Baseb (), Derivedb (); Baseb (), Deriveda (); Derived ();

c++--class inheritance and class initialization order

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.