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 1How 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 2The 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 3The 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 4We 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 5using 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 6Try 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 7m_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 8double 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 9Friend 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 10CGlassBox(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 11int 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 12it 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 13virtual 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 14the 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 15pBox = &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 16This 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 17the 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 20The 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 21The 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 23CContainer* 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 24Try 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 25CCan(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 27pC1->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 28The 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 29It’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 30class 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 31Try 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 32Popping 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 33Because 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 34state-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 35virtual 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 36spec-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 38using 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 39Box 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 40Interface 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