Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Beginning Visual C++ 2005 (2006) [eng]

.pdf
Скачиваний:
125
Добавлен:
16.08.2013
Размер:
18.66 Mб
Скачать

More on Classes

long lval; float fval;

} uinst = {1.5};

An instance of shareDLF occupies 8 bytes, as illustrated in Figure 8-2.

8 bytes

Ival

fval

dval

Figure 8-2

In the example, you defined an instance of the union, uinst, as well as the tag name for the union. You also initialized the instance with the value 1.5.

You can only initialize the first member of the union when you declare an instance.

Anonymous Unions

You can define a union without a union type name, in which case an instance of the union is automatically declared. For example, suppose you define a union like this:

union

{

char* pval;

double dval; long lval;

};

This statement defines both a union with no name and an instance of the union with no name. Consequently, you can refer the variables that it contains just by their names, as they appear in the union definition, pval, dval, and lval. This can be more convenient than a normal union with a type name, but you need to be careful that you don’t confuse the union members with ordinary variables. The members of the union still share the same memory. As an illustration of how the anonymous union above works, to use the double member, you could write this statement:

dval = 99.5;

// Using a member of an anonymous union

409

Chapter 8

As you can see, there’s nothing to distinguish the variable dval as a union member. If you need to use anonymous unions, you could use a naming convention to make the members more obvious and thus make your code a little safer from being misunderstood.

Unions in Classes and Structures

You can include an instance of a union in a class or in a structure. If you intend to store different types of value at different times, this usually necessitates maintaining a class data member to indicate what kind of value is stored in the union. There isn’t usually a great deal to be gained by using unions as class or struct members.

Operator Overloading

Operator overloading is a very important capability because it enables you to make standard C++ operators, such as +, -, * and so on, work with objects of your own data types. It allows you to write a function that redefines a particular operator so that it performs a particular action when it’s used with objects of a class. For example, you could redefine the operator > so that, when it was used with objects of the class CBox that you saw earlier, it would return true if the first CBox argument had a greater volume than the second.

Operator overloading doesn’t allow you to invent new operators, nor can you change the precedence of an operator, so your overloaded version of an operator has the same priority in the sequence of evaluating an expression as the original base operator. The operator precedence table can be found in Chapter 2 of this book and in the MSDN Library.

Although you can’t overload all the operators, the restrictions aren’t particularly oppressive. The following are the operators that you can’t overload:

The scope resolution operator

::

The conditional operator

?:

The direct member selection operator

.

The size-of operator

sizeof

The de-reference pointer to class member operator

.*

Anything else is fair game, which gives you quite a bit of scope. Obviously, it’s a good idea to ensure that your versions of the standard operators are reasonably consistent with their normal usage, or at least reasonably intuitive in their operation. It wouldn’t be a very sensible approach to produce an overloaded + operator for a class that performed the equivalent of a multiply on class objects. The best way to understand how operator overloading works is to work through an example, so let’s implement what I just referred to, the greater than operator, >, for the CBox class.

410

More on Classes

Implementing an Overloaded Operator

To implement an overloaded operator for a class, you have to write a special function. Assuming that it is a member of the class CBox, the declaration for the function to overload the > operator within the class definition is as follows:

class CBox

{

public:

bool operator>(CBox& aBox) const; // Overloaded ‘greater than’ // Rest of the class definition...

};

The word operator here is a keyword. Combined with an operator symbol or name, in this case, >, it defines an operator function. The function name in this case is operator>(). You can write an operator function with or without a space between the keyword operator and the operator itself, as long as there’s no ambiguity. The ambiguity arises with operators with names rather than symbols such as new or delete. If you were to write operatornew and operatordelete without a space, they are legal names for ordinary functions, so for operator functions with these operators, you must leave a space between the keyword operator and the operator name itself. Note that you declare the operator>() function as const because it doesn’t modify the data members of the class.

With the operator>()operator function, the right operand of the operator is defined by the function parameter. The left operand is defined implicitly by the pointer this. So, if you have the following if statement:

if(box1 > box2)

cout << endl << “box1 is greater than box2”;

the expression between the parentheses in the if will call our operator function and is equivalent to this function call:

box1.operator>(box2);

The correspondence between the CBox objects in the expression and the operator function parameters is illustrated in Figure 8-3.

if( box1 > box2 )

Function argument

bool CBox::operator>(const CBox& aBox) const

{

The object pointed to by this

return (this->volume()) > (aBox.Volume());

}

Figure 8-3

411

Chapter 8

Let’s look at how the code for the operator>() function works:

//Operator function for ‘greater than’ which

//compares volumes of CBox objects.

bool CBox::operator>(const CBox& aBox) const

{

return this->Volume() > aBox.Volume();

}

You use a reference parameter to the function to avoid unnecessary copying when the function is called. Because the function does not alter the object for which it is called, you can declare it as const. If you don’t do this, you could not use the operator to compare const objects of type CBox at all.

The return expression uses the member function Volume() to calculate the volume of the CBox object pointed to by this, and compares the result with the volume of the object aBox using the basic operator > . The basic > operator returns a value of type int (not a bool) and thus, 1 is returned if the CBox object pointed to by the pointer this has a larger volume than the object aBox passed as a reference argument, and 0 otherwise. The value that results from the comparison will be automatically converted to the return type of the operator function, type bool.

Try It Out

Operator Overloading

You can exercise the operator>() function with an example:

//Ex8_03.cpp

//Exercising the overloaded ‘greater than’ operator

#include <iostream>

// For stream I/O

using std::cout;

 

using std::endl;

 

class CBox

// Class definition at global scope

{

 

public:

// Constructor definition

CBox(double lv = 1.0, double wv = 1.0, double hv = 1.0): m_Length(lv), m_Width(wv), m_Height(hv)

{

cout << endl << “Constructor called.”;

}

// Function to calculate the volume of a box double Volume() const

{

return m_Length*m_Width*m_Height;

}

bool operator>(const CBox& aBox) const; // Overloaded ‘greater than’

// Destructor definition ~CBox()

{

cout << “Destructor called.” << endl;

412

 

More on Classes

 

 

}

 

private:

 

double m_Length;

// Length of a box in inches

double m_Width;

// Width of a box in inches

double m_Height;

// Height of a box in inches

};

//Operator function for ‘greater than’ that

//compares volumes of CBox objects.

bool CBox::operator>(const CBox& aBox) const

{

return this->Volume() > aBox.Volume();

}

int main()

{

CBox smallBox(4.0, 2.0, 1.0); CBox mediumBox(10.0, 4.0, 2.0); CBox bigBox(30.0, 20.0, 40.0);

if(mediumBox > smallBox) cout << endl

<< “mediumBox is bigger than smallBox”;

if(mediumBox > bigBox) cout << endl

<< “mediumBox is bigger than bigBox”;

else

cout << endl

<< “mediumBox is not bigger than bigBox”;

cout << endl; return 0;

}

How It Works

The prototype of the operator>() operator function appears in the public section of the class. As the function definition is outside the class definition, it won’t default to inline. This is quite arbitrary. You could just as well have put the definition in place of the prototype in the class definition. In this case, you wouldn’t need to qualify the function name with CBox:: in front of it. As you’ll remember, this is necessary when a function member is defined outside the class definition to tell the compiler that the function is a member of the class CBox.

The function main() has two if statements using the operator > with class members. These automatically invoke our overloaded operator. If you wanted to get confirmation of this, you could add an output statement to the operator function. The output from this example is:

Constructor called. Constructor called. Constructor called.

mediumBox is bigger than smallBox

413

Chapter 8

mediumBox is not bigger than bigBox Destructor called.

Destructor called. Destructor called.

The output demonstrates that the if statements work fine with our operator function, so being able to express the solution to CBox problems directly in terms of CBox objects is beginning to be a realistic proposition.

Implementing Full Support for an Operator

With our operator function operator>(), there are still a lot of things that you can’t do. Specifying a problem solution in terms of CBox objects might well involve statements such as the following:

if(aBox > 20.0)

// Do something...

Our function won’t deal with that. If you try to use an expression comparing a CBox object with a numerical value, you’ll get an error message. To support this capability, you would need to write another version of the operator>()function as an overloaded function.

You can quite easily support the type of expression that you’ve just seen. The declaration of the member function within the class would be:

// Compare a CBox object with a constant

bool operator>(const double& value) const;

This would appear in the definition of the class and the right operand for the > operator corresponds to the function parameter here. The CBox object that is the left operand is passed as the implicit pointer this.

The implementation of this overloaded operator is also easy. It’s just one statement in the body of the function:

// Function to compare a CBox object with a constant bool CBox::operator>(const double& value) const

{

return this->Volume() > value;

}

This couldn’t be much simpler, could it? But you still have a problem using the > operator with CBox objects. You may well want to write statements such as this:

if(20.0 > aBox)

// do something...

You might argue that this could be done by implementing the operator<() operator function that accepted a right argument of type double and rewriting the statement above to use it, which is quite true. Indeed, implementing the < operator is likely to be a requirement for comparing CBox objects anyway, but an implementation of support for an object type shouldn’t artificially restrict the ways in which

414

More on Classes

you can use the objects in an expression. The use of the objects should be as natural as possible. The problem is how to do it.

A member operator function always provides the left argument as the pointer this. Because the left argument, in this case, is of type double, you can’t implement it as a member function. That leaves you with two choices: an ordinary function or a friend function. Because you don’t need to access the private members of the class, it doesn’t need to be a friend function, so you can implement the overloaded > operator with a left operand of type double as an ordinary function. The prototype, placed outside the class definition, of course, because it isn’t a member, is:

bool operator>(const double& value, const CBox& aBox);

The implementation is:

// Function comparing a constant with a CBox object bool operator>(const double& value, const CBox& aBox)

{

return value > aBox.Volume();

}

As you have seen already, an ordinary function (and a friend function too for that matter) accesses the members of an object by using the direct member selection operator and the object name. Of course, an ordinary function only has access to the public members. The member function Volume() is public, so there’s no problem using it here.

If the class didn’t have the public function Volume(), you could either use a friend function that could access the private data members directly, or you could provide a set of member functions to return the values of the private data members and use those in an ordinary function to implement the comparison.

Try It Out

Complete Overloading of the > Operator

We can put all this together in an example to show how it works:

//Ex8_04.cpp

//Implementing a complete overloaded ‘greater than’ operator

#include <iostream>

// For stream I/O

using std::cout;

 

using std::endl;

 

class CBox

// Class definition at global scope

{

 

public:

// Constructor definition

CBox(double lv = 1.0, double wv = 1.0, double hv = 1.0): m_Length(lv), m_Width(wv), m_Height(hv)

{

cout << endl << “Constructor called.”;

}

// Function to calculate the volume of a box

415

Chapter 8

double Volume() const

{

return m_Length*m_Width*m_Height;

}

//Operator function for ‘greater than’ that

//compares volumes of CBox objects.

bool operator>(const CBox& aBox) const

{

return this->Volume() > aBox.Volume();

}

//Function to compare a CBox object with a constant bool operator>(const double& value) const

{

return this->Volume() > value;

}

//Destructor definition

~CBox()

{ cout << “Destructor called.” << endl;}

private:

double m_Length; double m_Width; double m_Height;

};

//Length of a box in inches

//Width of a box in inches

//Height of a box in inches

int operator>(const double& value, const CBox& aBox); // Function prototype

int main()

{

CBox smallBox(4.0, 2.0, 1.0); CBox mediumBox(10.0, 4.0, 2.0);

if(mediumBox > smallBox) cout << endl

<< “mediumBox is bigger than smallBox”;

if(mediumBox > 50.0) cout << endl

<< “mediumBox capacity is more than 50”;

else

cout << endl

<< “mediumBox capacity is not more than 50”;

if(10.0 > smallBox) cout << endl

<< “smallBox capacity is less than 10”;

else

cout << endl

<< “smallBox capacity is not less than 10”;

cout << endl;

416

More on Classes

return 0;

}

// Function comparing a constant with a CBox object int operator>(const double& value, const CBox& aBox)

{

return value > aBox.Volume();

}

How It Works

Note the position of the prototype for the ordinary function version of operator>(). It needs to follow the class definition because it refers to a CBox object in the parameter list. If you place it before the class definition, the example will not compile.

There is a way to place it at the beginning of the program file following the #include statement: use an incomplete class declaration. This would precede the prototype and would look like this:

class CBox;

//

Incomplete class declaration

int operator>(const double& value, CBox& aBox);

//

Function prototype

The incomplete class declaration identifies CBox to the compiler as a class and is sufficient to allow the compiler to process the prototype for the function properly because the compiler now knows that CBox is a user-defined type to be specified later.

This mechanism is also essential in circumstances such as those where you have two classes, each of which has a pointer to an object of the other class as a member. They each require the other to be declared first. It’s possible to resolve such an impasse through the use of an incomplete class declaration.

The output from the example is:

Constructor called. Constructor called.

mediumBox is bigger than smallBox mediumBox capacity is more than 50 smallBox capacity is less than 10 Destructor called.

Destructor called.

After the constructor messages due to the declarations of the objects smallBox and mediumBox, you have the output lines from the three if statements, each of which is working as you would expect. The first of these calls the operator function that is a class member and works with two CBox objects. The second calls the member function that has a parameter of type double. The expression in the third if statement calls the operator function that you have implemented as an ordinary function.

As it happens, you could have made both the operator functions that are class members ordinary functions because they only need access to the member function Volume(), which is public.

Any comparison operator can be implemented in much the same way as you have implemented these. They would only differ in the minor details and the general approach to implementing them would be exactly the same.

417

Chapter 8

Overloading the Assignment Operator

If you don’t provide an overloaded assignment operator function for your class, the compiler provides a default. The default version simply provides a member-by-member copying process, similar to that of the default copy constructor; however, don’t confuse the default copy constructor with the default assignment operator. The default copy constructor is called by a declaration of a class object that’s initialized with an existing object of the same class or by passing an object to a function by value. The default assignment operator, on the other hand, is called when the left side and the right side of an assignment statement are objects of the same class type.

For the CBox class, the default assignment operator works with no problem, but for any class that has space for members allocated dynamically, you need to look carefully at the requirements of the class in question. There may be considerable potential for chaos in your program if you leave the assignment operator out under these circumstances.

For a moment, return to the CMessage class that you used when I was talking about copy constructors. Remember it had a member, pmessage, that was a pointer to a string. Now consider the effect that

the default assignment operator could have. Suppose you had two instances of the class, motto1 and motto2. You could try setting the members of motto2 equal to the members of motto1 using the default assignment operator, as follows:

motto2 = motto1;

// Use default assignment operator

The effect of using the default assignment operator for this class is essentially the same as using the default copy constructor: disaster will result! Because each object has a pointer to the same string, if the string is changed for one object, it’s changed for both. There’s also the problem that when one of the instances of the class is destroyed, its destructor frees the memory used for the string and the other object is left with a pointer to memory that may now be used for something else.

What you need the assignment operator to do is to copy the text to a memory area owned by the destination object.

Fixing the Problem

You can fix this with your own assignment operator function, which we will assume is defined within the class definition:

// Overloaded assignment operator for CMessage objects CMessage& operator=(const CMessage& aMess)

{

//Release memory for 1st operand delete[] pmessage;

pmessage = new char[ strlen(aMess.pmessage) + 1];

//Copy 2nd operand string to 1st

strcpy(this->pmessage, aMess.pmessage);

// Return a reference to 1st operand return *this;

}

418