I was lazy yesterday. I want to make up for it today.
Clause 10: Make operator = return a reference to * this
Have assignment operators return areference to * this
One interesting thing about assignment is that you can write it as a chain.
Int x, y, z;
X = y = z = 15; // value-assigned chained form, equivalent to x = (y = (z = 15 ));
Here, 15 is assigned to z, the result of the assignment (the latest z) is assigned to y, and the result of the assignment (the latest y) is assigned to x.
To implement "chained assignment", the value assignment operator must return a real parameter pointing to the left of the operator by reference. This is the protocol that should be followed when assigning operators to classes:
Class Widget {
Public:
...
Widget & operator = (const Widget & rhs)
{// The return type is a reference, pointing to the current object
...
Return * this; // return the object on the left.
}
...
};
This protocol not only applies to the above standard assignment format, but also applies to all assignment-related operations, such:
Class Widget {
Public:
...
Widget & operator + = (const Widget & rhs) // This Protocol also applies to + =,-=, * =, and so on.
{
...
Return * this;
}
Widget & operator = (int rhs) // This function also applies, even if the parameter type of this operator does not conform to the Protocol
{
...
Return * this;
}
...
};
This is only an agreement and is not mandatory. Compilation can be performed without following the code. However, this protocol is shared by all built-in and standard library types such as string, vector, complex, tr1: shared_ptr. Therefore, unless there is a good reason for innovation, it is better to follow the crowd.
· Let the value assignment operator return a reference to * this.
Clause 11: handle "self-assignment" in operator ="
Handle assignment to self in operator =
Self-assignment occurs when an object is assigned to itself. It is legal, so do not assume that the customer will never do this. In addition, the assignment action is not so simple and can be identified at a Glance:
A [I] = a [j]; // If I = j
* Px = * py; // If px and py point to the same thing
These less obvious self-assignment values are caused by aliasing (alias) (more than one method references an object. Generally, when you use a reference or pointer to operate on multiple objects of the same type, consider the situations where the objects may be the same. In fact, if two objects come from the same inheritance system and do not even need to be declared as of the same type, the alias may be generated 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 actually be the same object
If you try to manage resources by yourself (if you are writing a resource management class), you may fall into the trap of accidentally releasing a resource before you use it. For example, suppose you create a class to save a pointer pointing to a dynamically allocated bitmap:
Class Bitmap {...};
Class Widget {
...
Private:
Bitmap * pb; // pointer to an object allocated from heap
};
The traditional approach is to use identity test at the beginning of operator = to achieve the goal of "self-assignment:
Widget & Widget: operator = (constWidget & rhs)
{
If (this = & rhs) return * this; // same as the test: if it is self-assigned, it will not do anything.
Delete pb;
Pb = new Bitmap (* rhs. pb );
Return * this;
}
If the statement for the same test is missing, * this (the target of the value assignment) and rhs may be the same object. In this case, delete not only destroys the bitmap of the current object, but also destroys the bitmap of rhs. At the end of the function, the Widget (which should not have been changed by the self-assigned value) finds itself holding a pointer to the deleted object.
The statement used in the test can ensure "self-assignment security", but does not have "exception Security ". More specifically, if the "new Bitmap" expression raises an exception (it may be caused by insufficient memory during allocation or an exception thrown by the Bitmap copy constructor ), the Widget finally points to a deleted Bitmap. Such pointers cannot be safely deleted and cannot be safely read.
Fortunately, operator = is often automatically assigned with "exceptional security ". Therefore, you can focus on exception security. In this example, do not delete the pb before copying the content indicated by pb:
Widget & Widget: operator = (constWidget & rhs)
{
Bitmap * pOrig = pb; // remember the original pb
Pb = new Bitmap (* rhs. pb); // point pb to a copy of * pb
Delete pOrig; // delete the original pb
Return * this;
}
Now, if "new Bitmap" throws an exception, pb (and its widgets) remain unchanged. Even if we do not pass the same test, the code here can handle the self-assignment, because we have copied the original bitmap, deleted the original bitmap, and then pointed to the copy we made. This may not be the most efficient way to handle self-assignment, but it can work.
Another way to ensure exceptions and self-assignment security is to use a technology called "copy and swap. This is a common and good way to write operator =:
Class Widget {
...
Void swap (Widget & rhs); // exchange * this and rhs data
...
};
Widget & Widget: operator = (constWidget & rhs)
{
Widget temp (rhs); // create a copy of rhs data
Swap (temp); // exchange * this data with the data of the preceding replica
Return * this;
}
The following variants take advantage of the following facts: (1) the copy assignment operator of a class can be declared as "accepting real arguments by passing values"; (2) A copy is generated when the value is passed:
Widget & Widget: operator = (Widget rhs)
{// Rhs is a copy of the object to be passed. Note that this is a value transfer, which swaps * this data with the copy data.
Swap (rhs );
Return * this;
}
This method sacrifices clarity on the flexible altar, but it is also true that the compiler sometimes produces more efficient code by transferring the copy operation from the function body to the function parameter construction phase.
· When assigning values to an object, ensure that operator = performs well. The technology includes comparing the addresses of "source object" and "target object", careful statement order, and copy-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.
From pandawuwyj's column