EffectiveC ++ clauses 10 to 12, and effectivec clauses
Clause 10: Make operator = return a reference to * this
This clause is very simple. Let's explain why there is a return value after the assignment. This is because we can use a connection like this:
int x, y, z; x = y = z;
Note that the equal signs are combined with the right, so the above calculation is actually equivalent:
x = (y = z);
If no value is returned, the above Code cannot be compiled.
As to why a reference is returned, this is not mandatory by the compiler, but there are three reasons for you to think it is wise to do so:
(1) returning a reference can save resources and it is not necessary to call the constructor for the returned value;
(2) the compiler can accept connections such as (x = y) = 3, because such a writing requires the value assignment operator to return a modifiable left value;
(3) The standard code in STL and boost libraries is written in this way. To be compatible with them, give up your unconventional ideas.
Similarly, + =,-=, and other assignment-related operations, it is best to return your own reference.
class Widget { public: Widget& operator=(const Widget& rhs) { ... return *this; } };
Class Widget {public :... widget & operator + = (const Widget & rhs) {// this Protocol applies to + =,-=, * =, etc... return * this;} Widget & operator = (int rhs) {// this function also applies, even if the parameter type does not comply with the rules... return * this ;}};
In a word, the value assignment operator returns a reference to * this.
Clause 11: handle self-assignment in operator =
The intuitive operator = is defined as follows:
class SampleClass{private: int a; double b; float* p;public: SampleClass& operator= (const SampleClass& s) { a = s.a; b = s.b; p = s.p; return *this; }};
Defines a WebBrower class, which clears the browser, including clearing the cache, clearing history records, and clearing Cookies. Now we need to package these three functions into a function, this function executes all the cleanup tasks. Does it put this cleanup function inside the class or outside the class?
If it is placed in the class, it is like this:
class SampleClass{private: int a; double b; float* p;public: SampleClass& operator= (const SampleClass& s) { a = s.a; b = s.b; delete p; p = new float(*s.p); return *this; }};
The general idea is to delete the old content pointed to by the pointer, and then use the pointer to point to a new space. The content of the space is filled with the content pointed to by s. p. But there are two things that will cause the code to crash. One is the "self-assignment" mentioned in these terms ". The reader may think about it. If this is the case:
SampleClass obj;
Obj = obj;
What happened. When the value assignment statement is executed, it is detected that obj. p has been pointed to. In this case, the space content pointed to by obj. p is released, but the next sentence is:
P = new float (* s. p );
Note:* S. p will cause the program to crash, because s. p is obj. p.Obj. p (based on the priority, which is equivalent(Obj. p), obj. p has been released in the previous sentence, so such an operation may have a bug.
Maybe the reader does not agree and thinks it is impossible for the user to write code such as obj = obj. In fact, it is true that obvious mistakes are unlikely to be made, but in case of writing one:
SampleClass obj; …SampleClass& s = obj;… s = obj;
SmapleClass* p = &obj; … *p = obj;
This error is not so intuitive, and even * pa = * pb may also cause problems, because pa and pb may point to the same address space. An error occurs when you assign a value to yourself. Never assume that you do not need to write a self-assignment statement.
Only one sentence is needed to resolve the self-assignment:
Class SampleClass {private: int a; double B; float * p; public: SampleClass & operator = (const SampleClass & s) {if (this = & s) return * this; // solve the self-assigned sentence a = s. a; B = s. b; delete p; p = new float (* s. p); return * this ;}};
However, the following program
Class SampleClass {private: int a; double B; float * p; public: SampleClass & operator = (const SampleClass & s) {if (* this = s) return * this; // note the differences in condition judgment, so there is a problem! A = s. a; B = s. B; delete p; p = new float (* s. p); return * this ;}};
This is wrong, because = is often used to determine whether each member variable in the object is the same, rather than whether the addresses overlap. Therefore, this = & s can be used to capture from the address whether it is actually a self-assigned value.
This can solve the first problem mentioned above: Self-assignment. In fact, another problem may cause the code to crash. Imagine if p = new float (* s. p) What should I do if the space cannot be allocated normally? What should I do if an exception is thrown suddenly? This will cause the content of the original space to be released, but the new content cannot be properly filled. Is there a good way to keep the original content unchanged when an exception occurs? (It can improve program robustness)
There are two ideas:
SampleClass & operator = (const SampleClass & s) {if (this = & s) return * this; // you can delete a = s. a; B = s. b; float * tmp = p; // The old pointer p = new float (* s. p); // apply for a new space. If the application fails, p still points to the original address space delete tmp; // if the application fails, the application is successful, return * this;} can be deleted from the old content ;}
The general idea is to save the old one and try again to apply for a new one. If there is a problem with the application, the old one can be saved. Here, we can delete the first sentence, because "giving operator exceptional security will automatically get the reward of self-assigned security ".
Another idea is to apply for a new space and fill in the content with a temporary pointer. If there is no problem, release the space pointed to by the local pointer, finally, point the local pointer to this temporary pointer, as shown in the following code:
SampleClass & operator = (const SampleClass & s) {if (this = & s) return * this; // you can delete a = s. a; B = s. b; float * tmp = new float (* s. p); // use a temporary pointer to apply for a space and fill in the content delete p; // if this step can be reached, the space is successfully applied, you can release the space p = tmp pointed to by the local pointer; // point the local pointer to the temporary pointer return * this ;}
The above two methods are both feasible, but pay attention to copying the code in the constructor and the code repetition. Imagine if you add a private pointer variable to the class at this time, the code above, there are also similar code in the copy constructor, which must be updated. Is there a way to do this once and for all?
This book provides the final solution:
SampleClass& operator= (const SampleClass& s){ SampleClass tmp(s); swap(*this, tmp); return *this;}
In this way, the burden is handed over to the copy constructor to ensure code consistency. If a problem occurs in the copy constructor, for example, the following swap function will not be executed because the local variable remains unchanged.
A further optimization scheme is as follows:
SampleClass& operator= (const SampleClass s) { swap(*this, s); return *this; }
Note that the reference of the form parameter is removed here, and the task of applying for temporary variables is placed on the form parameter, which can optimize the code.
Summary:
(1) Ensure that operator = has good behavior when the object is assigned a value to itself, the technology includes comparing the addresses of "source object" and "target object", careful statement order, and copy-and-swap;
(2) determine if any function operates on more than one object, and if multiple objects are the same object, its behavior is still correct.
Clause 12: Do not forget every component of an object when copying it.
This statement contains two parts: the first part is to take into account all member variables, especially those added later. The corresponding copy constructors and value assignment operators should be updated in a timely manner; the second part is not to forget the replication of the base class when inheritance exists. Let's take a look at the meaning of the first part. For example:
class SampleClass{private: int a;public: SampleClass(const SampleClass& s):a(s.a) {}};
Here is an example of copying a constructor. The assignment operator is similar. If a member variable, such as double B, is added at this time, the copy constructor and the value assignment operator must be updated accordingly (the constructor must also be updated, but the constructor will not be forgotten, while the copy constructor and the value assignment operator are often forgotten ). Like this:
class SampleClass{private: int a; double d;public: SampleClass(const SampleClass& s):a(s.a),d(s.d) {}};
Let's look at the meaning of the second part. When there is an inheritance relationship:
class Derived: public SampleClass{private: int derivedVar;public: Derived(const Derived& d):derivedVar(d.derivedVar){}};
In this way, it is easy to miss the part of the base class. As a result, the base class is not copied normally and should be changed to the following:
class Derived: public SampleClass{private: int derivedVar;public: Derived(const Derived& d):SampleClass(d), derivedVar(d.derivedVar){}};
The overload of the value assignment operator should be written as follows:
Derived& operator=(const Derived& d){ SampleClass::operator=(d); derivedVar = d.derivedVar; return *this;}
As you can see, the value assignment operator overload has a high degree of similarity with the code of the copy constructor, but the book says, "Do not try to implement another copying function with a copying function ". I think there is a debate here. In the previous clause, the book has already called the copy constructor in the value assignment operator, as shown in the following code:
Derived& operator=(const Derived& d){ Derived tmp(d); swap(*this, tmp); return *this;}
This is an example of calling the copy constructor in the value assignment operator. In some cases, the efficiency may not seem so high, but it provides a good guarantee for code consistency, it can also effectively provide exception security. Therefore, I don't quite agree with what I mentioned in the book. If you have any ideas, leave a message to discuss them. As mentioned in the book, it is also possible to create a common private function, such as init (), which can be called in the copy constructor and operator = respectively.
Summary:
The Copying function should make sure to copy "all member variables in the object" and "all base class components ".