ManyFriend hfi1(10);
ManyFriend hfi2(20);
ManyFriend hfdb(10.5);
cout << "hfi1, hfi2: ";
show2(hfi1, hfi2);
cout << "hfdb, hfi2: ";
show2(hfdb, hfi2);
return 0;
}
Here’s the output of the program in Listing 14.24:
hfi1, hfi2: 10, 20
hfdb, hfi2: 10.5, 20
Template Aliases (C++11)
It can be convenient, especially in template design, to create aliases for types. You can use typedef to create aliases for template specializations:
// define three typedef aliases
typedef std::array arrd;
typedef std::array arri;
typedef std::array arrst;
arrd gallons; // gallons is type std::array
arri days; // days is type std::array
arrst months; // months is type std::array
But if you find yourself writing code similar to the preceding typedefs, over and over, you might wonder if you’ve forgotten some language feature that simplifies the task or if the language has forgotten to supply such a feature. In this case, C++11 provides a feature previously missing—a way to use a template to provide a family of aliases. Here’s what the approach looks like:
template
using arrtype = std::array; // template to create multiple aliases
This makes arrtype a template alias that can be used as a type, as follows:
arrtype gallons; // gallons is type std::array
arrtype days; // days is type std::array
arrtype months; // months is type std::array
In short, arrtype means type std::array.
C++11 extends the using = syntax to non-templates too. In that case, it becomes equivalent to an ordinary typedef:
typedef const char * pc1; // typedef syntax
using pc2 = const char *; // using = syntax
typedef const int *(*pa1)[10]; // typedef syntax
using pa2 = const int *(*)[10]; // using = syntax
As you get used to it, you may find the new form more readable because it separates the type name from type information more clearly.
Another C++11 addition to templates is the variadic template, which allows you to define a template class or function that can take a variable number of initializers. Chapter 18, “Visiting with the New C++ Standard,” looks into this topic.
Summary
C++ provides several means for reusing code. Public inheritance, described in Chapter 13, “Class Inheritance,” enables you to model is-a relationships, with derived classes being able to reuse the code of base classes. Private and protected inheritance also let you reuse base-class code, this time modeling has-a relationships. With private inheritance, public and protected members of the base class become private members of the derived class. With protected inheritance, public and protected members of the base class become protected members of the derived class. Thus, in either case, the public interface of the base class becomes an internal interface for the derived class. This is sometimes described as inheriting the implementation but not the interface because a derived object can’t explicitly use the base-class interface. Thus, you can’t view a derived object as a kind of base object. Because of this, a base-class pointer or reference is not allowed to refer to a derived object without an explicit type cast.
You can also reuse class code by developing a class with members that are themselves objects. This approach, called containment, layering, or composition, also models the has-a relationship. Containment is simpler to implement and use than private or protected inheritance, so it is usually preferred. However, private and protected inheritance have slightly different capabilities. For example, inheritance allows a derived class access to protected members of a base class. Also it allows a derived class to redefine a virtual function inherited from the base class. Because containment is not a form of inheritance, neither of these capabilities are options when you reuse class code via containment. On the other hand, containment is more suitable if you need several objects of a given class. For example, a State class could contain an array of County objects.
Multiple inheritance (MI) allows you to reuse code for more than one class in a class design. Private or protected MI models the has-a relationship, and public MI models the is-a relationship. MI can create problems with multidefined names and multi-inherited bases. You can use class qualifiers to resolve name ambiguities and virtual base classes to avoid multi-inherited bases. However, using virtual base classes introduces new rules for writing initialization lists for constructors and for resolving ambiguities.
Class templates let you create a generic class design in which a type, usually a member type, is represented by a type parameter. A typical template looks like this:
template
class Ic
{
T v;
...
public:
Ic(const T & val) : v(val) { }