Effective C ++ 2e: Constructor, Destructor, and Assignment Operation

xiaoxiao2021-03-06  67

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 class namedptr {public: namedptr (const string & initname, t * initptr); ... private: string name; t * ptr;}; (because the object with pointer is possible when copying and assigning It will cause the pointer chaos (see Terms 11), namedptr must also implement these functions (see clause 2)) When writing the NAMEDPTR constructor, the parameter value must pass to the corresponding data member. There are two ways to achieve. The first method is to use member initialization list: Template Namedptr (const string & initname, t * initptr): name (initname), PTR (initptr) {} second method is constructed Functional Body Assignment: Template Namedptr (const string & initname, t * initptr) {name = initname; PTR = initptr;} There are significant differences. From the perspective of pure practical applications, in some cases, initialization must be used. In particular, const and reference data members can only be assigned with initialization. So, if you want the NamedPtr object to change its name or pointer member, you must follow the recommended statement of the Terms 21 as a const: template class namedptr {public: namedptr (const string & initname, t * initptr ); ... private: const string name; t * const ptr;}; definition of this class requires the use of a member to initialize a list, because const members can only be initialized and cannot be assigned. If the NamedPtr object contains a reference to an existing name, it will be very different. But still to initialize the reference in the initialization list of constructor. You can also declare const and reference at the same time, which generates a member of its name that can be modified outside the class and is within the interior.

Template class namedptr {public: namedptr (const string & initname, t * initptr); ... private: const string & name; // must initialize T * const ptr; / / must pass through member initialization list // Member initialization list // perform initialization}; however, the previous class template does not include const and reference members. Even in this way, the list of initialization is still better than the value in the constructor. This reason is efficient. When using a member to initialize a list, only one String member function is called. When it is assigned to the constructor, there will be two calls. To understand why, what happened when declaring the Namedptr object. The creation of the object is two steps: 1. Data member initializes. (See Terms 13) 2. Execute the action in which the invoked constructor is called. (For the object of the base class, the execution of member initialization and constructor of the base class occurs before the implementation of the members of the derived class, which means the NamedPtr class before the execution of the members of the derived class, which means the construction of the String object Name. The function is always called before the program execution of the Namedptr is already called. The problem is only: Which constructor is called in String? This depends on the list of members initialized by the NamedPtr class. If the initialization parameter is not specified for the NAME, the default constructor of String is called. When the name is assigned to the NAME in the constructor of the NamedPtr, the Operator = function is called. This has a total of two calls to String members: One is the default constructor, and the other is assignment. Conversely, if you initialize a list with a member to specify that Name must be initialized with initName, Name will be initialized by the copy constructor by the copy constructor. Even a very simple String type, unnecessary function calls can also cause high cost. As the class is getting bigger, it is increasingly complex, and their constructor is getting bigger and complicated, then the cost of object creation is also higher and higher. Developing habits that use Member initialization lists, not only meetings of consT and reference members, but also greatly reduce opportunities to initialize data. In other words, the initialization of the member initialization is always legal, and the efficiency is not less than the assignment in the constructor, it will only be more efficient. In addition, it simplifies the maintenance of class (see Terms M32), because if a data member is modified into a certain data type that must be initialized by the member, then nothing is used. In one case, the assignment of the class's data member is more reasonable with initialization. This is when there is a large number of fixed types of data to initialize in the same way in each constructor.

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 Class Array {public: array (int lowbound, int highbound); ... private: vector data; // array data stores in vector object / / About Vector Template See Terms 49 Size_t size; // A number of groups The number of medium elements int LBound, hBound; // lower limit, upper limit}; Template array :: array (int lowbound, int highbound): size (Highbound - Lowbound 1), LBound (lowbound), HBound (highbound), the data (size) {} constructor is legally checked to ensure that highbound is at least greater than or equal to Lowbound, but there is a bad error here: even if the array is legal, it is absolutely People will know how many elements will be in Data. "How is this possible?" I heard you called. "I carefully initialize Size to pass it to the constructor's constructor!" But unfortunately, you don't - you just want to do this, but not obey the rules of the game: class members are declared in the class The order is initialized, and they have no relationship with the order listed in the member initialization list. In the class generated by the Array Template above, DATA will always be initialized first, then Size, LBound, and HBound. It seems that it seems that it is common, but it is reasonable. Look at this situation: Class Wacko {public: WACKO (Const Char * S): S1 (s), S2 (0) {} WACKO (Const Wacko & RHS): S2 (rhs.s1), S1 (0) {} PRIVATE: STRING S1, S2;}; WACKO W1 = "Hello World!"; WACKO W2 = W1; if member is initialized in the order in which they appear on the initialization list, the order in W1 and W2 is created Will be different. We know that for all members of an object, their sequence of destructors is called, and they are always in the order in which they are created in the constructor. Then, if the above situation is allowed (ie, the member is initialized in the order in which they appear on the initialization list), the compiler will track the order of its members initialization for each object to ensure that their destructive functions are correct. The order is called. This will bring expensive overhead. Therefore, in order to avoid this opening, all objects of the same type are the same in the process of creating (constructed) and destroy (destructuring), regardless of the order in the initialization list. In fact, if you are studying, you will find that it is just an initialization of non-static data members to comply with the above rules. The behavior of static data members is a bit like a global and namespace object, so it will only be initialized (see Terms 47). In addition, the base class data is always initialized before the derived class data, so when inheriting, the initialization of the base class is initialized at the forefront of the member initialization list.

(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 // Base class template class array {// (from Terms 13) Public: array (int lowbound, int highbound); ~ array (); private: vector data; size_t size; int lbound, HBound;}; template Class NamedArray: public array {public: namedarray (int lowbound, int highbound, const string & name); ... private: string arrayname;}; if you are in an application Where you will point to the NameDarray type pointer to the Array type pointer, then use Delete to remove the Array pointer, then you will immediately fall into the trap of "Uncertain Behavior". NamedArray * PNA = New NamedArray (10, 20, "impending doom"); array * pa; ... pa = pna; // namedArray * -> array * ... Delete Pa; // Uncertain! In practice, PA-> ArrayName // will cause leaks, because * PA's nameDARRAY // will never be deleted in reality, this situation is better than you imagined frequently. Let an existing class do something, then derive a class to do the same thing, plus some special features, which is not uncommon in reality. NamedArray did not redefine any behavior of Array - it inherited all the features of Array without any modification - it just added some additional features. However, the problem of non-false argument functions still exists (there are other problems, see M33) Finally, it is worth noting that in some categories, the pure false prevention function is convenient. The pure virtual function will generate an abstract class - a class that cannot be instantiated (ie, this type of object cannot be created). Sometimes, you want a class to become an abstract class, but just there is no pure virtual function. How to do? Because the abstraction class is ready to be used, the base class must have a false preframe function. The pure virtual function generates an abstraction class, so the method is very simple: I want to be an abstract class to declare a pure epitope Structure function. Here is an example: Class AWOV {// AWOV = "Abstract w /// Virtuals" public: Virtual ~ AWOV () = 0; // Declare a pure false array function}; this class has a pure virtual function, So it is abstract, and it has a false patterned function, so it does not generate a destructive function problem.

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 // Template Class Namedptr {// (originating from Terms 12) public: NamedPtr (const string & initName, T * initPtr); NamedPtr & operator = (const NamedPtr & rhs); private: string name; T * ptr;}; template NamedPtr & NamedPtr :: operator = (Const Namedptr & RHS) {if (this == & r HS) Return * this; // See clause 17 // Assign to all data members name = rhs.name; // assignment value * PTR = * rhs .ptr; // For PTR, assignment value is the value referred to by the pointer, // is not a pointer itself return * this; // see the clause 15} Of course, it is important to remember the above principles, but equally important It is also necessary to remember the update assignment operator when adding new data members. For example, intend to upgrade the NamedPTR template allows the name to change a time tag, then add a new data member while need to update constructor and assignment operators. But in reality, this is often easily forgotten because of the specific functions of the upgrade class and add new member functions. When it comes to inherits, the situation is more interesting, because the assignment operator of the derived class must handle the assignment of its base class member! See below: Class Base {public: base (int initialvalue = 0): x (InitialValue) {} private: int x;}; class deive: public base {public: derived (int initialvalue): Base (InitialValue), Y (initialValue) {} Derived & operator = (const Derived & rhs); private: int y;}; logically, Derived assignment operator would look like this: // erroneous assignment operatorDerived & Derived :: operator = (const Derived & rhs) { IF (this == & r Hs) Return * this; // see clause 17 y = rhs.y; // Give virtual only // data member assignment return * this; // See clause 15} Unfortunately, it is The error, because the data member X of the DeriveD object's base portion is not affected in the assignment operator.

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 (* this) = rhs; / / Call Operator = y = rhs.y; return * this;} This weird code will be converted to BASE reference, and then assigns its conversion results. Here is just to assign a value for the BASE part of the DeriveD object. It is important to pay attention to that conversion is a reference to the Base object, not the base object itself. If * this is enforced to the Base object, it is necessary to cause the copy constructor to call the base, and the new object (see Terms M19) becomes the target of assignment, while * this remains unchanged. This is not the result you want. Regardless of the method used, after assigning the base part of the DeriveD object, it is followed by the assignment of the derived itself, that is, all the data members of Derived are assigned. Another similar problem related to frequently occurring and inheritance is to achieve a copy constructor of the derived class.

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.

转载请注明原文地址:https://www.9cbs.com/read-90366.html

New Post(0)