Chapter 18. Operator Overloading

Overloaded, undermanned, meant to founder, we Euchred God Almighty’s storm, bluffed the Eternal Sea!

Kipling

C++ is very good at giving you the tools to organize and use information in a way that’s most natural to you. Operator overloading is one of the features that facilitates this. It allows you to define functions to be called when ordinary C++ operators are used on the classes you’ve defined. For example, you can use operator overloading to tell C++ how to combine two boxes (a_box + b_box). (This assumes that you have a definition of what it means to add two boxes and that it makes sense to do so.) In this chapter we will go step by step through the creation of a fixed-point class and all the operators for it.

Creating a Simple Fixed-Point Class

In this section we define a fixed-point number class. Unlike floating-point numbers where the decimal point can move from place to place (0.1, 30.34, 0.0008) fixed-point numbers have a set number of digits after the decimal point.

Fixed-point numbers are very useful in applications in which speed is essential but you don’t need a lot of accuracy. For example, I’ve used fixed-point functions for color computations in the printing of color pictures. The logic had to decide which color to select for each pixel. For example, the logic had to determine whether or not to put a red dot on the paper. If the color value was more than half red, a dot was printed. So 0.95 was red and 0.23 was not. We didn’t need the extra precision of floating point because we didn’t care about the extra decimal places. (0.95 was red. 0.9500034 was still red.)

Since floating-point operations cost a lot more than the integer calculations used to implement fixed point, our use of fixed point sped up the processing considerably.

Fixed Point Basics

In our fixed-point class, all numbers have two digits after the decimal point. No more, no less. That’s because we’ve fixed our decimal point at the second position. Here are some examples of the types of numbers we are dealing with:

12.34 			0.01		5.00		853.82		68.10

Internally the numbers are stored as a long int. Table 18-1 shows the external representation of some numbers and their internal form.

Table 18-1. External and internal number formats

External representation

Internal representation

12.34

1234

0.00

0

0.01

1

68.10

6810

To add two fixed-point numbers together, just add their values:

 

External representation

Internal representation

 

12.34

1234

+

56.78

5678

=

69.12

6912

As you can see, addition is a simple, straightforward process. Also, since we are using integers instead of floating-point numbers, it is a fast process. For subtraction, the values are just subtracted.

Multiplication is a little tricker. It requires that you multiple by the values and then divide by a correction factor. (The correction factor is 10digits, where digits is the number of digits after the decimal point.) For example:

 

External representation

Internal representation

 

1.01

101

*

20.0

2000

Before correction

-- --

202000

= (corrected)

20.20

2020

Division is accomplished in a similar manner, but the correction is multiplied, not divided.

Creating the fixed_pt Class

Externally, our fixed-point number looks much like a floating-point number but the decimal point is always in the same place. Internally we store the number as a long int, so the definition for our fixed_pt class begins with a declaration of the internal data:

namespace fixed_pt {

class fixed_pt
{
    private:
        long int value; // Value of our fixed point number

Next we define several member functions. These include the usual constructors and destructors:

public:
     // Default constructor, zero everything
     fixed_pt(  ): value(0) { }

     // Copy constructor
     fixed_pt(const fixed_pt& other_fixed_pt) :
         value(other_fixed_pt.value)
     { }

     // Destructor does nothing
     ~fixed_pt(  ) {}

Now we define a conversion constructor that lets you initialize a fixed-point number using a double:

// Construct a fixed_pt out of a double
fixed_pt(const double init_real) :
    value(static_cast<long int>(
         init_real * static_cast<double>(fixed_exp)))
{}

It should be noted that all we are really doing is setting the value to init_real * fixed_exp, but we want to do our calculations in floating point so we must cast fixed_exp to a double. Also, the result is a long int, so we must add another cast to change the result into the right type. So when all the details are added, a simple statement becomes a little messy.

Notice that we did not declare the constructor as an explicit constructor. That means that it can be used in implied conversions. So the following two statements are both valid:

fixed_pt::fixed_pt p1(1.23); // Explict does not matter
fixed_pt::fixed_pt = 4.56;   // Constructor must not be explicit

If we declared the constructor explicit, the implied conversion of 4.56 to a fixed-point number would not be allowed by the compiler.

We’ve decided to truncate floating-point numbers when converting them to fixed point. For example, 4.563 becomes fixed-point 4.56. Also 8.999 becomes 8.99. But there is a problem: the floating-point number 1.23 becomes fixed-point 1.22.

How can this happen? The conversion is obvious. It may be obvious to you and me, but not to the computer. The internal format used to store most floating-point numbers does not have an exact way of storing 1.23. Instead, the computer approximates the number as best it can. The result is that the number stored is 1.229999999999999982. This truncates to 1.22.

So how do we get around this problem? The answer is to fudge things a little and add in a fudge factor to help make the answer come out the way we want it to.

So our full constructor is:

// Construct a fixed_pt out of a double
fixed_pt(const double init_real) :
    value(static_cast<long int>(
         init_real * static_cast<double>(fixed_exp) +
         fudge_factor))
{}

Actually, as we were writing the code for our fixed-point class, we noticed that we convert a double to a fixed-point number a lot of times. So to avoid redundant code, we moved the conversion from the constructor into its own function. Thus, the final version of the conversion constructor (and support routine) looks like:

private:
    static long int double_to_fp(const double the_double) {
        return (
            static_cast<long int>(
                the_double *
                static_cast<double>(fixed_exp) +
                fixed_fudge_factor));
    }
public:
    // Construct a fixed_pt out of a double
    fixed_pt(const double init_real) :
        value(double_to_fp(init_real))
    {}

Finally we add some simple access functions to get and set the value of the number.

// Function to set the number
void set(const double real) {
    value = double_to_fp(real);
            
}

// Function to return the value
double get(  ) const {
    return (static_cast<double>(value) / fixed_exp);
}

Tip

As you may recall, the const appearing after the get function was discussed in Chapter 14.

Now we want to use our fixed numbers. Declaring variables is simple. Even initializing them with numbers such as 3.45 is easy:

fixed_pt::fixed_pt start;    // Starting point for the graph
fixed_pt::fixed_pt end(3.45);    // Ending point

But what happens when we want to add two fixed numbers? We need to define a function to do it:

namespace fixed_pt {

// Version 1 of the fixed point add function
inline fixed_pt add(const fixed_pt& oper1, const fixed_pt& oper2) 
{
    fixed_pt result.value = oper1.value + oper2.value;
    return (result);
}

A few things should be noted about this function. First, we defined it to take two fixed-point numbers and return a fixed-point number. That way we group additions:

// Add three fixed point numbers
answer = add(first, add(second, third));

Constant reference parameters are used (const fixed_pt&) for our two arguments. This is one of the most efficient ways of passing structures into a function. Finally, because it is such a small function, we’ve defined it as an inline function for efficiency.

In our add function, we explicitly declare a result and return it. We can do both in one step:

// Version 2 of the fixed_pt add function
inline fixed_pt add(const fixed_pt& oper1, const fixed_pt& oper2) 
{
    return (fixed_pt(oper1.value + oper2.value));
}

Although it is a little harder to understand, it is more efficient.

There’s some bookkeeping required to support this function. First, we need a constructor that will create a fixed-point number from an integer. Since this is not used outside of functions that deal with the internals of the fixed-point class, we declare it private:

private:
    // Used for internal conversions for our friends
    fixed_pt(const long int i_value) : value(i_value){}

Since our add function needs access to this constructor, we must declare the function as a friend to our fixed-point class.

friend fixed_pt add(const fixed_pt& oper1, const fixed_pt& oper2)

It is important to understand what C++ does behind your back. Even such a simple statement as:

answer = add(first, second);

calls a constructor, an assignment operator, and a destructor—all in that little piece of code.

In version 1 of the add function, we explicitly allocated a variable for the result. In version 2, C++ automatically creates a temporary variable for the result. This variable has no name and doesn’t really exist outside the return statement.

Creating the temporary variable causes the constructor to be called. The temporary variable is then assigned to answer; thus we have a call to the assignment function. After the assignment, C++ no longer has any use for the temporary variable and throws it away by calling the destructor.

Operator Functions

Using the add function for fixed-point numbers is a little awkward. It would be nice to be able to convince C++ to automatically call this function whenever we try to add two fixed numbers together with the + operator. That’s where operator overloading comes in. All we have to do is to turn the add function into a function named operator + (technically you can’t have a space in a function name, but this is a special case):

inline fixed_pt operator + (const fixed_pt& oper1, 
                            const fixed_pt& oper2)
{
  return fixed_pt(oper1.value + oper2.value);
}

When C++ sees that the fixed_pt class has a member function named operator + it automatically generates code that calls it when two fixed-point objects are added.

Note

The operator overloading functions should be used carefully. You should try to design them so they follow common-sense rules. That is, + should have something to do with addition; -, with subtraction; and so on. The C++ I/O streams break this rule by defining the shift operators (<< and >>) as input and output operators. This can lead to some confusion, such as:

std::cout << 8 << 2;

Does this output “8” followed by “2,” or does it output the value of the expression (8 << 2)? Unless you’re an expert, you can’t tell. In this case, the numbers “8” and “2” will be output.

You’ve seen how you can overload the + operator. Now let’s explore what other operators you can use.

Binary Arithmetic Operators

Binary operators take two arguments, one on each side of the operator. For example, multiplication and division are binary operators:

x * y;
a / b;

Unary operators take a single parameter. Unary operators include unary - and the address-of (&) operator:

-x
&y

The binary arithmetic operator functions take two constant parameters and produce a result. One of the parameters must be a class or structure object. The result can be anything. For example, the following functions are legal for binary addition:

fixed_pt operator +(const fixed_pt& v1, const fixed_pt& v2);
fixed_pt operator +(const fixed_pt& v1, const float v2);
fixed_pt operator +(const float v1,     const fixed_pt& v2);

fixed_pt operator +(float v1,    float v2);               // Illegal
// You can't overload a basic C++ operator such as adding two floating
// point numbers.

It makes sense to add real (float) and fixed-point numbers together. The result is that we’ve had to define a lot of different functions just to support the addition for our fixed-point class. Such variation of the definition is typical when overloading operators.

Table 18-2 lists the binary operators that can be overloaded.

Table 18-2. Binary operators that can be overloaded

Operator

Meaning

+

Addition

-

Subtraction

*

Multiplication

/

Division

%

Modulus

^

Bitwise exclusive OR

&

Bitwise AND

|

Bitwise OR

<<

Left shift

>>

Right shift

Relational Operators

The relational operators include such operators as equal (==) and not equal (!=). Normally they take two constant objects and return either true or false. (Actually they can return anything, but that would violate the spirit of relational operators.)

The equality operator for our fixed-point class is:

inline bool operator == (const fixed_pt& oper1, const fixed_pt& oper2)
{
    return (oper1.value == oper2.value);
}

Table 18-3 lists the relational operators.

Table 18-3. Relational operators

Operator

Meaning

==

Equality

!=

Inequality

<

Less than

>

Greater than

<=

Less than or equal to

>=

Greater than or equal to

Unary Operators

Unary operators, such as negative (-), take a single parameter. The negative operator for our fixed-point type is:

inline fixed_pt operator - (const fixed_pt& oper1)
    {
       return fixed_pt(-oper1.value);
    }

Table 18-4 lists the unary operators.

Table 18-4. Unary operators

Operator

Meaning

+

Positive

-

Negative

*

Dereference

&

Address of

~

Ones complement

Shortcut Operators

Operators such as += and -= are shortcuts for more complicated operators. But what are the return values of += and -=? A very close examination of the C++ standard reveals that these operators return the value of the variable after the increase or decrease. For example:

i = 5;
j = i += 2;    // Don't code like this

assigns j the value 7. The += function for our fixed-point class is:

inline fixed_pt& operator += (fixed_pt& oper1, 
                              const fixed_pt& oper2) 
{
    oper1.value += oper2.value;
    return (oper1);
}

Note that unlike the other operator functions we’ve defined, the first parameter is not a constant. Also, we return a reference to the first variable, not a new variable or a copy of the first.

Table 18-5 lists the shortcut operators.

Table 18-5. Simple shortcut operators

Operator

Meaning

+=

Increase

-=

Decrease

*=

Multiply by

/=

Divide by

%=

Remainder

^=

Exclusive OR into

&=

AND into

|=

OR into

<<=

Shift left

>>=

Shift right

Increment and Decrement Operators

The increment and decrement operators have two forms: prefix and postfix. For example:

i = 5;
j = i++;    // j = 5
i = 5;
j = ++i;    // j = 6

Both these operators use a function named operator ++. So how do you tell them apart? The C++ language contains a hack to handle this case. The prefix form of the operator takes one argument, the item to be incremented. The postfix takes two, the item to be incremented and an integer. The actual integer used is meaningless; it’s just a position holder to differentiate the two forms of the operation.

Our functions to handle the two forms of ++ are:

// Prefix      x = ++f
inline fixed_pt& operator ++(fixed_pt& oper) 
{
    oper.value += fixed_exp;
    return (oper);
}

// Postfix     x = f++
inline fixed_pt operator ++(fixed_pt& oper,int) 
{
    fixed_pt result(oper);   // Result before we incremented
    oper.value += fixed_exp;
    return (result);
}

This is messy. C++ has reduced us to using cute tricks: the unused integer parameter. In actual practice, I never use the postfix version of increment and always put the prefix version on a line by itself. That way, I can avoid most of these problems.

The choice, prefix versus postfix, was decided by looking at the code for the two versions. As you can see, the prefix version is much simpler than the postfix version. Restricting yourself to the prefix version not only simplifies your code, but it also makes the compiler’s job a little easier.

Table 18-6 lists the increment and decrement operators.

Table 18-6. Increment and decrement operators

Operator

Meaning

++

Increment

--

Decrement

Logical Operators

Logical operators include AND (&&), OR (||), and NOT (!). They can be overloaded, but just because you can do it doesn’t mean you should. In theory, logical operators work only on bool values. In practice, because numbers can be converted to the bool type (0 is false, nonzero is true), these operators work for any number. But don’t confuse the issue more by overloading them.

Table 18-7 lists the logical operators.

Table 18-7. Logical operators

Operation

Meaning

||

Logical OR

&&

Logical AND

!

Logical NOT

I/O Operators

You’ve been using the operators << and >> for input and output. Actually these operators are overloaded versions of the shift operators. This has the advantage of making I/O fairly simple, at the cost of some minor confusion.

We would like to be able to output our fixed-point numbers just like any other data type. To do this we need to define a << operator for it.

We are sending our data to the output stream class std::ostream. The data itself is fixed_pt. So our output function is:

inline std::ostream& operator << (std::ostream& out_file, 
                                  const fixed_pt& number)
{
    long int before_dp = number.value / fixed_exp;
    long int after_dp1  = abs(number.value % fixed_exp);
    long int after_dp2  = after_dp1 % 10;
    after_dp1 /= 10;

    out_file << before_dp << '.' << after_dp1 << after_dp2;
    return (out_file);
}

The function returns a reference to the output file. This enables the caller to string a series of << operations together, such as:

fixed_pt a_fixed_pt(1.2);
std::cout << "The answer is " << a_fixed_pt << '
';

The result of this code is:

The answer is 1.20

Normally the << operator takes two constant arguments. In this case, the first parameter is a nonconstant std::ostream . This is because the << operator, when used for output, has side effects, the major one being that the data goes to the output stream. In general, however, it’s not a good idea to add side effects to an operator that doesn’t already have them.

Input should be just as simple as output. You might think all we have to do is read the numbers (and the related extra characters):

// Simple-minded input operation
inline istream& operator >> (istream& in_file, fixed_pt& number) {
    int before_dp;       // Part before the decimal point
    char dot;            // The decimal point
    char after_dp1;      // After decimal point (first digit)
    char after_dp2;      // After decimal point (second digit)

    in_file >> before_dp >> dot >> after_dp1 >> after_dp2;
    number.value = before_dp * fixed_exp +
                   after_dp1 - '0' * 10 +
                   after_dp2 - '0';
    return (in_file);
}

In practice, it’s not so simple. Something might go wrong. For example, the user may type in 1x23 instead of 1.23. What do we do then?

The answer is that we would fail gracefully. In our new reading routine, the first thing we do is set the value of our number to 0.00. That way, if we do fail, there is a known value in the number:

inline std::istream& operator >> (std::istream& in_file, 
                                  fixed_pt& number)
{
    number.value = 0;

Next we create a std::istream::sentry variable. This variable protects the std::istream in case of failure:

std::istream::sentry the_sentry(in_file, true);

The second parameter tells the sentry to skip any leading whitespace. (It’s optional. The default value is false, which tells the sentry to not skip the whitespace.)

Now we need to check to see if everything went well when the sentry was constructed:

if (the_sentry) {
    // Everything is OK, do the read
   ....
} else {
    in_file.setstate(std::ios::failbit); // Indicate failure
}

The function setstate is used to set a flag indicating that the input operation found a problem. This allows the caller to test to see whether the input worked by calling the bad function. (This function can also cause an exception to be thrown. See Chapter 22 for more information.)

Let’s assume that everything is OK. We’ve skipped the whitespace at the beginning of the number, so we should now be pointing to the digits in front of the decimal point. Let’s grab them. Of course we check for errors afterwards:

in_file >> before_dp;   // Get number before the decimal point
if (in_file.bad(  )) return (in_file);

The next step is to read the decimal point, make sure that nothing went wrong, and that we got the decimal point:

in_file >> ch;  // Get first character after number

if (in_file.bad(  )) return (in_file);

// Expect a decimal point
if (ch != '.') {
   in_file.setstate(std::ios::failbit);
   return (in_file);
}

Now we get the two characters after the decimal point and check for errors:

in_file >> after_dp1 >> after_dp2;
if (in_file.bad(  )) return (in_file);

Both characters should be digits (we’re not very flexible in our input format—that’s a feature, not a bug). To make sure that the correct characters are read, we use the standard library function isdigit to check each of them to make sure they are digits. (See your library documentation for information on isdigit and related functions.)

// Check result for validity
if ((!isdigit(after_dp1)) || (!isdigit(after_dp2))) {
   in_file.setstate(std::ios::failbit);
   return (in_file);
}

Everything is OK at this point, so we set the number and we’re done:

// Todo make after db two digits exact
number.value = before_dp * fixed_exp +
    (after_dp1 - '0') * 10 +
    (after_dp2 - '0'),

The complete version of the fixed-point number reader appears in Example 18-1.

Example 18-1. fixed_pt/fixed_pt.read
/********************************************************
 * istream >> fixed_pt -- read a fixed_pt number        *
 *                                                      *
 * Parameters                                           *
 *      in_file -- file to read                         *
 *      number -- place to put the number               *
 *                                                      *
 * Returns                                              *
 *      reference to the input file                     *
 ********************************************************/
std::istream& operator >> (std::istream& in_file, fixed_pt& number)
{
    long int before_dp; // Part before decimal point (dp)
    char after_dp1, after_dp2;  // Part after decimal point (dp)
    char ch;            // Random character used to verify input

    number = 0.0;       // Initialize the number (just in case)

    // We only work for 2 digit fixed point numbers
    assert(fixed_exp == 100);

    // Sentry to protect the I/O
    std::istream::sentry the_sentry(in_file, true);     

    if (the_sentry)
    {
        if (in_file.bad(  )) return (in_file);    

        // Get the number that follows the whitespace
        in_file >> before_dp;

        if (in_file.bad(  )) return (in_file);

        in_file >> ch;  // Get first character after number

        if (in_file.bad(  )) return (in_file);

        // Expect a decimal point
        if (ch != '.') {
           in_file.setstate(std::ios::failbit);
           return (in_file);
        }

        in_file >> after_dp1 >> after_dp2;
        if (in_file.bad(  )) return (in_file);

        // Check result for validity
        if ((!isdigit(after_dp1)) || (!isdigit(after_dp2))) {
           in_file.setstate(std::ios::failbit);
           return (in_file);
        }

        // Todo make after db two digits exact
        number.value = before_dp * fixed_exp +
            (after_dp1 - '0') * 10 +
            (after_dp2 - '0'),

    }
    else
    {
       in_file.setstate(std::ios::failbit);
    }
   return (in_file);
}

Index Operator “[ ]”

The operator [ ] is used by C++ to index arrays. As you will see in Chapter 20, this operator is very useful when defining a class that mimics an array. Normally, this function takes two arguments, a class that simulates an array and an index, and returns a reference to an item in the array:

double& operator[](array_class& array, int index)

We cover the [] operator in more detail in Chapter 23.

new and delete

We’ll say very little about overloading the global operators new and delete at this time. First of all, they aren’t introduced until Chapter 20, so you don’t know what they do. Second, when you know what they do, you won’t want to override them.

I’ve seen only one program where the new and delete operators (or at least their C equivalents) were overridden. That program was written by a very clever programmer who liked to do everything a little strangely. The result was code that was a nightmare to debug.

So unless you are a very clever programmer, leave new and delete alone. And if you are a clever programmer, please leave new and delete alone anyway. Some day I might have to debug your code.

Exotic Operators

C++ contains a very rich set of operators. Some of these are rarely, if ever, used. These include:

( )

Allows you to define a default function for a class.

,

Comma operator. Allows two expressions to be concatenated. It is rarely used and probably should not be overloaded.

->*

Pointer to member. Rarely used.

->

Class member.

All of these operators are discussed in Chapter 29.

Operator Member Functions

So far we’ve been using operator overloading functions just like ordinary functions. They can also be defined as member functions. The only difference is that as member functions the first argument, the class itself, is implied. For example, you can write the operator += as an ordinary function or as a member function. Here’s the ordinary version that you’ve already seen:

inline fixed_pt& operator +=(fixed_pt& oper1, const fixed_pt& oper2) 
{
    oper1.value += oper2.value;
    return (oper1);
}

Here’s the member function:

class fixed_pt {
    // .....
    public:
        inline fixed_pt& operator +=(const fixed_pt& oper2) 
        {
            value += oper2.value;
            return (*this);
        }

The only trick used in this function is the keyword this. This is a predefined variable that refers to the current object. For example, you can access the data member value using the statement:

value += oper2.value;

The same statement can be written as:

this->value += oper2.value;

In most cases, you don’t need to use this. In a few cases, however, such as with the += operator, it comes in handy.

Which flavor of the operator overloading functions should you use? The one that makes your program the clearest and easiest to read. In general, I use the standard functions for the simple operators, such as +, -, *, and /, while I use member functions for the shortcut and unary operators, such as +=, -=, ++, and unary -.

Some overloaded functions work only as member functions. These include the casting operators and class-specific versions of new and delete.

All overload operator functions that have the class type as the left argument should be member functions. This helps keep everything in one well-designed class.

Casting

Finally we come to the cast operators. Casting is a way of changing one type to another, such as when we cast our fixed_pt type to a long int (truncating the two digits after the decimal point). We can define a cast operator for this function as:

class fixed_pt {
    public:
        // (We didn't really put this in our fixed_point class)
         operator double(  ) {return (value / fixed_exp);}

C++ automatically calls this function whenever it wants to turn a fixed_pt into a long int.

The trouble is that by defining a cast, you give C++ something else that it can call behind your back. Personally, I like to know whenever C++ calls something, so I avoid creating cast operators. Unless you have a very good reason to define one, don’t create a cast operator function.

Warts

The fixed_pt class described in this chapter has been simplified a bit to make it easy to understand and to best teach operator overloading. There are some limitations to this code that you should be aware of, however.

First, although the number of digits after the decimal point is controlled by the constant fixed_exp, in reality the code is limited to two digits after the decimal point. That’s because the input and output functions have this limit hardcoded in. (They should be made general.)

Also, with C++ templates (see Chapter 24) there is no reason to hardcode the location of the decimal point at all. You can create a general-purpose template that lets you specify the fixed point when you declare the class. More on this later.

In spite of these problems, this class does serve as a good illustration of how to perform operator overloading in C++.

Full Definition of the Fixed-Point Class

Example 18-2 and Example 18-3 list the entire fixed-point class. The beginning of the header file summarizes all the functions that are defined. In creating this class, I discovered that it consisted of many (29 to be exact) little one- and two-line functions. Commenting each of these with a full-function comment block would obscure the code. In other words, this is one of the few cases (the very few) where adding comments would cause confusion, so most of the small functions have no comments.

When creating this class, I noticed that a lot of the functions have a similar structure. For example, += looks a lot like -= and so on. As a matter of fact, I created the -= operator by copying the += functions and editing a little. C++ contains a rich operator set that causes this sort of repetition to happen when you’re trying to define a complete set of operators for a class.

Finally, the simple operations are defined in the file fixed_pt.h ( Example 18-2) while the longer functions are left in the file fixed_pt.cpp ( Example 18-3). Finally, we’ve included a limited unit test in fixed_test.cpp (Example 18-4).

Example 18-2. fixed_pt/fixed_pt.h
#ifndef _  _fixed_pt_h_  _  // Avoid double includes
#define _  _fixed_pt_h_  _  // Prevent double include

#include <iostream>
#include <cassert>
#include <stdlib.h>

namespace fixed_pt {

/* Note: This should be made into a template so that multiple
 * fixed points may be used, but the purpose of this class
 * is to teach operator overloading and templates would be a
 * needless complication.
 */

const int fixed_exp = 100;      // 10**fixed_point */

/* Fudge factor to make doubles into fixed point numbers */
const double fixed_fudge_factor = 0.0001; 


/********************************************************
 * Fixed point class                                    *
 *                                                      *
 * Members defined                                      *
 *      fixed_pt(  )            // Default constructor  *
 *      fixed_pt(double)        // Specify an inital   *
 *                              // value                *
 *      fixed_pt(fixed_pt)      // Copy constructor     *
 *                                                      *
 *      set(double)             // Set the value        *
 *      double get(  );         // Return the value     *
 *                              // as a double          *
 *                                                      *
 * Operator member functions                            *
 *                      f -- a fixed_pt number          *
 *                      s -- a scalar (double)          *
 *      f = f                                           *
 *      f += f;                                         *
 *      f += s;                                         *
 *      f -= f;                                         *
 *      f -= s;                                         *
 *      f /= f;                                         *
 *      f /= s;                                         *
 *      f *= f;                                         *
 *      f *= s;                                         *
 *      f++                                             *
 *      ++f                                             *
 *      f--                                             *
 *      --f                                             *
 *                                                      *
 * Arithmetic operators defined                         *
 *      f = f + f;                                      *
 *      f = s + f;                                      *
 *      f = f + s;                                      *
 *      f = f - f;                                      *
 *      f = s - f;                                      *
 *      f = f - s;                                      *
 *      f = f * f;                                      *
 *      f = s * f;                                      *
 *      f = f * s;                                      *
 *      f = f / f;                                      *
 *      f = s / f;                                      *
 *      f = f / s;                                      *
 *      -f                                              *
 *      +f                                              *
 *      ostream << f    // Output function              *
 *      istream >> f    // Input function               *
 ********************************************************/
class fixed_pt
{
    private:
        long int value; // Value of our fixed point number

        static long int double_to_fp(const double the_double) {
            return (
                static_cast<long int>(
                    the_double *
                    static_cast<double>(fixed_exp) +
                    fixed_fudge_factor));
        }


    public:
        // Default constructor, zero everything
        fixed_pt(  ): value(0) { }

        // Copy constructor
        fixed_pt(const fixed_pt& other_fixed_pt) :
            value(other_fixed_pt.value)
        { }
            
        // Construct a fixed_pt out of a double
        fixed_pt(const double init_real) :
            value(double_to_fp(init_real))
        {}

        // Destructor does nothing
        ~fixed_pt(  ) {}

        // Function to set the number
        void set(const double real) {
            value = double_to_fp(real);
                    
        }

        // Function to return the value
        double get(  ) const {
            return (static_cast<double>(value) / fixed_exp);
        }

        // Note: Because of the way we store internal data
        // we do not have to check for self assignment
        fixed_pt operator = (const fixed_pt& oper2) {
           value = oper2.value;
           return (*this);
        }

        fixed_pt& operator += (const fixed_pt& oper2) {
           value += oper2.value;
           return (*this);
        }

        fixed_pt& operator += (double oper2) {
           value += double_to_fp(oper2);
           return (*this);
        }

        fixed_pt& operator -= (const fixed_pt& oper2) {
           value -= oper2.value;
           return (*this);
        }

        fixed_pt& operator -= (double oper2) {
           value -= double_to_fp(oper2);
           return (*this);
        }

        fixed_pt& operator *= (const fixed_pt& oper2) {
           value *= oper2.value;
           value /= fixed_exp;
           return *this; 
        }

        fixed_pt& operator *= (double oper2) {
           value *= double_to_fp(oper2);
           value /= fixed_exp;
           return (*this);
        }

        fixed_pt& operator /= (const fixed_pt& oper2) {
            assert(oper2.value != 0.0);
            value /= oper2.value;
            value *= fixed_exp;
        }

        fixed_pt& operator /= (double oper2) {
            assert(double_to_fp(oper2) != 0.0);
            value /= double_to_fp(oper2);
            value *= fixed_exp;
            return (*this);
        }

        // f++
        fixed_pt operator ++(int) {
           fixed_pt result(*this);
           value += fixed_exp;
           return (result);
        }

        // ++f
        fixed_pt& operator ++(  ) {
           value += fixed_exp;
           return (*this);
        }

        // f--
        fixed_pt operator --(int) {
           fixed_pt result(*this);
           value -= fixed_exp;
           return (result);
        }

        // --f
        fixed_pt& operator --(  ) {
           value -= fixed_exp;
           return (*this);
        }

    private:
        // Used for internal conversions for our friends
        fixed_pt(const long int i_value) : value(i_value){}

    friend fixed_pt operator + (const fixed_pt& oper1, const fixed_pt& oper2);
    friend fixed_pt operator + (const fixed_pt& oper1, const double oper2);
    friend fixed_pt operator + (const double oper1, const fixed_pt& oper2);

    friend fixed_pt operator - (const fixed_pt& oper1, const fixed_pt& oper2);
    friend fixed_pt operator - (const fixed_pt& oper1, const double oper2);
    friend fixed_pt operator - (double oper1, const fixed_pt& oper2);

    friend fixed_pt operator * (const fixed_pt& oper1, const fixed_pt& oper2);
    friend fixed_pt operator * (const fixed_pt& oper1, const double oper2);
    friend fixed_pt operator * (double oper1, const fixed_pt& oper2);

    friend fixed_pt operator / (const fixed_pt& oper1, const fixed_pt& oper2);
    friend fixed_pt operator / (const fixed_pt& oper1, const double oper2);
    friend fixed_pt operator / (const double& oper1, const fixed_pt& oper2);

    friend bool operator == (const fixed_pt& oper1, const fixed_pt& oper2);
    friend fixed_pt operator - (const fixed_pt& oper1);
    friend std::ostream& operator << (std::ostream& out_file, const fixed_pt& number);
    friend std::istream& operator >> (std::istream& in_file, fixed_pt& number);
};

inline fixed_pt operator + (const fixed_pt& oper1, const fixed_pt& oper2)
{
  return fixed_pt(oper1.value + oper2.value);
}

inline fixed_pt operator + (const fixed_pt& oper1, const double oper2)
{
  return fixed_pt(oper1.value + fixed_pt::double_to_fp(oper2));
}

inline fixed_pt operator + (double oper1, const fixed_pt& oper2)
{
  return fixed_pt(fixed_pt::double_to_fp(oper1) + oper2.value);
}

inline fixed_pt operator - (const fixed_pt& oper1, const fixed_pt& oper2)
{
  return fixed_pt(oper1.value - oper2.value);
}

inline fixed_pt operator - (const fixed_pt& oper1, const double oper2)
{
  return fixed_pt(oper1.value - fixed_pt::double_to_fp(oper2));
}

inline fixed_pt operator - (double oper1, const fixed_pt& oper2)
{
  return fixed_pt(fixed_pt::double_to_fp(oper1) - oper2.value);
}

inline fixed_pt operator * (const fixed_pt& oper1, const fixed_pt& oper2)
{
    return fixed_pt(oper1.value * oper2.value / fixed_exp);
}

inline fixed_pt operator * (const fixed_pt& oper1, const double oper2)
{
    return fixed_pt(oper1.value * fixed_pt::double_to_fp(oper2) / fixed_exp);
}

inline fixed_pt operator * (const double oper1, const fixed_pt& oper2)
{
    return fixed_pt(fixed_pt::double_to_fp(oper1) * oper2.value / fixed_exp);
}

inline fixed_pt operator / (const fixed_pt& oper1, const fixed_pt& oper2) 
{
    assert(oper2.value != 0);
    return fixed_pt((oper1.value * fixed_exp) / oper2.value);
}


inline fixed_pt operator / (const double& oper1, const fixed_pt& oper2) 
{
    assert(oper2.value != 0);
    return fixed_pt((fixed_pt::double_to_fp(oper1) * fixed_exp) / oper2.value);
}

inline fixed_pt operator / (const fixed_pt& oper1, const double oper2) 
{
    assert(oper2 != 0);
    return fixed_pt((oper1.value  * fixed_exp) / fixed_pt::double_to_fp(oper2));
}

inline bool operator == (const fixed_pt& oper1, const fixed_pt& oper2)
{
    return (oper1.value == oper2.value);
}

inline bool operator != (const fixed_pt& oper1, const fixed_pt& oper2)
{
    return (!(oper1 == oper2));
}

inline fixed_pt operator - (const fixed_pt& oper1)
{
    return fixed_pt(-oper1.value);
}

inline fixed_pt operator + (const fixed_pt& oper1)
{
    return fixed_pt(oper1);
}

inline std::ostream& operator << (std::ostream& out_file, const fixed_pt& number)
{
    long int before_dp = number.value / fixed_exp;
    long int after_dp1  = abs(number.value % fixed_exp);
    long int after_dp2  = after_dp1 % 10;
    after_dp1 /= 10;

    out_file << before_dp << '.' << after_dp1 << after_dp2;
    return (out_file);
}

extern std::istream& operator >> (std::istream& in_file, fixed_pt& number);

}
#endif /* _  _fixed_pt_h_  _ */     // Avoid double includes
Example 18-3. fixed_pt/fixed_pt.cpp
#include <iostream>

#include "fixed_pt.h"
#include "ctype.h"

namespace fixed_pt {

/********************************************************
 * istream >> fixed_pt -- read a fixed_pt number        *
 *                                                      *
 * Parameters                                           *
 *      in_file -- file to read                         *
 *      number -- place to put the number               *
 *                                                      *
 * Returns                                              *
 *      reference to the input file                     *
 ********************************************************/
std::istream& operator >> (std::istream& in_file, fixed_pt& number)
{
    long int before_dp; // Part before decimal point (dp)
    char after_dp1, after_dp2;  // Part after decimal point (dp)
    char ch;            // Random character used to verify input

    number = 0.0;       // Initialize the number (just in case)

    // We only work for 2 digit fixed point numbers
    assert(fixed_exp == 100);

    // Sentry to protect the I/O
    std::istream::sentry the_sentry(in_file, true);     

    if (the_sentry)
    {
        if (in_file.bad(  )) return (in_file);    

        // Get the number that follows the whitespace
        in_file >> before_dp;

        if (in_file.bad(  )) return (in_file);

        in_file >> ch;  // Get first character after number

        if (in_file.bad(  )) return (in_file);

        // Expect a decimal point
        if (ch != '.') {
           in_file.setstate(std::ios::failbit);
           return (in_file);
        }

        in_file >> after_dp1 >> after_dp2;
        if (in_file.bad(  )) return (in_file);

        // Check result for validity
        if ((!isdigit(after_dp1)) || (!isdigit(after_dp2))) {
           in_file.setstate(std::ios::failbit);
           return (in_file);
        }

        // Todo make after db two digits exact
        number.value = before_dp * fixed_exp +
            (after_dp1 - '0') * 10 +
            (after_dp2 - '0'),

    }
    else
    {
       in_file.setstate(std::ios::failbit);
    }
   return (in_file);
}

}
Example 18-4. fixed_pt/fixed_test.cpp
#include <iostream>
#include "fixed_pt.h"


int main(  )
{
    std::cout << "Expect 1.23 " << fixed_pt::fixed_pt(1.23) << std::endl;
    std::cout << "Expect 1.00 " << fixed_pt::fixed_pt(1.00) << std::endl;
    std::cout << "Expect 1.02 " << fixed_pt::fixed_pt(1.02) << std::endl;
    std::cout << "Expect 1.20 " << fixed_pt::fixed_pt(1.20) << std::endl;
    fixed_pt::fixed_pt f3 = 1.23;
    std::cout << "Expect 1.23 " << f3 << std::endl;

    fixed_pt::fixed_pt f1(1.23 + 0.005);
    fixed_pt::fixed_pt f2(4.56 + 0.005);

    std::cout << f1 << " + " << f2 << " = " << f1 + f2 << std::endl;
    std::cout << f1 << " - " << f2 << " = " << f1 - f2 << std::endl;
    std::cout << f1 << " * " << f2 << " = " << f1 * f2 << std::endl;
    std::cout << f1 << " / " << f2 << " = " << f1 / f2 << std::endl;

    return (0);
}

Question 18-1: Why does Example 18-5 fail? When run it prints out:

Copy constructor called
Copy constructor called

over and over. Hint: Review Section 13.4.4. Thanks to Jeff Hewett for this problem.

Example 18-5. equal/equal.cpp
#include <iostream>

class trouble {
    public:
        int data;

        trouble(  );
        trouble(const trouble& old);
        trouble operator = (const trouble old_trouble);
};

trouble::trouble(  ) {
   data = 0;
}

trouble::trouble(const trouble& old) {
    std::cout << "Copy constructor called
";
    *this = old;
}

trouble trouble::operator = (const trouble old_trouble) {
    std::cout << "Operator = called
";
    data = old_trouble.data;
    return (*this);
}

int main(  )
{
    trouble trouble1;
    trouble trouble2(trouble1);

    return (0);
}

Programming Exercises

Exercise 18-1: Write a class to handle fractions such as “1/3.” Define addition, subtraction, multiplication, and division operators for these fractions.

For example: 1/3 + 1/2 = 5/6.

Exercise 18-2: Write a fixed-point number class to handle numbers. All numbers are of the form DDDDD.D. In other words, all numbers have only a single digit to the right of the decimal point. Use integers to implement this class.

Exercise 18-3: Write a class to implement a sparse integer array. This is much like a simple integer array:

int simple_array[100];

But unlike a simple array, the indices can go from 0 to 1,000,000. That’s the bad news. The good news is that at most 100 elements will be set at any time. The rest of the elements will be zero.

Exercise 18-4: Write a time class. Implement functions to add, subtract, read, and print times.

Exercise 18-5: Write a date class that allows you to add, subtract, read, and print simple dates of the form MM/DD. Assume year is not a leap year.

Exercise 18-6: (Advanced) Write a full-date class that allows you to add, subtract, read, and print dates of the form MM/DD/YY.

Answers to Chapter Questions

Answer 18-1: The copy constructor calls the operator = function. The parameter list to this function is:

trouble trouble::operator = (trouble old_trouble) {

The parameter to this function is being passed as a call-by-value parameter. When C++ sees this type of parameter it calls the copy constructor to put the parameter on the stack.

image with no caption

So we have an infinite loop. The copy constructor calls the operator = function. C++ sees the call-by-value parameter and calls the copy constructor, which calls operator = and causes the copy constructor to be called. This keeps up until the system runs out of stack space or the user gets disgusted and aborts the program.

The solution is to pass the parameter to operator = as a reference. This not only is more efficient, but also works:

trouble trouble::operator = (const trouble& old_trouble) {

Unfortunately, this only solves part of the problem. Now, we don’t call the copy constructor going into the operator = function. But when we return (*this), the return value has to be copied, so we still call the copy constructor. The solution is to return a reference to the class instead of a copy of the class. Thus, our declaration of the operator = function should be:

trouble& trouble::operator = (const trouble& old_trouble) {
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.226.66