1. Trang chủ
  2. » Công Nghệ Thông Tin

Ivor Horton’s BeginningVisual C++ 2008 phần 5 ppt

139 275 0

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Tiêu đề Class Inheritance and Virtual Functions
Trường học University of WroxPro
Chuyên ngành Computer Science
Thể loại Lecture Notes
Năm xuất bản 2008
Thành phố Unknown
Định dạng
Số trang 139
Dung lượng 1,25 MB

Các công cụ chuyển đổi và chỉnh sửa cho tài liệu này

Nội dung

If you modify the last example by adding the base copy constructorcall, the output is as follows: CBox constructor called CCandyBox constructor2 called CBox copy constructor called CCand

Trang 1

How It Works

In this example you calculate the volumes of the two CCandyBoxobjects by invoking the Volume()function that is a member of the derived class This function accesses the inherited members m_Length,m_Width, and m_Heightto produce the result The members are declared as protectedin the baseclass and remain protectedin the derived class The program produces the output shown as follows:CBox constructor called

CCandyBox constructor1 called

CBox constructor called

CCandyBox constructor2 called

myCandyBox volume is 1

myToffeeBox volume is 24

CCandyBox destructor called

CBox destructor called

CCandyBox destructor called

CBox destructor called

The output shows that the volume is being calculated properly for both CCandyBoxobjects The firstobject has the default dimensions produced by calling the default CBoxconstructor, so the volume is 1,and the second object has the dimensions defined as initial values in its declaration

The output also shows the sequence of constructor and destructor calls, and you can see how each derivedclass object is destroyed in two steps

Destructors for a derived class object are called in the reverse order to the constructors for the object.

This is a general rule that always applies Constructors are invoked starting with the base class

con-structor and then the derived class concon-structor, whereas the decon-structor for the derived class is called

first when an object is destroyed, followed by the base class destructor.

You can demonstrate that the protectedmembers of the base class remain protectedin the derivedclass by uncommenting the statement preceding the returnstatement in the function main() If you

do this, you get the following error message from the compiler,

error C2248: ‘m_Length’: cannot access protected member declared in class ‘CBox’which indicates quite clearly that the member m_Lengthis inaccessible

The Access Level of Inherited Class Members

You know that if you have no access specifier for the base class in the definition of a derived class, thedefault specification is private This has the effect of causing the inherited publicand protectedmembers of the base class to become privatein the derived class The privatemembers of the baseclass remain privateto the base and therefore inaccessible to member functions of the derived class

In fact, they remain privateto the base class regardless of how the base class is specified in the derivedclass definition

You have also used publicas the specifier for a base class This leaves the members of the base class withthe same access level in the derived class as they had in the base, so publicmembers remain publicandprotectedmembers remain protected

Trang 2

The last possibility is that you declare a base class as protected This has the effect of making the ited publicmembers of the base protectedin the derived class The protected(and private) inheritedmembers retain their original access level in the derived class This is summarized in Figure 9-3.

inher-Figure 9-3

This may look a little complicated, but you can reduce it to the following three points about the inheritedmembers of a derived class:

❑ Members of a base class that are declared as privateare never accessible in a derived class

❑ Defining a base class as publicdoesn’t change the access level of its members in the derivedclass

❑ Defining a base class as protectedchanges its publicmembers to protectedin the derivedclass

Being able to change the access level of inherited members in a derived class gives you a degree of bility, but don’t forget that you cannot relax the level specified in the base class; you can only make theaccess level more stringent This suggests that your base classes need to have publicmembers if youwant to be able to vary the access level in derived classes This may seem to run contrary to the idea ofencapsulating data in a class in order to protect it from unauthorized access, but, as you’ll see, it is oftenthe case that you define base classes in such a manner that their only purpose is to act as a base for other

flexi-inherited as inherited as

inherited as

inherited as inherited as

Trang 3

The Copy Constr uctor in a Derived Class

Remember that the copy constructor is called automatically when you declare an object that is initializedwith an object of the same class Look at these statements:

CBox myBox(2.0, 3.0, 4.0); // Calls constructor

CBox copyBox(myBox); // Calls copy constructor

The first statement calls the constructor that accepts three arguments of type double, and the secondcalls the copy constructor If you don’t supply your own copy constructor, the compiler supplies onethat copies the initializing object member by member to the corresponding members of the new object

So that you can see what is going on during execution, you can add your own version of a copy structor to the class CBox You can then use this class as a base for defining the CCandyBoxclass.// Box.h in Ex9_05

cout << endl << “CBox copy constructor called”;

m_Length = initB.m_Length;

m_Width = initB.m_Width;

m_Height = initB.m_Height;

}// CBox destructor - just to track calls

~CBox(){ cout << “CBox destructor called” << endl; }protected:

so you’ll be able to see from the output when this is happening

Trang 4

We will use the version of CCandyBoxclass from Ex9_04.cpp, shown again here:

:CBox(lv, wv, hv) // Constructor{

cout << endl <<”CCandyBox constructor2 called”;

m_Contents = new char[ strlen(str) + 1 ];

strcpy_s(m_Contents, strlen(str) + 1, str);

}// Constructor to set contents// calls default CBox constructor automaticallyCCandyBox(char* str = “Candy”) // Constructor{

cout << endl << “CCandyBox constructor1 called”;

m_Contents = new char[ strlen(str) + 1 ];

strcpy_s(m_Contents, strlen(str) + 1, str);

}

~CCandyBox() // Destructor{

cout << “CCandyBox destructor called” << endl;

delete[] m_Contents;

}};

This doesn’t have a copy constructor added yet, so you’ll be relying on the compiler-generated version

Try It Out The Copy Constructor in Derived Classes

You can exercise the copy constructor that you have just defined with the following example:

// Ex9_05.cpp// Using a derived class copy constructor

#include <iostream> // For stream I/O

#include <cstring> // For strlen() and strcpy()

Trang 5

using std::cout;

using std::endl;

int main()

{

CCandyBox chocBox(2.0, 3.0, 4.0, “Chockies”); // Declare and initialize

CCandyBox chocolateBox(chocBox); // Use copy constructor

How It Works (or Why It Doesn’t)

When you run the Debugversion of this example, in addition to the expected output, you’ll see the dialogshown in Figure 9-4 displayed

Click Abortto clear the dialog box and you’ll see the output in the console window that you might expect.The output shows that the compiler-generated copy constructor for the derived class automatically calledthe copy constructor for the base class

However, as you’ve probably realized, all is not as it should be In this particular case, the generated copy constructor causes problems because the memory pointed to by the m_Contentsmem-ber of the derived class in the second object declared points to the same memory as the one in the firstobject When one object is destroyed (when it goes out of scope at the end of main()), it releases thememory occupied by the text When the second object is destroyed, the destructor attempts to releasesome memory that has already been freed by the destructor call for the previous object — and that’sthe reason for the error message in the dialog box

compiler-The way to fix this is to supply a copy constructor for the derived class that allocates some additionalmemory for the new object

Figure 9-4

Trang 6

Try It Out Fixing the Copy Constructor Problem

You can do this by adding the following code for the copy constructor to the publicsection of the derivedCCandyBoxclass in Ex9_05:

// Derived class copy constructorCCandyBox(const CCandyBox& initCB){

cout << endl << “CCandyBox copy constructor called”;

// Get new memorym_Contents = new char[ strlen(initCB.m_Contents) + 1 ];

// Copy stringstrcpy_s(m_Contents, strlen(initCB.m_Contents) + 1, initCB.m_Contents);

}You can now run this new version of the last example with the same function main()to see how the newcopy constructor works

How It Works

Now when you run the example, it behaves better and produces the following output:

CBox constructor calledCCandyBox constructor2 calledCBox constructor calledCCandyBox copy constructor calledVolume of chocBox is 24

Volume of chocolateBox is 1CCandyBox destructor calledCBox destructor calledCCandyBox destructor calledCBox destructor calledHowever, there is still something wrong The third line of output shows that the default constructor for theCBoxpart of the object chocolateBoxis called, rather than the copy constructor As a consequence, theobject has the default dimensions rather than the dimensions of the initializing object, so the volume isincorrect The reason for this is that when you write a constructor for an object of a derived class, you areresponsible for ensuring that the members of the derived class object are properly initialized This includesthe inherited members

The fix for this is to call the copy constructor for the base part of the class in the initialization list for thecopy constructor for the CCandyBoxclass The copy constructor then becomes:

// Derived class copy constructorCCandyBox(const CCandyBox& initCB): CBox(initCB){

cout << endl << “CCandyBox copy constructor called”;

// Get new memory

Trang 7

m_Contents = new char[ strlen(initCB.m_Contents) + 1 ];

// Copy stringstrcpy_s(m_Contents, strlen(initCB.m_Contents) + 1, initCB.m_Contents);}

Now the CBoxclass copy constructor is called with the initCBobject Only the base part of the object ispassed to it, so everything works out If you modify the last example by adding the base copy constructorcall, the output is as follows:

CBox constructor called

CCandyBox constructor2 called

CBox copy constructor called

CCandyBox copy constructor called

Volume of chocBox is 24

Volume of chocolateBox is 24

CCandyBox destructor called

CBox destructor called

CCandyBox destructor called

CBox destructor called

The output shows that all the constructors and destructors are called in the correct sequence and thecopy constructor for the CBoxpart of chocolateBoxis called before the CCandyBoxcopy constructor.The volume of the object chocolateBoxof the derived class is now the same as that of its initializingobject, which is as it should be

You have, therefore, another golden rule to remember:

If you write any kind of constructor for a derived class, you are responsible for the initialization of all

members of the derived class object, including all its inherited members.

Class Member s as Friends

You saw in Chapter 7 how a function can be declared as a friendof a class This gives the friendtion the privilege of free access to any of the class members Of course, there is no reason why a friendfunction cannot be a member of another class

func-Suppose you define a CBottleclass to represent a bottle:

Trang 8

double m_Height; // Bottle heightdouble m_Diameter; // Bottle diameter};

You now need a class to represent the packaging for a dozen bottles that automatically has customdimensions to accommodate a particular kind of bottle You could define this as:

class CCarton{

The constructor here sets the height to be the same as that of the bottle it is to accommodate, and thelength and width are set based on the diameter of the bottle so that twelve fit in the box

As you know by now, this won’t work The data members of the CBottleclass are private, so theCCartonconstructor cannot access them As you also know, a frienddeclaration in the CBottleclassfixes it:

class CBottle{

double m_Height; // Bottle heightdouble m_Diameter; // Bottle diameter// Let the carton constructor in

friend CCarton::CCarton(const CBottle& aBottle);

};

The only difference between the frienddeclaration here and what you saw in Chapter 7 is that youmust put the class name and the scope resolution operator with the friendfunction name to identify it.For this to compile correctly, the compiler needs to have information about the CCartonclass construc-tor, so you would need to put an #includestatement for the header file containing the CCartonclassdefinition before the definition of the CBottleclass

Trang 9

Friend Classes

You can also allow all the function members of one class to have access to all the data members of another

by declaring it as a friend class You could define the CCartonclass as a friend of the CBottleclass byadding a friend declaration within the CBottleclass definition:

friend CCarton;

With this declaration in the CBottleclass, all function members of the CCartonclass now have freeaccess to all the data members of the CBottleclass

Limitations on Class Friendship

Class friendship is not reciprocated Making the CCartonclass a friendof the CBottleclass does notmean that the CBottleclass is a friendof the CCartonclass If you want this to be so, you must add afrienddeclaration for the CBottleclass to the CCartonclass

Class friendship is also not inherited If you define another class with CBottleas a base, members of the CCartonclass will not have access to its data members, not even those inherited from CBottle

V ir tual Functions

Look more closely at the behavior of inherited member functions and their relationship with derivedclass member functions You could add a function to the CBoxclass to output the volume of a CBoxobject The simplified class then becomes:

<< “CBox usable volume is “ << Volume();

}// Function to calculate the volume of a CBox objectdouble Volume() const

{ return m_Length*m_Width*m_Height; }// Constructor

CBox(double lv = 1.0, double wv = 1.0, double hv = 1.0)

:m_Length(lv), m_Width(wv), m_Height(hv) {}

Trang 10

CGlassBox(double lv, double wv, double hv): CBox(lv, wv, hv){}

};

There could conceivably be other additional members of the derived class, but we’ll keep it simple and centrate on how the inherited functions work for the moment The constructor for the derived class objectsjust calls the base class constructor in its initialization list to set the data member values No statements arenecessary in its body You have included a new version of the Volume()function to replace the version fromthe base class, the idea being that you can get the inherited function ShowVolume()to call the derived classversion of the member function Volume()when you call it for an object of the class CGlassBox

con-Try It Out Using an Inherited Function

Now see how your derived class works in practice You can try this out very simply by creating an object

of the base class and an object of the derived class with the same dimensions and then verifying that thecorrect volumes are being calculated The main()function to do this is as follows:

// Ex9_06.cpp// Behavior of inherited functions in a derived class

#include <iostream>

#include “GlassBox.h” // For CBox and CGlassBoxusing std::cout;

using std::endl;

Trang 11

int main()

{

CBox myBox(2.0, 3.0, 4.0); // Declare a base box

CGlassBox myGlassBox(2.0, 3.0, 4.0); // Declare derived box - same size

myBox.ShowVolume(); // Display volume of base box

myGlassBox.ShowVolume(); // Display volume of derived box

cout << endl;

return 0;

}

How It Works

If you run this example, it produces the following output:

CBox usable volume is 24

CBox usable volume is 24

This isn’t only dull and repetitive, but it’s also disastrous It isn’t working the way you want at all, and theonly interesting thing about it is why Evidently, the fact that the second call is for an object of the derivedclass CGlassBoxis not being taken into account You can see this from the incorrect result for the volume

in the output The volume of a CGlassBoxobject should definitely be less than that of a basic CBoxwiththe same dimensions

The reason for the incorrect output is that the call of the Volume()function in the function ShowVolume()

is being set once and for all by the compiler as the version defined in the base class ShowVolume()is abase class function and when CBoxis compiled the call to Volume()is resolved at that time to the baseclass Volume()function; the compiler has no knowledge of any other Volume()function This is called

static resolutionof the function call since the function call is fixed before the program is executed This is

also sometimes called early binding because the particular Volume()function chosen is bound to the callfrom the function ShowVolume()during the compilation of the program

What we were hoping for in this example was that the question of which Volume()function call to use inany given instance would be resolved when the program was executed This sort of operation is referred

to as dynamic linkage, or late binding We want the actual version of the function Volume()called byShowVolume()to be determined by the kind of object being processed, and not arbitrarily fixed by thecompiler before the program is executed

No doubt you’ll be less than astonished that C++ does, in fact, provide you with a way to do this,because this whole discussion would have been futile otherwise! You need to use something called a

virtual function

What Is a Virtual Function?

A virtual function is a function in a base class that is declared using the keyword virtual If you specify

a function in a base class as virtualand there is another definition of the function in a derived class,

Trang 12

it signals to the compiler that you don’t want static linkage for this function What you do want is the

selection of the function to be called at any given point in the program to be based on the kind of objectfor which it is called

Try It Out Fixing the CGlassBox

To make this example work as originally hoped, you just need to add the keyword virtualto the tions of the Volume()function in the two classes You can try this in a new project, Ex9_07 Here’s howthe definition of CBoxshould be:

<< “CBox usable volume is “ << Volume();

}// Function to calculate the volume of a CBox objectvirtual double Volume() const

{ return m_Length*m_Width*m_Height; }// Constructor

CBox(double lv = 1.0, double wv = 1.0, double hv = 1.0)

:m_Length(lv), m_Width(wv), m_Height(hv) {}

Trang 13

virtual double Volume() const{ return 0.85*m_Length*m_Width*m_Height; }// Constructor

CGlassBox(double lv, double wv, double hv): CBox(lv, wv, hv){}

};

The Ex9_07.cppfile version of main()is the same as for the previous example:

// Ex9_07.cpp (the same as Ex9_06.cpp)

// Using a virtual function

CBox myBox(2.0, 3.0, 4.0); // Declare a base box

CGlassBox myGlassBox(2.0, 3.0, 4.0); // Declare derived box - same size

myBox.ShowVolume(); // Display volume of base box

myGlassBox.ShowVolume(); // Display volume of derived box

CBox usable volume is 24

CBox usable volume is 20.4

This is now clearly doing what you wanted in the first place The first call to the function ShowVolume()with the CBoxobject myBoxcalls the CBoxclass version of Volume() The second call with the CGlassBoxobject myGlassBoxcalls the version defined in the derived class

Note that although you have put the keyword virtualin the derived class definition of the functionVolume(), it’s not essential to do so The definition of the base version of the function as virtualis suf-

ficient However, I recommend that you do specify the keyword for virtual functions in derived classes

because it makes it clear to anyone reading the derived class definition that they are virtual functionsand that they are selected dynamically

For a function to behave as virtual, it must have the same name, parameter list, and return type in anyderived class as the function has in the base class, and if the base class function is const, the derived classfunction must be, too If you try to use different parameters or return types, or declare one as constand

Trang 14

the other not, the virtual function mechanism won’t work The function operates with static linkage lished and fixed at compile time.

estab-The operation of virtual functions is an extraordinarily powerful mechanism You may have heard the term

polymorphismin relation to object-oriented programming, and this refers to the virtual function capability.Something that is polymorphic can appear in different guises, like a werewolf, or Dr Jekyll, or a politicianbefore and after an election for example Calling a virtual function produces different effects depending onthe kind of object for which it is being called

Note that the Volume()function in the derived CGlassBoxclass actually hides the base class version from the view of derived class functions If you wanted to call the base version of Volume()from a derived class function, you would need to use the scope resolution operator to refer to the function as

CBox::Volume().

Using Pointers to Class Objects

Using pointers with objects of a base class and of a derived class is an important technique A pointer

to a base class object can be assigned the address of a derived class object as well as that of the base Youcan thus use a pointer of the type ‘pointer to base’ to obtain different behavior with virtual functions,depending on what kind of object the pointer is pointing to You can see how this works more clearly

by looking at an example

Try It Out Pointers to Base and Derived Classes

You’ll use the same classes as in the previous example, but make a small modification to the function main()so that it uses a pointer to a base class object Create the Ex9_08project with Box.hand GlassBox.hheader files the same as in the previous example You can copy the Box.hand Glassbox.hfiles from the Ex9_07project to this project folder Adding an existing file to a proj-ect is quite easy; you right-click Ex9_08in the Solution Explorertab, select Add > New Itemfrom the pop-up menu; then select a header file to add it to the project When you have added the headers, modify Ex9_08.cppto the following:

// Ex9_08.cpp// Using a base class pointer to call a virtual function

CBox myBox(2.0, 3.0, 4.0); // Declare a base boxCGlassBox myGlassBox(2.0, 3.0, 4.0); // Declare derived box of same sizeCBox* pBox = 0; // Declare a pointer to base class objects

Trang 15

pBox = &myBox; // Set pointer to address of base object

pBox->ShowVolume(); // Display volume of base box

pBox = &myGlassBox; // Set pointer to derived class object

pBox->ShowVolume(); // Display volume of derived box

The output produced is as follows:

CBox usable volume is 24

CBox usable volume is 20.4

This is exactly the same as that from the previous example where you used explicit objects in the tion call

func-You can conclude from this example that the virtual function mechanism works just as well through apointer to a base class, with the specific function being selected based on the type of object being pointed

to This is illustrated in Figure 9-5

Figure 9-5

classCBox virtual double Volume () const { }

pBox pointing to CBox object pBox pointing to CGlassBox object

Trang 16

This means that, even when you don’t know the precise type of the object pointed to by a base classpointer in a program (when a pointer is passed to a function as an argument, for example), the virtualfunction mechanism ensures that the correct function is called This is an extraordinarily powerfulcapability, so make sure you understand it Polymorphism is a fundamental mechanism in C++ thatyou will find yourself using again and again.

Using References with Virtual Functions

If you define a function with a reference to a base class as a parameter, you can pass an object of a derivedclass to it as an argument When your function executes, the appropriate virtual function for the objectpassed is selected automatically We could see this happening by modifying the function main()in thelast example to call a function that has a reference as a parameter

Try It Out Using References with Virtual Functions

Let’s move the call to ShowVolume()to a separate function and call that separate function from main():// Ex9_09.cpp

// Using a reference to call a virtual function

return 0;

}void Output(const CBox& aBox){

Trang 17

the appropriate version of the virtual function Volume()is called, depending on the object that is izing the reference.

initial-The program produces exactly the same output as the previous example, demonstrating that the virtualfunction mechanism does indeed work through a reference parameter

Incomplete Class Definitions

At the beginning of the previous example, you have the prototype declaration for the Output()function

To process this declaration the compiler needs to have access to the definition of the CBoxclass because theparameter is of type CBox& In this case the definition of the CBoxclass is available at this point becauseyou have an #includedirective for GlassBox.hthat has its own #includedirective for Box.h However, there may be situations where you have such a declaration and the class definition cannot beincluded in this way, in which case you would need some other way to at least identify that the name CBox

refers to a class type In this situation you could provide an incomplete definition of the class CBoxing the prototype of the output function The statement that provides an incomplete definition of the CBoxclass is simply:

preced-class CBox;

The statement just identifies that the name CBoxrefers to a class that is not defined at this point, but this

is sufficient for the compiler know of that CBoxis the name of a class, and this allows it to process theprototype of the function Output() Without some indication that CBoxis a class, the prototype causes

an error message to be generated

Pure Virtual Functions

It’s possible that you’d want to include a virtual function in a base class so that it may be redefined in aderived class to suit the objects of that class, but that there is no meaningful definition you could give for the function in the base class

For example, you could conceivably have a class CContainer, which could be used as a base for definingthe CBoxclass, or a CBottleclass, or even a CTeapotclass The CContainerclass wouldn’t have datamembers, but you might want to provide a virtual member function Volume()for any derived classes.Because the CContainerclass has no data members, and therefore no dimensions, there is no sensibledefinition that you can write for the Volume()function You can still define the class, however, includingthe member function Volume(), as follows:

// Container.h for Ex9_10

Trang 18

// Function for calculating a volume - no content// This is defined as a ‘pure’ virtual function, signified by ‘= 0’

virtual double Volume() const = 0;

// Function to display a volumevirtual void ShowVolume() const{

cout << endl

<< “Volume is “ << Volume();

}};

The statement for the virtual function Volume()defines it as having no content by placing the equals sign

and zero in the function header This is called a pure virtual function Any class derived from this class must

either define the Volume()function or redefine it as a pure virtual function Because you have declaredVolume()as const, its implementation in any derived class must also be const Remember that constandnon-constvarieties of a function with the same name and parameter list are different functions In otherwords you can overload a function using const

The class also contains the function ShowVolume(), which displays the volume of objects of derivedclasses Because this is declared as virtual, it can be replaced in a derived class, but if it isn’t, the baseclass version that you see here is called

Abstract Classes

A class containing a pure virtual function is called an abstract class It’s called abstract because you

can’t define objects of a class containing a pure virtual function It exists only for the purpose of ing classes that are derived from it If a class derived from an abstract class still defines a pure virtualfunction of the base as pure, it too is an abstract class

defin-You should not conclude, from the previous example of the CContainerclass, that an abstract class can’thave data members An abstract class can have both data members and function members The presence

of a pure virtual function is the only condition that determines that a given class is abstract In the samevein, an abstract class can have more than one pure virtual function In this case, a derived class musthave definitions for every pure virtual function in its base; otherwise, it too will be an abstract class If youforget to make the derived class version of the Volume()function const, the derived class will still beabstract because it contains the pure virtual Volume()member function that is const, as well as the non-const Volume()function that you have defined

Try It Out An Abstract Class

You could implement a CCanclass, representing beer or cola cans perhaps, together with the originalCBoxclass and derive both from the CContainerclass that you defined in the previous section Thedefinition of the CBoxclass as a subclass of CContaineris as follows:

// Box.h for Ex9_10

#pragma once

#include “Container.h” // For CContainer definition

#include <iostream>

using std::cout;

Trang 19

<< “CBox usable volume is “ << Volume();

}// Function to calculate the volume of a CBox objectvirtual double Volume() const

{ return m_Length*m_Width*m_Height; }// Constructor

CBox(double lv = 1.0, double wv = 1.0, double hv = 1.0)

:m_Length(lv), m_Width(wv), m_Height(hv){}

essen-You could define the CCanclass in the Can.hheader file like this:

// Can.h for Ex9_10

#pragma once

#include “Container.h” // For CContainer definition

extern const double PI; // PI is defined elsewhere

class CCan: public CContainer

CCan(double hv = 4.0, double dv = 2.0): m_Height(hv), m_Diameter(dv){}

protected:

double m_Height;

double m_Diameter;

};

Trang 20

The CCanclass also defines a Volume()function based on the formula hπr2, where h is the height of a can and r is the radius of the cross-section of a can The volume is calculated as the height multiplied by

the area of the base The expression in the function definition assumes a global constant PIis defined, so

we have the extern statement indicating that PIis a global variable of type const doublethat isdefined elsewhere — in this program it is defined in the Ex9_10.cppfile Also notice that we redefinedthe ShowVolume()function in the CBoxclass, but not in the CCanclass You can see what effect this haswhen we get some program output

You can exercise these classes with the following source file containing the main()function:

// Ex9_10.cpp// Using an abstract class

#include “Box.h” // For CBox and CContainer

#include “Can.h” // For CCan (and CContainer)

#include <iostream> // For stream I/Ousing std::cout;

using std::endl;

const double PI= 3.14159265; // Global definition for PIint main(void)

{// Pointer to abstract base class// initialized with address of CBox objectCContainer* pC1 = new CBox(2.0, 3.0, 4.0);

// Pointer to abstract base class// initialized with address of CCan objectCContainer* pC2 = new CCan(6.5, 3.0);

pC1->ShowVolume(); // Output the volumes of the twopC2->ShowVolume(); // objects pointed to

it to store the address of any object whose type is a direct or indirect subclass of CContainer Thepointer pC1is assigned the address of a CBoxobject created in the free store by the operator new The second pointer is assigned the address of a CCanobject in a similar manner

Of course, because the derived class objects were created dynamically, you must use the delete operator

to clean up the free store when you have finished with them You learned about the deleteoperator back in Chapter 4.

Trang 21

The output produced by this example is as follows:

CBox usable volume is 24

Volume is 45.9458

Because you have defined ShowVolume()in the CBoxclass, the derived class version of the function iscalled for the CBoxobject You did not define this function in the CCanclass, so the base class version thatthe CCanclass inherits is invoked for the CCanobject Because Volume()is a virtual function implemented

in both derived classes (necessarily, because it is a pure virtual function in the base class), the call to it isresolved when the program is executed by selecting the version belonging to the class of the object beingpointed to Thus, for the pointer pC1, the version from the class CBoxis called and, for the pointer pC2,the version in the class CCanis called In each case, therefore, you obtain the correct result

You could equally well have used just one pointer and assigned the address of the CCanobject to it (aftercalling the Volume()function for the CBoxobject) A base class pointer can contain the address of any

derived class object, even when several different classes are derived from the same base class, and soyou can have automatic selection of the appropriate virtual function across a whole range of derivedclasses Impressive stuff, isn’t it?

Indirect Base Classes

At the beginning of this chapter, I said that a base class for a subclass could in turn be derived fromanother, ‘more’ base class A small extension of the last example provides you with an illustration ofthis, as well as demonstrates the use of a virtual function across a second level of inheritance

Try It Out More Than One Level of Inheritance

All you need to do is add the class CGlassBoxto the classes you have from the previous example Therelationship between the classes you now have is illustrated in Figure 9-6

The class CGlassBoxis derived from the CBoxclass exactly as before, but we omit the derived class sion of ShowVolume()to show that the base class version still propagates through the derived classes.With the class hierarchy shown above, the class CContaineris an indirect base of the class CGlassBox,and a direct base of the classes CBoxand CCan

ver-The GlassBox.hheader file for the example contains:

// GlassBox.h for Ex9_11

#pragma once

#include “Box.h” // For CBox

class CGlassBox: public CBox // Derived class

Trang 22

// ConstructorCGlassBox(double lv, double wv, double hv): CBox(lv, wv, hv){}

#include “Box.h” // For CBox and CContainer

#include “Can.h” // For CCan (and CContainer)

#include “GlassBox.h” // For CGlassBox (and CBox and CContainer)

#include <iostream> // For stream I/Ousing std::cout;

using std::endl;

const double PI = 3.14159265; // Global definition for PIint main()

{// Pointer to abstract base class initialized with CBox object address

class CContainer Direct base of CBox

Indirect base of CGlassBox Direct base of CCan

Direct base of CGlassBox More General

class CBox

class CGlassBoxclass CCan

More Specialized

Trang 23

CContainer* pC1 = new CBox(2.0, 3.0, 4.0);

CCan myCan(6.5, 3.0); // Define CCan object

CGlassBox myGlassBox(2.0, 3.0, 4.0); // Define CGlassBox object

pC1->ShowVolume(); // Output the volume of CBox

delete pC1; // Now clean up the free store

// initialized with address of CCan object

pC1 = &myCan; // Put myCan address in pointer

pC1->ShowVolume(); // Output the volume of CCan

pC1 = &myGlassBox; // Put myGlassBox address in pointer

pC1->ShowVolume(); // Output the volume of CGlassBox

A separate branch from the base CContainerdefines the derived class CCan

The example produces this output:

CBox usable volume is 24

Volume is 45.9458

CBox usable volume is 20.4

The output shows that one of the three different versions of the function Volume()is selected for tion according to the type of object involved

execu-Note that you must delete the CBoxobject from the free store before you assign another address value to the pointer If you don’t do this, you won’t be able to clean up the free store, because you would have no record of the address of the original object This is an easy mistake to make when reassigning pointers and using the free store.

Virtual Destructors

One problem that arises when dealing with objects of derived classes using a pointer to the base class isthat the correct destructor may not be called You can see this effect by modifying the last example

Trang 24

Try It Out Calling the Wrong Destructor

You just need to add a destructor to each of the classes in the example that outputs a message so thatyou can track which destructor is called when the objects are destroyed The Container.hfile for thisexample is:

// Container.h for Ex9_12

virtual double Volume() const = 0;

// Function to display a volumevirtual void ShowVolume() const{

cout << endl

<< “Volume is “ << Volume();

}};

The contents of Can.hin the example is:

// Can.h for Ex9_12

{ return 0.25*PI*m_Diameter*m_Diameter*m_Height; }// Constructor

Trang 25

CCan(double hv = 4.0, double dv = 2.0): m_Height(hv), m_Diameter(dv){}protected:

double m_Height;

double m_Diameter;

};

The contents of Box.hshould be:

// Box.h for Ex9_12

{cout << endl

<< “CBox usable volume is “ << Volume();

}// Function to calculate the volume of a CBox objectvirtual double Volume() const

{ return m_Length*m_Width*m_Height; }// Constructor

CBox(double lv = 1.0, double wv = 1.0, double hv = 1.0)

:m_Length(lv), m_Width(wv), m_Height(hv){}protected:

double m_Length;

double m_Width;

double m_Height;

};

The GlassBox.hheader file should contain:

// GlassBox.h for Ex9_12

#pragma once

#include “Box.h” // For CBox

class CGlassBox: public CBox // Derived class

Trang 26

// Destructor

~CGlassBox(){ cout << “CGlassBox destructor called” << endl; }

// Function to calculate volume of a CGlassBox// allowing 15% for packing

virtual double Volume() const{ return 0.85*m_Length*m_Width*m_Height; }// Constructor

CGlassBox(double lv, double wv, double hv): CBox(lv, wv, hv){}

};

Finally, the source file Ex9_12.cppfor the program should be as follows:

// Ex9_12.cpp// Destructor calls with derived classes// using objects via a base class pointer

#include “Box.h” // For CBox and CContainer

#include “Can.h” // For CCan (and CContainer)

#include “GlassBox.h” // For CGlassBox (and CBox and CContainer)

#include <iostream> // For stream I/Ousing std::cout;

using std::endl;

const double PI = 3.14159265; // Global definition for PIint main()

{// Pointer to abstract base class initialized with CBox object addressCContainer* pC1 = new CBox(2.0, 3.0, 4.0);

CCan myCan(6.5, 3.0); // Define CCan objectCGlassBox myGlassBox(2.0, 3.0, 4.0); // Define CGlassBox objectpC1->ShowVolume(); // Output the volume of CBoxcout << endl << “Delete CBox” << endl;

delete pC1; // Now clean up the free storepC1 = new CGlassBox(4.0, 5.0, 6.0); // Create CGlassBox dynamicallypC1->ShowVolume(); // output its volume

cout << endl << “Delete CGlassBox” << endl;

delete pC1; // and delete itpC1 = &myCan; // Get myCan address in pointerpC1->ShowVolume(); // Output the volume of CCanpC1 = &myGlassBox; // Get myGlassBox address in pointer

Trang 27

pC1->ShowVolume(); // Output the volume of CGlassBox

CBox usable volume is 24

Delete CBox

CContainer destructor called

CBox usable volume is 102

Delete CGlassBox

CContainer destructor called

Volume is 45.9458

CBox usable volume is 20.4

CGlassBox destructor called

CBox destructor called

CContainer destructor called

CCan destructor called

CContainer destructor called

You can see from this that when you delete the CBoxobject pointed to by pC1, the destructor for thebase class CContaineris called but there is no call of the CBoxdestructor recorded Similarly, when the CGlassBoxobject that you added is deleted, again the destructor for the base class CContainer

is called but not the CGlassBoxor CBoxdestructors For the other objects, the correct destructor callsoccur with the derived class constructor being called first, followed by the base class constructor Forthe first CGlassBoxobject created in a declaration, three destructors are called: first, the destructor for the derived class, followed by the direct base destructor and, finally, the indirect base destructor.All the problems are with objects created in the free store In both cases, the wrong destructor is called Thereason for this is that the linkage to the destructors is resolved statically, at compile time For the automaticobjects, there is no problem — the compiler knows what they are and arranges for the correct destructors

to be called With objects created dynamically and accessed through a pointer, things are different Theonly information that the compiler has when the deleteoperation is executed is that the pointer type is apointer to the base class The type of object the pointer is actually pointing to is unknown to the compilerbecause this is determined when the program executes The compiler therefore simply ensures that thedeleteoperation is set up to call the base class destructor In a real application, this can cause a lot of prob-lems, with bits of objects left strewn around the free store and possibly more serious problems, depending

on the nature of the objects involved

Trang 28

The solution is simple You need the calls to be resolved dynamically — as the program is executed You

can organize this by using virtual destructors in your classes As I said when I first discussed virtual

func-tions, it’s sufficient to declare a base class function as virtual to ensure that all functions in any derivedclasses with the same name, parameter list, and return type are virtual as well This applies to destructorsjust as it does to ordinary member functions You need to add the keyword virtualto the definition ofthe destructor in the class CContainerin Container.hso that the class definition is as follows:

class CContainer // Generic base class for containers{

public:

// Destructorvirtual ~CContainer(){ cout << “CContainer destructor called” << endl; }// Rest of the class as before

};

Now the destructors in all the derived classes are automatically virtual, even though you don’t itly specify them as such Of course, you’re free to specify them as virtual if you want the code to beabsolutely clear

explic-If you rerun the example with this modification, it produces the following output:

CBox usable volume is 24Delete CBox

CBox destructor calledCContainer destructor calledCBox usable volume is 102Delete CGlassBox

CGlassBox destructor calledCBox destructor calledCContainer destructor calledVolume is 45.9458

CBox usable volume is 20.4CGlassBox destructor calledCBox destructor calledCContainer destructor calledCCan destructor calledCContainer destructor called

As you can see, all the objects are now destroyed with a proper sequence of destructor calls Destroyingthe dynamic objects produces the same sequence of destructor calls as the automatic objects of the sametype in the program

The question may arise in your mind at this point, can constructors be declared as virtual? The answer is

no — only destructors and other member functions

Trang 29

It’s a good idea always to declare your base class destructor as virtual as a matter of course when using inheritance There is a small overhead in the execution of the class destructors, but you won’t notice it

in the majority of circumstances Using virtual destructors ensures that your objects will be properly

destroyed and avoids potential program crashes that might otherwise occur.

Casting Between Class Types

You have seen how you can store the address of a derived class object in a variable of a base class type

so a variable of type CContainer*can store the address of a CBoxobject for example So if you have anaddress stored in a pointer of type CContainer*can you cast it to type CBox*? Indeed you can and thedynamic_castoperator is specifically intended for this kind of operation Here’s how it works:

CContainer* pContainer = new CGlassBox(2.0, 3.0, 4.0);

CBox* pBox = dynamic_cast<CBox*>( pContainer);

CGlassBox* pGlassBox = dynamic_cast<CGlassBox*>( pContainer);

The first statement stores the address of the CGlassBoxobject created on the heap in a base class pointer

of type CContainer* The second statement casts pContainerup the class hierarchy to type CBox* Thethird statement casts the address in pContainerto its actual type, CGlassBox*

You can apply the dynamic_castoperator to references as well as pointers The difference betweendynamic_castand static_castis that the dynamic_castoperator checks the validity of a cast atrun-time whereas the static_castoperator does not If a dynamic_castoperation is not valid, theresult is null The compiler relies on the programmer for the validity of a static_castoperation soyou should always use dynamic_castfor casting up and down a class hierarchy and check for a nullresult if you want to avoid abrupt termination of your program as a result of using a null pointer

Nested Classes

You can put the definition of one class inside the definition of another, in which case you have defined a

nested class A nested class has the appearance of being a static member of the class that encloses it and issubject to the member access specifiers, just like any other member of the class If you place the definition

of a nested class in the private section of the class, the class can only be referenced from within the scope ofthe enclosing class If you specify a nested class as public, the class is accessible from outside the enclosingclass but the nested class name must be qualified by the outer class name in such circumstances

A nested class has free access to all the static members of the enclosing class All the instance members can

be accessed through an object of the enclosing class type, or a pointer or reference to an object The ing class can only access the public members of the nested class, but in a nested class that is private in theenclosing class the members are frequently declared as publicto provide free access to the entire nestedclass from functions in the enclosing class

enclos-A nested class is particularly useful when you want to define a type that is only to be used within anothertype, whereupon the nested class can be declared as private Here’s an example of that:

// A push-down stack to store Box objects

Trang 30

class CStack{

private:

// Defines items to store in the stackstruct CItem

{CBox* pBox; // Pointer to the object in this nodeCItem* pNext; // Pointer to next item in the stack or null// Constructor

CItem(CBox* pB, CItem* pN): pBox(pB), pNext(pN){}

// Pop an object off the stackCBox* Pop()

{ if(pTop == 0) // If the stack is emptyreturn 0; // return null

CBox* pBox = pTop->pBox; // Get box from itemCItem* pTemp = pTop; // Save address of the top ItempTop = pTop->pNext; // Make next item the top delete pTemp; // Delete old top Item from the heapreturn pBox;

}};

The CStackclass defines a push-down stack for storing CBoxobjects To be absolutely precise it storespointers to CBoxobjects so the objects pointed to are still the responsibility of the code making use of the Stackclass The nested struct, CItem, defines the items that are held in the stack I chose to defineItemas a nested structrather than a nested class because members of a structare public by default.You could define CItemas a class and then specify the members as public so they can be accessed fromthe functions in the CStackclass The stack is implemented as a set of CItemobjects where each CItemobject stores a pointer to a CBoxobject plus the address of the next CItemobject down in the stack ThePush()function in the CStackclass pushes a CBoxobject on to the top of the stack and the Pop()func-tion pops an object off the top of the stack

Pushing an object on to the stack involves creating a new CItemobject that stores the address of the object

to be stored plus the address of the previous item that was on the top of the stack — this is null the firsttime you push an object on to the stack Popping an object off the stack returns the address of the object inthe item, pTop The top item is deleted and the next item becomes the item at the top of the stack Let’s see

if it works

Trang 31

Try It Out Using a Nested Class

This example uses CContainer, CBox, and CGlassBoxclasses from Ex9_12so create an emptyWIN32 console project, Ex9_13, and add the header files containing those class definitions to it Then add Stack.hto the project containing the definition of the CStackclass from the previous section, and add Ex9_13.cppto the project with the following contents:

// Ex9_13.cpp

// Using a nested class to define a stack

#include “Box.h” // For CBox and CContainer

#include “GlassBox.h” // For CGlassBox (and CBox and CContainer)

#include “Stack.h” // For the stack class with nested struct Item

#include <iostream> // For stream I/O

cout << “The array of boxes have the following volumes:”;

for (int i = 0 ; i<4 ; i++)

pBoxes[i]->ShowVolume(); // Output the volume of a boxcout << endl << endl

<< “Now pushing the boxes on the stack ”

<< endl;

CStack* pStack = new CStack; // Create the stack

for (int i = 0 ; i<4 ; i++)

pStack->Push(pBoxes[i]);

cout << “Popping the boxes off the stack presents them in reverse order:”;

for (int i = 0 ; i<4 ; i++)

pStack->Pop()->ShowVolume();

cout << endl;

return 0;

}

The output from this example is:

The array of boxes have the following volumes:

CBox usable volume is 24

CBox usable volume is 20.4

CBox usable volume is 120

CBox usable volume is 102

Now pushing the boxes on the stack

Trang 32

Popping the boxes off the stack presents them in reverse order:

CBox usable volume is 102CBox usable volume is 120CBox usable volume is 20.4CBox usable volume is 24

How It Works

You create an array of pointers to CBoxobjects so each element in the array can store the address of a CBoxobject or an address of any type that is derived from CBox The array is initialized with the adresses of fourobjects created on the heap:

CBox* pBoxes[] = { new CBox(2.0, 3.0, 4.0),

new CGlassBox(2.0, 3.0, 4.0),new CBox(4.0, 5.0, 6.0),new CGlassBox(4.0, 5.0, 6.0)};

The objects are two CBoxobject and two CGlassBoxobjects with the same dimensions as the CBoxobjects.After listing the volumes of the four objects, you create a CStackobject and push the objects on to thestack in a forloop:

CStack* pStack = new CStack; // Create the stackfor (int i = 0 ; i<4 ; i++)

pStack->Push(pBoxes[i]);

Each element in the pBoxesarray is pushed on to the stack by passing the array element as the argument

to the Push()function for the CStackobject This results in the first element from the array being at thebottom of the stack and the last element at the top

You pop the objects off the stack in another forloop:

for (int i = 0 ; i<4 ; i++)pStack->Pop()->ShowVolume();

The Pop()function returns the address of the element at the top of the stack and you use this to call theShowVolume()function for the object Because the last element was at the top of the stack, the loop liststhe volumes of the objects in reverse order From the output you can see that the CStackclass does indeedimplement a stack using a nested structto define the items to be stored in the stack

C++/CLI ProgrammingAll C++/CLI classes, including classes that you define, are derived classes by default This is because bothvalue classes and reference classes have a standard class, System::Object, as a base class This meansthat both value classes and reference classes inherit from the System::Objectclass and therefore havethe capabilities of the System::Objectclass in common Because the ToString()function is defined as

a virtual function in System::Object, you can override it in your own classes and have the function

Trang 33

Because System::Objectis a base class for all C++/CLI classes, the handle type System::Object^fulfils a similar role to the void*type in native C++ in that it can be used to reference any type of object.

Boxing and Unboxing

The System::Objectbase class for all value class types is also responsible for enabling the boxing andunboxing of values of the fundamental types Boxing a value type instance converts it to an object on thegarbage-collected heap, so it will carry full type information along with the basic value Unboxing is thereverse of boxing The boxing/unboxing capability means that values of the fundamental types can behave

as objects, but can participate in numerical operations without carrying the overhead of being objects Values

of the fundamental types are stored on the stack just as values for the purposes of normal operations and areonly converted to an object on the heap that is referenced by a handle of type System::Object^when theyneed to behave as objects For example, if you pass an unboxed value to a function with a parameter that is

an appropriate value class type, the compiler will arrange for the value to be converted to an object on the

heap; this is achieved by creating a new object on the heap containing the value Thus you get implicit ingand the argument value will be boxed automatically

box-Of course, explicit boxing is also possible You can force a value to be boxed by assigning it to a variable

of type Object^ For example:

double value = 3.14159265;

Object^ boxedValue = value;

The second statement forces the boxing of valueand the boxed representation is referenced by the handleboxedValue

You can also force boxing of a value using gcnewto create a boxed value on the garbage-collected heap,for example:

long^ number = gcnew(999999L);

This statement implicitly boxes the value 999999Land stores it on the heap in a location referenced bythe handle number

You can unbox a value type using the dereference operator, for example:

Trang 34

state-Inheritance in C++/CLI Classes

Although value classes always have the System::Objectclass as a base, you cannot derive a valueclass from an existing class To put it another way, when you define a value class you are not allowed tospecify a base class This implies that polymorphism in value classes is limited to the functions that aredefined as virtual in the System::Objectclass These are the virtual functions that all value classesinherit from System::Object:

Of course, because System::Objectis also a base class for reference classes you may want to overridethese functions in reference classes, too

You can derive a reference class from an existing reference class in the same way as you define a derivedclass in native C++ Let’s re-implement Ex9_12as a C++/CLI program as this also demonstrates nestedclasses in a CLR program We can start by defining the Containerclass:

// Container.h for Ex9_14

#pragma onceusing namespace System;

// Abstract base class for specific containersref class Container abstract

{public:

// Function for calculating a volume - no content// This is defined as an ‘abstract’ virtual function,// indicated by the ‘abstract’ keyword

String^ ToString() Returns a Stringrepresentation of an object and the

imple-mentation in the System::Objectclass returns the class name

as a string You would typically override this function in yourown classes to return a string representation of the value of anobject

bool Equals(Object^ obj) Compares the current object to objand returns trueif they are

equal and falseotherwise Equal in this case means referentialequality — that is the objects are one and the same Typicallyyou would override this function in your own classes to returntruewhen the current object is the same value as the argu-ment — in other words, when the fields are equal

int GetHashCode() Returns an integer that is a hash code for the current object

Hash codes are used as keys to store objects in a collection thatstores (key,object)pairs Objects are subsequently retrievedfrom such a collection by supplying the key that was usedwhen the object was stored

Trang 35

virtual double Volume() abstract;

// Function to display a volumevirtual void ShowVolume() {

Console::WriteLine(L”Volume is {0}”, Volume());

}};

The first thing to note is the abstractkeyword following the class name If a C++/CLI class contains thenative C++ equivalent of a pure virtual function, you must specify the class as abstract You can, however,also specify a class as abstract that does not contain any abstract functions, which prevents you from creat-ing objects of that class type The abstractkeyword also appears at the end of the Volume()functionmember declaration to indicate that it is be defined for this class You could also add the “ = 0”to the end

of the member declaration for Volume()as you would for a native C++ member, but it is not required.Both the Volume()and ShowVolume()functions are virtual here so they can be called polymorphicallyfor objects of class types that are derived from Container

You can define the Boxclass like this:

// Box.h for Ex9_14

#pragma once

#include “Container.h” // For Container definition

ref class Box : Container // Derived class

}// Function to calculate the volume of a Box objectvirtual double Volume() override

{ return m_Length*m_Width*m_Height; }// Constructor

Box() : m_Length(1.0), m_Width(1.0), m_Height(1.0){}

// ConstructorBox(double lv, double wv, double hv)

: m_Length(lv), m_Width(wv), m_Height(hv){}

Trang 36

spec-the native C++ version of spec-the class, you define spec-the no-arg constructor so that it initializes all three fields

to 1.0 The Boxclass defines the Volume()function as an override to the inherited base class version Youmust always specify the overridekeyword when you want to override a function in the base class If theBoxclass did not implement the Volume()function, it would be abstract and you would need to specify

it as such to compile the class successfully

Here’s how the GlassBoxclass definition looks:

// GlassBox.h for Ex9_14

#pragma once

#include “Box.h” // For Boxref class GlassBox : Box // Derived class{

GlassBox(double lv, double wv, double hv): Box(lv, wv, hv){}

private:

// Defines items to store in the stackref struct Item

{Object^ Obj; // Handle for the object in this itemItem^ Next; // Handle for next item in the stack or nullptr// Constructor

Item(Object^ obj, Item^ next): Obj(obj), Next(next){}

Trang 37

// Pop an object off the stack

Object^ Pop()

{

if(Top == nullptr) // If the stack is emptyreturn nullptr; // return nullptrObject^ obj = Top->Obj; // Get object from itemTop = Top->Next; // Make next item the top return obj;

}

};

The first difference to notice is that the function parameters and fields are now handles because you aredealing with ref class objects The inner struct, Item, now stores a handle of type Object^, which allowsobjects of any CLR class type to be stored in the stack; this means either value class or ref class objectscan be accommodated which is a significant improvement over the native C++ CStackclass You don’tneed to worry about deleting Item objects when the Pop()function is called, because the garbage collec-tor takes care of that

Here’s a summary of the differences from native C++ that these classes have demonstrated:

❑ Only ref classes can be derived class types

❑ A base class for a derived ref class is always public

❑ A function that has no definition for a ref class is an abstract function and must be declaredusing the abstractkeyword

❑ A class that contains one or more abstract functions must be explicitly specified as abstract byplacing the abstractkeyword following the class name

❑ A class that does not contain abstract functions can be specified as abstract, in which caseinstances of the class cannot be defined

❑ You must explicitly use the overridekeyword when specifying a function that overrides afunction inherited from the base class

All you need to try out these classes is a CLR console project with a definition of main()so let’s do it

Try It Out Using Derived Reference Classes

Create a CLR console program with the name Ex9_14and add the classes in the previous section to theproject; then add the following contents to Ex9_14.cpp:

// Ex9_14.cpp : main project file

// Using a nested class to define a stack

#include “stdafx.h”

#include “Box.h” // For Box and Container

#include “GlassBox.h” // For GlassBox (and Box and Container)

#include “Stack.h” // For the stack class with nested struct Item

Trang 38

using namespace System;

int main(array<System::String ^> ^args){

array<Box^>^ boxes = { gcnew Box(2.0, 3.0, 4.0),

gcnew GlassBox(2.0, 3.0, 4.0),gcnew Box(4.0, 5.0, 6.0),gcnew GlassBox(4.0, 5.0, 6.0)};

Console::WriteLine(L”The array of boxes have the following volumes:”);

for each(Box^ box in boxes)box->ShowVolume(); // Output the volume of a boxConsole::WriteLine(L”\nNow pushing the boxes on the stack ”);

Stack^ stack = gcnew Stack; // Create the stackfor each(Box^ box in boxes)

Console::Write(L”{0,5}”,i);

stack->Push(i);

}Console::WriteLine(L”\n\nPopping integers off the stack produces:”);

while((item = stack->Pop()) != nullptr)Console::Write(L”{0,5}”,item);

Console::WriteLine();

return 0;

}The output from this example is:

The array of boxes have the following volumes:

Box usable volume is 24Box usable volume is 20.4Box usable volume is 120Box usable volume is 102Now pushing the boxes on the stack

Popping the boxes off the stack presents them in reverse order:

Box usable volume is 102

Trang 39

Box usable volume is 120

Box usable volume is 20.4

Box usable volume is 24

Now pushing integers on to the stack:

2 4 6 8 10 12Popping integers off the stack produces:

12 10 8 6 4 2

How It Works

You first create an array of handles to strings:

array<Box^>^ boxes = { gcnew Box(2.0, 3.0, 4.0),

gcnew GlassBox(2.0, 3.0, 4.0),gcnew Box(4.0, 5.0, 6.0),gcnew GlassBox(4.0, 5.0, 6.0)};

Because Boxand GlassBoxare ref classes, you create the objects on the CLR heap using gcnew Theaddresses of the objects initialize the elements of the boxes array

You then create a Stackobject and push the strings on to the stack:

Stack^ stack = gcnew Stack; // Create the stack

for each(Box^ box in boxes)

By casting the handle to type Container^you are able to call the ShowVolume()function cally, so the function is selected for the ultimate class type of the object that the handle references In thiscase you could have achieved the same result by casting itemto type Box^ You use safe_castherebecause you are casting up the class hierarchy and it’s as well to use a checked cast operation in such cir-cumstances The safe_castoperator checks the cast for validity and, if the conversion fails, the opera-tor throws an exception of type System::InvalidCastException You could use dynamic_castbut it

polymorphi-is better to use safe_castin CLR programs

Trang 40

Interface Classes

The definition of an interface class looks quite similar to the definition of a ref class but it is quite a ferent concept An interface is a class that specifies a set of functions that are to be implemented by otherclasses to provide a standardized way of providing some specific functionality Both value classes andref classes can implement interfaces An interface does not define any of its function members — theseare defined by each class that implements the interface

dif-You have already met the System::IComparableinterface in the context of generic functions where you specified the IComparableinterface as a constraint The IComparableinterface specifiesthe CompareTo()function for comparing objects so all classes that implement this interface have the same mechanism for comparing objects You specify an interface a class implements in the sameway as a base class For example, here’s how you could make the Boxclass from the previous exampleimplement the System::IComparableinterface:

ref class Box : Container, IComparable // Derived class{

public:

// The function specified by IComparable interfacevirtual int CompareTo(Object^ obj)

{if(Volume() < safe_cast<Box^>(obj)->Volume())return -1;

else if(Volume() > safe_cast<Box^>(obj)->Volume())return 1;

elsereturn 0;

}// Rest of the class as before

Defining Interface Classes

You define an interface class using either of the keywords interface classor interface struct.Regardless of whether you use the interface classor the interface structkeyword to define

an interface, all the members of an interface are always publicby default and you cannot specify them

to be otherwise The members of an interface can be functions including operator functions, properties,static fields, and events, all of which you’ll learn about later in this chapter An interface can also specify

a static constructor and can contain a nested class definition of any kind In spite of all that potential

Ngày đăng: 12/08/2014, 19:20

TỪ KHÓA LIÊN QUAN