Operators
Another very nice and powerful feature of C++ is the ability to overload operators, that is you can define arbitrary behaviour for logical and mathematical operators like+, -,
* etc. This feature enables the creation of new objects that behave like
built in data types, for example vectors, complex numbers or
rational number of arbitrary precision.
| Rule 54. Do not over use operators. |
Unfortunately the power described above also harbour the danger of over using the possibility of overloading operators. Especially overloading operators with an intrinsic mathematical meaning for non-mathematical objects may be dangerous, because an interpretation of an operator in another context that is absolutely obvious and natural to you may seem absolutely abnormal to other users. There are some exception of safe operator overloads though:
- The assignment operator. This is a very special operator whose meaning is obvious to everybody.
- Compare operators. In many cases it is helpful to
have at least the two compare operators
==and!=in order to check for equality of two given objects. You can also equip your objects with an order using the remaining compare operators<,<=,>,>=if needed. - Index operator. The index operator
[]is very useful when implementing arrays or vectors. - Dereference operator. The dereference operators
->and*are needed when writing iterator classes or smart pointer classes. Such operators should always return a pointer or reference to another object. - Function call operator. This is again a very special operator needed to write functors, these are objects that can also act like functions. Such objects are useful as arguments to functions that are working on a set of objects by applying a given functor.
+,
-,* and /, and binary bit operators like
&, | and ^ are not well defined in a general
setting and thus to be avoided in any non-arithmetic context.
| Rule 55. A class which uses ''new'' to allocate instances managed by the class, must define an assignment operator. |
When a class allocates a memory block using new, the assignment operator has to take care of this fact in the same way as the copy constructor has to be specifically designed for this class. Again consider the old example introduced with the copy constructor.
class Block {
private:
void* m_Memory;
unsigned int m_Size;
public:
explicit Block(unsigned int size);
Block(const Block& );
~Block();
const Block& operator=(const Block& );
};
const Block& Block::operator=(const Block& block) {
// Check for self assignment
if (&block == this)
return *this;
// First free current block
delete[] (char*)m_Memory;
// Copy memory from other block;
m_Size = block.m_Size;
m_Memory = new char[m_size];
memcpy(m_Memory, block.m_Memory, m_Size);
return *this;
}
|
This example also contains two other important features: First the assignment operator returns a const reference to the object itself. This is important if you want to reuse the object immediately after assignment. The second important feature is the check whether the object is assigned to itself. This will give us the next rule.
| Rule 56. An assignment operator must handle correctly the case of a self assignment. |
Of course a self assignment would not make much sense, but it might happen in some more complex scenarios. In this case we not only do not need to execute the assignment, but it also would be dangerous to do so, because we release the old memory block before the copy operation. And if the parameter was the same object, there would be no more source available any more.
The problem with self assignments can also be solved in simple
cases by carefully reassigning the member variables, like in
the following code snippet:
const Block& Block::operator=(const Block& block) {
// Copy memory from other block;
void* newblock = new char[block.m_size];
memcpy(newblock, block.m_Memory, block.m_Size);
// Finally free current block
delete[] (char*)m_Memory;
m_Memory = newblock;
m_Size = block.m_Size;
// return reference to object
return *this;
}
|
Of course here we do a lot more work in the case of a self assignment, but hopefully this is a rare event.
Note that problems with self assignments arise most commonly in cases where classes contain pointers to allocated memory. If a class does only contain simple data types or objects that are already safe, you do not have to care about this.
| Recommendation 57. Do not write an assignment operator if shallow copies are sufficient. |
Instead let the compiler do the job for you - the compiler probably will even produce better code than with an explicit assignment operator because he has less restrictions.
| Rule 58. An assignment operator returns a const reference to the object. |
As we already mentioned the return type of every assignment operator should be a const reference to the object itself. As a justification consider the following simple statement:
a = b = c; |
In this case first c will be assigned to b and then the result of the assignment (which should be a const reference to b) will be assigned to a. If the assignment operator returned something different, this statement would not compile or produce something undesired.
| Rule 59. Compare operators are const and take a const reference as their argument. |
A compare operator never should change the state of an object, so the method itself has to be be const and its parameter has to be a const reference to an object of the same type.
Kaya Memisoglu 2005-01-06
