Chapter 2 class constructor, destructor, and assignment function
Constructors, destructor, and assignment functions are the most basic functions of each class. They are so common that they are easy to paralyze. In fact, these seemingly simple functions are as dangerous as the sewers without the top cover.
Each class has only one destructor and one value assignment function, but it can have multiple Constructors (including one copy constructor, and others are called normal constructor ). If you do not want to write the above functions for any class A, the C ++ compiler will automatically generate four default functions for Class A, such as a (void ); // default non-parameter constructor A (const A & A); // default copy constructor ~ A (void); // default destructor A & operate = (const A & A); // default value assignment function
This is confusing why functions can be automatically generatedProgramPrepared by members? The reasons are as follows: (1) if "Default non-parametric constructor" and "Default destructor" are used, the chance of independent "initialization" and "Clearing" is abandoned, the good intentions of C ++ inventor stroustrup are in vain. (2) Both the "Default copy constructor" and "Default Value assignment function" are implemented by "bit copy" instead of "value copy, if the class contains pointer variables, these two functions are doomed to go wrong.
For C ++ programmers who have not suffered enough, if he says it is easy to write constructor, destructor, and assignment function, he does not have to worry about it, indicating that his understanding is superficial, the level needs to be improved. This chapter takes the design and implementation of the string class as an example to explain in depth the principles ignored by many textbooks. The string structure is as follows: Class string {public: string (const char * STR = NULL); // The string (const string & Other) of the common constructor; // copy the constructor ~ String (void); // destructor string & operate = (const string & other); // value assignment function PRIVATE: char * m_data; // used to save the string }; 9.1 The origins of constructor and destructor as a more advanced language than C, C ++ provides a better mechanism to enhance program security. The C ++ compiler has a strict type security check function, which can almost find all the Syntax problems in the program, which indeed helps programmers. However, after the program passes the compilation check, it does not mean that the error does not exist. In the "error" family, the "syntax error" status can only be regarded as a younger brother. High-level errors are often hidden in depth, just like a sly criminal. It is not easy to catch him. Based on experience, many program errors that are hard to detect are caused by the variable being correctly initialized or cleared, and initialization and cleanup are easily forgotten. Stroustrup fully considers this problem when designing the C ++ language and solves it well: Put the initialization work of the object in the constructor, and put the cleanup work in the destructor. When an object is created, the constructor is automatically executed. When an object dies, the Destructor is automatically executed. You don't have to worry about object initialization and cleanup. The name of the constructor and the Destructor cannot start at will, and must be recognized by the compiler before it can be automatically executed. The naming method of stroustrup is simple and reasonable: the constructor and destructor have the same name as the class. because the purpose of the constructor is opposite to that of the constructor, the prefix '~ 'To show the difference. Except for the name, the constructor and the Destructor have no return value type, which is different from the void function. The mission of constructor and destructor is very clear, just like birth and death. If they have return value types, the compiler will be overwhelmed. In order to prevent out-of-the-box branches, it is simply specified that there is no return value type. (The above reference to the document [eekel, p55-p56]) 9.2 constructor initialization table constructor has a special initialization method called "initialization expression table" (referred to as the initialization table ). The initialization table is located after the function parameter table, but before the function body. This indicates that the initialization work in this table occurs in anyCode Before execution.
Usage rules of the constructor initialization table: * If the class has an inheritance relationship, the derived class must call the constructor of the base class in its initialization table. For example, Class {... A (int x); // constructor of a}; Class B: Public {... B (int x, int y); // constructor of B}; B: B (INT X, int y): A (x) // call the constructor {… of a in the initialization table {... } * The const constant of the class can only be initialized in the initialization table, because it cannot be initialized using the value Assignment Method in the function body (see section 5.4 ).
* Class data members can be initialized using the initialization table or function body assignment method. The efficiency of the two methods is different. Non-Internal data type member objects should be initialized in the first way to achieve higher efficiency. For example, Class {... A (void); // const A & Other) without parameters; // copy constructor A & operate = (const A & other ); // value assignment function };
Class B {public: B (const A & A); // constructor private: A M_a; // member object };
In Example 9-2 (a), the constructor of Class B calls the copy constructor of Class A in its initialization table to initialize the member object M_a. In Example 9-2 (B), the constructor of Class B initializes the member object M_a by assigning values in the function body. We only see a value assignment statement, but in fact, the constructor of B does two things: Create the M_a object in the dark (call the non-parameter constructor of ), call the value assignment function of Class A to assign parameter A to M_a.
B: B (const A & A): M_a (){... } Example 9-2 (a) The member object is initialized in the initialization table
B: B (const A & A) {M_a = ;... } Example 9-2 (B) the member object is initialized in the function body
For data members of the internal data type, the efficiency of the two initialization methods is almost the same, but the latter program layout seems clearer. If the declaration of Class F is as follows: Class F {public: F (int x, int y); // constructor PRIVATE: int m_x, m_y; int m_ I, m_j ;} in Example 9-2 (c), the F constructor adopts the first initialization method. In Example 9-2 (d), the F constructor adopts the second initialization method. F: F (int x, int y): m_x (x), m_y (y) {m_ I = 0; m_j = 0 ;}
Example 9-2 (c) The data member is initialized in the initialization table
F: F (int x, int y) {m_x = x; m_y = y; m_ I = 0; m_j = 0;} Example 9-2 (d) data members are initialized in the function body.
9.3 structure and structure order construction start from the root of the class hierarchy. In each layer, the base class constructor is called first, and then the member object constructor is called. The structure is executed strictly in the order opposite to the structure. This order is unique. Otherwise, the compiler cannot automatically execute the structure process. An interesting phenomenon is that the initialization order of member objects is not affected by their order in the initialization table. It is determined only by the Order declared by the member objects in the class. This is because the class declaration is unique, and the class constructor can have multiple, so there will be multiple initialization tables in different order. If the member object is constructed in the order of the initialization table, the Destructor cannot obtain a unique reverse order. [Eckel, p260-261]
9.4 example: constructor of the string type and normal constructor of The Destructor // string: string (const char * Str) {If (STR = NULL) {m_data = new char [1]; * m_data = '';} else {int length = strlen (STR); m_data = new char [Length + 1]; strcpy (m_data, str) ;}} // string destructor string ::~ String (void) {Delete [] m_data; // Since m_data is an internal data type, you can also write it as delete m_data ;} 9.5 do not underestimate the copy constructor and assign value function. Since not all objects use copy constructor and assign value function, programmers may despise these two functions. Remember the following warning first, and read the text with caution:
* As mentioned at the beginning of this chapter, if you do not actively write the copy constructor and the value assignment function, the compiler will automatically generate the default function in "bit copy" mode. If the class contains pointer variables, the two default functions are implicitly incorrect. Take the two objects A and B in the string type as an example. Assume that the content of A. m_data is "hello", and the content of B. m_data is "world ".
Now assign A to B. The "bit copy" of the default value assignment function means that B. m_data = A. m_data is executed. This will cause three errors: B. m_data memory is not released, causing memory leakage; B. m_data and. m_data points to the same memory. Changes to either of A or B will affect the other. Third, m_data is released twice when the object is destructed. The copy constructor and the value assignment function are very confusing and often lead to incorrect writing and usage. A copy constructor is called when an object is created, and a value assignment function can only be called by an existing object. In the following program, the third statement is very similar to the fourth statement. Do you know which one calls the copy constructor and which one calls the value assignment function?
String A ("hello"); string B ("world"); string c = A; // call the copy constructor. It is best to write it as C (a); C = B; // The Value assignment function is called. In this example, the third statement has a poor style and should be rewritten to string C (A) to distinguish it from the fourth statement.
9.6 example: copy constructor and value assignment function of the string class // copy constructor string: string (const string & other) {// The Private member m_data int length = strlen (Other. m_data); m_data = new char [Length + 1]; strcpy (m_data, other. m_data) ;}// value assignment function string & string: operate = (const string & Other) {// (1) Check auto-assigned if (this = & other) return * This; // (2) release the original memory resource Delete [] m_data; // (3) allocate new memory resources and copy the content int length = strlen (Other. m_data); m_data = ne W char [Length + 1]; strcpy (m_data, other. m_data); // (4) return the reference return * This;} the difference between the class string copy constructor and the common Constructor (see section 9.4) is: you do not need to compare it with null at the function entrance, because "Reference" cannot be null, and "Pointer" can be null. The value assignment function of the string type is much more complex than the constructor. It is implemented in four steps: (1) The first step is to check the self-assignment. You may think that, in this case, someone will be stupid enough to write an auto-assigned statement like a =! No. However, indirect auto-assigned values may still appear, for example
// Content self-assigned value B = ;... C = B ;... A = C;
// Address Auto-assigned B = & ;... A = * B; some people may say, "even if there is a self-assigned value, I can ignore it. If it is a big deal, let the object copy itself. It will not make any mistakes !" He is really wrong. Check the delete in step 2. Can I copy myself after suicide? Therefore, if the auto-assigned value is found, the function should be terminated immediately. Do not mistakenly write the IF (this = & Other) statement that checks the self-assigned values into if (* This = Other) (2) Step 2, use Delete to release original memory resources. If it is not released now, there will be no chance in the future, which will cause memory leakage. (3) Step 3: allocate new memory resources and copy strings. Note that the strlen function returns a Valid String Length, excluding the terminator ''. The strcpy function is reconnected.
(4) Step 4: return the reference of this object to implement chained expressions like a = B = C. Do not write the return * this error as return this. Can it be written as return other? Isn't the same effect?
No! Because we do not know the lifecycle of the parameter Other. It is possible that other is a temporary object, and it disappears immediately after the assignment is complete, then return other will return garbage.
9.7 What should we do if we really don't want to write a copy constructor or a value assignment function and do not allow others to use the default function generated by the compiler? The method of laziness is to declare the copy constructor and the value assignment function as a private function without writing code. Example: Class {... PRIVATE: A (const A & A); // private copy constructor A & operate = (const A & A); // Private value assignment function }; if someone tries to write the following program: a B (a); // calls the private copy constructor B = A; // the compiler that calls the private value assignment function will indicate an error, because the outside world cannot operate a's private function. 9.8 how to implement the basic function base class constructor, destructor, and value assignment functions in a derived class cannot be inherited by the derived class. If there is an inheritance relationship between classes, pay attention to the following when writing the above basic functions:
* the constructor of the derived class should call the constructor of the base class in its initialization table. The destructor of the base class and derived class should be virtual (that is, virtual)