The new feature to note in Listings 13.14 and 13.15 is how derived classes can make use of a friend to a base class. Consider, for example, the following friend to the hasDMA class:
friend std::ostream & operator<<(std::ostream & os,
const hasDMA & rs);
Being a friend to the hasDMA class gives this function access to the style member. But there’s a problem: This function is not a friend to the baseDMA class, so how can it access the label and rating members? The solution is to use the operator<<() function that is a friend to the baseDMA class. The next problem is that because friends are not member functions, you can’t use the scope-resolution operator to indicate which function to use. The solution to this problem is to use a type cast so that prototype matching will select the correct function. Thus, the code type casts the type const hasDMA & parameter to a type const baseDMA & argument:
std::ostream & operator<<(std::ostream & os, const hasDMA & hs)
{
// type cast to match operator<<(ostream & , const baseDMA &)
os << (const baseDMA &) hs;
os << "Style: " << hs.style << endl;
return os;
}
Listing 13.16 tests the baseDMA, lacksDMA, and hasDMA classes in a short program.
Listing 13.16. usedma.cpp
// usedma.cpp -- inheritance, friends, and DMA
// compile with dma.cpp
#include
#include "dma.h"
int main()
{
using std::cout;
using std::endl;
baseDMA shirt("Portabelly", 8);
lacksDMA balloon("red", "Blimpo", 4);
hasDMA map("Mercator", "Buffalo Keys", 5);
cout << "Displaying baseDMA object:\n";
cout << shirt << endl;
cout << "Displaying lacksDMA object:\n";
cout << balloon << endl;
cout << "Displaying hasDMA object:\n";
cout << map << endl;
lacksDMA balloon2(balloon);
cout << "Result of lacksDMA copy:\n";
cout << balloon2 << endl;
hasDMA map2;
map2 = map;
cout << "Result of hasDMA assignment:\n";
cout << map2 << endl;
return 0;
}
Here’s the output of the program in Listings 13.14, 13.15, and 13.16:
Displaying baseDMA object:
Label: Portabelly
Rating: 8
Displaying lacksDMA object:
Label: Blimpo
Rating: 4
Color: red
Displaying hasDMA object:
Label: Buffalo Keys
Rating: 5
Style: Mercator
Result of lacksDMA copy:
Label: Blimpo
Rating: 4
Color: red
Result of hasDMA assignment:
Label: Buffalo Keys
Rating: 5
Style: Mercator
Class Design Review
C++ can be applied to a wide variety of programming problems, and you can’t reduce class design to some paint-by-numbers routine. However, there are some guidelines that often apply, and this is as good a time as any to go over them by reviewing and amplifying earlier discussions.
Member Functions That the Compiler Generates for You
As first discussed in Chapter 12, the compiler automatically generates certain public member functions, termed
Default Constructors
A default constructor is one that has no arguments, or else one for which all the arguments have default arguments. If you don’t define any constructors, the compiler defines a default constructor for you. Its existence allows you to create objects. For example, suppose Star is a class. You need a default constructor to use the following:
Star rigel; // create an object without explicit initialization
Star pleiades[6]; // create an array of objects
One more thing an automatic default constructor does is call the default constructors for any base classes and for any members that are objects of another class.
Also if you write a derived-class constructor without explicitly invoking a base-class constructor in the member initializer list, the compiler uses the base class default constructor to construct the base class portion of the new object. If there is no base-class default constructor, you get a compile-time error in this situation.
If you define a constructor of any kind, the compiler does not define a default constructor for you. In that case, it’s up to you to provide a default constructor if one is needed.
Note that one of the motivations for having constructors is to ensure that objects are always properly initialized. Also if a class has any pointer members, they certainly should be initialized. Thus, it’s a good idea to supply an explicit default constructor that initializes all class data members to reasonable values.
Copy Constructors
A copy constructor for a class is a constructor that takes an object of the class type as its argument. Typically, the declared parameter is a constant reference to the class type. For example, the copy constructor for a Star class would have this prototype:
Star(const Star &);
A class copy constructor is used in the following situations:
• When a new object is initialized to an object of the same class
• When an object is passed to a function by value
• When a function returns an object by value