Containing and Inheriting from Classes

Dr. Lawlor, CS 202, CS, UAF

The simplest way to stick several objects together is "containment": you declare sub-objects as data members of the containing object.  We've done this before:
class person {
public:
string first; // first name
string last; // last name
person(string firstName,string lastName) {
first=firstName;
last=lastName;
}
};
int foo(void) {
person p("Doc","Lawlor");
cout<<"My name: "<<p.first<<" "<<p.last<<"\n";
return 0;
}

(Try this in NetRun now!)

You can use a special and hideous "initializer" syntax for initializing your class members from your constructor.  This does exactly the same thing as above:
class person {
public:
string first; // first name
string last; // last name
person(string firstName,string lastName)
:first(firstName), //<- call "first"'s constructor with "firstName"
last(lastName)
{
cout<<"Made a person.\n";
}
};
int foo(void) {
person p("Doc","Lawlor");
cout<<"My name: "<<p.first<<" "<<p.last<<"\n";
return 0;
}

(Try this in NetRun now!)

Here's a simpler example of the initializer syntax:

class gotta_int { 
public:
int my_int;
gotta_int(void)
:my_int(3) // <- initialize my_int
{ /* don't need anything here! */ }
};

int foo(void) {
gotta_int g;
return g.my_int;
}

(Try this in NetRun now!)

The initializer list begins with a colon, and then you initialize each member by name, passing the parameters for that member's constructor.  If you've got several members to initialize, you separate the members with a comma.  Initializers are always run in the order that you declared the data members, NOT the order you list the initializers!  Using the initializer syntax is good because it avoids calling the default constructor and then the assignment operator for each member, which is a little more efficient, and works for members without assignment operators.

I don't like the syntax much, but I use initializers in almost all my class constructors.

Class Inheritance

There's a much stranger way to glom onto an existing class, and that's to inherit from it.  The existing class is the "parent", and the new class is the "child".   For example, here "string" is the parent, and the hideous mutated "thing" is the child class:
class thing : public string  //<- "thing" is also a "string"
{
public:
int my_int;
};

int foo(void) {
thing t;
t.my_int=2; // it's a class...
t.append("WHY!?"); // ..but it's *also* a string!?
cout<<"Its int is "<<t.my_int<<" and its second letter is "<<t.at(1)<<"\n";
return 0;
}

(Try this in NetRun now!)

The "thing" inherited the "append" and "at" methods from its parent.  It has its own integer, though.

For another example, here we've made a "person" a child of a "string".  The parent string contains the person's first name, which lets us print a person the same way we'd print a string.  We can call our parent class's constructor using the initializer syntax above:
class person : public string  //<- we *are* a string: our first name
{
public:
string last; // last name
person(string firstName,string lastName)
:string(firstName), //<- call parent class's constructor
last(lastName)
{
cout<<"Made a person.\n";
}
};
int foo(void) {
person p("Doc","Lawlor");
cout<<"My name: "<<p<<" "<<p.last<<"\n";
return 0;
}

(Try this in NetRun now!)

Like cloning, the child shares nearly all the features of the parent (member data, member functions, static members, even most operators), but can add new features of its own.  The only thing the child doesn't inherit is the parent's constructors (except those the child calls explicitly), assignment operators, and friends.

You can inherit from your own classes, that themselves inherit from other classes.  Here string is the parent class, person is the child class, and old_person is the "grandchild".  You can only call the constructors of your direct parents though, not your "grandparents":
class person : public string  //<- we *are* a string: our first name
{
public:
string last; // last name
person(string firstName,string lastName)
:string(firstName), //<- call parent class's constructor
last(lastName)
{
cout<<"Made a person.\n";
}
};

class old_person : public person //<- we *are* a person
{
public:
int age;
old_person(int ageNow,string firstName,string lastName)
:person(firstName,lastName), // must call parent constructor first
age(ageNow) // ... then you can initialize your own members
{
cout<<"...an old person.\n";
}
};

int foo(void) {
old_person p(33,"Doc","Lawlor");
cout<<"My name: "<<p<<" "<<p.last<<", age "<<p.age<<"\n";
return 0;
}

(Try this in NetRun now!)

You can even inherit from several different classes, called "multiple inheritance".  You then glom together the methods, members, and data from all your parent classes.  This is usually a terrible idea, but the language supports it!
class A { 
public:
int from_A;
A(void) :from_A(2) {} // constructor
};
class B {
public:
string from_B;
B(void) :from_B("I was once a man... like you!") {} // constructor
};
class C : public A, public B // Multiple inheritance: C inherits from both A and B
{
public:
void print(void) {
cout<<"I inherited both "<<from_A<<" and '"<<from_B<<"'\n";
}
};

int foo(void) {
C kid;
kid.print();
return kid.from_A;
}

(Try this in NetRun now!)

There are a few times inheritance is useful, and C++ provides a lot of interesting features to support those times (such as the "virtual" method we'll cover next week).  However, inheritance is often overused, in situations where plain object containment like above would work much better.  I used to have my classes inherit stuff from all over the place, but I don't use inheritance very often now!