Z::operator+()
) and the assignment
operator (Z::operator=()
), the compiler will not generate a
definition of Z::operator+=()
. Each operator
must be overloaded independently. I want to talk about overloading
a few other operators.
class USDollar { friend USDollar operator+(USDollar&, USDollar&); friend USDollar& operator++(USDollar&); public: USDollar(unsigned int d, unsigned int c); private: unsigned int dollars; unsigned int cents; };The
operator++()
increments the cents field. If it goes over
100, it increments the dollar field and zeros out the cents.
This operator returns a reference to USDollar because it
changes the value of the provided object.
USDollar& operator++(USDollar &s) { s.cents++; if (s.cents >=100) { s.cents -= 100; s.dollars++; } return s; }If you want to overload the prefix operator
++x
separately
from the postfix version x++
, here is the rule.
operator++(ClassName)
refers to the prefix operator and operator++(ClassName,int)
refers to the postfix operator.
The int is never used; the argument is simply a dummy used
to distinguish between prefix and postfix applications. The same
rule applies to operator--()
. If you only provide one
operator++()
or operator--()
, most compilers will
use it for both the prefix and postfix versions.
class USDollar { public: USDollar(double value = 0.0); //constructor with default value //the following function acts as a cast operator operator double() { return dollars + cents / 100.0; } private: unsigned int dollars; unsigned int cents; }; USDollar::USDollar(double value) //constructor-converts double to USDollar { dollars = (int)value; cents = (int)((value - dollars) * 100 + 0.5); } int main() { USDollar d1(2.0), d2(1.5), d3; //invoke cast operator explicitly... d3 = USDollar((double)d1 + (double)d2); //...or implicitly d3 = d1 + d2; return 0; }A cast operator is the word operator followed by the desired type. The member function
USDollar::operator double()
provides a
mechanism for converting an object of class USDollar into a
double. Cast operators have no return type. As the preceding
example shows, conversions using the cast operator can be invoked
either explicitly or implicitly. Look at the implicit case carefully.
In trying to make sense of the expression d3 = d1 + d2
,
C++ first looked for member function USDollar::operator+(USDollar)
.
When that wasn't found, it looked for the non-member version
operator+(USDollar,USDollar)
. Lacking that as well, it
started looking for an operator+()
that it could use by
converting one or the other arguments into a different type.
Finally it found a match: by converting both d1 and
d2 to doubles, it could use the intrinsic
operator+(double, double)
. It then has to convert the resulting
double back to USDollar using the constructor.
This demonstrates both the advantage and disadvantage of providing a cast operator. Providing a conversion path from USDollar to double relieves programmers of the need to provide their own set of operators. USDollar can just piggyback on the operators defined for double.
On the other hand, it also removes the ability of the programmer to control which operators are defined. By providing a conversion path to double, USDollar gets all of double's operators whether they make sense or not. In addition, going through the extra conversions may not be the most efficient process in the world. For example, the simple addition just noted involves 3 type conversions with all of the attendant function calls, multiplications, divisions, and so on.
Be careful not to provide 2 conversion paths to the same type. This confuses the compiler.
operator()()
or operator()(arguments)
.
These overloaded operators must be member functions. The first
pair of parentheses represents the name of an object of the
class. The arguments go
inside the second pair of parentheses. For example:
#include <iostream.h> #include <math.h> class Distance{ private: double xc, yc; public: Distance(double x, double y) //constructor {xc=x; yc=y;} double operator()() //returns a double {return sqrt(xc*xc+yc*yc);} double operator()(double x) //returns a double {xc=x; //reset xc return (*this)();} //use definition of operator ()() }; main() { double xx, yy; cin >> xx >> yy; Distance d(xx,yy); //call constructor double oldlength=d(); //call operator ()() xx=5.0; //reset xx double newlength=d(xx); //call operator ()(double x) cout << oldlength << " " << newlength<<endl; }Notice that overloading the function call operator has the form
double operator()()
or double operator()(arguments)
while the
cast operator has the form operator double()
. Remember that the
cast operator has no return type.
#include <iostream.h> class Number { public: Number(double x) //constructor {xc=x;} private: double xc; }; void func(Number N) {cout << "In function" << endl;} main() { double x; cin >> x; func(x); }func is defined to take an argument that is a Number. However, in main func(x) is called with an argument that is a double. So C++ tries to convert the argument to a Number. It notices that the constructor for Number essentially converts a double into a Number. So it uses the constructor to do the type conversion. If ambiguities arise, the compiler will generate an error. For example,
#include <iostream.h> class Number { public: Number(double x) //constructor {xc=x;} private: double xc; }; class AnotherNumber { public: AnotherNumber(double x) //constructor {xc=x;} private: double xc; }; void func(Number N) {cout << "In function 1" << endl;} void func(AnotherNumber N) {cout << "In function 2" << endl;} main() { double x; cin >> x; func(x); }To implement func(x) C++ could convert x to a Number and use func(Number N), or to AnotherNumber and use func(AnotherNumber N). With no way to resolve the ambiguity, the compiler won't compile the program. To resolve the ambiguity, we add an explicit call to the intended constructor:
main() { double x; cin >> x; func(Number(x)); }Notice how the preceding call to the constructor is similar to a cast. Here we have cast a double to a Number. The similarity is more than superficial. C++ allows this format for specifying a cast. This new format can also be used for intrinsic casts, plus the old format can be used for constructor conversions, as shown in the following:
void fn(int *pI) { float x = 10.5; int i = int(x); //same as int i = (int)x; func(Number(x)); //new format for a cast func((Number)x); //old format for a cast double *pD = (double*)pI; //older format must be used when //casting pointers }Either format is fine. However, you must use the older format when casting from one pointer type to another due to the syntactical confusion that
*
by itself would cause.
Note that a constructor cannot specify an implicit conversion from a
user-defined type to a basic type because the basic types are not
classes. An overloaded cast operator must be used in this case.
The following rules are very good ideas:
If you do not want the constructor to be used implicitly as a conversion operator, then declare the constructor explicit. An explicit constructor will be invoked only explicitly and implicit conversion will be suppressed. For example,
#include <iostream.h> class Number { public: explicit Number(double x) //explicit constructor {xc=x;} private: double xc; }; void func(Number N) {cout << "In function" << endl;} main() { double x; cin >> x; func(x); //error: no implicit double -> Number conversion }
//This is the Boolean.h file. #ifndef BOOLEANH #define BOOLEANH class Boolean{ public: // Constants enum constants{ False = 0, True = 1 }; //writing "false" or "true" //will clash with built-in types. // Constructors used for type conversion. Boolean() {} // Uninitialized. Boolean(int i) : v(i != 0) {} // Initialize v to (i != 0). Boolean(float f) : v(f != 0) {} // Initialize v to (f != 0). Boolean(double d) : v(d != 0) {} // Initialize v to (d != 0). Boolean(void* p) : v(p != 0) {} // Initialize v to (p != 0). // Overloading cast operator to convert Boolean to int. operator int() const{ return v; } // To allow "if (boolean-value)..." // Overloading negation operator. Boolean operator!() const { return !v; } private: char v; }; #endifWe use char v in order to store 0(false) and 1(true) in the smallest amount of space in memory. Each of the single argument constructors defines a conversion from a built-in type to a Boolean. The Boolean object created is initialized to true if the constructor argument is nonzero and to false otherwise. A
void*
is a pointer
to an object of unknown type; any pointer can be converted automatically
to a void*
in a manner that converts a null pointer to a null
void*
and a non-null pointer to a non-null void*
.
The cast operator is overloaded to return an int. One might
worry that ``return v'' might result in a char, but
the compiler knows to convert the char into an int.
Here are some examples of how Boolean is used:
Boolean b1(Boolean::True); //calls Boolean(int) Boolean b2(3); //calls Boolean(int) int* pI=new int(3); //pI points to an integer initialized to 3 Boolean b3(pI); //calls Boolean(void*) Boolean b4(3.0); //calls Boolean(float)
void f(int a, int b) { bool b1=a==b; //.... }If a and b have the same value, b1 becomes true; otherwise b1 is false.
A function that tests for some condition often returns a bool. For example:
bool greater(int a, int b) {return a>b;}By definition, true has the value 1 when converted to an integer and false has the value 0. Conversely, integers can be implicitly converted to bool values: nonzero integers convert to true and 0 converts to false. For example:
bool b = 8; //bool(8) is true, so b becomes true int i = true; //int(true) is 1, so i=1In arithmetic and logical expressions, bools are converted to ints; integer arithmetic and logical operations are performed on the converted values. If the result is converted back to bool, a 0 is converted to false and a nonzero value is converted to true.
bool a = true; bool b = true; bool x = a + b; //a+b is 2, so x=true bool y = a|b; //a|b=1, so y=trueA pointer can be implicitly converted to a bool. A nonzero pointer converts to a true; zero-valued pointers convert to false.
The Boolean class defined by Barton and Nackman has the line
enum constants{ false = 0, true = 1 };This won't compile because ``false'' and ``true'' are already defined by the built-in bool. So if you change ``false'' to False and ``true'' to True everywhere in Boolean.h and Boolean.C, it compiles just fine.