using
Public members of a base class become protected or private when you use protected or private derivation. Suppose you want to make a particular base-class method available publicly in the derived class. One option is to define a derived-class method that uses the base-class method. For example, suppose you want the Student
class to be able to use the valarray sum()
method. You can declare a sum()
method in the class declaration and then define the method this way:
double Student::sum() const // public Student method
{
return std::valarray<double>::sum(); // use privately-inherited method
}
Then a Student
object can invoke Student::sum()
, which, in turn, applies the valarray<double>::sum()
method to the embedded valarray
object. (If the ArrayDb typedef
is in scope, you can use ArrayDb
instead of std::valarray<double>
.)
There is an alternative to wrapping one function call in another: use a using
declaration (such as those used with namespaces) to announce that a particular base-class member can be used by the derived class, even though the derivation is private. For example, suppose you want to be able to use the valarray min()
and max()
methods with the Student
class. In this case, in studenti.h
, you can add using
declarations to the public section:
class Student : private std::string, private std::valarray<double>
{
...
public:
using std::valarray<double>::min;
using std::valarray<double>::max;
...
};
The using
declaration makes the valarray<double>::min()
and valarray<double>::max()
methods available as if they were public Student
methods:
cout << "high score: " << ada[i].max() << endl;
Note that the using
declaration just uses the member name—no parentheses, no function signatures, no return types. For example, to make the valarray operator[]()
method available to the Student
class, you’d place the following using
declaration in the public section of the Student
class declaration:
using std::valarray<double>::operator[];
This would make both versions (const
and non-const
) available. You could then remove the existing prototypes and definitions for Student::operator[]()
. The using
declaration approach works only for inheritance and not for containment.
There is an older way to redeclare base-class methods in a privately derived class: You place the method name in the public section of the derived class. Here’s how you would do that:
class Student : private std::string, private std::valarray<double>
{
public:
std::valarray<double>::operator[]; // redeclare as public, just use name
...
};
This looks like a using
declaration without the using
keyword. This approach is deprecated, meaning that the intention is to phase it out. So if your compiler supports the using
declaration, you can use it to make a method from a private base class available to the derived class.
MI describes a class that has more than one immediate base class. As with single inheritance, public MI should express an is-a relationship. For example, if you have a Waiter
class and a Singer
class, you could derive a SingingWaiter
class from the two:
class SingingWaiter : public Waiter, public Singer {...};
Note that you must qualify each base class with the keyword public
. That’s because the compiler assumes private derivation unless instructed otherwise:
class SingingWaiter : public Waiter, Singer {...}; // Singer is a private base
As discussed earlier in this chapter, private and protected MI can express a has-a relationship; the studenti.h
implementation of the Student
class is an example. We’ll concentrate on public inheritance now.
MI can introduce new problems for programmers. The two chief problems are inheriting different methods with the same name from two different base classes and inheriting multiple instances of a class via two or more related immediate base classes. Solving these problems involves introducing a few new rules and syntax variations. Thus, using MI can be more difficult and problem-prone than using single inheritance. For this reason, many in the C++ community object strongly to MI; some want it removed from the language. Others love MI and argue that it’s very useful, even necessary, for particular projects. Still others suggest using MI cautiously and in moderation.
Let’s explore a particular example and see what the problems and solutions are. You need several classes to create an MI situation. For this example, we’ll define an abstract Worker
base class and derive a Waiter
class and a Singer
class from it. Then we can use MI to derive a SingingWaiter
class from the Waiter
and Singer
classes (see Figure 14.3). This is a case in which a base class (Worker
) is inherited via two separate derivations, which is the circumstance that causes the most difficulties with MI. We start with declarations for the Worker
, Waiter
, and Singer
classes, as shown in Listing 14.7.
// worker0.h -- working classes
#ifndef WORKER0_H_
#define WORKER0_H_
#include <string>
class Worker // an abstract base class
{
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 destructor
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]; // string equivs of voice types
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
The class declarations in Listing 14.7 include some internal constants that represent voice types. An enumeration makes alto
, contralto
, and so on symbolic constants for voice types, and the static array pv
holds pointers to the C-style string equivalents. The implementation file, shown in Listing 14.8, initializes this array and provides method definitions.
// worker0.cpp -- working class methods
#include "worker0.h"
#include <iostream>
using std::cout;
using std::cin;
using std::endl;
// Worker methods
// must implement virtual destructor, even if pure
Worker::~Worker() {}
void Worker::Set()
{
cout << "Enter worker's name: ";
getline(cin, fullname);
cout << "Enter worker's ID: ";
cin >> id;
while (cin.get() != '
')
continue;
}
void Worker::Show() const
{
cout << "Name: " << fullname << "
";
cout << "Employee ID: " << id << "
";
}
// Waiter methods
void Waiter::Set()
{
Worker::Set();
cout << "Enter waiter's panache rating: ";
cin >> panache;
while (cin.get() != '
')
continue;
}
void Waiter::Show() const
{
cout << "Category: waiter
";
Worker::Show();
cout << "Panache rating: " << panache << "
";
}
// Singer methods
char * Singer::pv[] = {"other", "alto", "contralto",
"soprano", "bass", "baritone", "tenor"};
void Singer::Set()
{
Worker::Set();
cout << "Enter number for singer's vocal range:
";
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() != '
')
continue;
}
void Singer::Show() const
{
cout << "Category: singer
";
Worker::Show();
cout << "Vocal range: " << pv[voice] << endl;
}
Listing 14.9 provides a brief test of the classes, using a polymorphic array of pointers.
// 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;
}
Here is the output of the program in Listings 14.7, 14.8, and 14.9:
Enter waiter's name: Waldo Dropmaster
Enter worker's ID: 442
Enter waiter's panache rating: 3
Enter singer's name: Sylvie Sirenne
Enter worker's ID: 555
Enter number for singer's vocal range:
0: other 1: alto 2: contralto 3: soprano
4: bass 5: baritone 6: tenor
3
Category: waiter
Name: Bob Apple
Employee ID: 314
Panache rating: 5
Category: singer
Name: Beverly Hills
Employee ID: 522
Vocal range: soprano
Category: waiter
Name: Waldo Dropmaster
Employee ID: 442
Panache rating: 3
Category: singer
Name: Sylvie Sirenne
Employee ID: 555
Vocal range: soprano
The design seems to work, with pointers to Waiter
invoking Waiter::Show()
and Waiter::Set(
), and pointers to Singer
invoking Singer::Show()
and Singer::Set()
. However, it leads to some problems if you add a SingingWaiter
class derived from both the Singer
class and Waiter
class. In particular, you’ll need to face the following questions:
• How many workers?
• Which method?
3.142.173.238