
Beginning Visual C++ 2005 (2006) [eng]-1
.pdf
Class Inheritance and Virtual Functions
CBox usable volume is 20.4
CBox usable volume is 120
CBox usable volume is 102
Now pushing the boxes on the stack...
Popping the boxes off the stack presents them in reverse order:
CBox usable volume is 102
CBox usable volume is 120
CBox usable volume is 20.4
CBox usable volume is 24
Press any key to continue . . .
How It Works
You create an array of pointers to CBox objects so each element in the array can store the address of a CBox object or an address of any type that is derived from CBox. The array is initialized with the adresses of four objects 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 CBox object and two CGlassBox objects with the same dimensions as the CBox objects.
After listing the volumes of the four objects, you create a CStack object and push the objects on to the stack in a for loop:
CStack* pStack |
= new CStack; |
// Create the stack |
for (int i = 0 |
; i<4 ; i++) |
|
pStack->Push(pBoxes[i]); |
|
Each element in the pBoxes array is pushed on to the stack by passing the array element as the argument to the Push() function for the CStack object. This results in the first element from the array being at the bottom of the stack and the last element at the top.
You pop the objects off the stack in another for loop:
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 the ShowVolume() function for the object. Because the last element was at the top of the stack, the loop lists the volumes of the objects in reverse order. From the output you can see that the CStack class does indeed implement a stack using a nested struct to define the items to be stored in the stack.
519

Chapter 9
C++/CLI Programming
All C++/CLI classes, including classes that you define, are derived classes by default. This is because both value classes and reference classes have a standard class, System::Object, as a base class. This means that both value classes and reference classes inherit from the System::Object class and therefore have the capabilities of the System::Object class 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 called polymorphically when required. This is what you have been doing in previous chapters when you defined the ToString() function in a class.
The System::Object base class for all value class types is also responsible for enabling the boxing and unboxing of values of the fundamental types. This process 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 just as values for the purposes of normal operations and are only converted to an object that is referenced by a handle of type System::Object^ when they need to behave as objects. Thus you get the benefit of being able to treat fundamental values as objects when you need to without incurring the overhead of them being objects in general.
Inheritance in C++/CLI Classes
Although value classes always have the System::Object class as a base, you cannot define a value classes derived from an existing class. To put it another way, when you define a value class you are not allowed to specify a base class. This implies that polymorphism in value classes is limited to the functions that are defined as virtual in the System::Object class. These are the virtual functions that all value classes inherit from System::Object:
Function |
Description |
|
|
String^ ToString() |
Returns a String representation of an object and the imple- |
|
mentation in the System::Object class returns the class name |
|
as a string. You would typically override this function in your |
|
own classes to return a string representation of the value of an |
|
object. |
bool Equals(Object^ obj) |
Compares the current object to obj and returns true if they |
|
are equal and false otherwise. Equal in this case means refer- |
|
ential equality — that is the objects are one and the same. Typi- |
|
cally you would override this function in your own classes to |
|
return true when the current object is the same value as the |
|
argument — 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 that |
|
stores (key,object) pairs. Objects are subsequently retrieved |
|
from such a collection by supplying the key that was used |
|
when the object was stored. |
|
|
Of course, because System::Object is also a base class for reference classes you may want to override these functions in reference classes too.
520

Class Inheritance and Virtual Functions
You can derive a reference class from an existing reference class in the same way as you define a derived class a native C++. Let’s re-implement Ex9_12 as a C++/CLI program as this also demonstrates nested classes in a CLR program. We can start by defining the Container class:
//Container.h for Ex9_14 #pragma once
using namespace System;
//Abstract base class for specific containers ref class Container abstract
{
public:
//Function for calculating a volume - no content
//This is defined as an ‘abstract’ virtual function,
//indicated by the ‘abstract’ keyword
virtual double Volume() abstract;
// Function to display a volume virtual void ShowVolume()
{
Console::WriteLine(L”Volume is {0}”, Volume());
}
};
The first thing to note is the abstract keyword following the class name. If a C++/CLI class contains the native 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 creating objects of that class type. The abstract keyword also appears at the end of the Volume() function member 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 polymorphically for objects of class types that are derived from Container.
You can define the Box class like this:
// Box.h for Ex9_14 |
|
#pragma once |
|
#include “Container.h” |
// For Container definition |
ref class Box : Container |
// Derived class |
{ |
|
public: |
|
//Function to show the volume of an object virtual void ShowVolume() override
{
Console::WriteLine(L”Box usable volume is {0}”, Volume());
}
//Function to calculate the volume of a Box object
virtual double Volume() override
521

Chapter 9
{ return m_Length*m_Width*m_Height; }
// Constructor
Box() : m_Length(1.0), m_Width(1.0), m_Height(1.0){}
// Constructor
Box(double lv, double wv, double hv)
: m_Length(lv), m_Width(wv), m_Height(hv){}
protected:
double m_Length; double m_Width; double m_Height;
};
A base class for a ref class is always public and the public keyword is assumed by default. You can specify the base class explicitly as public but it is not necessary to do so. A base class to a ref class cannot be specified as anything other than public. Because you cannot supply default values for parameters as in the native C++ version of the class, you define the no-arg constructor so that it initializes all three fields to 1.0. The Box class defines the Volume() function as an override to the inherited base class version. You must always specify the override keyword when you want to override a function in the base class. If the Box class 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 GlassBox class definition looks:
// GlassBox.h for Ex9_14 |
|
#pragma once |
|
#include “Box.h” |
// For Box |
ref class GlassBox : Box |
// Derived class |
{ |
|
public: |
|
//Function to calculate volume of a GlassBox
//allowing 15% for packing
virtual double Volume() override
{ return 0.85*m_Length*m_Width*m_Height; }
// Constructor
GlassBox(double lv, double wv, double hv): Box(lv, wv, hv){}
};
The base class is Box, which is public by default. The rest of the class is essentially the same as the original.
Here’s the Stack class definition:
//Stack.h for Ex9_14
//A push-down stack to store objects of any ref class type #pragma once
ref class Stack
522

Class Inheritance and Virtual Functions
{
private:
// Defines items to store in the stack ref struct Item
{ |
|
Object^ Obj; |
// Handle for the object in this item |
Item^ Next; |
// Handle for next item in the stack or nullptr |
// Constructor
Item(Object^ obj, Item^ next): Obj(obj), Next(next){}
}; |
|
Item^ Top; |
// Handle for item that is at the top |
public: |
|
// Push an object on to the stack |
|
void Push(Object^ obj) |
|
{ |
|
Top = gcnew Item(obj, Top); |
// Create new item and make it the top |
} |
|
// Pop an object off the stack |
|
Object^ Pop() |
|
{ |
|
if(Top == nullptr) |
// If the stack is empty |
return nullptr; |
// return nullptr |
Object^ obj = Top->Obj; |
// Get object from item |
Top = 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 are dealing with ref class objects. The inner struct, Item, now stores a handle of type Object^, which allows objects of any CLR class type to be stored in the stack; this means either value class or ref class objects can be accommodated which is a significant improvement over the native C++ CStack class. You don’t need to worry about deleting Item objects when the Pop() function is called, because the garbage collector 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 declared using the abstract keyword.
A class that contain s one or more abstract functions must be explicitly specified as abstract by placing the abstract keyword following the class name.
523

Chapter 9
A class that does not contain abstract functions can be specified as abstract, in which case instances of the class cannot be defined.
You must explicitly use the override keyword when specifying a function that overrides a function 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_14 and add the classes in the previous section to the project; 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 |
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 box |
Console::WriteLine(L”\nNow pushing the boxes on the stack...”);
Stack^ stack = gcnew Stack; |
// Create the stack |
for each(Box^ box in boxes) |
|
stack->Push(box); |
|
Console::WriteLine(
L”Popping the boxes off the stack presents them in reverse order:”); Object^ item;
while((item = stack->Pop()) != nullptr) safe_cast<Container^>(item)->ShowVolume();
Console::WriteLine(L”\nNow pushing integers on to the stack:”); for(int i = 2 ; i<=12 ; i += 2)
{
Console::Write(L”{0,5}”,i); stack->Push(i);
524

Class Inheritance and Virtual Functions
}
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 24
Box usable volume is 20.4
Box usable volume is 120
Box usable volume is 102
Now pushing the boxes on the stack...
Popping the boxes off the stack presents them in reverse order:
Box usable volume is 102
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 |
12 |
Popping integers off the stack produces: 12 10 8 6 4 2
Press any key to continue . . .
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 Box and GlassBox are ref classes, you create the objects on the CLR heap using gcnew. The addresses of the objects initialize the elements of the boxes array.
You then create a Stack object and push the strings on to the stack:
Stack^ stack = gcnew Stack; |
// Create the stack |
for each(Box^ box in boxes) |
|
stack->Push(box); |
|
525

Chapter 9
The parameter to the Push() function is of type Object^ so the function accepts any class type as the argument. The for each loop pushes each of the elements in the boxes array on to the stack.
Popping the elements off the stack occurs in a while loop:
Object^ item;
while((item = stack->Pop()) != nullptr) safe_cast<Container^>(item)->ShowVolume();
The loop condition stores the value returned from the Pop() function for the stack object in item, and compares it with nullptr. As long as item has not been set to nullptr, the statement that is the body of the while loop is executed. Within the loop you cast the handle stored in item to type Container^. The item variable is of type Object^, and because the Object class does not define the ShowVolume() function, you cannot call the ShowVolume() function using a handle of this type; to call a function polymorphically, you must use a handle of a base class type that declares the function to be a virtual member. By casting the handle to type Container^ you are able to call the ShowVolume() function polymorphically, so the function is selected for the ultimate class type of the object that the handle references. In this case you could have achieved the same result by casting item to type Box^. You use safe_cast here because you are casting up the class hierarchy and it’s as well to use a checked cast operation in such circumstances. The safe_cast operator checks the cast for validity and, if the conversion fails, the operator throws an exception of type System::InvalidCastException. You could use dynamic_cast but it is better to use safe_cast in CLR programs.
Interface Classes
The definition of an interface class looks quite similar to the definition of a ref class but it is quite a different concept. An interface is a class that specifies a set of functions that are to be implemented by other classes to provide a standardized way of providing some specific functionality. Both value classes and ref classes can implement interfaces. An interface does not define any of its function members — these are defined by each class that implements the interface.
You have already met the System::Comparable interface in the context of generic functions where you specified the IComparable interface as a constraint. The IComparable interface specifies the 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 same way as a base class. For example, here’s how you could make the Box class from the previous example implement the
System::IComparable interface:
ref class Box : Container, IComparable |
// Derived class |
{ |
|
public:
// The function specified by IComparable interface virtual int CompareTo(Object^ obj)
{
if(Volume() < safe_cast<Box^>(obj)->Volume())
return -1;
else if(Volume() > safe_cast<Box^>(obj)->Volume()) return 1;
else
526

Class Inheritance and Virtual Functions
return 0;
}
// Rest of the class as before...
};
The name of the interface follows the name of the base class, Container. If there were no base class, the interface name alone would appear here. A ref class can only have one base class but it can implement as many interfaces as you want. The class must define every function specified by each of the interfaces that it claims to implement. The IComparable interface only specifies one function but there can be as many functions in an interface as you want. The Box class now defines the CompareTo() function with the same signature as the IComparable interface specifies for the function. Because the parameter to the CompareTo() function is of type Object^, you have to cast it to type Box^ before you can access members of the Box object it references.
Defining Interface Classes
You define an interface class using either of the keywords interface class or interface struct. Regardless of whether you use the interface class or the interface struct keyword to define an interface, all the members of an interface are always public by 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 diversity of members, most interfaces are relatively simple. Note that you can derive one interface from another in basically the same way as you use to derive one ref class from another. For example:
interface class IController : ITelevison, IRecorder
{
// Members of IController...
};
The IController interface contains its own members, and it also inherits the members of the ITelevision and IRecorder interfaces. A class that implements the IController interface has to define the member functions from IController, ITelevision, and IRecorder.
You could use an interface instead of the Container base class in Ex9_14. Here’s how the definition of this interface would look:
// IContainer.h for Ex9_15 #pragma once
interface class IContainer
{ |
|
double Volume(); |
// Function for calculating a volume |
void ShowVolume(); |
// Function to display a volume |
}; |
|
527

Chapter 9
By convention the names of interface start with I in C++/CLI so the interface name is IContainer. It has two members: the Volume() function and the ShowVolume() function, which is public because members of an interface are always public. Both functions are effectively abstract because an interface never includes function definitions — indeed, you could add the abstract keyword to both here but it is not required. Instance functions in an interface definition can be specified as virtual and abstract but it is not necessary to do so as they are anyway.
Any class that implements the IContainer interface must implement both functions if the class is not to be abstract. Let’s see how the Box class looks:
// Box.h for Ex9_15 #pragma once
#include “IContainer.h” |
// For interface definition |
using namespace System;
ref class Box : IContainer
{
public:
//Function to show the volume of an object virtual void ShowVolume()
{
Console::WriteLine(L”CBox usable volume is {0}”, Volume());
}
//Function to calculate the volume of a Box object
virtual double Volume()
{ return m_Length*m_Width*m_Height; }
// Constructor
Box() : m_Length(1.0), m_Width(1.0), m_Height(1.0){}
// Constructor
Box(double lv, double wv, double hv)
: m_Length(lv), m_Width(wv), m_Height(hv){}
protected:
double m_Length; double m_Width; double m_Height;
};
The name of the interface goes after the colon in the first line of the class definition, just as if it were a base class. Of course, there could also be a base class, in which case the interface name would follow the base class name separated from it by a comma. A class can implement multiple interfaces in which case the names of the interfaces are separated by commas.
The Box class must implement both function members of the IContainer interface class; otherwise, it would be an abstract class and would need to be declared as such. The definitions for these functions in the Box class do not have the override keyword appended because you are not overriding existing function definitions here; you are implementing them for the first time.
528