Click Here!
home account info subscribe login search My ITKnowledge FAQ/help site map contact us


 
Brief Full
 Advanced
      Search
 Search Tips
To access the contents, click the chapter and section titles.

C++ in Plain English
(Publisher: IDG Books Worldwide, Inc.)
Author(s): Brian Overland
ISBN: 1558284729
Publication Date: 04/01/96

Bookmark It

Search this book:
 
Previous Table of Contents Next


There are couple of problems with this function. First, it is inefficient. It is not obvious here, but C++ always constructs an object by first calling the constructor for the base class, if any. Because no base-class constructor is specified, the compiler generates a call to the CAuto default constructor.

Consequently, all the original members of CAuto are already initialized—in this case, to zero—before the first statement in this function starts executing. The function initializes all CAuto members twice.

The second problem is potentially much worse. The CAuto members were declared public, but had they been declared as private, this CSportsCar constructor would be impossible to write at all!

The solution is to specify the appropriate base-class constructor in the CSportsCar constructor function definition. Here is the syntax:

class::class(arglist1) : base_class(arglist2) {
   statements
}

In this syntax, arglist2 contains a subset of arguments from arglist1. These arguments are passed through to the base-class constructor. This base-class constructor must match, by number and type of arguments, a constructor defined in base_class.

In the following function definition, the CSportsCar argument list is followed by a colon (:) and the appropriate CAuto constructor. The first four arguments are passed through to the base CAuto class constructor, which takes four arguments. If you review the CAuto declaration, you’ll see that CAuto has such a constructor.

CSportsCar::CSportsCar(char mak[], char mod[], int y,
    int c, double hp, double a) : CAuto(mak, mod, y, c)
    {
    horse_power = hp;
    accel_0_60 = a;
}

Now the four data members inherited from CAuto are initialized only once. You probably also notice a fringe benefit: the resulting CSportsCar constructor is shorter and therefore easier to write.

Base Classes and Pointers

Our final look at inheritance sets up much of the rationale behind virtual functions, the subject of the next chapter. Many programmers consider the virtual-function capability to be at the heart of object orientation, so it’s worth taking a few minutes to understand the problem it addresses.

Inheritance creates a kind of one-sided relationship between types in C++. Generally speaking, C++ does not allow you to assign pointers of one type to pointers of a different type without using a data cast. But you can assign an object of a derived type to a pointer of base-class type. Figure 8.5 illustrates this rule.


Figure 8.5  Pointer assignment between base and derived types.

Interestingly, this pointer assignment is allowed in only one direction: a pointer of base type can point to a derived type. This arrangement makes sense if you think about it. Consider the following case:

CStr   *pBase;
CIoStr DerivedObj;

pBase = &DerivedObj;    //Assgn. to base-class ptr OK.

When the pointer pBase is declared, the compiler assumes that it will point to an object of type CStr. Errors could arise if pBase is made to point to an int variable, for example, because the int type does not support CStr members. But there is no problem in making pBase point to an object of a derived type (in this case, CIoStr), because the derived type must include at least the same members that CStr does. The following statement works equally well whether pBase points to a CStr object or to a CIoStr object:

pBase->cat(“ blah blah blah”);

But the converse does not hold. Consider what would happen if C++ let you freely assign the address of an object of base type (CStr) to a pointer to the derived type (CPrStr):

CStr   BaseObj;
CPrStr pDerived;

pDerived = &BaseObj;  // ERROR! Cannot assgn. to
                     // derived type without cast

The problem is that the base type, CStr, is a subset, and not a superset, of the type pointed to by pDerived. The object pointed to by pDerived should include at least all the members that CIoStr does, but CStr does not include everything in CIoStr. For example, what happens when the following function call is made?

pDerived->input();

In the CIoStr declaration, the input function is a new member above and beyond those declared by CStr. This operation would be invalid if pDerived points to a CStr object.

In summary, a pointer to a class can be assigned the address of something in the same class or a derived class. The pointer’s type controls which members can be accessed. You could declare an array of pointers to CStr, for example, and each pointer could point to a different type: CStr, CIoStr, or CFontIoStr. But, assuming that you don’t recast the pointer, you can use the pointer to refer only to members, such as the cpy function, that are declared in CStr itself.

CStr *pStr[10];
PStr[0] = new CStr;
pStr[1] = new CIoStr;
PStr[2] = new CFontIoStr;

An interesting question arises when one of the member functions is overridden. Suppose that CPrStr overrides the cpy function with its own implementation, as described earlier in this chapter. This operation creates two versions of the function: CStr::cpy and CIoStr::cpy. Which version of cpy do the following statements call?

CIoStr prstr;
CStr pStr = &prstr;
pStr->cpy(“Initialize me.”);

The last statement calls CStr::cpy (the base-class version) and not CIoStr::cpy! When the compiler scans the code and attempts to resolve this function call to an address, the compiler must use the type information it has. In this case, all it knows is that pStr points to something of type CStr. It therefore calls CStr::cpy.

pStr->cpy(“Initialize me.”);

But wait a minute, you say, that can’t be right! The object knows that it is an object of type CIoStr and not CStr. The object should be able to call the version of the function found in the CIoStr class.

But that determination cannot be made until run time. The problem is that at compile time, the compiler cannot know the precise type pointed to, but it must make a determination of the function’s address. Its only choice is to use the type information given and assume CStr scope.

For example, in the following code, the expression pStr[n] could point to any object of type CStr, CIoStr, or CFontIoStr, but the compiler has to assume CStr as the type.

CStr *pStr[10];
. . .
pStr[n]->cpy(“hi!”);

During running of the program, the precise type pointed to by pStr[n] is finally determined. If the decision of which function to call could be delayed until run time, then the expression pStr[n]->cpy(“hi!”) could call the right function, either CStr::cpy or CIoStr::cpy, as appropriate.

This is just what happens with virtual functions, which are the subject of the next chapter. Virtual functions use late binding, which means that a reference to a function, such as pStr[n]->cpy, is not bound to an actual address in code until run time.


Previous Table of Contents Next
[an error occurred while processing this directive]

Products |  Contact Us |  About Us |  Privacy  |  Ad Info  |  Home

Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is prohibited. Read EarthWeb's privacy statement.