Default constructor
What is constructor doing? It is an opportunity for programmers to initialize objects when constructing class objects. not only that, but also an opportunity for the compiler to initialize the object. of course, the roles played by programmers and compilers are different, and the considerations are also different.
When the programmer thinks that this class object is not necessary for initialization, he will not specifically declare the constructor.
When the programmer does not declare any constructor for a class, the compiler may declare a default constructor for the class. the reason for this is that the compiler is also very lazy. If he thinks this class is unnecessary for initialization, he will not actually construct the default constructor. the default constructor is created only when he considers this constructor as nontrivial, that is, he does need to perform some initialization operations on class objects.
The following is an example:Programmer's perspectiveWhen do we need Constructors,
Class Foo {public: int val; Foo * pnext ;};
Void foo_BAR ()
{
// Oops: program needs bar's members zeroed out
Foo bar;
If (bar. Val | bar. pnext)
//... Do something
//...
}
In the code above, the programmer expects that the default constructor will be generated when the object is created to initialize and clear the member variables. In fact, in the compiler's opinion, this kind of activity should be done by the programmer. therefore, the compiler does not specially construct the default constructor to initialize class members.
This logic is prone to bugs, which is also an easy mistake for new users,
Global objects are guaranteed to have their associated memory "zeroed out" at program start-up. local objects allocated on the program stack and heap objects allocated on the free-store do not have their associated memory zeroed out; rather, the memory retains the arbitrary bit pattern of its previous use.
For global variables, the program will be cleared at the beginning, but for the memory allocated on the stack and heap, the legacy data in the last use will be maintained, so if you do not manually clear the data, you may get any value. in this step, the programmer should be responsible for calling the constructor, rather than the compiler.
FromCompiler perspectiveWhen the object initialization operation must be added to the constructor
There are four situations,
Member Class Object with default constructor
The object member is also a class object and the class defines the default constructor. the compiler must ensure that the member object is correctly initialized, that is, the default constructor is called.
Base class with default constructor
Similarly, the compiler must ensure that the base class is correctly initialized.
Class with a virtual function
There are two cases in this case,
1. The class either declares (or inherits) a virtual function
2. The class is derived from an inheritance chain in which one or more base classes are virtual
For classes with virtual functions, the compiler must perform the following two additional tasks to ensure that polymorphism works normally,
1. A virtual function table (referred to as the class vtbl in the original cfront implementation) is generated and populated with the addresses of the active virtual functions for that class.
2. Within each class object, an additional pointer member (the vptr) is synthesized to hold the address of the associated class vtbl.
To put it bluntly, the compiler must create a virtual table and add a pointer to the virtual table to each object.
Class with a virtual base class
For the implementation of virtual base class, each compiler is very different.
What is common to each implementation is the need to make the virtual base class location within each derived class object available at runtime.
Therefore, the compiler must ensure that the address of the virtual base class during the execution period is determined in the constructor.
In the preceding four cases, the compiler must create the default constructor for special processing. If the programmer has defined the constructor, the compiler must also insert special processing into the constructor. in addition to the above four cases, the compiler will not generate a default constructor, nor will it perform any additional initialization operations on the object. If you want to do anything, the programmer should do it by himself.
Copy constructor
In three cases, the copy constructor is used to take the content of one object as the initial value of another object.
Perform Explicit initialization on an object,
Class X {...};
X;
// Explicit initialization of one class object with another
X xx = X;
In the other two cases, an object is sent to a function as a parameter, or when a function returns an object.
Similarly, the copy constructor is used to give the programmer or compiler a chance to perform some special processing when initializing another Class Object using a class object.
Similarly, programmers and compilers need to consider different issues,
For programmers, a typical example is provided to illustrate the significance of the copy constructor,
When the object member is a pointer, if only the pointer is assigned, multiple pointers will point to the same object. When a pointer releases the object, an error will be reported elsewhere.
Therefore, in the copy constructor, the programmer needs to create a new pointer member pointing to the copy object, and point the member pointer of the new object to the copy object. This is the complete copy.
When a programmer does not specifically create a copy constructor, the compiler defaultsBitwise copySemantics: Copies the data in an object to the object to be initialized in bits. therefore, when the above-mentioned member is a pointer, it may lead to unreasonable situation for the member pointers of two objects to the same object.
However, in the following four cases, the compiler must generate a copy constructor to ensure the correctness of the copy,
1. When the class containsMember objectOf a class for which a copy constructor exists
2. When the class is derived fromBase ClassFor which a copy constructor exists
3. When the class declares one or moreVirtual Functions
4. When the class is derived from an inheritance chain in which one or moreBase classes are virtual
1 and 2 are easy to understand. The compiler must ensure that the copy constructor written by programmers in the member object or base class is tuned
3 and 4 are relatively complicated. In fact, for the two cases, if the objects of the same class are initialized to each other, no special processing is required. See the example below.
Class zooanimal {......}
Class bear: Public zooanimal {......}
Bear Yogi;
Bear Winnie = Yogi; // in this case, copy it directly.
Zooanimal Franny = Yogi; // However, in this case, the compiler must adjust the virtual table pointer of Franny, because the vptr of Yogi is directed to bear and should be changed to zooanimal.
For classes with virtual functions, the compiler must generate a copy constructor to adjust the value of vptr during initialization between the base class and the derived class.
In the case of a virtual base class, when the base class and the derived class are initialized, the compiler needs to generate a copy constructor to determine the location of the virtual base class object.
To sum up, the default constructor and copy constructor must be called only when there are special constructor objects in the class or base class, or classes that contain virtual functions and virtual base classes require special processing. The Compiler does not perform any initialization operations on class objects. You do not need to suspect that many operations are performed in the compilation background, he is also very lazy...
Program conversion caused by copying Constructors
The above three cases of calling the copy constructor are described, but they are not explicitly called, so the compiler needs to perform program conversion. The following describes the situation.
1. Explicit initialization
X x0;
The following three definitions each explicitly initialize its class object with x0:
Void foo_BAR (){
X x1 (x0 );
X X2 = x0;
X X3 = x (x0 );
//...
}
For a rigorous C ++ compiler, the definition refers to memory usage. For it, the definition and initialization are separated.
// Possible program transformation
// Pseudo C ++ code
Void foo_BAR (){
X x1;
X X2;
X X3;
// Compiler inserted invocations
// Of copy constructor for X
X1.x: X (x0 );
X2.x: X (x0 );
X3.x: X (x0 );
//...
}
This conversion is easy to understand.
2. parameter initialization
Void Foo (x X0 );
An invocation of the form
X xx;
//...
Foo (XX );
For such parameter passing, the compiler will actually perform the following conversions:
// Pseudo C ++ code
// Compiler generated temporary
X _ temp0;
// Compiler invocation of copy constructor
_ Temp0.x: X (XX );
// Rewrite function call to take temporary
Foo (_ temp0 );
Here, we will first use the copy constructor to initialize the Temporary Variable _ temp0. The temporary variable is required because XX is invisible outside the function when it enters the foo function, therefore, a XX backup must be retained first.
Well, the first thing that follows the foo function is to initialize _ temp0 through the copy constructor.
It is inefficient to pass a parameter and call the copy constructor twice.
Therefore, the compiler can rewrite the function declaration
Void Foo (X & X0 );
In this way, you can direct x0 to _ temp0 to improve efficiency.
Of course, the rewrite policy here is related to the compiler. Here is only a solution, and other compiler solutions will not be introduced.
3. Return Value Initialization
X Bar ()
{
X xx;
// Process XX...
Return xx;
}
For the returned values, the compiler converts the values as follows:
// Function transformation to reflect
// Application of copy constructor
// Pseudo C ++ code
Void
Bar (X & _ result)
{
X xx;
// Compiler generated Invocation
// Of default constructor
XX. X: X ();
//... Process xx
// Compiler generated Invocation
// Of copy constructor
_ Result. X: X (XX );
Return;
}
It can be seen that the so-called return value, in fact, the compiler only adds a result parameter, and finally initializes the result through the copy constructor xx.
Therefore
X xx = bar ();
The compiler will rewrite it
// Note: no default constructor applied
X xx;
Bar (XX );
To improve efficiency, we can try to avoid calling the copy constructor for the returned values.
A. User Optimization
X Bar (const T & Y, const T & Z)
{
X xx;
//... Process XX using Y and Z
Return xx;
}
Optimized,
X Bar (const T & Y, const T & Z)
{
Return X (Y, Z );
}
This code, the compiler converts
// Pseudo C ++ code
Void
Bar (X & __ result)
{
_ Result. X: X (Y, Z );
Return;
}
This avoids calling the copy constructor and can directly call the constructor to generate the return value. However, the biggest problem is that you must define a new constructor to construct x using Y and Z, this will cause the wide spread of constructors for this special purpose. this method is not reliable
B. Compiler Optimization
X Bar ()
{
X xx;
//... Process xx
Return xx;
}
If the compiler is smart, it will automatically optimize it and replace xx with _ result directly. This eliminates the need to call the copy constructor later.
Void
Bar (X & __ result)
{
// Default constructor Invocation
// Pseudo C ++ code
_ Result. X: X ();
//... Process in _ result directly
Return;
}
This compiler optimization, sometimes referred to as the named return value (nrv) optimization.
This nrv optimization improves the efficiency of cases that require a large number of calls to this function.
However, such optimization is powerless for functions with complicated logic.