Item 11: Processing assignment to self in operator = (Auto assigned)
By Scott Meyers
Translator: fatalerror99 (itepub's nirvana)
Release: http://blog.csdn.net/fatalerror99/
When an object is assigned to itself, assignment to self occurs ):
Class widget {...};
Widget W;
...
W = W; // assignment to self
This looks silly, but it is legal, so be sure the customer will do so. In addition, assignment (Value assignment) is not always so easy to identify. For example,
A [I] = A [J]; // potential assignment to self
If I and j have the same value as assignment to self (self-assigned), and
* PX = * py; // potential assignment to self
If PX and Py happen to point to the same thing, this is also an assignment to self (Auto assigned ). These less obvious assignments to self (auto-assigned) are composedAliasing(Alias) (more than one method references an object. Generally, the code that uses references (reference) or pointers (pointer) to operate multiple objects of the same type needs to consider the situations where those objects may be the same. In fact, if two objects (objects) come from the same hierarchy (Inheritance System) and do not even need to be declared as the same type, because a base class (base class) Reference) or pointer (pointer) can also lead to or point to an object (object) of the derived class (derived class) type ):
Class base {...};
Class derived: public base {...};
Void dosomething (constBase &RB, // Rb and * PD might actually be
Derived *PD); // the same object
If you follow the suggestions of items 13 and 14, you should always use objects to manage resources (resources), and you should ensure that those resource-Managed Objects (resource management objects) it performs well when being copied. In this case, your assignment operators (Value assignment operator) may also be self-assignment-safe (self-assigned security) when you do not consider self-assigned values. However, if you try to manage resources by yourself (if you are writing a resource-management class, you must do so of course ), you may fall into the trap of accidentally releasing a resource before you use it. For example, suppose you have created a class that holds a raw pointer (bare pointer) pointing to a dynamically allocated Bitmap (Bitmap ):
Class bitmap {...};
Class widget {
...
PRIVATE:
Bitmap * pb; // PTR to a heap-allocated object
};
The following is a seemingly reasonable implementation of operator =, but assignment to self (self-assigned value) is not safe. (It is not exception-safe, but it will be involved in a while .)
Widget &
Widget: Operator = (const widget & RHs )//Unsafe impl. of operator =
{
Delete Pb; // stop using current bitmap
PB = new Bitmap (* RHS. Pb); // start using a copy of RHS's bitmap
Return * This; // see item 10
}
Here, the self-assignment (self-assignment) problem is within operator =. * This (the target of the assignment) and RHS may be the same object (object ). If they are, the delete operation will not only destroy the bitmap of the current object (the current object), but also destroy the bitmap of The RHS ). At the end of the function, the widget-via assignment to self (self-assigned value) should remain unchanged-finds itself holding a pointer to the deleted object!
The traditional method to prevent this error is to passIdentity Test(Consistency detection) to prevent assignment to self (self-assigned value ):
Widget & Widget: Operator = (const widget & RHs)
{
If (this = & RHs) return * this;// Identity test: If a self-assignment,
// Do nothing
Delete Pb;
PB = new Bitmap (* RHS. Pb );
Return * this;
}
This can also work, but earlier versions I mentioned earlier about operator = are not only self-assignment-unsafe (auto-assigned insecure), but also exception-unsafe (abnormal and insecure) and this version is still abnormal. In details, if the "New bitmap" expression raises an exception (exception) (this may be caused by insufficient memory for allocation or an exception thrown by the copy constructor (copy constructor) of Bitmap ), the widget ends with a pointer to the deleted bitmap. Such pointers are toxic and you cannot safely delete them. You cannot even read them safely. The only thing you can do with their security is probably to spend a lot of debugging effort to determine where they came from.
Fortunately, operator = exception-safe (abnormal security) also makes up for its self-assignment-safe (self-assigned security ). This leads to a more general way to handle the self-assignment (auto-assignment) problem, which is to ignore it and focus on exception safety (exception security ). Item 29 further explores exception safety (abnormal security), but in this item, it is sufficient to see that in many cases, by carefully adjusting the statement order, you can get the code of exception-safe (exception security) (also self-assignment-safe (self-assigned security. For example, here, we only need to be careful not to delete Pb until we copy the target to which it points:
Widget & Widget: Operator = (const widget & RHs)
{
Bitmap * porig = Pb;// Remember 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, the trace of Pb (and its widget) is not changed. Even without identity test (consistency check), the code here can also process assignment to self (self-assigned value), because we have made a copy of the original bitmap (Bitmap, delete the original bitmap and point to the Copied object. This may not be the most efficient way to handle self-assignment (self-assignment), but it can work.
If you are concerned about efficiency, you can restore identity test at the beginning of the function ). However, before doing so, first ask yourself how often do you think self-assignments (self-assignment) occurs, because this test is not a free lunch. It will make the code (source code and target code) slightly increase, and it will introduce a branch in the control flow, both of which will reduce the running speed. For example, the effectiveness of instruction prefetching (command preread), caching (cache), and pipelining (pipeline operation) will be reduced.
Another optional manual arrangement of operator = In the statement order to ensure the implementation is exception-and self-assignment-safe (exception and self-assignment security) the method is to use the technology called "copy and swap. This technique is closely related to exception safety (abnormal security) and will be described in item 29. However, this is a general enough method to write operator =. It is worth noting that such an implementation usually looks like the following:
Class widget {
...
Void swap (widget & RHs); // exchange * This's and RHS's data;
... // See item 29 For details
};
Widget & Widget: Operator = (const widget & RHs)
{
Widget temp (RHs );// Make a copy of RHS's data
Swap (temp );// Swap * This's data with the copy's
Return * this;
}
A variant in this topic utilizes the following facts: (1) Copy assignment (copy assignment operator) of a clsaa (class) can be declared as take its argument by value (get its parameter by passing value); (2) pass something by passing value to make one of itsCopy(Copy) (see item 20 ):
Widget & Widget: Operator = (WidgetRHS) // RHS isCopyOf the object
{// Passed in-note pass by Val
Swap (RHs); // swap * This's data
// The copy's
Return * this;
}
For me personally, I am worried that this method sacrifices clarity on the flexible altar, but by transferring the copy operation from the function body to the parameter structure, it is also true that sometimes the compiler can generate more efficient code.
Things to remember
- When an object is assigned to itself, ensure that operator = is in good behavior. The skills include comparing the addresses of source and target objects, following the order of statements, and copying-and-swap.
- If two or more objects are the same, make sure that the function behavior of any operation on more than one object is correct.