An overloaded operator must take at least one argument of a user-defined type (operators new and delete are an exception). This rule ensures that users cannot alter the meaning of expressions that contain only fundamental types. For example:
int i,j,k;
k = i + j; //always uses built-in = and +
Recall that enum types are user-defined types, and as such, you can define overloaded operators for them too.
Overloaded Operators May Not Have Default Parameters
Unlike ordinary functions, overloaded operators cannot declare a parameter with a default value (overloaded operator() is the only exception):
class Date
{
private:
int day, month, year;
public:
Date & operator += (const Date & d = Date() ); //error, default arguments are not allowed
};
This rule may seem arbitrary. However, it captures the behavior of built-in operators, which never have default operands either.
Member and Non-Member Overloaded Operators
Most of the overloaded operators can be declared either as non-static class members or as non-member functions. In this example, operator == is overloaded as a non-static class member:
class Date{
//…
public:
bool operator == (const Date & d ); // 1: member function
};
Alternatively, one can declare it as a friend function:
bool operator ==( const Date & d1, const Date& d2); // 2: extern function
class Date{
friend bool operator ==( const Date & d1, const Date& d2);
};
Nonetheless, the operators [], (), = and ->can only be declared as non-static member functions. This ensures that their first operand is a l-value.
Overloading Operators for enum Types
For some enum types, it may be useful to define overloaded operators, such as ++ and --, that can iterate through the enumerator values:
#include <iostream>
using namespace std;
enum Days {Mon, Tue, Wed, Thur, Fri, Sat, Sun};
Days& operator++(Days& d, int) // int denotes postfix++
{
if (d == Sun) return d = Mon; //rollover
int temp = d;
return d = static_cast<Days> (++temp);
}
int main()
{
Days day = Mon;
for (;;) //display days as integers
{
cout<< day <<endl;
day++;
if (day == Mon) break;
}
return 0;
}
Overload New and Delete in a Class
It is possible to override the global operators new and delete for a given class. For example, you can use this technique to override the default behavior of operator new in case of a failure. Instead of throwing a std::bad_alloc exception, the class-specific version of new throws a char array:
#include <cstdlib> //declarations of malloc and free
#include <new>
#include <iostream>
using namespace std;
class C {
public:
C();
void* operator new (size_t size); //implicitly declared as a static member function
void operator delete (void *p); //implicitly declared as a static member function
};
void* C::operator new (size_t size) throw (const char *){
void * p = malloc(size);
if (p == 0) throw "allocation failure"; //instead of std::bad_alloc
return p;
}
void C::operator delete (void *p){
C* pc = static_cast<C*>(p);
free(p);
}
int main() {
C *p = new C; // calls C::new
delete p; // calls C::delete
}
Note that the overloaded new and delete implicitly invoke the object's constructor and destructor, respectively. Remember also to define a matching operator delete when you override operator new.
Operator overloading rules of thumb
When overloading an operator to support a user-defined type (object), it is best to adhere to the basic semantics of that built-in operator. For instance, the built-in operator ==, which does not modify any of its operands, should also be overloaded in such a way that it does not modify any of its operands (and should be declared as a const member function, as a matter of fact). On the other hand, operators such as + =, which do modify their left operand, should be overloaded in a way that reflects that, i.e., by changing their objects. Note that in many cases, the implementer's code (e.g, a commercial library package) may not (and should not) be accessible to its user, so overloading an operator in an unexpected, unintuitive manner is not recommended.
class Date{
int day, month, year;
Date();
//...
public:
bool operator == (const Date & d) const; //none of the operands //is changed
Date & operator += (const Date & d); //builtin += does change //its left operand but not //its right one when //applied to numerals; the //same behavior is //maintained here.
One more thing to remember when defining copy constructor and operator=
Assigning an object to itself is disastrous, so whenever you have to define a copy constructor and assignment operator, make sure your code is guarded from such a mistake:
class Date {
int day, month, year;
//...
Date & operator= (const Date & other){ *this=other; //Dangerous
return *this;}
//...
}
void f () {
Date d;
Date *pdate = &d;
//...many lines of code
*pdate=d; //oops, object assigned to itself; disastrous
}//f()
A safe version should look like this:
Date & Date::operator= (const Date & other)
{
if (&other != this) //guarded from self assignment
{
*this = other;
//...
}
return *this;
}
Overloading postfix and prefix ++
For primitive types the C++ language distinguishes between ++x; and x++; as well as between --x; and x--; For objects requiring a distinction between prefix and postfix overloaded operators, the following rule is used:
class Date {
//...
public:
Date& operator++(); //prefix
Date& operator--(); //prefix
Date& operator++(int unused); //postfix
Date& operator--(int unused); //postfix
};
Postfix operators are declared with a dummy int argument (which is ignored) in order to distinguish them from the prefix operators, which take no arguments:
void f()
{
Date d, d1;
d1 = ++d;//prefix: first increment d and then assign to d1
d1 = d++; //postfix; first assign, increment d afterwards
}
Assignment operator is not inherited
Unlike ordinary base class member functions, assignment operator is not inherited. It may be re-defined by the implementer of the derived class or else it is automatically synthesized by the compiler, so there's not much point in declaring it virtual.
Hiding a base class member function
A derived class may hide a member function declared in its base class by using the same function name, but with a different signature, or list of arguments. Usually, this case suggests a programmer's mistake, especially if the hidden function is virtual. However, it can also be used as a means of hiding a base class member function when the designer of the derived class decides that any invocation of the original member function is undesirable or even dangerous:
class B {
private:
FILE *pf;
public:
//...
virtual void close(FILE *f) //may throw an exception
};
class D : public B { //no file I/O in D, calling B::close from
//here is dangerous and never needed.
public:
//a 'neutralized' close, harmless
void close(int i){} //hiding B::close(), not overriding it
};
void f()
{
B b;
FILE *p;
//....
D d;
d.close(p);//compile-time error: B::close()not accessible
}
No comments:
Post a Comment