- Modern C++:Efficient and Scalable Application Development
- Richard Grimes Marius Bancila
- 573字
- 2021-06-10 18:28:16
Operator overloading
One of behaviors of a type is the operations you can apply to it. C++ allows you to overload the C++ operators as part of a class so that it's clear that the operator is acting upon the type. This means that for a unary operator the member method should have no parameters and for a binary operator you need only one parameter, since the current object will be on the left of the operator, and hence the method parameter is the item on the right. The following table summarizes how to implement unary and binary operators, and four exceptions:
Here the ■ symbol is used to indicate any of the acceptable unary or binary operators except for the four operators mentioned in the table.
There are no strict rules over what an operator should return, but it helps if an operator on a custom type behaves like operators on a built-in type. There also has to be some consistency. If you implement the + operator to add two objects together, then the same plus action should be used for the += operator. Also, you could argue that the plus action will also determine what the minus action should be like, and hence the - and -= operators. Similarly, if you want to define the < operator, then you should define <=. >, >=, ==, and != too.
The Standard Library's algorithms (for example, sort) will only expect the < operator to be defined on a custom type.
The table shows that you can implement almost all the operators as either a member of the custom type class or as a global function (with the exception of the four listed that have to be member methods). In general, it is best to implement the operator as part of the class because it maintains encapsulation: the member function has access to the non-public members of the class.
An example of a unary operator is the unary negative operator. This usually does not alter an object but returns a new object that is the negative of the object. For our point class, this means making both co-ordinates negative, which is equivalent to a mirror of the Cartesian point in a line y = -x:
// inline in point
point operator-() const
{
return point(-this->x, -this->y);
}
The operator is declared as const because it's clear the operator does not change the object and hence it's safe to be called on a const object. The operator can be called like this:
point p1(-1,1);
point p2 = -p1; // p2 is (1,-1)
To understand why we have implemented the operator like this, review what the unary operator would do when applied to a built-in type. The second statement here, int i, j=0; i = -j;, will only alter i and will not alter j, so the member operator- should not affect the value of the object.
The binary negative operator has a different meaning. First, it has two operands, and, second, in this example, the result is a different type to the operands because the result is a vector that indicates a direction by taking one point away from another. Assuming that the cartesian_vector is already defined with a constructor that has two parameters, then we can write:
cartesian_vector point::operator-(point& rhs) const
{
return cartesian_vector(this->x - rhs.x, this->y - rhs.y);
}
The increment and decrement operators have a special syntax because they are unary operators that can be prefixed or postfixed, and they alter the object they are applied to. The major difference between the two operators is that the postfixed operator returns the value of the object before the increment/decrement action, so a temporary has to be created. For this reason, the prefix operator almost always has better performance than the postfix operator. In a class definition, to distinguish between the two, the prefix operator has no parameters and the postfix operator has a dummy parameter (in the preceding table, 0 is given). For a class mytype, this is as follows:
class mytype
{
public:
mytype& operator++()
{
// do actual increment
return *this;
}
mytype operator++(int)
{
mytype tmp(*this);
operator++(); // call the prefix code
return tmp;
}
};
The actual increment code is implemented by the prefix operator, and this logic is used by the postfix operator through an explicit call to the method.