Clause 10: Make operator= return a reference to *this
Has assignment operators return a reference to *this
With regard to assignments, they can be written in a chain form:
int x, y, z;x = y = z = 15;
The assignment uses the right binding law, so the chain assignment above is parsed as:
Here 15 is first assigned to Z, and then the result (
updated z) is then assigned to Y., and the result (updated y) is then assigned to X.
in order to implement "chained assignment", the assignment operator must return a reference to the left argument of the operator, which is the protocol that should be followed when implementing the assignment operator for classes:
Class Widget {public: widget& operator= (const widget& RHS) { ... return *this;
This Protocol applies not only to the standard assignment form above, but also to all assignment-related operations, such as:
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 match the specification ... return *this;} ;
This is only an agreement, and there is no compulsion. If you do not follow it, the code can be compiled as well. However, this agreement is adhered to by all the built-in types and the types provided by the standard library, so it is best to follow it.
Note :
The Assign value (Assignment) operator returns a reference to *this.
Article 11: Handling "self-assignment" in operator=
Handle assignment to self in operator=
"Self-assignment" occurs when an object is assigned to its own value:
Class Widget {...}; Widget W;...W = w; Assign to yourself
This may seem a bit silly, but it's legal, so don't assume that the client will never do that. In addition, the assignment is not always recognizable by a single eye, such as:
A[i] = A[j];
And I and J have the same value, which is a self-assignment. Look again:
*PX = *py;
If PX and py happen to point to the same thing, this is also self-assignment. These are not obvious self-assignments, which are the result of an "alias" (aliasing): the so-called "alias" means "there is more than one method pointing to an object". Generally speaking
If a piece of code operates pointers or references and they are used to "point to multiple objects of the same type," you need to consider whether the objects are the same.In fact, as long as two objects from the same inheritance system, they do not even need to be declared as the same type can cause "alias", because a base class reference or pointer can point to a derived class object:
Class Base {...}; Class Derived:public Base {...}; void dosomething (const base& RB, derived* PD); RB and *PD may be the same object
If you try to manage your own resources, you might fall into the trap of releasing it before you stop using resources. Suppose you create a clas to hold a pointer to a dynamically allocated bitmap:
Class Bitmap {...}; Class Widget { ... private: bitmap* PB; Pointer to an object allocated from the heap};
Here is the operator= implementation code, which looks reasonable on the surface, but not exactly when the self-assignment occurs:
Widget &widget::operator= (const widget& RHS) { //an unsafe operator= implementation version of delete PB; PB = new Bitmap (&RHS.PB);
Here's
The self-assignment problem is that the *this in the Operator= function (the destination end of the assignment) and RHS are likely to be the same object. If so, delete is not just destroying the bitmap of the current object, it also destroys RHS. At the end of the function, Widget finds itself holding a pointer to an object that has been deleted.
To prevent such errors,The
traditional approach is to test the "self-assignment" by a "operator= test" , the first of its kind.:
widget& widget::operator= (const widget& RHS) { if (this = = &RHS) return *this; Delete PB; PB = new Bitmap (*RHS.PB); return *this;}
It makes sense to do so.
but the new version still has a problem with the anomaly. If "new Bitmap" causes an exception(either because of insufficient memory at the time of allocation or because bitmap's copy constructor throws an exception),
The widget will eventually hold a pointer pointing to a deleted bitmap. Such pointers are harmful, they cannot be safely deleted, and they cannot even be read safely.
Happily,
giving operator= "exceptional security" tends to automatically get "self-assigning security" in return. So just focus on achieving "exceptional security"For example, the following code only needs to be careful not to remove PB before replicating PB:
widget& widget::operator= (const widget& RHS) { bitmap* porig = PB; Remember the original PB PB = new Bitmap (*RHS.PB); Make PB point to a copy of *PB delete porig; Delete the original PB return *this;}
Now, if "new Bitmap" throws an exception, PB will remain intact. Even without a test, this code can handle self-assignment, because a copy of the original Bitmap is removed, the original Bimap is deleted, and then the new manufactured copy is pointed to. It may not be handling "self-assignment". The most efficient way, but it can work.
an alternative to manually arranging statements within the operator= function (to ensure that the code is not only "exceptionally safe" and "self-assigning security") is to use the so-called copy and swap technique.This technique is closely related to "exceptional security", so it is explained in detail in clause 29. However, since it is a common and well-operator= method of writing, it is worth looking at how its implementation looks like:
Class Widget { ... void swap (widget& rhs); Exchange *this and RHS data, see clause 29}; widget& widget::operator= (const widget& RHS) { Widget temp (RHS); Make a copy swap (temp) for RHS data; Exchange the *this data and the data of the above copy to return *this;}
Another variation of this theme takes advantage of the fact that (1) a class's copy assignment operator may be declared as "accept an argument by value";(2) passing something by value will result in a copy (see clause 20);
widget& widget::operator= (Widget rhs) { //RHS is a copy of the transmitted object Swap (RHS); Pass by value return *this;}
It sacrifices clarity for clever tinkering, but moving the "copying action" from the function body to the "function parameter construction phase" can make the compiler sometimes produce more efficient code.
(High efficiency is similar to return value optimization????)
Note:
Make sure that operator= has good behavior when the object is self-assigned. The techniques include comparing the addresses of "source objects" and "target objects", the thoughtful sequence of statements, and the Copy-and-swap.
Determines if any function operates on more than one object, and when multiple objects are the same object, its behavior is still correct.
Article 12: Do not forget every ingredient when copying an object
Copy All part of an object
A well-designed object-oriented system encapsulates the interior of an object, leaving only two functions responsible for the object copy, which is the copy constructor with the appropriate name and the copy The assignment operator is called the copying function. Clause 5 observes that the compiler creates a copying function for class when necessary, and describes the behavior of these compiler-generated versions: Make a copy of all member variables of the copied object.
If you reject the compiler writing out the copying function, you need to copy all the variables in the copying function. To add a member variable for class, you must modify the copying function at the same time.
So be sure to copy the base class component very carefully when composing the copying function for derived class. Those ingredients are often private, so you can't access them directly, so you should let derived class The copying function calls the corresponding base class function:
derivedcustomer& derivedcustom::operator= (const derivedcustom& RHS) { custom::operator= (RHS); ... return *this;}
The words "copy every ingredient" in these terms should now be clear. When writing a copying function, make sure (1) Copy all local member variables, (2) Call the appropriate copying function within all base classes.
If you find that the copy constructor and the copy assignment operator have similar code, the practice of eliminating duplicate code is to create a new member function to use for both. Such functions are often private and is often named Init. This strategy safely eliminates code duplication between the copy constructor and the copy assignment operator.
Note :
The copying function should ensure that all member variables within the object and all base class components are copied.
Do not attempt to implement another copying function with one of the copying functions. The common function should be put into a third function and called by two copying functions.
Copyright NOTICE: This article for Bo Master original article, without Bo Master permission not reproduced.
Effective c++--clause 10, Clause 11 and Clause 12 (chapter 2nd)