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

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

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

Class Inheritance and Virtual Functions

error C2248: ‘m_Length’: cannot access protected member declared in class ‘CBox’

which indicates quite clearly that the member m_Length is 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, the default specification is private. This has the effect of causing the inherited public and protected members of the base class to become private in the derived class. The private members of the base class remain private to the base and therefore inaccessible to member functions of the derived class. In fact, they remain private to the base class regardless of how the base class is specified in the derived class definition.

You have also used public as the specifier for a base class. This leaves the members of the base class with the same access level in the derived class as they had in the base, so public members remain public and protected members remain protected.

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

 

 

class CABox:public CBox

 

 

{

 

inherited as

public:

 

inherited as

•••

 

protected:

 

 

•••

 

 

}

class CBox

 

 

{

 

class CBBox:protected CBox

public:

 

 

{

•••

inherited as

protected:

protected:

inherited as

•••

•••

protected:

 

 

private:

 

•••

•••

 

}

 

 

}

 

 

No access - ever.

 

class CCBox:private CBox

 

 

{

 

inherited as

private:

 

 

•••

 

inherited as

private:

 

 

•••

 

 

}

Figure 9-3

489

Chapter 9

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

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

Defining a base class as public doesn’t change the access level of its members in the derived class

Defining a base class as protected changes its public members to protected in the derived class

Being able to change the access level of inherited members in a derived class gives you a degree of flexibility, but don’t forget that you cannot relax the level specified in the base class; you can only make the access level more stringent. This suggests that your base classes need to have public members if you want to be able to vary the access level in derived classes. This may seem to run contrary to the idea of encapsulating data in a class in order to protect it from unauthorized access, but, as you’ll see, it is often the case that you define base classes in such a manner that their only purpose is to act as a base for other classes, and they aren’t intended to be used for instantiating objects in their own right.

The Copy Constructor in a Derived Class

Remember that the copy constructor is called automatically when you declare an object that is initialized with 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 second calls the copy constructor. If you don’t supply your own copy constructor, the compiler supplies one that 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 constructor to the class CBox. You can then use this class as a base for defining the CCandyBox class.

// Box.h in Ex9_05 #pragma once #include <iostream> using std::cout; using std::endl;

class CBox

// Base class definition

{

 

public:

// Base class constructor

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

{cout << endl << “CBox constructor called”; }

// Copy constructor CBox(const CBox& initB)

{

490

Class Inheritance and Virtual Functions

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:

double m_Length; double m_Width; double m_Height;

};

Also recall that the copy constructor must have its parameter specified as a reference to avoid an infinite number of calls to itself, which would otherwise result from the need to copy an argument that is transferred by value. When the copy constructor in our example is invoked, it outputs a message to the screen, so you’ll be able to see from the output when this is happening.

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

// CandyBox.h in Ex9_05 #pragma once

#include “Box.h” #include <iostream> using std::cout; using std::endl;

class CCandyBox: public CBox

{

public:

char* m_Contents;

//Derived class function to calculate volume double Volume() const

{ return m_Length*m_Width*m_Height; }

//Constructor to set dimensions and contents

//with explicit call of CBox constructor

CCandyBox(double lv, double wv, double hv, char* str = “Candy”) :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 automatically

CCandyBox(char* str = “Candy”)

// Constructor

{

 

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

491

Chapter 9

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()

#include “CandyBox.h”

// For CBox and CCandyBox

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

cout << endl

<<“Volume of chocBox is “ << chocBox.Volume()

<<endl

<<“Volume of chocolateBox is “ << chocolateBox.Volume()

<<endl;

return 0;

}

How It Works (or Why It Doesn’t)

When you run the Debug version of this example, in addition to the expected output, you’ll see the dialog shown in Figure 9-4 displayed.

Click Abort to 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 called the copy constructor for the base class.

492

Class Inheritance and Virtual Functions

Figure 9-4

However, as you’ve probably realized, all is not as it should be. In this particular case, the compiler-gen- erated copy constructor causes problems because the memory pointed to by the m_Contents member of the derived class in the second object declared points to the same memory as the one in the first object.

When one object is destroyed (when it goes out of scope at the end of main()), it releases the memory occupied by the text. When the second object is destroyed, the destructor attempts to release some memory that has already been freed by the destructor call for the previous object — and that’s the reason for the error message in the dialog box.

The way to fix this is to supply a copy constructor for the derived class that allocates some additional memory for the new object.

Try It Out

Fixing the Copy Constructor Problem

You can do this by adding the following code for the copy constructor to the public section of the derived CCandyBox class in Ex9_05:

// Derived class copy constructor CCandyBox(const CCandyBox& initCB)

{

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

// Get new memory

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

// Copy string

strcpy_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 new copy constructor works.

493

Chapter 9

How It Works

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

CBox constructor called

CCandyBox constructor2 called

CBox constructor called

CCandyBox copy constructor called

Volume of chocBox is 24

Volume of chocolateBox is 1

CCandyBox destructor called

CBox destructor called

CCandyBox destructor called

CBox destructor called

However, there is still something wrong. The third line of output shows that the default constructor for the CBox part of the object chocolateBox is called, rather than the copy constructor. As a consequence, the object has the default dimensions rather than the dimensions of the initializing object, so the volume is incorrect. The reason for this is that when you write a constructor for an object of a derived class, you are responsible for ensuring that the members of the derived class object are properly initialized. This includes the inherited members.

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

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

{

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

// Get new memory

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

// Copy string

strcpy_s(m_Contents, strlen(initCB.m_Contents) + 1, initCB.m_Contents);

}

Now the CBox class copy constructor is called with the initCB object. Only the base part of the object is passed to it, so everything works out. If you modify the last example by adding the base copy constructor call, 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

494

Class Inheritance and Virtual Functions

The output shows that all the constructors and destructors are called in the correct sequence and the copy constructor for the CBox part of chocolateBox is called before the CCandyBox copy constructor. The volume of the object chocolateBox of the derived class is now the same as that of its initializing object, 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 Members as Friends

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

Suppose you define a CBottle class to represent a bottle:

class CBottle

{

public:

CBottle(double height, double diameter)

{

m_Height = height; m_Diameter = diameter;

}

 

private:

 

double m_Height;

// Bottle height

double m_Diameter;

// Bottle diameter

};

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

class CCarton

 

{

 

public:

 

CCarton(const CBottle& aBottle)

 

{

 

m_Height = aBottle.m_Height;

// Bottle height

m_Length = 4.0*aBottle.m_Diameter;

// Four rows of ...

m_Width = 3.0*aBottle.m_Diameter;

// ...three bottles

}

 

private:

 

double m_Length;

// Carton length

double m_Width;

// Carton width

double m_Height;

// Carton height

};

 

 

 

495

Chapter 9

The constructor here sets the height to be the same as that of the bottle it is to accommodate, and the length 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 CBottle class are private, so the CCarton constructor cannot access them. As you also know, a friend declaration in the CBottle class fixes it:

class CBottle

{

public:

CBottle(double height, double diameter)

{

m_Height = height; m_Diameter = diameter;

}

 

private:

 

double m_Height;

// Bottle height

double m_Diameter;

// Bottle diameter

// Let the carton constructor in

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

};

The only difference between the friend declaration here and what you saw in Chapter 7 is that you must put the class name and the scope resolution operator with the friend function name to identify it. For this to compile correctly, the compiler needs to have information about the CCarton class constructor, so you would need to put an #include statement for the header file containing the CCarton class definition before the definition of the CBottle class.

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 CCarton class as a friend of the CBottle class by adding a friend declaration within the CBottle class definition:

friend CCarton;

With this declaration in the CBottle class, all function members of the CCarton class now have free access to all the data members of the CBottle class.

Limitations on Class Friendship

Class friendship is not reciprocated. Making the CCarton class a friend of the CBottle class does not mean that the CBottle class is a friend of the CCarton class. If you want this to be so, you must add a friend declaration for the CBottle class to the CCarton class.

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

496

Class Inheritance and Virtual Functions

Virtual Functions

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

// Box.h in Ex9_06 #pragma once #include <iostream> using std::cout; using std::endl;

class CBox

// Base class

{

 

public:

 

//Function to show the volume of an object void ShowVolume() const

{

cout << endl

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

}

//Function to calculate the volume of a CBox object 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;

};

Now you can output the usable volume of a CBox object just by calling the ShowVolume() function for any object for which you require it. The constructor sets the data member values in the initialization list, so no statements are necessary in the body of the function. The data members are as before and are specified as protected, so they are accessible to the member functions of any derived class.

Suppose you want to derive a class for a different kind of box called CGlassBox, to hold glassware. The contents are fragile, and because packing material is added to protect them, the capacity of the box is less than the capacity of a basic CBox object. You therefore need a different Volume() function to account for this, so you add it to the derived class:

// GlassBox.h in Ex9_06 #pragma once

#include “Box.h”

class CGlassBox: public CBox

// Derived class

{

 

public:

 

497

Chapter 9

//Function to calculate volume of a CGlassBox

//allowing 15% for packing

double Volume() const

{ return 0.85*m_Length*m_Width*m_Height; }

// Constructor

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 concentrate on how the inherited functions work for the moment. The constructor for the derived class objects just calls the base class constructor in its initialization list to set the data member values. No statements are necessary in its body. You have included a new version of the Volume() function to replace the version from the base class, the idea being that you can get the inherited function ShowVolume() to call the derived class version of the member function Volume() when you call it for an object of the class CGlassBox.

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 the correct 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 CGlassBox

using std::cout;

 

using std::endl;

 

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, it’s also disastrous. It isn’t working the way you want at all, and the only interesting thing about it is why. Evidently, the fact that the second call is for an object of the

498