PART 6

Advanced Features

and

Class Libraries

Templates

Exception Handling

Multiple Inheritance

Class Libraries

Advanced Features

“Advanced features” in C++ refer to feature ideas added on since AT&T Cfront 1.2, or, in other words, ideas not described in Bjarne Stroustrup’s 1986 edition of The C++ Programming Language.

These ideas fall along three major lines:

  • Multiple Inheritance, first implemented in AT&T 2.0 compilers
  • Function Templates, first implemented in AT&T 3.0 compilers
  • Exception Handling, first implemented in AT&T 4.0 compilers and compilers based on the current proposed ANSI standard

template.cpp

#include <iostream.h>

template <class T> T max( T a, T b)

{

return (a > b) ? a : b ;

}

int max(int, int);

int main()

{

int x = 1, y = 2;

double u = 6.5, v = 2.5;

cout < "2 const ints " < max (5, 6) < endl;

cout < "2 varia ints " < max (x, y) < endl;

cout < "const/var int " < max (7, x) < endl;

cout < "2 var doubles " < max (u, v) < endl;

cout < "2 const longs " < max (9L, 11L) < endl;

cout < "2 const chars " < max ('F', 'a') < endl;

cout < "int & double " < max (5, u) < endl;

cout < "int & double " < max (7, u) < endl;

cout < "char and int " < max ('F', 3) < endl;

return 0;

}

TEMPLATES

  • Templates are used to relieve the tedium of writing functions for every possible combination of data types.
  • Templates are generally superior to macros because they allow type checking and function overloading.
  • Templates can get tricky when all combinations of various classes and built-in data types need to be considered.
  • The code in template.cpp uses a template instead of a macro to implement the max function. It will work fine in all cases where the data types of the two arguments are the same. The line

int max(int, int);

is needed if we wish to mix data types and expect some kind of “default” behavior for “standard” data types. In this case, with mixed data types the results will be converted to integer. Without this line the code

cout < "int & double " < max (5, u) < endl;

cout < "int & double " < max (7, u) < endl;

cout < "char and int " < max ('F',3)< endl;

would fail

TRIVIAL CONVERSIONS

FROM: / TO:
T / const T
T / T&
T& / T
T[] / T*
T(args) / (*T)(args)
T / volatile T
T* / const T*
T* / volatile T*

ARGUMENT MATCHING RULES

When functions are overloaded, the compiler needs to know which function to call. C++ does not require that you write a function for every possible variant of parameter type, particularly where numeric types and pointers to various parts of a class hierarchy are concerned. In addition, templates provide an “unlimited” number of function prototypes.

To resolve which functions are called with what parameters converted in what ways, argument matching rules are applied. The best matching function is the intersection of sets of functions that best match on each argument. If this intersection has no members or more than one member, then the call is illegal.

1.Exact matches match first. These are matches that involve only trivial conversions. The fewer conversions the better, with conversions to const and volatile being less desirable than the rest.

2.Next comes matches that require promotions in addition to trivial conversions. The fewer promotions the better, with promotions of a char, signed char, short int, or enum to an int; promotions of a char, unsigned char, or unsigned short to an unsigned int; and promotions of a float to a double being more desirable than the rest.

3.Next comes matches that require standard conversions in addition to trivial conversions and promotions. These include standard arithmetic conversions (as in ANSI C) as well as pointer and reference conversions. Suppose we have class A, publicly derive class B from class A, and then publicly derive class C from class B. Converting from B* to A* is better than converting from B* to void* or converting from B* to const void*. Converting from C* to B* is better than converting from C* to A*. Likewise, converting from C& to B& is better than converting from C& to A&.

4.Next comes matches that require user-defined conversions.

5.Finally comes matches to an ellipsis.

hello6.cpp

// hello6.cpp - test exception handling

#include <iostream.h>

#include <string.h>

#include <ctype.h>

int getinfo (char *, char &, int &);

void getsex (char &);

int main()

{

char name[20], sex;

int status = 1, age;

try

{

status = getinfo (name, sex, age);

}

catch (char* message)

{

cout < "Name error: " < message < endl;

}

catch (char gender)

{

cerr < "Sex error: " < gender < endl;

}

catch (int howold)

{

cerr < "Age error: " < howold < endl;

}

if (status == 0)

cout < name < ", " < sex < ", " < age < endl;

return 0;

}

Exception Handling

  • Exception handling is necessary whenever an error of a catastrophic nature occurs while nested many levels deep in function calls, or where the called function is not sure how the caller wishes to attempt recovery from the error.
  • Historically, many designers argued that the goto statement and global variables were absolutely essential to provide adequate error recovery.
  • C provided a mechanism for exception handling using the setjmp/longjmp mechanism. However, this mechanism does not always provide the kind to stack cleanup necessary when unceremoniously exiting several levels of function calls, particularly if the functions have declared class instances that have constructors that dynamically allocate storage.
  • C++ 4.0 provides a much more robust mechanism for exception handling using the try, catch, and throw keywords.
  • The try block must be immediately followed by all of the catch blocks related to the try. Exception handling can only take place in try blocks, so if it is desired to enable exception handling throughout the program virtually the entire contents of main will be in a try block.
  • The throw blocks may occur anywhere in code executed within or called from a try block. Which catch block handles the thrown message depends on the data type of the expression thrown. The thrown message need not be the same as the return type of the function called or any of its parameters.
  • When a throw statement is executed all called functions are destroyed and the called stacks restored in an orderly manner. The only problem with that behavior is that you cannot throw back a pointer to an object dynamically created in any of the called functions.

hello6.cpp (continued)

int getinfo (char * name, char & sex, int & age)

{

char inname[21];

inname[19] = '\0';

cout < "What is your name? ";

cin.width(sizeof(inname));

cin > inname;

if (inname[19] != '\0')

{

inname[19] = '\0';

strcpy (name, inname);

throw name; // throw inname will NOT work

}

else

{

strcpy (name, inname);

}

getsex (sex);

cout < "What is your age? ";

cin > age;

if (age < 0)

throw age;

return 0;

}

void getsex (char & sexcode)

{

char response[10];

cout < "What sex are you? ";

cin.width(sizeof (response));

cin > response;

sexcode = response[0];

if (toupper(response[0]) != 'M'

& toupper(response[0]) != 'F')

throw sexcode; // throw response won't work

return;

}

Multiple Inheritance

  • Object-oriented design seldom yields a complete problem solution in which the classes form a perfect tree structure with all classes inheriting from a single parent class.
  • Fortunately, C++ has support for multiple inheritance (since AT&T version 2.0). In fact, the iostream library (since AT&T version 3.0) uses multiple inheritance in its definition.
  • Conceptually, multiple inheritance can be used whenever we desire to create a class that has attributes of two existing classes.
  • Syntactically, the new class can be created similar to the way other derived classes are created, except that two parent classes are listed.
  • Existing classes, however, may need to be modified to allow the new class to inherit what it needs:

-if the derived class needs access to the private data of any parent, those data elements must be changed from private to protected

-if there are virtual functions called from the derived class that in turn calls the function with the same name in the parent class, a scope resolution operator may be needed

-if there are functions to be inherited from a common grandparent (or farther up) class, the intermediate classes may need to be declared virtual so that the derived class “sees through” the multiple immediate parents

house.h (page 1 of 4)

/* Shape, triangle, rectangle, house inheritance */

#ifndef house

#define house

#include <iostream.h>

#include <math.h>

class SHAPE

{

public:

SHAPE(void);

double operator +(const SHAPE& x) const;

double operator *(const SHAPE& x) const;

virtual double perimeter(void) const = 0;

virtual double area(void) const = 0;

};

SHAPE::SHAPE(void) { };

double SHAPE::operator+ (const SHAPE& x) const

{

return perimeter() + x.perimeter();

}

double SHAPE::operator* (const SHAPE& x) const

{

return area() + x.area();

}

house.h (page 2 of 4)

class TRIANGLE : virtual public SHAPE

//to allow + and * to filter through to HOUSE class

{

protected:

double side1;

double side2;

double side3;

public:

TRIANGLE(double a, double b, double c);

double perimeter(void) const;

double area(void) const;

};

TRIANGLE::TRIANGLE(double a, double b, double c)

:SHAPE()

{

side1 = a;

side2 = b;

side3 = c;

}

double TRIANGLE::perimeter(void) const

{

return side1 + side2 + side3;

}

double TRIANGLE::area(void) const

{

double s;

s = TRIANGLE::perimeter()/2.0; //scope needed in case

//perimeter is calculated for a HOUSE object

return sqrt(s*(s - side1)*(s - side2)*(s - side3));

}

house.h (page 3 of 4)

class RECTANGLE : virtual public SHAPE

{

protected:

double side1;

double side2;

public:

RECTANGLE(double a, double b);

double perimeter(void) const;

double area(void) const;

};

RECTANGLE::RECTANGLE(double a, double b)

:SHAPE()

{

side1 = a;

side2 = b;

}

double RECTANGLE::perimeter(void) const

{

return 2*side1 + 2*side2;

}

double RECTANGLE::area(void) const

{

return side1 * side2;

}

house.h (page 4 of 4)

class HOUSE : public TRIANGLE, public RECTANGLE

//Multiple inheritance

{

public:

HOUSE(double a, double b, double c, double d);

double perimeter(void) const;

double area(void) const;

};

HOUSE::HOUSE(double a, double b, double c, double d)

:RECTANGLE(a, b), TRIANGLE(b, c, d) { }

// side b is common to the rectangle and triangle

double HOUSE::perimeter(void) const

{

return 2*RECTANGLE::side1 + RECTANGLE::side2 +

TRIANGLE::side2 + TRIANGLE::side3;

}

double HOUSE::area(void) const

{

return RECTANGLE::area() + TRIANGLE::area();

}

#endif

house.cpp

#include "house.h"

main()

{

const RECTANGLE r(1.0, 5.0);

cout < "\nRec. Perimeter: " < r.perimeter();

cout < "\nRec. Area: " < r.area();

const TRIANGLE t(1.0, 1.0, 0.5);

cout < "\nTri. Perimeter: " < t.perimeter();

cout < "\nTri. Area: " < t.area();

cout < "\n+ Operator, 2 rec.: " < (r + r);

cout < "\n+ Operator, 2 tri.: " < (t + t);

cout < "\n* Operator, 2 rec.: " < (r * r);

cout < "\n* Operator, rec&tri: " < (r * t);

cout < "\n* Operator, tri&rec: " < (t * r);

cout < "\n* Operator, 2 tri.: " < (t * t);

const TRIANGLE v(5.0, 3.0, 3.0);

cout < "\n Roof Area: " < v.area();

const RECTANGLE w(10.0, 5.0);

cout < "\n Wall Area: " < w.area();

const HOUSE u(10.0,5.0,3.0,3.0);

cout < "\nPerimeter: " < u.perimeter();

cout < "\nArea: " < u.area();

cout < "\n+ Operator, 2 houses: " < (u + u);

cout < "\n+ Operator, tri&house: " < (t + u);

cout < "\n* Operator, 2 houses: " < (u * u);

cout < "\n* Operator, tri&house: " < (t * u);

return 0;

}

Class Libraries

  • Class libraries can speed software development since the majority of the time spent developing a complete system from scratch can be spent in the design and implementation of the classes and their member functions.
  • A variety of class libraries can be purchased to assist in commercial software development
  • Borland provides a large number of classes, but the largest number of classes are associated with the following:

BIDS (Borland International Data Structures)

-provides “container classes” for both the built-in data types and programmer-defined types

OWL 2.0 (Object Windows Library)

-provides classes for building Microsoft Windows applications

BIDS

  • BIDS replaces the CLASSLIB container class. CLASSLIB was derived from a base class Object in earlier versions of C++. While this made the class library part of C++ look like SmallTalk, none of the built-in data types could be part of class Object or the CLASSLIB.
  • BIDS contains class templates that are class definitions parameterized by data type
  • BIDS contains two levels of classes

FDS (Fundamental Data Structure)
-low-level containers for data structures such as
binary search trees
doubly-linked lists
hash tables
singly-linked lists
vectors

ADT (Abstract Data Type)
-higher-level structures declared using class templates
arrays
associations
bags
deques
dictionaries
queues
sets
stacks

  • In addition, Borland C++ provides four simple classes: string, TFile, TDate, and TTime.

Lab 1 for Part 6.

1. Write a function that implements one of the string functions. Test the function. Place some distinctive code in the function that notifies you of its execution. Now name the function the same as the library function, hiding the library version.

2. Write a function that implements one of the character "functions." Test the function. Place some distinctive code in the function that notifies you of its execution. Now name the function the same as the library function in an attempt to hide the library version. Describe what happens.

Advanced FeaturesPart 6, Page 1