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

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

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

Class Inheritance and Virtual Functions

the program no longer compiles. The compiler generates the following message:

error C2247: ‘CBox::m_Length’ not accessible because ‘CCandyBox’ uses ‘private’ to inherit from ‘CBox’

It says quite clearly that the m_Length member from the base class is not accessible because m_Length has become private in the derived class. This is because there is a default access specifier of private for a base class when you define a derived class — it’s as if the first line of the derived class definition had been

class CCandyBox: private CBox

There always has to be an access specification for a base class that determines the status of the inherited members in the derived class. If you omit the access specification for a base class, the compiler assumes that it’s private. If you change the definition of the CCandyBox class in CandyBox.h to the following,

class CCandyBox: public CBox

{

public:

char* m_Contents;

 

CCandyBox(char* str = “Candy”)

// Constructor

{

 

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

 

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

 

}

 

~CCandyBox()

// Destructor

{ delete[] m_Contents; };

 

};

 

the m_Length member is inherited in the derived class as public and is accessible in the function main(). With the access specifier public for the base class, all the inherited members originally specified as public in the base class have the same access level in the derived class.

Access Control Under Inheritance

The whole question of the access of inherited members in a derived class needs to be looked at more closely. Consider the status of the private members of a base class in a derived class.

There was a good reason to choose the version of the class CBox with public data members in the previous example, rather than the later, more secure version with private data members. The reason was that although private data members of a base class are also members of a derived class, they remain private to the base class in the derived class so member functions added to the derived class cannot access them. They are only accessible in the derived class through function members of the base class that are not in the private section of the base class. You can demonstrate this very easily by changing all the CBox class data members to private and putting a Volume()function in the derived class CCandyBox, so that the class definition is as follows:

479

Chapter 9

// Version of the classes that will not compile class CBox

{

public:

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

private:

double m_Length; double m_Width; double m_Height;

};

class CCandyBox: public CBox

{

public:

char* m_Contents;

// Function to calculate the volume of a CCandyBox object

double Volume() const // Error - members not accessible { return m_Length*m_Width*m_Height; }

CCandyBox(char* str = “Candy”)

// Constructor

{

 

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

}

 

~CCandyBox()

// Destructor

{ delete[] m_Contents; }

 

};

A program using these classes does not compile. The function Volume() in the class CCandyBox attempts to access the private members of the base class, which is not legal.

Try It Out

Accessing Private Members of the Base Class

It is, however, legal to use the Volume() function in the base class, so if you move the definition of the function Volume() to the public section of the base class, CBox, not only will the program compile but you can use the function to obtain the volume of a CCandyBox object. Create a new WIN32 project, Ex9_02, with the Box.h contents as the following:

// Box.h in Ex9_02 #pragma once

class CBox

{

public:

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

480

Class Inheritance and Virtual Functions

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

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

{ return m_Length*m_Width*m_Height; }

private:

double m_Length; double m_Width; double m_Height;

};

The CandyBox.h header in the project contains:

// Header file CandyBox.h in project Ex9_02 #pragma once

#include “Box.h”

class CCandyBox: public CBox

{

public:

char* m_Contents;

 

CCandyBox(char* str = “Candy”)

// Constructor

{

 

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

 

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

 

}

 

~CCandyBox()

// Destructor

{ delete[] m_Contents; };

 

};

 

The Ex9_02.cpp file in the project contains:

 

//Ex9_02.cpp

//Using a function inherited from a base class

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

 

 

{

 

 

CBox myBox(4.0,3.0,2.0);

 

// Create CBox object

CCandyBox myCandyBox;

 

 

CCandyBox myMintBox(“Wafer Thin Mints”);

// Create CCandyBox object

cout << endl

 

 

<< “myBox occupies “ << sizeof

myBox

// Show how much memory

<< “ bytes” << endl

 

// the objects require

<< “myCandyBox occupies “ << sizeof myCandyBox

481

Chapter 9

<<“ bytes” << endl

<<“myMintBox occupies “ << sizeof myMintBox

<<“ bytes”; cout << endl

<<“myMintBox volume is “ << myMintBox.Volume(); // Get volume of a

//CCandyBox object

cout << endl; return 0;

}

This example produces the following output:

myBox occupies 24 bytes myCandyBox occupies 32 bytes myMintBox occupies 32 bytes myMintBox volume is 1

How It Works

The interesting additional output is the last line. This shows the value produced by the function Volume(), which is now in the public section of the base class. Within the derived class, it operates on the members of the derived class that are inherited from the base. It is a full member of the derived class, so it can be used freely with objects of the derived class.

The value for the volume of the derived class object is 1 because, in creating the CCandyBox object, the CBox()default constructor was called first to create the base class part of the object, and this sets default CBox dimensions to 1.

Constructor Operation in a Derived Class

Although I said the base class constructors are not inherited in a derived class, they still exist in the base class and are used for creating the base part of a derived class object. This is because creating the base class part of a derived class object is really the business of a base class constructor, not the derived class constructor. After all, you have seen that private members of a base class are inaccessible in a derived class object, even though they are inherited, so responsibility for these has to lie with the base class constructors.

The default base class constructor was called automatically in the last example to create the base part of the derived class object, but this doesn’t have to be the case. You can arrange to call a particular base class constructor from the derived class constructor. This enables you to initialize the base class data members with a constructor other than the default, or indeed to choose to call a particular class constructor, depending on the data supplied to the derived class constructor.

Try It Out

Calling Constructors

You can see this in action through a modified version of the previous example. To make the class usable, you really need to provide a constructor for the derived class that allows you to specify the dimensions of the object. You can add an additional constructor in the derived class to do this, and call the base class constructor explicitly to set the values of the data members that are inherited from the base class.

482

Class Inheritance and Virtual Functions

In the Ex9_03 project, Box.h contains:

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

class CBox

{

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”; }

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

{ return m_Length*m_Width*m_Height; }

private:

double m_Length; double m_Width; double m_Height;

};

The CandyBox.h header file should contain:

// CandyBox.h in Ex9_03 #pragma once

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

using std::endl;

class CCandyBox: public CBox

{

public:

char* m_Contents;

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

{

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

{

483

Chapter 9

cout << endl << “CCandyBox constructor1 called”; m_Contents = new char[ strlen(str) + 1 ]; strcpy_s(m_Contents, strlen(str) + 1, str);

}

~CCandyBox()

// Destructor

{ delete[] m_Contents; }

 

};

The #include directive for the <iostream> header and the two using declarations are not strictly necessary here because Box.h contains the same code, but it does no harm to put them in. On the contrary, putting these statements in here also means that if you were to remove this code from Box.h because it was no longer required there, CandyBox.h still compiles.

The contents of Ex9_03.cpp is:

//Ex9_03.cpp

//Calling a base constructor from a derived class 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()

{

CBox myBox(4.0, 3.0, 2.0); CCandyBox myCandyBox;

CCandyBox myMintBox(1.0, 2.0, 3.0, “Wafer Thin Mints”);

cout <<

endl

 

<<

“myBox occupies “ << sizeof myBox

// Show how much memory

<<

“ bytes” << endl

// the objects require

<<“myCandyBox occupies “ << sizeof myCandyBox

<<“ bytes” << endl

<<“myMintBox occupies “ << sizeof myMintBox

<<“ bytes”;

cout <<

endl

 

 

<<

“myMintBox volume is “

//

Get volume of a

<<

myMintBox.Volume();

//

CCandyBox object

cout <<

endl;

 

 

return 0;

}

How It Works

As well as adding the additional constructor in the derived class, you have added an output statement in each constructor so you know when either gets called. The explicit call of the constructor for the CBox class appears after a colon in the function header of the derived class constructor. You have perhaps noticed that the notation is exactly the same as what you have been using for initializing members in a constructor anyway:

484

Class Inheritance and Virtual Functions

// Calling the base class constructor

CCandyBox(double lv, double wv, double hv, char* str= “Candy”):

CBox(lv, wv, hv)

{

...

}

This is perfectly consistent with what you are doing here because you are essentially initializing a CBox sub-object of the derived class object. In the first case, you are explicitly calling the default constructor for the double members m_Length, m_Width and m_Height in the initialization list. In the second instance, you are calling the constructor for CBox. This causes the specific CBox constructor you have chosen to be called before the CCandyBox constructor is executed.

If you build and run this example, it produces the output shown as follows:

CBox constructor called CBox constructor called

CCandyBox constructor1 called CBox constructor called CCandyBox constructor2 called myBox occupies 24 bytes myCandyBox occupies 32 bytes myMintBox occupies 32 bytes myMintBox volume is 6

The calls to the constructors are explained in the following table:

Screen output

Object being constructed

CBox constructor called

MyBox

CBox constructor called

MyCandyBox

CCandyBox constructor1 called

MyCandyBox

CBox constructor called

MyMintBox

CCandyBox constructor2 called

MyMintBox

The first line of output is due to the CBox class constructor call, originating from the declaration of the CBox object, myBox. The second line of output arises from the automatic call of the base class constructor caused by the declaration of the CCandyBox object myCandyBox.

Notice how the base class constructor is always called before the derived class constructor.

The following line is due to your version of the default derived class constructor being called for the myCandyBox object. This constructor is invoked because the object is not initialized. The fourth line of output arises from the explicit identification of the CBox class constructor to be called in our new constructor for CCandyBox objects. The argument values specified for the dimensions of the CCandyBox object are passed to the base class constructor. Next comes the output from the new derived class constructor itself, so constructors are again called for the base class first, followed by the derived class.

485

Chapter 9

It should be clear from what you have seen up to now that when a derived class constructor is executed, a base class constructor is always called to construct the base part of the derived class object. If you don’t specify the base class constructor to be used, the compiler arranges for the default base class constructor to be called.

The last line in the table shows that the initialization of the base part of the myMintBox object is working as it should be, with the private members having been initialized by the CBox class constructor.

Having the private members of a base class only accessible to function members of the base class isn’t always convenient. There will be many instances where you want to have private members of a base class that can be accessed from within the derived class. As you surely have anticipated by now, C++ provides a way to do this.

Declaring Class Members to be Protected

In addition to the public and private access specifiers for members of a class, you can also declare members of a class as protected. Within the class, the protected keyword has the same effect as the private keyword: members of a class that are protected can only be accessed by member functions of the class, and by friend functions of the class (also by member functions of a class that is declared as a friend of the class — you will learn about friend classes later in this chapter). Using the protected keyword, you could redefine the CBox class as follows:

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

class CBox

{

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”; }

// CBox destructor - just to track calls ~CBox()

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

protected:

double m_Length; double m_Width; double m_Height;

};

Now the data members are still effectively private, in that they can’t be accessed by ordinary global functions, but they’ll still be accessible to member functions of a derived class.

486

Class Inheritance and Virtual Functions

Try It Out

Using Protected Members

You can demonstrate the use of protected data members by using this version of the class CBox to derive a new version of the class CCandyBox, which accesses the members of the base class through its own member function, Volume():

// CandyBox.h in Ex9_04 #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”;

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;

}

};

The code for main() in Ex9_04.cpp is:

//Ex9_04.cpp

//Using the protected access specifier

#include <iostream>

// For stream I/O

487

Chapter 9

#include <cstring>

// For strlen() and strcpy()

#include “CandyBox.h”

// For CBox and CCandyBox

using std::cout;

 

using std::endl;

 

int main()

{

CCandyBox myCandyBox;

CCandyBox myToffeeBox(2, 3, 4, “Stickjaw Toffee”);

cout << endl

<<“myCandyBox volume is “ << myCandyBox.Volume()

<<endl

<<“myToffeeBox volume is “ << myToffeeBox.Volume();

//cout << endl << myToffeeBox.m_Length; // Uncomment this for an error

cout << endl; return 0;

}

How It Works

In this example you calculate the volumes of the two CCandyBox objects 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_Height to produce the result. The members are declared as protected in the base class and remain protected in 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 CCandyBox objects. The first object has the default dimensions produced by calling the default CBox constructor, 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 derived class 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 constructor and then the derived class constructor, whereas the destructor for the derived class is called first when an object is destroyed, followed by the base class destructor.

You can demonstrate that the protected members of the base class remain protected in the derived class by uncommenting the statement preceding the return statement in the function main(). If you do this, you get the following error message from the compiler,

488