Cpp Chapter 14: Reusing code in C++ Part1
14.1 Classes with object members
) The valarray class
To declare an object, you follow the identifier valarray
with angle brackets that contain the desired type:
valarray<int> q_values;
valarray<double> weights;
Several examples that use the constructors:
double gpa[5] = {3.1, 3.5, 3.8, 2.9, 3.3};
valarray<double> v1; // an array of double, size 0
valarray<int> v2(8); // an array of 8 int elements
valarray<int> v3(10,8); // an array of 8 int elements, each set to 10
valarray<double> v4(gpa,4); // an array of 4 double elements, set to the first 4 elements of array gpa
After creating such array, you could use initializer list:
valarray<int> v5 = {20, 32, 17, 9};
) Design of a student class
Student has a name and a set of scores, which marks a has-a relationship:
class Student
{
private:
string name;
valarray<double> scores;
...
};
) public inheritance inherits an interface and implementation, while a has-a relationship doesn't. In this case, the string class overloads the operator+() for string concatenation, but the Student class couldn't be "concatenated" through + directly. But in some the contained class's interface makes sense in the containing class, thus you could define a interface for containing class that actually calls the interface of the contained class for implementation
Next comes the Student class definition:
// studentc.h -- defining a student class using containment
#ifndef STUDENTC_H_INCLUDED
#define STUDENTC_H_INCLUDED
#include <iostream>
#include <string>
#include <valarray>
class Student
{
private:
typedef std::valarray<double> ArrayDb;
std::string name;
ArrayDb scores;
std::ostream & arr_cout(std::ostream & os) const;
public:
Student() : name("Null Student"), scores() {} // member initializer list
explicit Student(const std::string & s) : name(s), scores() {}
explicit Student(int n) : name("Nully"), scores(n) {}
Student (const std::string & s, int n) : name(s), scores(n) {}
Student (const std::string & s, const ArrayDb & a) : name(s), scores(a) {}
Student (const char * str, double * pd, int n) : name(str), scores(pd, n) {}
~Student() {}
double Average() const;
const std::string & Name() const;
double & operator[] (int i);
double operator[] (int i) const; // two functions above guarantees both const and non-const cases
friend std::istream & operator>>(std::istream & is, Student & stu);
friend std::istream & getline(std::istream & is, Student & stu);
friend std::ostream & operator<<(std::ostream & os, const Student & stu);
};
#endif // STUDENTC_H_INCLUDED
Noteworthy:
1 note this code:
typedef std::valarray<double> ArrayDb;
This makes ArrayDb equivalent to valarray
2 recall explicit
turns off implicit conversion while constructor only takes one argument thus it could be used as conversion functions.
3 Overloading operator[]
enables one to access the scores with the object name, view this:
Student a;
cout << a[0] << endl;
As the [] operator is overloaded, this will output the first element of the scores valarray which is the member of the object
4 initializing member objects
Recall that for inherited objects, constructors use the class name in member initializer list to invoke base-class constructor:
hasDMA::hasDMA(const hasDMA & hs) : baseDMA(hs) {...}
For member objects, constructors use the member name:
Student(const char * str, const double * pd, int n) : name(str), scores(pd, n) {}
C++ requires all member objects to be constructed before the rest of an object is constructed. So if you omit the member initializer list, C++ uses default constructor for those classes.
Next comes the code for class methods and use the Student class:
// studentc.cpp -- Student class using containment
#include "studentc.h"
using std::ostream;
using std::endl;
using std::istream;
using std::string;
double Student::Average() const
{
if (scores.size() > 0)
return scores.sum() / scores.size();
else
return 0;
}
const string & Student::Name() const
{
return name;
}
double Student::operator[](int i) const
{
return scores[i];
}
double & Student::operator[](int i)
{
return scores[i];
}
ostream & Student::arr_out(ostream & os) const
{
int i;
int lim = scores.size();
if (lim > 0)
{
for (i = 0; i < lim; i++)
{
os << scores[i] << " ";
if (i % 5 == 4)
os << endl;
}
if (i % 5 != 0)
os << endl;
}
else
os << " empty array ";
return os;
}
istream & operator>>(istream & is, Student & stu)
{
is >> stu.name;
return is;
}
istream & getline(istream & is, Student & stu)
{
getline(is, stu.name);
return is;
}
ostream & operator<<(ostream & os, const Student & stu)
{
os << "Scores for " << stu.name << ":\n";
stu.arr_out(os);
return os;
}
// use_stuc.cpp -- using a composite class
// compile with studentc.cpp
#include <iostream>
#include "studentc.h"
using std::cin;
using std::cout;
using std::endl;
void set(Student & sa, int n);
const int pupils = 3;
const int quizzes = 5;
int main()
{
Student ada[pupils] = {Student(quizzes), Student(quizzes), Student(quizzes)};
int i;
for (int i = 0; i < pupils; i++)
{
set(ada[i], quizzes);
}
cout << "\nStudent List:\n";
for (int i = 0; i < pupils; i++)
{
cout << ada[i].Name() << endl;
}
cout << "\nResults:";
for (i = 0; i < pupils; i++)
{
cout << endl << ada[i];
cout << "average: " << ada[i].Average() << endl;
}
cout << "Done.\n";
return 0;
}
void set(Student & sa, int n)
{
cout << "Please enter the student's name: ";
getline(cin, sa);
cout << "Please enter " << n << " quiz scores:\n";
for (int i = 0; i < n; i++)
cin >> sa[i]; // b.c. operator[] for Student is defined as returning a reference
while (cin.get() != '\n')
{
continue;
}
}
14.2 Private Inheritance
) Private inheritance: With private inheritance, public and protected members of the base class become private members of the derived class, which marks a has-a relationship.
) Containment adds a named member object to the class, whereas private inheritance adds unnamed inherited object to a class. subobject
class Student : private std::string, private std::valarray<double>
{
public:
...
}
Simply use keyword private. Except from that, the class need no private data members because the two privately inherited classes already provides unnamed versions of them.
) In member initializer list, you use class name for private inheritance
Student(const char * str, const double * pd, int n) : std::string(str), ArrayDb(pd, n) {}
) Differences for using private inheritance than using containment
1 To invoke base-class methods, you use class name and scope resolution operator:
double Student::Average() const
{
if (ArrayDb::size() > 0)
return ArrayDb::sum() / ArrayDb::size();
else
return 0;
}
2 To access base-class objects, you use explicit type cast
const string & Student::Name() const
{
return (const string &) *this;
}
3 To access base-class friends, you use explicit type cast
os << "Scores for " << (const String &) stu << ":\n";
) Containment or private inheritance
Containment is explicit, clear and preferable, but there are situations using inheritance:
1 access protected members of the "base class"
2 redefine virtual methods of "base class"
under such circumstances, private inheritance is used
) protected inheritance
class Student : protected std::string, protected std::valarray<double>
{
...
}
With protected inheritance, public and protected members of the base class become protected members of the derived class. This is especially efficient if you derive a new class from the derived class, then the protected members could be passed to the newly-derived class.
) redefining access with using
You could use using declaration to make base-class methods as public members of the derived class speaking of private inheritance
class Student : private std::string, private std::valarray<double>
{
public:
using std::valarray<double>::min;
using std::valarray<double>::max;
...
};
By this way, you make min() and max() ,which could be called by using ArrayDb::min() and ArrayDb::max(), as accessible as public methods of the derived class
14.3 Multiple Inheritance
) MI describes a class that has more than one immediate base class, MI uses public inheritance and should mark a is-a relationship.
Now lets create an abstract class Worker, and derive class Singer and class Waiter from it, and eventually derive SingingWaiter from the two classes, which marks an example of MI:
(recall that letting a virtual function prototype = 0 creates a pure virtual function, which could not be implemented which marks the use of abstract base class)
// worker0.h -- working classes
#ifndef WORKER0_H_INCLUDED
#define WORKER0_H_INCLUDED
#include <string>
class Worker
{
private:
std::string fullname;
long id;
public:
Worker() : fullname("no one"), id(0L) {}
Worker(const std::string & s, long n) : fullname(s), id(n) {}
virtual ~Worker() = 0; // pure virtual function
virtual void Set();
virtual void Show() const;
};
class Waiter : public Worker
{
private:
int panache;
public:
Waiter() : Worker(), panache(0) {}
Waiter(const std::string & s, long n, int p = 0) : Worker(s, n), panache(p) {}
Waiter(const Worker & wk, int p = 0) : Worker(wk), panache(p) {}
void Set();
void Show() const;
};
class Singer : public Worker
{
protected:
enum {other, alto, contralto, soprano, bass, baritone, tenor};
enum {Vtypes = 7};
private:
static char *pv[Vtypes];
int voice;
public:
Singer() : Worker(), voice(other) {}
Singer(const std::string & s, long n, int v = other) : Worker(s, n), voice(v) {}
Singer(const Worker & wk, int v = other) : Worker(wk), voice(v) {}
void Set();
void Show() const;
};
#endif // WORKER0_H_INCLUDED
// worker0.cpp -- working class methods
#include "worker0.h"
#include <iostream>
using std::cout;
using std::cin;
using std::endl;
// Worker methods
Worker::~Worker() {}
void Worker::Set()
{
cout << "Enter worker's name: ";
getline(cin, fullname);
cout << "Enter worker's id: ";
cin >> id;
while (cin.get() != '\n')
continue;
}
void Worker::Show() const
{
cout << "Name: " << fullname << "\n";
cout << "Employee ID: " << id << "\n";
}
// Waiter methods
void Waiter::Set()
{
Worker::Set();
cout <<"Enter waiter's panache rating: ";
cin >> panache;
while (cin.get() != '\n')
continue;
}
void Waiter::Show() const
{
cout << "Category: waiter\n";
Worker::Show();
cout << "Panache rating: " << panache << "\n";
}
char * Singer::pv[] = {"other", "alto", "contralto", "soprano", "bass", "baritone", "tenor"};
void Singer::Set()
{
Worker::Set();
cout << "Enter number for singer's vocal range:\n";
int i;
for (i = 0; i < Vtypes; i++)
{
cout << i << ": " << pv[i] << " ";
if (i % 4 == 3)
cout << endl;
}
if (i % 4 != 0)
cout << endl;
while (cin >> voice && (voice < 0 || voice >= Vtypes))
cout << "Please enter a value >= 0 and < " << Vtypes << endl;
while (cin.get() != '\n')
continue;
}
void Singer::Show() const
{
cout << "Category: singer\n";
Worker::Show();
cout << "Vocal range: " << pv[voice] << endl;
}
// worktest.cpp -- test worker class hierarchy
#include <iostream>
#include "worker0.h"
const int LIM = 4;
int main()
{
Waiter bob("Bob Apple", 314L, 5);
Singer bev("Beverly Hills", 522L, 3);
Waiter w_temp;
Singer s_temp;
Worker * pw[LIM] = {&bob, &bev, &w_temp, &s_temp};
int i;
for (i = 2; i < LIM; i++)
pw[i]->Set();
for (i = 0; i < LIM; i++)
{
pw[i]->Show();
std::cout << std::endl;
}
return 0;
}
) Now let's come to the SingingWaiter class. Suppose you do this:
class SingingWaiter : public Singer, public Waiter {...};
In this way you will have two copies of the Worker object within a SingingWorker object(because Singer and Waiter all inherited from Worker, which also inherits data members)
To avoid this ambiguous thing, declare Singer and Waiter inherits from a virtual base class Worker:
class Singer : virtual public Worker {...};
class Waiter : virtual public Worker {...};
In this way when you do class SingingWatier : public Singer, public Waiter {...};
again, you will only have one Worker object within the SingingWaiter object
) new constructor rules
Suppose you have the constructor for SingingWaiter class like this:
SingingWaiter(const Worker & wk, int p = 0, int v = Singer::other) : Waiter(wk,p), Singer(wk,v) {}
This makes wk to be passed to the very base class Worker by two routes. So if you declare Worker class as virtual base class by using the way described above, C++ bans this passing to virtual base class. Thus you need to explicitly call the virtual base class constructor:
SingingWaiter(const Worker & wk, int p = 0, int v = Singer::other) : Worker(wk), Waiter(wk,p), Singer(wk,v) {}
If not explicitly calling the virtual base class constructors, a default constructor would be used.
) Which method?
Suppose you have code like this:
SingingWaiter newhire("Elise Hawks", 2005, 6, soprano);
newhire.Show();
The function call is ambiguous because the both direct ancestral classes Singer and Waiter both defined a Show() method.
You could specify by using scope resolution operator:
newhire.Singer::Show();
You could also redefine a Show() method for SingingWaiter class, but it comes to more problem:
void SingingWaiter::Show()
{
Singer::Show();
Waiter::Show();
}
The common member(id and name) would be printed twice, which is not desirable.
One solution is to redefine the methods, taking their functions apart by only showing Worker contents, only showing Singer contents and only showing Waiter contents, eventually SIngingWaiter could call the combination of these methods:
void Worker::Data() const
{
cout << "Name: " << fullname << "\n";
cout << "Employee ID: " << id << "\n";
}
void Waiter::Data() const
{
cout << "Panache rating: " << panache << "\n";
}
void Singer::Data() const
{
cout << "Vocal range: " << pv[voice] << "\n";
}
void SingingWaiter::Show() const
{
cout << "Category: singing waiter\n";
Worker::Data();
Data();
}
Here comes the code:
// workermi.h -- working classes with MI
#ifndef WORKERMI_H_INCLUDED
#define WORKERMI_H_INCLUDED
#include <string>
class Worker
{
private:
std::string fullname;
long id;
protected:
virtual void Data() const;
virtual void Get();
public:
Worker() : fullname("no one"), id(0L) {}
Worker(const std::string & s, long n) : fullname(s), id(n) {}
virtual ~Worker() = 0; // pure virtual function, pure for inheritance, no need to be implemented
virtual void Set() = 0;
virtual void Show() const = 0;
};
class Waiter : virtual public Worker
{
private:
int panache;
protected:
void Data() const;
void Get();
public:
Waiter() : Worker(), panache(0) {}
Waiter(const std::string & s, long n, int p = 0) : Worker(s, n), panache(p) {}
Waiter(const Worker & wk, int p = 0) : Worker(wk), panache(p) {}
void Set();
void Show() const;
};
class Singer : virtual public Worker
{
protected:
enum {other, alto, contralto, soprano, bass, baritone, tenor};
enum {Vtypes = 7};
void Data() const;
void Get();
private:
static char *pv[Vtypes];
int voice;
public:
Singer() : Worker(), voice(other) {}
Singer(const std::string & s, long n, int v = other) : Worker(s, n), voice(v) {}
Singer(const Worker & wk, int v = other) : Worker(wk), voice(v) {}
void Set();
void Show() const;
};
class SingingWaiter : public Singer, public Waiter
{
protected:
void Data() const;
void Get();
public:
SingingWaiter() {}
SingingWaiter(const std::string & s, long n, int p = 0, int v = other) : Worker(s,n), Waiter(s,n,p),Singer(s,n,v) {}
SingingWaiter(const Waiter & wt, int v = other) : Worker(wt), Waiter(wt), Singer(wt,v) {}
SingingWaiter(const Singer & wt, int p = 0) : Worker(wt), Waiter(wt,p), Singer(wt) {}
void Set();
void Show() const;
};
#endif // WORKERMI_H_INCLUDED
// workermi.cpp -- working class methods with MI
#include "workermi.h"
#include <iostream>
using std::cout;
using std::cin;
using std::endl;
// Worker methods
Worker::~Worker() {}
void Worker::Data() const
{
cout << "Name: " << fullname << endl;
cout << "Employee ID: " << id << endl;
}
void Worker::Get()
{
getline(cin, fullname);
cout << "Enter worker's ID: ";
cin >> id;
while (cin.get() != '\n')
continue;
}
// Waiter methods
void Waiter::Set()
{
cout << "Enter waiter's name: ";
Worker::Get();
Get();
}
void Waiter::Show() const
{
cout << "Category: waiter\n";
Worker::Data();
Data();
}
void Waiter::Data() const
{
cout << "Panache rating: " << panache << endl;
}
void Waiter::Get()
{
cout << "Enter waiter's panache rating: ";
cin >> panache;
while (cin.get() != '\n')
continue;
}
char * Singer::pv[] = {"other", "alto", "contralto", "soprano", "bass", "baritone", "tenor"};
void Singer::Get()
{
cout << "Enter number for singer's vocal range:\n";
int i;
for (i = 0; i < Vtypes; i++)
{
cout << i << ": " << pv[i] << " ";
if (i % 4 == 3)
cout << endl;
}
if (i % 4 != 0)
cout << endl;
cin >> voice;
while (cin.get() != '\n')
continue;
}
void Singer::Data() const
{
cout << "Vocal range: " << pv[voice] << endl;
}
void Singer::Show() const
{
cout << "Category: singer\n";
Worker::Data();
Data();
}
void Singer::Set()
{
cout << "Enter singer's name: ";
Worker::Get();
Get();
}
void SingingWaiter::Data() const
{
Singer::Data();
Waiter::Data();
}
void SingingWaiter::Get()
{
Waiter::Get();
Singer::Get();
}
void SingingWaiter::Set()
{
cout << "Enter singing waiter's name: ";
Worker::Get();
Get();
}
void SingingWaiter::Show() const
{
cout << "Category: singing waiter\n";
Worker::Data();
Data();
}
// workmi.cpp -- multiple inheritance
// compile with workermi.cpp
#include <iostream>
#include <cstring>
#include "workermi.h"
const int SIZE = 5;
int main()
{
using std::cin;
using std::cout;
using std::endl;
using std::strchr;
Worker * lolas[SIZE];
int ct;
for (ct = 0; ct < SIZE; ct++)
{
char choice;
cout << "Enter the employee category:\n";
cout << "w: waiter s: singer t: singing waiter q: quit\n";
cin >> choice;
while (strchr("wstq", choice) == NULL)
{
cout << "Please enter a w, s, t, or q: ";
cin >> choice;
}
if (choice == 'q')
break;
switch(choice)
{
case 'w' : lolas[ct] = new Waiter;
break;
case 's' : lolas[ct] = new Singer;
break;
case 't' : lolas[ct] = new SingingWaiter;
break;
}
cin.get();
lolas[ct]->Set();
}
cout << "\nHere is your staff:\n";
int i;
for (i = 0; i < ct; i++)
{
cout << endl;
lolas[i]->Show();
}
for (i = 0; i < ct; i++)
delete lolas[i];
cout << "Bye.\n";
return 0;
}