[Switch] C ++ Operator Overloading guidelines
Http://www.cs.caltech.edu/courses/cs11/material/cpp/donnie/cpp-ops.html
One of the nice features of C ++ is that you can give special meanings to operators, when they are used with user-defined classes. This is calledOperator Overloading. You can implement C ++ operator overloads by providing special Member-Functions on your classes that follow a participant naming convention. For example, to overload+Operator for your class, you wowould provide a member-function namedOperator +On your class.
The following set of operators is commonly overloaded for user-defined classes:
- =(Assignment operator)
- + -*(Binary Arithmetic Operators)
- + =-= * =(Compound assignment operators)
- =! =(Comparison operators)
Here are some guidelines for implementing these operators. These guidelines are very important to follow, so definitely get in the habit early.
Assignment operator =
The assignment operator has a signature like this:
Class myclass {public :... myclass & operator = (const myclass & RHs );...} myclass a, B ;... B = A; // same as B. operator = ();
Notice that=Operator takes a const-Reference to the right hand side of the assignment. the reason for this shoshould be obvious, since we don't want to change that value; we only want to change what's on the left hand side.
Also, you will notice that a reference is returned by the assignment operator. This is to allowOperator chaining. You typically see it with primitive types, like this:
Int A, B, C, D, E; a = B = c = d = E = 42;
This is interpreted by the compiler:
A = (B = (C = (D = (E = 42 ))));
In other words, assignment isRight-associative. The last assignment operation is evaluated first, and is propagated leftward through the series of assignments. Specifically:
- E = 42Assigns 42E, Then returnsEAs the result
- The valueEIs then assignedD, And thenDIs returned as the result
- The valueDIs then assignedC, And thenCIs returned as the result
- Etc.
Now, in order to support operator chaining, the assignment operator must return some value. The value that shoshould be returned is a reference toLeft-hand sideOf the assignment.
Notice that the returned reference isNotDeclaredConst. This can be a bit confusing, because it allows you to write crazy stuff like this:
Myclass A, B, C;... (A = B) = C; // What ??
At first glance, you might want to prevent situations like this, by havingOperator =ReturnConstReference. However,Statements like thisWillWork with primitive types.And, even worse, some tools actually rely on this behavior. Therefore, it is important to returnNon-ConstReference from yourOperator =. The rule of thumb is, "If it's good enoughIntS, it's good enough for user-defined data-types ."
So, for the hypotheticalMyclassAssignment operator, you wocould do something like this:
// Take a const-Reference to the right-hand side of the assignment. // return a non-const reference to the left-hand side. myclass & myclass: Operator = (const myclass & RHs ){... // do the assignment operation! Return * This; // return a reference to myself .}
Remember,ThisIs a pointer to the object that the member function is being called on. SinceA = BIs treatedA. Operator = (B), You can see why it makes sense to return the object that the function is called on; objectA IsThe left-hand side.
But, the member function needs to return a reference to the object, not a pointer to the object. So, it returns* This, Which returns whatThisPoints at (I. e. the object), not the pointer itself. (IN C ++, instances are turned into references, and vice versa, pretty much automatically, so even though* ThisIs an instance, C ++ implicitly converts it into a reference to the instance .)
Now, one moreVery importantPoint about the assignment operator:
You must check for self-assignment!
This is especially important when your class does its own memory allocation. here is why: the typical sequence of operations within an assignment operator is usually something like this:
Myclass & myclass: Operator = (const myclass & RHs) {// 1. deallocate any memory that myclass is using internally // 2. allocate some memory to hold the contents of RHS // 3. copy the values from RHS into this instance // 4. return * This}
Now, what happens when you do something like this:
Myclass MC;... MC = MC; // Blammo.
You can hopefully see that this wowould wreak havoc on your program. BecauseMCIs on the left-hand sideAndOn the right-hand side, the first thing that happens is thatMCReleases any memory it holds internally. But, this is where the values were going to be copied from, sinceMCIs also on the right-hand side! So, you can see that this completely messes up the rest of the assignment operator's internals.
The easy way to avoid this isCheck for self-assignment.There are always ways to answer the question, "Are these two instances the same? "But, for our purposes, just compare the two objects 'ses SSEs. If they are the same, then don't do assignment. If they are different, then do the assignment.
So, the correct and safe version ofMyclassAssignment operator wocould be this:
Myclass & myclass: Operator = (const myclass & RHs) {// check for self-assignment! If (this = & RHs) // same object? Return * This; // Yes, So skip assignment, and just return * This... // deallocate, allocate new space, copy values... return * This ;}
Or, you can simplify this a bit by doing:
Myclass & myclass: Operator = (const myclass & RHs) {// only do assignment if RHS is a different object from this. If (this! = & RHs) {... // deallocate, allocate new space, copy values...} return * This ;}
Remember that in the comparison,ThisIs a pointer to the object being called, and& RHSIs a pointer to the object being passed in as the argument. So, you can see that we avoid the dangers of Self-assignment with this check.
In summary, the guidelines for the assignment operator are:
- Take a const-reference for the argument (the right-hand side of the assignment ).
- Return a reference to the left-hand side, to support safe and reasonable operator chaining. (do this by returning* This.)
- Check for self-assignment, by comparing the pointers (ThisTo& RHS).
Compound assignment operators + =-= * =
I discuss these before the Arithmetic Operators for a very specific reason, but we will get to that in a moment. The important point is that these areDestructiveOperators, because they update or replace the values on the left-hand side of the assignment. So, you write:
Myclass a, B;... A + = B; // same as a. Operator + = (B)
In this case, the valuesAAreModifiedBy+ =Operator.
How those values are modified isn't very important-obviusly, whatMyclassRepresents will dictate what these operators mean.
The member function signature for such an operator shoshould be like this:
Myclass & myclass: Operator + = (const myclass & RHs ){...}
We have already covered the reason whyRHSIs a const-reference. And, the implementation of such an operation shoshould also be straightforward.
But, you will notice that the operator returnsMyclass-Reference, and a non-const one at that. This is so you can do things like this:
Myclass MC;... (MC + = 5) + = 3;
Don't ask me why somebody wocould want to do this, but just like the normal assignment operator, this is allowed by the primitive data types. our user-defined datatypes shocould match the same general characteristics of the primitive data types when it comes to operators, to make sure that everything works as expected.
This is very straightforward to do. Just write your compound assignment operator implementation, and return* ThisAt the end, just like for the regular assignment operator. So, you wowould end up with something like this:
Myclass & myclass: Operator + = (const myclass & RHs) {... // do the compound assignment work. Return * This ;}
As one last note,In generalYou shoshould beware of Self-assignment with compound assignment operators as well. fortunately, none of the C ++ track's labs require you to worry about this, But you shoshould always give it some thought when you are working on your own classes.
Binary Arithmetic Operators + -*
The binary arithmetic operators are interesting because they don't modify either operand-they actually return a new value from the two arguments. you might think this is going to be an annoying bit of extra work, but here is the secret:
Define your binary arithmetic operators using your compound assignment operators.
There, I just saved you a bunch of time on your homeworks.
So, you have implemented your+ =Operator, and now you want to implement+Operator. The function signature shocould be like this:
// Add this instance's value to other, and return a new instance // with the result. const myclass: Operator + (const myclass & Other) const {myclass result = * This; // make a copy of myself. same as myclass result (* This); Result + = Other; // use + = to add other to the copy. return result; // all done! }
Simple!
Actually, this explicitly spells out all of the steps, and if you want, youCanCombine them all into a single statement, like so:
// Add this instance's value to other, and return a new instance // with the result. const myclass: Operator + (const myclass & Other) const {return myclass (* This) + = Other ;}
This creates an unnamed instanceMyclass, Which isCopyOf* This. Then,+ =Operator is called on the temporary value, and then returns it.
If that last statement doesn't make sense to you yet, then stick with the other way, which spells out all of the steps. but, if you understand exactly what is going on, then you can use that approach.
You will notice that+Operator returnsConstInstance,NotAConstReference. This is so that people can't write strange statements like this:
Myclass A, B, C;... (a + B) = C; // Wuh ...?
This statement wowould basically do nothing, but if+Operator returns a non-ConstValue, itWillCompile! So, we want to returnConstInstance, so that such madness will not even be allowed to compile.
To summarize, the guidelines for the Binary Arithmetic Operators are:
- Implement the compound assignment operators from scratch, and then define the Binary Arithmetic Operators in terms of the corresponding compound assignment operators.
- ReturnConstInstance, to prevent worthless and confusing assignment operations that shouldn't be allowed.
Comparison Operators = And ! =
The comparison operators are very simple. Define=First, using a function signature like this:
Bool myclass: Operator = (const myclass & Other) const {... // compare the values, and return a bool result .}
The internals are very obvious and straightforward, andBoolReturn-value is also very obvious.
The important point here is that! =Operator can also be defined in terms of=Operator, And You shoshould do this to save effort. You can do something like this:
Bool myclass: Operator! = (Const myclass & Other) const {return! (* This = Other );}
that way you get to reuse the hard work you did on implementing your = operator. also, your code is far less likely to exhibit inconsistencies between = and ! = , since one is implemented in terms of the other.