䊐 Concrete or Abstract?If a class comprises pure virtual methods, you cannot create objects of this class type.. 䊐 Deriving Abstract Classes When a class is derived from an abstract clas
Trang 1䊐 Concrete or Abstract?
If a class comprises pure virtual methods, you cannot create objects of this class type
Example: Coworker worker("Black , Michael");
The compiler will issue an error message here, as the Coworkerclass contains the pure virtual method income() This avoids calling a method for worker that still needs to be defined
A class that does not allow you to create any objects is referred to as an abstract class.
In contrast, a class that allows you to create objects is referred to as a concrete class.
䊐 Deriving Abstract Classes
When a class is derived from an abstract class, it inherits all the methods the base class contains, particularly the pure virtual methods If all of these pure virtual methods are implemented in the derived class, you can then create an object of the derived class type
This means you need to implement the income() method in the Laborer class shown opposite Since the hourly wage and the number of hours worked are both defined for blue-collar workers, it is possible to implement that method
Example: double Laborer::income()
{ return ( wages * hr );
}
A class derived from a concrete class can again contain pure virtual methods, due to additional definitions in the derived class In other words, an abstract class can be derived from a concrete class
An abstract class does not necessarily need to contain pure virtual functions If the class contains a protected constructor, objects of the class type cannot be created The constructor can only be called then by methods in derived classes A constructor of this type normally acts as base initializer, when an object of a derived class type is cre-ated
A class with pure virtual methods is an abstract class
✓ NOTE
A class derived from a class containing pure virtual methods is a concrete class, if it contains a definition for each pure virtual function
✓ NOTE
Trang 2570 C H A P T E R 2 6 A B S T R A C T C L A S S E S
// coworker.h: Extending the headerfile
//
-class Employee : public Coworker
{
private:
double salary; // Pay per month
public:
Employee( const string& s="", double sa = 0.0)
: Coworker(s), salary(g){ } double getSalary() const { return salary; } void setSalary( double sa){ salary = sa; } void display() const;
double income()const { return salary; }
Employee& operator=( const Coworker& );
Employee& operator=( const Employee& );
};
// coworker_t.cpp : Using the Coworker classes
//
-#include "coworker.h"
int main() {
Coworker* felPtr[2];
felPtr[0] = new Laborer("Young, Neil",45., 40); felPtr[1] = new Employee("Smith, Eve", 3850.0);
for( int i = 0; i < 2; ++i) {
felPtr[i]->display();
cout << "\nThe income of " << felPtr[i]->getName()
<< " : " << felPtr[i]->income() << endl;
} delete felPtr[0]; delete felPtr[1];
return 0;
}
The derived class Employee
Sample program
Trang 3Although you cannot define objects for abstract classes, you can declare pointers and ref-erences to abstract classes
Example: Coworker *felPtr, &coRef;
The pointer felPtris a base class pointer that can address objects belonging to derived concrete classes The reference coRefcan also address objects of this type
䊐 References to Abstract Base Classes
References to base classes are often used as parameters in functions The copy construc-tor in the Coworkerclass is just one of them
Example: Coworker( const Coworker& );
The copy constructor expects an object belonging to a derived class, since the base class
is abstract
The assignment in the Coworkerclass has a reference as a parameter and returns a reference to the abstract class
䊐 Pointers to Abstract Base Classes
Pointers to base classes are generally used to address dynamically allocated objects If the base class is abstract, you can only allocate memory for objects belonging to derived, con-crete classes
Example: Coworker* felPtr;
felPtr = new Laborer("Young, Neil",45.,40);
cout << felPtr->income();
Since the income()method is virtual, a corresponding function found in the derived classLaboreris executed
䊐 Polymorphic Interface
Defining pure virtual methods also determines interfaces for general operations, although the interfaces still need to implemented in the derived classes If a derived class contains its own definition of a virtual method, this version will also be executed if an object is referenced by a base class pointer or reference Abstract classes are therefore also referred
to as polymorphic interfaces to derived classes.
The opposite page shows the definition of the Employee class, which was also derived from the abstract class Coworker The operator functions for the assignments are discussed and implemented in the following section
Trang 4572 C H A P T E R 2 6 A B S T R A C T C L A S S E S
// Virtual Assignment in the base class
Coworker& operator=(const Coworker & m)
{ if( this != &m ) // No self assignment name = m.name;
return *this;
}
Redefining the virtual operator function operator=(), which returns a reference to the derived class,
is not yet supported by all compilers In this case the return type must be a reference to the base class
Coworker
✓ NOTE
// Redefinition: virtual
Employee& operator=(const Coworker& m)
{ if( this != &m ) // No self assignment {
Coworker::operator=( m );
salary = 0.0;
} return *this;
}
// Standard Assignment: not virtual
Employee& operator=(const Employee& a)
{ if( this != &a ) {
Coworker::operator=( a );
salary = a.salary;
} return *this;
}
Assignment for class Coworker
Assignments for class Employee
Trang 5䊐 Virtual Operator Functions
Operator functions implemented as methods can also be virtual In this case, you can ensure that the right version of an operator function will be executed when using a pointer or reference to a base class to address a derived class object
One example of this is the operator function for an assignment If the function decla-ration is not virtual, and if the function is called via a base class pointer, only the base data of the object is overwritten Any additional data members of the derived class remain unchanged
䊐 Using Virtual Assignments
The assignment was declared virtual in the Coworker base class The derived classes LaborerandEmployeeboth contain their own versions Thus, in the following
Example: void cpy(Coworker& a,const Coworker& b)
{ a = b; }
the assignment of the Employeeclass is executed, if an object of this class type is the first argument passed to it If the object is a Laborer type, the assignment of the Laborerclass is performed
In the case of the cpy()function, you can therefore assign two objects of any class, including classes derived at a later stage, without having to modify the function itself! However, you definitely need to define a version of the assignment for each derived class
䊐 Redefining the Standard Assignment
When you define a new version for a virtual method in a derived class, this implies using the signature of the original method Since the standard assignment of a derived class has
a signature of its own, it is not virtual The standard assignment for the Laborerclass has the following prototype:
Example: Laborer& operator=(const Laborer&);
The type const Laborer& is different from the const Coworker& type of the parameter in the virtual operator function of the base class The standard assignment thus masks the virtual assignment in the base class This gives rise to two issues:
■ the virtual operator function for the assignment must be defined for every derived class
■ to ensure that the standard assignment is also available, the standard assignment must also be redefined in every derived class
Trang 6574 C H A P T E R 2 6 A B S T R A C T C L A S S E S
// cell.h: Defining the classes Cell, BaseEl, and DerivedEl //
-#ifndef _CELL_
#define _CELL_
#include <string>
#include <iostream>
using namespace std;
class Cell
{
private:
Cell* next;
protected:
Cell(Cell* suc = NULL ){ next = suc; }
public:
virtual ~Cell(){ } // Access methods to be declared here
virtual void display() const = 0;
};
class BaseEl : public Cell
{
private:
string name;
public:
BaseEl( Cell* suc = NULL, const string& s = "") : Cell(suc), name(s){}
// Access methods would be declared here
void display() const;
};
class DerivedEl : public BaseEl
{
private:
string rem;
public:
DerivedEl(Cell* suc = NULL,const string& s="",
const string& b="") : BaseEl(suc, s), rem(b) { } // Access methods would be declared here
void display() const;
};
#endif
The abstract base class Cell and derived classes
Trang 71st list element
Info Info
Pointer
2nd list element 3rd list element Info
Pointer Pointer
䊐 Terminology
Let’s look into implementing an application that uses an inhomogeneous list An inho-mogeneous list is a linear list whose elements can be of different types If the data you need to store consists of objects in a class hierarchy, one list element could contain an object belonging to the base class, whereas another could contain an object of a derived class
Due to implicit type conversions in class hierarchies, you can use the base class point-ers to manage the list elements, that is, you can manage the elements in a linked list The following graphic illustrates this scenario:
䊐 Representing List Elements
To separate the management of list elements from the information contained in the list,
we have defined an abstract class called Cell as a base class for all list elements The class contains a pointer of type Cell* as the data member used to link list elements Since Cell type objects are not be created, the constructor in the Cell class has a protecteddeclaration
The Cellclass does not contain any data that might need to be output However, each class derived from Cellcontains data that need to be displayed For this reason, Cellcontains a declaration of the pure virtual method display(), which can be mod-ified for multiple derivations
The classes BaseEl andDerivedEl, which are derived from Cell, represent list elements used for storing information To keep things simple, the BaseElclass contains only a name, and the DerivedElclass additionally contains a comment The public declaration section contains a constructor and access method declarations In addition, a suitable version of the display() method is defined Both classes are thus concrete classes
Trang 8576 C H A P T E R 2 6 A B S T R A C T C L A S S E S
Info Info Pointer
New element:
Info Pointer Pointer Info
// List.h: Defining the class InhomList //
-#ifndef _LIST_H
#define _LIST_H
#include "cell.h"
class InhomList
{ private:
Cell* first;
protected:
Cell* getPrev(const string& s);
void insertAfter(const string& s,Cell* prev);
void insertAfter(const string& s,
const string& b,Cell* prev);
public: // Constructor, Destructor etc
void insert(const string& n);
void insert(const string& n, const string& b);
void displayAll() const;
};
#endif
void InhomList::insertAfter(const string& s, Cell* prev) {
if( prev == NULL ) // Insert at the beginning, first = new BaseEl( first, s);
else // middle, or end
{ Cell* p = new BaseEl(prev->getNext(), s);
prev->setNext(p);
} }
Defining class InhomList
Inserting a list element in the middle
Definition of insertAfter() version
Trang 9䊐 The InhomList Class
The inhomogeneous list must allow sorted insertion of list elements It is no longer suffi-cient to append elements to the end of the list; instead, the list must allow insertion at any given point A pointer to the first element in the list is all you need for this task You can then use the pointer to the next list element to access any given list element The definition of the InhomList class is shown opposite A pointer to Cell has been declared as a data member The constructor has very little to do It simply sets the base class pointer to NULL, thus creating an empty list
The list will be sorted by name When inserting a new element into the list, the inser-tion point—that is the posiinser-tion of the element that will precede the new element—must first be located In our example, we first locate the immediate lexicographical predeces-sor The getPrev()method, shown opposite, performs the search and returns either the position of the predecessor or NULL if there is no predecessor In this case, the new list element is inserted as the first element in the list
䊐 Inserting a New List Element
After finding the insertion position you can call the insertAfter()method that allo-cates memory for a new list element and inserts the element into the list There are two cases that need to be looked at:
1 If the new element has to be inserted at the start of the list, what was originally the first element now becomes the second The new element becomes the first element The firstpointer thus needs updating
2 If the new element is inserted at any other position in the list, the firstpointer
is not affected Instead, you have to modify two pointers The pointer in the pre-ceding list element must be pointed at the new element and the pointer in the new element must be pointed at what was formerly the successor of the preceding element This situation also applies in cases where the successor was a NULL pointer, in other words, when the new element is appended to the list
Since the list contains objects of the BaseEl and DerivedEl types, the insertAfter()method has been overloaded with two versions They differ only in different calls to the newoperator
Theinsert()method was overloaded for the same reason Both versions first call the getPrev() method and the corresponding version of the insertAfter() method
Trang 10578 C H A P T E R 2 6 A B S T R A C T C L A S S E S
class InhomList
{
private:
Cell* first;
protected:
Cell* getPrev(const string& s);
Cell* getPos(const string& s);
void insertAfter(const string& s, Cell* prev); void insertAfter(const string& s,const string& b,
Cell* prev);
void erasePos(Cell* pos);
public:
InhomList(){ first = NULL; } InhomList(const InhomList& src);
~InhomList();
InhomList& operator=( const InhomList& src);
void insert(const string& n);
void insert(const string& n, const string& b); void erase(const string& n);
void displayAll() const;
};
The complete class InhomList