The constructor, the destructory function, and assignment operator have almost all classes have one or more constructor, a destructor and an assignment operator. This is not surprising, because they are all the most basic functions. Construct the basic operation of the function control object generation, and ensure that the object is initialized; the destructive function destroys an object and guarantees that it is completely cleared; assigning the operator gives an object a new value. In these functions, an error will bring endless negative impact on the entire class, so be sure to ensure its correctness. This chapter will guide how to use these functions to build a good structure. Terms 11: To see a copy constructor that needs to dynamically allocate memory, a copy constructor and an assignment operator look at the following a class representing the String object: // A simple string class class string {public: string (const char * value); ~ String (); ... // No copy constructor and operator = private: char * data;}; string :: string (const char * value) {if (value) {data = new char [strlen (value) 1]; strcpy (data, value);} else {data = new char [1]; * data = '/ 0';}} inline string :: ~ String () {delete [] data;} Please note this There is no declaration operator and copy constructor in the class. This will bring some adverse consequences. If you define two objects: string a ("Hello"); String B ("World"); its result is as follows: A: Data -> "Hello / 0" B: Data -> "World / 0 "The internal object A is a pointer to memory containing a string" hello ", and the internal direction of the object B is a pointer to memory containing a string" world ". If the following assignment is performed: b = a; because there is no custom Operator = can be called, C will generate and call a default Operator = operator (see Terms 45). This default assignment operator executes the assignment operation of the members of the members from A to B, which is a bit copying of the pointer (A.DATA and B.DATA). The results of assignment are as follows: A: DATA --------> "Hello / 0" / B: Data - / "world / 0" This is at least two problems. First, B-pointed memory will never be deleted, so it will be lost forever. This is a typical example of generating memory leakage. Second, now A and B pointers point to the same string, then as long as one of them leaves its living space, the destructive function deletes the block of the other pointer to point to.
String a ("Hello"); // Defines and constructs A {// Opening a new survival String B ("world"); // Defines and constructs B ... b = a; // Execute Operator =, // Lost b's memory} // Leave the living space, call // b, the destructor string c = a; // c.data's value cannot be determined! // A.Data has been removed in the last statement call The copy constructor is not defined in the class, C generates a copy constructor in the same way as the processing assignment operator and performs the same action: the pointer in the object is bitten. This will result in the same problem, but don't worry about memory leaks, because the object being initialized cannot point to any memory. For example, in the case of the code, there is no memory leak when C.DATA is initialized with A.DATA's value, as C.Data did not point anywhere. However, if C is initialized by A, C.Data and A.Data points to the same place, that place will be deleted twice: when C is destroyed at C, the other is destroyed in A. The case and the assignment operator is a bit different from the copy constructor. It will generate a problem when the value is called. Of course, as explained in Terms 22, rarely collect values to the object, but still look at the example below: void donothing (string localstring) {} string s = "the truth is out there"; donothing (s); Everything is normal. However, because of the delivered localstring is a value, it must initialize from S by (default) copy constructor. The Localstring has a copy of a pointer in one s. When Donothing ends, localstring leaves its living space and calls the destructor. The result will also be: S contains a pointer to the memory that point to LOCALSTRING. Incident, use Delete to delete a pointer that has been deleted, and the result is unpredictable. So even if S will never be used, it will also bring problems when it leaves its living space. Solution to solve this type of pointer confusion problem is that as long as there is a pointer in the class, you must write your own version of the copy constructor and assignment operator function. In these functions, you can copy those data structures that are pointed out, so that each object has its own copy; or you can use some reference counting mechanism (see Terms M29) to track how many objects currently point to one data structure. The reference to the count is more complex, and it requires more work inside the constructor and the designer function, but in some (although not all) programs, it will save a lot of memory and effectively improve the speed. For some classes, when the copy constructor and assignment operators are very troublesome, especially when it is confident that there is no copy and assignment operation in the program, it will be relatively unpaid.
The exempted copy constructor and assignment operator, it is a bad design, and what should I do when I realize them unrealistic? Very simple, the recommendations of the article: You can only declare these functions (declare for Private members) without defining (implementation). This prevents someone from calling them, but also prevents the compiler from generating them. For details on this playful tip, see Terms 27. Pay attention to the String class used in this Terfection. In the constructor, it is carefully used in the two calls new place [], although there is only a single object only. As the Terms 5 said, it must be used in the same form when supporting New and Delete, so it is done here. Be sure to pay attention to the use of DELETE when and only when the corresponding New is used [] []. Terms 12: Try to use initialization instead of assigning such a template in the constructor, it generates classes that generate a name and a pointer to a T type object. Template
Template
For example, there is a class that can be used here: Class ManyDataMBrs {public: // Default constructor MANYDATAMBRS (); // Copy constructor ManydataMBRS (Const ManyDataMBRS & X); Private: Int A, B, C, D, E, F, G, H; Double I, J, K, L, M;}; if all INT initializes all Double to 0, then use the member initialization list to write: Manydatambrs :: Manydatambrs (): A (1), B (1), C (1), D (1), E (1), F (1), G (1), H (1), I (0 ), J (0), K (0), L (0), M (0) {...} Manydatambrs :: MANYDATAMBRS (Const ManydataMBRS & X): A (1), B (1), C (1) , D (1), E (1), F (1), G (1), H (1), I (0), J (0), K (0), L (0), M (0) {...} This is not just an annoying and boring work, and it is easy to errors from the short term. It is difficult to maintain from a long time. However, you can use the fixed data type (non-Const, non-reference) object whose initialization and assignment does not operate different features, safely use a member initialization column to use a call to the normal initialization function. Class ManyDataMBRS {public: // Default constructor manydatambrs (); // copy constructor MANYDATAMBRS (Const ManydataMBRS & X); Private: Int A, B, C, D, E, F, G, H; Double I, J , K, L, M; Void init (); // for initialization data member}; void manDatambrs :: init () {a = b = c = D = f = g = h = 1; i = j = k = l = m = 0;} Manydatambrs :: bodydatambrs () {init (); ...} Manydatambrs :: portambrs (const portrative DataMBRS & x) {init (); ...} Because the initialization function is just a class Implement details, of course, to declare it as a private member. Note that Static class members will never initialize the constructor of the class. Static members are only initialized once in the process of running, so it doesn't make anything when it is created when the class object is created. At least this will affect efficiency: since it is "initialization", why do you want to do multiple times? Moreover, the initialization of static members is very different from non-static members, which has a special Terms M47 to illustrate. Terms 13: Initialization lists The order listed in the list and their sequentially stubborn PASCAL and ADA programmers in the sequence declared in the class often miss the function of the lower limit of the lower limit of the array of arbitraries, that is, the range of array subscripts Can be set to 10 to 20, not necessarily 0 to 10.
Senior C programmer will insist on counting from 0, but I want to meet this requirement to meet those people who use Begin / End, this only needs to define a self-owned Array class template: Template
(If you use multiple inheritance, the base class is initialized in the order of initialization and the order inherited by the derived class, and their order in the member initialization list will be ignored. There are many inheritances to use many places to consider. Terms 43 About more inheritance should be considered What are the issues of suggestions.) Basic one is: If you want to figure out how the object is initialized, please make sure that the order in your initialization list and the order of members in the category declaration . Terms 14: Determine the base class has a false prevention function, sometimes a class wants to track how many objects it has. A simple method is to create a static class member to count the number of objects. This member is initialized to 0, plus 1 in the constructor, and the destructive function is reduced. (Terms M26 illustrates how to encapsulate this method to easily add to any class, "My Article on Counting Objects" provides another improvement to this technology) to imagine that there is one in a military app. represents enemy target class: class EnemyTarget {public: EnemyTarget () { numTargets;} EnemyTarget (const EnemyTarget &) { numTargets;} ~ EnemyTarget () {--numTargets;} static size_t numberOfTargets () {return numTargets; } Virtual Bool Destroy (); // After destroying the enemyTarget object // Returns success private: static size_t numTargets; // Object counter}; // static member to define external definitions; // Default is initialized to 0Size_t EnemyTarget: : NumTarget; this class won't win a government defense contract, which is too far from the Ministry of Defense, but it is enough to meet the needs of our explanation. The enemy's tank is a special enemy goal, so it will naturally think of abstracting it into a class that is derived from EnemyTarget in public inheritance (see Terms 35 and M33). Because not only cares about the total number of enemy goals, it is also necessary to care about the total number of enemy tanks. So, like the base class, in the derived class, the same skills mentioned above: Class Enemytank: Public EnemyTarget {public: Enemytank () { numTanks;} EnemyTank (const EnemyTank & rhs): EnemyTarget (rhs) { numTanks;} ~ EnemyTank () {--numTanks;} static size_t numberOfTanks () {return numTanks;} virtual bool destroy (); private: Static size_t nuMtanks; // Tank Object counter}; (After writing the above two classes, you can understand the General Solutions for this issue.) Finally, assume that the program is used to use New Dynamics Created a Enemytank object, then delete it with Delete: EnemyTarget * targetptr = New Enemytank; ... Delete targetptr; everything to do so, is normal: two classes in the destructor The operation made is cleared; the application is obviously not wrong, and the object generated with new will be deleted in the end. However, there is a big problem here.
The behavior of the program is unpredictable - can't know what will happen. C language standards About this problem is very clear: This means that the code generated by the compiler will do anything it like: re-format your hard drive, send your boss to send your own email, send your program source code to your opponent, no matter what . (I often happen actually, the destructor of the derived class will never be called. In this case, this means that when TargetPtr is deleted, the number of Enemytank will not change, then the number of enemy tanks is wrong. What consequences will be caused to avoid this problem with highly dependent on accurate information?) Only need to make the EnemyTarget's destructor is Virtual. The declaration of the patterned function will bring you a good behavior that you want: Object memory is released, the destructor of the Enemytank and EnemyTarget is called. Like most of the base classes, the EnemyTarget class now contains a virtual function. The purpose of virtual functions is to let the derive class to customize their behavior (see Terms 36), so almost all base classes contain virtual functions. If a class does not contain a virtual function, it is usually used to use it as a base class. When a class is not prepared as a base class, the destructive function is generally a bad idea. Refer to the example below, this example is based on a special discussion of the book book book by ARM ("The Annotated C Reference Manual). // A class of classes that represent 2D points Class Point {public: Point (Short Int Xcoord, short int ycoord); ~ Point (); private: short int x, y;}; if a short int is 16 bits, a point object It is just suitable for placing a 32-bit register. In addition, a POINT object can be used as a 32-bit data transfer to a function written in other languages such as C or Fortran. However, if the destructor of Point is virtual, the situation changes. Implementing the virtual function requires some additional information to enable the object to determine which virtual function of the call when the object is running. For most compilers, the specific form of this additional information is a pointer called VPTR (virtual function table pointer). VPTR points to a function pointer array called VTBL (virtual function table). Each class with virtual functions comes with a VTBL. When requesting a virtual function of an object, the actually called function is determined based on the VPTR to the VTBL to find the corresponding function pointer in VTBL. Detail of virtual function is not important (of course, if you are interested, you can read the terms M24), it is important that if the Point class contains a virtual function, its object's volume will not know unconsciously, from 2 16-bit SHORT became 2 16-bit Short plus a 32-bit VPTR! Point objects can never be placed in a 32-bit register. Moreover, the POINT object in C does not have the same structure as the other language as declared in C, because there is no VPTR in these languages. So, use other language writes to pass the Point is no longer possible, unless it is designed to design VPTR, which itself is implemented, which will cause the code to not be ported. So the basic one is that no reason is wrong, and never declares. In fact, many people summarize: When and only when the class contains at least one virtual function, the false argument function is declared.
This is a good guideline, most of which apply. But unfortunately, when there is no virtual function in the class, it will also bring non-false argument function. For example, the clause 13 has a class template that implements the upper limit of the user's custom array. Assuming you (advice on the Terms M33) decided to write a derived class template to represent some group of arrays (ie, there is a name per array). Template
But there is still one thing: the definition of the pure false arrangement function must be provided: AWOV :: ~ AWOV ()} // The definition of the pure false aromatic function This definition is required, because the mode of the false prevention function is : The description function of the most underlying derived class is first called, and then the destructor of each base class is called. That is to say, even if it is an abstract class, the compiler also produces a call to ~ AWOV, so to ensure that it provides a function body. If you don't do this, the linker will detect, and finally I have to go back to add it. You can do anything in a function, but just as the above example, nothing is not common. If this is the case, it naturally thinks that the destructor declares as an inline function, thereby avoiding the overhead of the call to an empty function. This is a good way, but there is one thing to be clear. Because the designer function is the virtual, its address must enter the VTBL of the class (see Terms M24). However, the inline function is not existing as a separate function (this is the meaning of "inline"), so they must be obtained with special methods. Terms 33 have a comprehensive introduction to this, and its basic point is that if the false argument function is inline, it will avoid calling the expenses generated, but the compiler is still inevitable to generate a copy of this function. Terms 15: Let Operator = Return * This reference C designer Bjarne Stroustrup under great effort wants to customize the user's custom type as similar to the fixed type of work. That's why you can overload the operator, write type conversion functions (see Terms M5), control assignment and copy constructor, and so on. He did so much effort, then you should continue to do it. Let us see assignment. In the case of a fixed type, the assignment operation can be chained below: int W, x, y, z; w = x = y = z = 0; so you should also you can customize the type of assignment of the user Get up: String W, X, Y, Z; // String is the type // (see Terms 49) w = x = y = z = "Hello"; assignment operator The binding is naturally left, so the above assignment can be parsed to: w = (x = (y = (z = "))); it is worthwhile to write it into a complete equivalent function. Unless it is a Lisp programmer, the following example will be very happy because it defines a prefix operator: w.operator = (x.operator = (Y.operator = ("Hello" )))))); This format is very illustrative because it emphasizes W.Operator =, X.Operator = and Y.Operator = parameters are the return value of the previous Operator = call. So the return value of Operator = must be able to be accepted as an input parameter.
In a class C, the default version of the Operator = function has the following form (see Terms 45): C & C :: Operator = (Const C &); Under normal circumstances, almost always follow the Operator = input and return all kinds of objects The principle of reference, however, it is sometimes overloading Operator = to accept different types of parameters. For example, the standard String type provides two different versions of assay operators: string & // gives a stringOperator = (const string & r HS); // to a stringstring & // will be a char * Operator = (const char * rhs); // Give a String, please note that the return type is also a reference to the object of the class even when overloaded. One error that C programmers often makes Operator = returns Void, which seems to be unreasonable, but it hinders continuous (chain) assignment operation, so don't do this. Another common mistake is to let Operator = reference to a const object, like this: class widget {public: ... const widget & operator = (const widget & rhs); ...}; doing this is usually to prevent In the program, the following stupid operations: Widget W1, W2, W3; // W2 assignment W1, then W3 is assigned to the result // (give Operator = a const Back Value // This statement cannot be compiled) This may be very stupid, but the fixed type is not stupid: INT I1, I2, I3; ... (i1 = i2) = I3; // Legal! I2 Give I1 // then assign it to I1! This kind of practice is rarely seen, but it is ok to int, which can be used for me and my class. Then it should be able to you and your class. Why is it not compatible with no reason and fixed types of regular practices? In the assignment operator defined by the default form, there are two obvious candidates: the object left on the left side of the assignment statement (the object pointing to the THIS pointer) and the object on the right side of the assignment statement (the object named in the parameter table) ). Which one is correct? For example, a String class (assuming that you want to write assignment operators in this class, see two possibilities: string & string :: operator = (const string & rings) {... return * this ; // Return the object} string & string :: operator = (const string & rings) {... return rhs; // Return to the object} to you, this is like a six one and twelve Half is more difficult. In fact, they have a big difference.
First, the version returned to RHS will not be compiled because RHS is a reference to const string, and Operator = to return a string reference. When you want to return a non-Const's reference and the object itself is constant, the compiler will bring you endless pain. It is easy to solve this problem - only use like this to re-declare Operator =: string & string :: operator = (string & rings) {...} This time I use the application that uses it cannot be compiled! Then look at the back section of the initial continuous assignment statement: x = "Hello"; // and x.op = ("Hello"); the same because the right side of the assignment statement is not the correct type - it is a character array, Not a string - compiler to generate a temporary String object (via the Stirng constructor - see the Terms M19) causes the function to continue running. That is to say, the compiler must generate a rough code: const string temp ("Hello"); // Generate temporary stringx = Temp; // Temporary string is transmitted to the Operator = compiler generally produces such a temporary value (unless Explicitly define the required constructor - see clause 19), but pay attention to the temporary value is a const. This is important because it prevents temporary values that are passed to the function. Otherwise, the programmer will be very strange to find that only the temporary value generated by the compiler can be modified and the parameters that actually pass in when the function is called. (About this, there is a fact that the early version of C allows this type of temporary value to be generated, transmitted, modified, and the resultman is very strange) Now we can know if the string operator = declaration passes a non- Const's stirng parameters, the application cannot be compiled: For functions that do not declare the corresponding parameters for const, it is illegal. This is a very simple rule about const. Therefore, the conclusion is that you will not choose: When defining your assignment operator, you must return the reference to the left argument of the assignment operator, * this. If it does not do this, it will result in a continuous assignment, or if the implicit type conversion when the call is invoked, or both cases occur. Terms 16: In Operator = Assignment Terms 45 for all data Members 45 illustrates that if you don't write an assignment, the compiler will generate one for you. Terms 11 illustrate why you often don't like the compiler to generate you. This assignment operator, so you will want to have a good way to make the compiler generate a default assignment operator, and then you can selectively rewrite the parts that don't like. This is impossible! As long as you want to control a part of the assignment process, you must be responsible for all things in the assignment process.
In actual programming, this means writing assignment operators, must assign each data member of the object: Template
For example, consider the following code segment: void assignmenttester () {Derived D1 (0); // D1.x = 0, D1.Y = 0 Derived D2 (1); // d2.x = 1, d2.y = 1 D1 = D2; // D1.x = 0, D1.Y = 1!} Please note that the BASE portion of D1 is not assigned operation. The most obvious way to solve this problem is to assign the X in Derived :: Operator =. But this is not legal because X is private member of Base. So you must explicitly assign a value to the Derive's base part in the Deerive's assignment operator. That is to do: // Correct assignment operator Derived & Derived :: Operator = (const derived & r Hs) {if (this == & r Hs) Return * this; Base :: Operator = (rhs); // Call this-> Base :: Operator = y = rhs.y; return * this;} This is just explicitly calling Base :: Operator =, this call and general case in the member function calls another member function, with * THIS As its implicit left value. Base :: Operator = Perform all the works of the BASE part of * this - just as what you want. However, if the base class assignment operator is generated by the compiler, some compilers will reject this call for the base class assignment operator (see Terms 45). In order to adapt to this compiler, you must implement Derived :: Operator =: Derived & Derived :: Operator = (this == & r HS) Return * this; static_cast
Take a look at the constructor, its code and the above-mentioned similar: Class base {public: base (int initialvalue = 0): x (const base & rhs): x (rhs.x) {} Private: int x;}; class deact: public base {public: derived (int initialValue): Base (InitialValue), Y (InitialValue) {} Derived (const derived & rhs) // Error copy: y (rhs.y) {} // Constructor private: int y;}; class DeriveD exhibits a bug generated in all C environments: When DeriveD is created, there is no copy of the base class. Of course, this DeriveD object's base section is still created, but it is created with the BASE default constructor, member X is initialized to 0 (the default parameter value of the default constructor), and does not take care of the copy object What is the x value! To avoid this problem, the Derive copy constructor must ensure that the call is the base constructor of the base instead of the BASE default constructor. This is easy to do, as long as the member initialization list is specified in the list in the member of the Derived copy constructor: Class Derived: Public Base {public: Derived (const derived & rhs): base (rhs), y (rhs.y) {} ...}; Now, when you use an existing same type of object to copy the creation of a DeriveD object, its base section will also be copied. Terms 17: Check the situation like the following things in Operator =, you will have you assign you to your own value: Class X {...}; x a; a = a; // a assignment It is very boring, but it is completely legitimate, so it is a matter of concern to seeing this programmer. More importantly, it can appear more concealed in the case where you assign a value: a = b; if B is another name of A (for example, it has been initialized into a reference), then this It is also assigned to yourself, although it looks not like the surface. This is an example of an alias: there are more than two names in the same object. The final meeting will be seen in this Territor, the alias can appear in a large number of arbitrary forms, so it is necessary to take into account it when writing functions. Special attention in the assignment operator may have an alias that may have an alias, which is based on two points. One of them is efficiency. If you can detect the value of the assignment of the operator, you can return it immediately, so that you can save a lot of work, otherwise you must implement the entire assignment operation. For example, clause 16 pointed out that a correct derived assignment operator must call the assignment operator of each base class, so the operation of omitting the value of the operator in the derived class will avoid a lot of other functions. transfer. Another more important reason is to ensure the correctness. An assignment operator must first release an object's resource (removed the old value) and then assign new resources based on the new value. In the case of assigning yourself, the release of the old resource will be disastrous because old resources are required when allocating new resources.
See the assignment of the String object below, the assignment operator does not check the case to assign yourself: Class string {public: string (const char * value); // function definition See Terms 11 / / ~ String (); // Function Definition See Terms 11 // ... String & Operator = (const string & r Hs); private: char * data;}; // Ignore the case // of the assignment of the value String & string :: operator = (const) String & rhs) {delete [] data; // delete old memory // Assign new memory, copy RHS value to it Data = new char [strlen (rhs.data) 1]; struct (data, rhs.data) Return * this; // See Item 15} See what will happen below: string a = "hello"; a = a; // Same as a.operator = (a) assignment operator interior, * This and RHS seem to be different objects, but in this case they happen to be the same name of the same object. It can be used to indicate this: * this data ------------> "Hello / 0" / / rhs data ---- "The first thing to assign the operator does use Delete Delete DATA, the result will be as follows: * this data ------------> ??? / / rhs data ----- Now, when the assignment operator calls Strlen When the results will not be determined. This is because the DATA is deleted, rhs.data is also deleted, DATA, THIS-> DATA and RHS.DATA are actually the same pointer! From this point, the situation will only become more and more bad. It is now possible to solve the problem of the problem is to check the case that may happen first, if you have this situation, return it now. Unfortunately, this kind of check is easy to do, because you must define how two objects are "the same". The problem you face is academically called Object Identity, which is a very famous topic in the object-oriented field. This book is not a place to describe Object Identity, but it is necessary to mention two basic methods to solve this problem. One way is that if the two objects have the same value, they are the same (with the same identity). For example, if both String objects are represented by the same sequence character sequence, they are the same: string a = "hello"; string b = "world"; string c = "hello"; A and C have the same value, Therefore, they are considered exactly the same; B and they are different.
If this definition is used in the String class, the assignment operator looks like this: string & string :: Operator = (const string & r Hs) {IF (strcmp (data, rhs.data) == 0) Return * this; ..} value is typically detected by Operator ==, so for a class C that detects the identity of the object, its general form of the assignment operator is: C & C :: Operator = (Const C & RHS) {// Check the case if you assign you if (* this == rhs) // hypothesis Operator = there is Return * this; ...} Note that this function is object (via Operator =) instead of a pointer. Use a value equal to determining the identity of the object identity and whether the same memory is used in the same memory; there is a relationship only the value they represent. Another way to determine if the identity is the same is to use a memory address. This definition is used, and two objects are the same when they have the same address. This definition is widely used in the C program, which may be because it is easy to implement and the calculation is fast, and the definition of the use value is not necessarily with these two advantages. Using the definition of the address, a normal assignment operator looks like this: C & C :: Operator = (const c & rhs) {// Check the case IF (this == & r HS) return * this; .. It applies to many programs. If a more complex mechanism is required to determine if the two objects are the same, this is to achieve it by the programmer. The most common method is to implement a member function that returns an object identifier: Class C {public: ObjectId Identity () const; // See Terms 36 ...}; For two objects pointers A and B, and only When A-> Identity () == b-> iDENTITY (), the object they refer is exactly the same. Of course, you must implement ObjectIDS's Operator ==. Alias and Object Identity's issues are not only limited to Operator =. It may be encountered in any of the functions used. In the case of reference and pointer, any two compatible types of object names may refer to the same object. The other scenarios are listed below: Class Base {Void MF1 (Base & RB); // RB and * this may be the same ...}; Void F1 (Base & RB1, Base & RB2); // RB1 and RB2 possible Same // Class Derived: Public Base {Void MF2 (Base & RB); // RB and * This may be the same // ...}; int F2 (Derived & RD, Base & RB); // RD and RB may be the same ///// These examples just use the reference, the pointer is the same. It can be seen that alias can appear in various forms, so don't forget it or expect you to never touch it. Maybe you won't touch it, but most of us will encounter. Obviously one is that processing it will reach the effect of halving.