Operator Overloading
Dr. Lawlor, CS
202, CS, UAF
There are many different objects that can be added together:
- Integers
- Floats
- Strings
- Vectors (like x,y,z vectors in 3D)
- Complex numbers (a+ib, where i=sqrt(-1.0))
- Rational numbers (a/b, where a and b are integers)
- Ranges (between a and b, both floats)
- and many others
C++ directly supports adding ints and floats. What's the best way to support the rest?
- You could add support into the language for each of them. The language
would get even more hideously complicated, though, and you'd never get
all of them.
- You could write an "add" method for each of them. Sadly,
"e=a.add(b.add(c.add(d)));" is really a lot tougher to read than
"e=a+b+c+d;".
- You could make up a way to redefine the "+" operator for your class.
In C++, they chose number 3, which lets you build your own "+"
operators for your classes. The syntax for overloading the
addition operator inside a class is usually:
Classname operator+(const Classname &other) const;
This adds your instance to the "other" instance, and returns the sum in a new instance of your class. For example:
class Range {
public:
float lo,hi;
Range(float l,float h) {lo=l; hi=h;} // constructor
Range operator+(const Range &r) const { // addition operator (written as a member)
return Range(lo+r.lo, hi+r.hi);
}
};
int foo(void) {
Range a(1.0,1.1), b(10.4,10.7);
Range c=a+b; // woa!
cout<<c.lo<<" ... "<<c.hi<<"\n";
return 0;
}
(Try this in NetRun now!)
Note that you really don't want to modify your own variables inside
your operator+, because "c=a+b;" should never change a or b!
Adding "const" after your function parameters guarantees this--it makes
this current instance a constant, and modifying your own variables will
be a compile error.
You can also overload operators from outside the class, which is written just like a function, but works exactly like above:
class Range {
public:
float lo,hi;
Range(float l,float h) {lo=l; hi=h;} // constructor
};
Range operator+(const Range &l,const Range &r) { // addition operator
return Range(l.lo+r.lo, l.hi+r.hi);
}
(Try this in NetRun now!)
One operator that's very commonly overloaded from outside the class is the "<<" operator, used by cout:
class Range {
public:
float lo,hi;
Range(float l,float h) {lo=l; hi=h;} // constructor
};
Range operator+(const Range &l,const Range &r) { // addition operator
return Range(l.lo+r.lo, l.hi+r.hi);
}
ostream &operator<<(ostream &s,const Range &r) { // stream output operator
s<<r.lo<<" ... "<<r.hi;
return s;
}
int foo(void) {
Range a(1.0,1.1), b(10.4,10.7);
cout<<"The sum is "<<a+b<<"\n"; // add, then output!
return 0;
}
(Try this in NetRun now!)
You can also make these function-type overloaded operators a "friend"
of your class, which lets them access the private members. This
is usually cleaner than leaving them outside the class, because they're
usually directly related to the class.
A huge range of operators can be overloaded; everything from + and
<< (above) to +=, ++x (prefix increment), x++ (postfix
increment), (int)x (cast-to-int operator int), x(3) (function call
operator()), x[3] (indexing operator[]) and more!
Name
|
Example
|
Class member operator (for a "class Stuff")
|
Notes
|
Addition
|
c=a+b;
|
Stuff operator+(const Stuff &b) const {...}
|
Return a new object
|
Subtraction
|
c=a+b; |
Stuff operator+(const Stuff &b) const {...} |
|
Multiplication
|
c=a*b;
|
Stuff operator*(const Stuff &b) const {...} |
|
Division
|
c=a/b;
|
Stuff operator/(const Stuff &b) const {...} |
|
Assignment
|
a=b;
|
Stuff &operator=(const Stuff &b) {...}
|
Overwrite this object with b, and return *this.
|
Addition-assignment
|
a+=b;
|
Stuff &operator+=(const Stuff &b) {...}
|
Modify existing object, and return *this;
|
Output
|
cout<<a;
|
friend ostream &operator<<(ostream &s,const Stuff &a) {...}
|
Print to s, then return s.
|
Indexing
|
c=a[b];
|
Something &operator[](int b) {...}
|
Makes a class act like an array. Return a reference to index b.
|
Parenthesis
|
d=a(b,c);
|
Something operator()(int b,string c) {...}
|
Makes a class act like a function. Can take multiple parameters.
|
Preincrement
|
++a;
|
Stuff &operator++(void) {...}
|
Increment self, and return self.
|
Postincrement
|
a++;
|
Stuff &operator++(int ignored) {...}
|
Make a copy of self, increment self, return un-incremented copy.
|
Less-than
|
if (a<b) ...
|
bool operator<(const Stuff &b) const {...}
|
Compare self with b, and return true or false.
|
Equality
|
if (a==b) ...
|
bool operator==(const Stuff &b) const {...} |
|
Less-than-or-equal
|
if (a<=b) ...
|
bool operator<=(const Stuff &b) const {...} |
You'd think C++ would figure these out, but you need to list them explicitly!
|
Typecast
|
c=(int)a;
|
operator int () {...}
|
Convert a into an integer (or any type!).
|
Here's an extended example of my "vec3" class, with a huge number of overloaded operators:
/*
A 3D vector: a direction or location in 3D space.
*/
class vec3 {
public:
float x,y,z;
vec3(void) {}//Default consructor
/// Simple 1-value constructors
vec3(int init) {x=y=z=(float)init;}
vec3(float init) {x=y=z=(float)init;}
vec3(double init) {x=y=z=(float)init;}
/// 3-value constructor
vec3(const float Nx,const float Ny,const float Nz) {x=Nx;y=Ny;z=Nz;}
/// float array constructor
vec3(const float *arr) {x=arr[0];y=arr[1];z=arr[2];}
// Copy constructor & assignment operator are by default
/// This lets you typecast a vector to a float array
operator float *() {return (float *)&x;}
operator const float *() const {return (const float *)&x;}
//Basic mathematical operators
int operator==(const vec3 &b) const {return (x==b.x)&&(y==b.y)&&(z==b.z);}
int operator!=(const vec3 &b) const {return (x!=b.x)||(y!=b.y)||(z!=b.z);}
vec3 operator+(const vec3 &b) const {return vec3(x+b.x,y+b.y,z+b.z);}
vec3 operator-(const vec3 &b) const {return vec3(x-b.x,y-b.y,z-b.z);}
vec3 operator*(const float scale) const
{return vec3(x*scale,y*scale,z*scale);}
vec3 operator/(const float &div) const
{float scale=1.0/div;return vec3(x*scale,y*scale,z*scale);}
vec3 operator-(void) const {return vec3(-x,-y,-z);}
void operator+=(const vec3 &b) {x+=b.x;y+=b.y;z+=b.z;}
void operator-=(const vec3 &b) {x-=b.x;y-=b.y;z-=b.z;}
void operator*=(const float scale) {x*=scale;y*=scale;z*=scale;}
void operator/=(const float div) {float scale=1.0/div;x*=scale;y*=scale;z*=scale;}
//Vector-specific 3D operations
/// Return the square of the magnitude of this vector
float magSqr(void) const {return x*x+y*y+z*z;}
/// Return the magnitude (length) of this vector
float mag(void) const {return sqrt(magSqr());}
/// Return the square of the distance to the vector b
float distSqr(const vec3 &b) const
{return (x-b.x)*(x-b.x)+(y-b.y)*(y-b.y)+(z-b.z)*(z-b.z);}
/// Return the distance to the vector b
float dist(const vec3 &b) const {return sqrt(distSqr(b));}
/// Return the dot product of this vector and b
float dot(const vec3 &b) const {return x*b.x+y*b.y+z*b.z;}
/// Return the cosine of the angle between this vector and b
float cosAng(const vec3 &b) const {return dot(b)/(mag()*b.mag());}
/// Return the "direction" (unit vector) of this vector
vec3 dir(void) const {return (*this)/mag();}
/// Return the right-handed cross product of this vector and b
vec3 cross(const vec3 &b) const {
return vec3(y*b.z-z*b.y,z*b.x-x*b.z,x*b.y-y*b.x);
}
/// Make this vector have unit length
void normalize(void) { *this=this->dir();}
/// Return the largest coordinate in this vector
float max(void) const {
float big=x;
if (big<y) big=y;
if (big<z) big=z;
return big;
}
/// Make each of this vector's coordinates at least as big
/// as the given vector's coordinates.
void enlarge(const vec3 &by) {
if (x<by.x) x=by.x;
if (y<by.y) y=by.y;
if (z<by.z) z=by.z;
}
};
/** Utility wrapper functions */
inline float dist(const vec3 &a,const vec3 &b)
{ return a.dist(b); }
inline float dot(const vec3 &a,const vec3 &b)
{ return a.dot(b); }
inline vec3 cross(const vec3 &a,const vec3 &b)
{ return a.cross(b); }
/// Allows "3.0*vec" to compile:
inline vec3 operator*(float scale,const vec3 &v)
{return vec3(v.x*scale,v.y*scale,v.z*scale);}
/// Nice vector print function
inline ostream &operator<<(ostream &s,const vec3 &v) {
s<<"("<<v.x<<", "<<v.y<<", "<<v.z<<")";
return s;
}
// Tiny example of using vectors
int foo(void) {
vec3 C(100.0,110.0,120.0), D(0.1,0.4,0.3); // ray start and direction
for (float t=0.5;t<4.0;t+=1.0) {
vec3 P=C+t*D; // move down ray by t units
cout<<"t="<<t<<" and P="<<P<<"\n";
}
return 0;
}
(Try this in NetRun now!)
The C++ FAQ has a good section on operator overloading. There's a lot more to talk about regarding the assignment operator and copy constructor, which we'll see next week!