Inheritance
What is inheritance good for?
- Classes with strong "is-a" relations between them:
Circle,Square, ... is-aShapeCouch,Chair,Bench, ... is-aSeat;Seat,Table, ... is-aFurniturePoint3Dis-aPoint2Dif we project outzChecking,Savingsis-aBankAccount;MoneyMarketis-aSavings- Recall the C++ I/O inheritance hierarchy
- Terminology: subclass is-a superclass
- Means subclass behaviors extend behaviors of superclass, so there is some common code
- Inheritance lets subclass get ("inherit") superclass code "for free".
Class Relationships: Has-a vs. Is-a
- Has-a is for composition: put it in a field
Bicyclehas-aWheelWindowhas-aMenu
- Is-a is for inheritance
General syntax
To declare a derived (child) class use class DerivedClass : access BaseClass
- Default
accessisprivateif none specified, but standard inheritance like in Java ispublicform - usepublicunless you have a reason not to! - If access is
public, it means code outside of classes is aware of relationship; for example,superObj = subObjassignment is legal - If access is
private, outsiders can never see relationship- Nonstatic members of
BaseClassbecome de facto private members inDerivedClass superObj = subObjnow illegal- Restricts access to base class functions that have no meaning for derived class; can make has-a sense
- Individual base class members can be granted public access
with an explicit
usingaccess declaration
- Nonstatic members of
Using protected access specifier on fields and methods
- Means "only my subclasses can see it"
- Appears just like
privateif you are not a subclass (i.e. you can't see it)
Access mode member/class combinations
- Its a bit confusing with access specifiers on both classes themselves and members.
- Here is a table of what the different access specifier combinations do, taken from Wikibooks.
| private | protected | public | |
| private inheritance | The member is inaccessible. | The member is private. | The member is private. |
| protected inheritance | The member is inaccessible. | The member is protected. | The member is protected. |
| public inheritance | The member is inaccessible. | The member is protected. | The member is public. |
- The column headers are the case of using
public/private/protectedon member functions/fields - The rows are for access specifiers on the class inheritance specification.
- Note that
public/protectedmembers are still visible internally to a subclass in the case ofprivateinheritance (the yellow boxes).
Constructors destructors and inheritance
- Constructors are not inherited
- You need to invoke superclass constructor in subclass initializer list
- Will invoke default superclass constructor implicitly if no explicit constructor call
class A {
protected:
double d;
public:
A(double num = 0): d(num) { };
};
class B : public A {
private:
int b;
public:
// implicitly invokes default A():
B(int val = 0): b(val) { };
// Put A(dval) in initializer list to call non-default constructor of A:
B(int bval, double dval): A(dval), b(bval) { };
};
Constructor and destructor execution order with inheritance
- Constructors for
BaseClassare executed before constructors forDerivedClassmembers - Destructor execution order is opposite of constructors
- Constructors, destructors and assignment are not inherited! (remember: defaults are created unless you specify them)
Virtual member functions
Virtual functions
- Virtual functions are dynamically dispatched - method invoked based on actual runtime object, not declared type
- In all object languages besides C++,
virtualis the default dispatch - Decision which member function version to use may be made at run-time
virtualprefix declares a member function virtualvirtualonly matters for inherited methods that are overridden
- Any non-static member function except constructor can be virtual
- Do not need prefix
virtualin derived class declarations (its optional) - Should have
virtualdestructor (even if empty) in base class with virtual functions - otherwise derived class destructors may not be called even if they exist
Overriding vs overloading vs hiding
- Overriding is when derived and base class functions have the
same function name, param types, return types, const-ness and the base function is
virtual - Overloading is when a function has same name but different argument types as another function
- Hiding is when derived function has same name (regardless of arguments) as non-virtual base function - base function is hidden
- It is illegal for function in derived class to have same signature as virtual function in base with different return type
- If functions in base and derived classes have the same signature, can use the scope resolution op (::) to get to function in the base class (the derived class is default)
Implicit conversions between base and derived classes
Derived class objects can be passed as base class objects - will implicitly slice off any derived class fields:
void poly(Point2D p)
{ // .. p will just be a true 2D point
}
int main()
{
Point3D p3(30, 40, 50);
poly(p3); // 3D point will be copy converted to 2D point - z gone
}
Also derived class references can be passed as base class references - derived class fields implicitly there for virtual methods to "see"
void poly(Point2D &p)
{ // .. p will still have the z field
}
int main()
{
Point3D p3(30, 40, 50);
poly(p3); // reference to 3D point passed
}
Similarly to references, base class pointer types can also point to derived class objects:
void poly(Point2D *p)
{ // .. *p will still have the z field
}
int main()
{
Point3D p3(30, 40, 50);
poly(&p3); // address of 3D point passed
}
- Base from derived type conversions can be implicit (smaller from larger class)
- Assignment
Point2D p2 = p3also implicitly converts in a similar way - sliced off if not a reference or pointer, otherwise not sliced off
Explicit type conversions
- Base to derived type conversions must be explicit - base may in fact be missing derived fields at run-time
-
dynamic_castconverts type (but not data) and checks validity at run-time - Returns
NULLif it cannot convert
Base * base; dynamic_cast<Derived*>(base);
dynamic_castdoesn't work on reference variables -- they can't beNULL-
static_castconverts data at run-time - calls conversion constructor; errors on nonsense conversions
-
reinterpret_castwill body-slam one type into another, without discretion - it re-interprets the raw memory as the new type and may lead to seg faults -
const_castwill add or remove constness
Abstract base classes
- Abstract classes are designed so you should not ever make instances of the class
- Very useful for enforcing common interface among related (derived) classes
- Abstract base classes serve some of the purpose of Java's interfaces (C++ has no interfaces)
- A pure virtual function in a header should not be implemented:
virtual void myfun() = 0;- "= 0" means myfun is pure virtual - Trying to create object of abstract base class will give compile error
- Each non-abstract derived class must have its own definition of every pure virtual function in the base class
- abstract base class needs at least one pure virtual function; can have data members
- If there is a pure virtual function without a definition in a derived class, that derived class is also an abstract class
- Can mix non-virtual, virtual and pure virtual functions in one class
- Base class data members likely need to be
protectedfor the pure virtual functions in the derived classes to access them
- Abstract class object instances cannot be created by any means
- But, variables can be declared as references or pointers to abstract class type
- At run-time, such pointers/references must point to derived object instances
- references to abstract types may be function arguments
- usefulness: array of pointers to base objects to form collection of related but different class objects
Multiple Inheritance
Multiple inheritance means subclass class can be derived from multiple base classes.- Java, Ruby, C#, etc have only single inheritance; C++, Python, etc have multiple inheritance
- Multiple inheritance subsumes Java interfaces in power
-- to implement multiple interfaces a la Java, inherit from multiple abstract classes in C++
class Widget
{
public:
virtual void click();
virtual void update();
};
class Drawable
{
public:
virtual void draw() = 0; // Drawable is an abstract class with no code
};
class Snowman : public Widget, public Drawable
{
void draw(); // override pure virtual draw to draw Snowman
};
class Bunny : public Widget, public Drawable
{
void draw(); // override pure virtual draw to draw a Bunny
};
void fun(Drawable &d) // d can be either a Snowman or a Bunny - both "have the Drawable interface"
{
d.draw();
}
The Diamond Problem
A thorny issue for any language supporting multiple inheritance is the following scenario:
A
/ \
B C
\ /
D
- You can have two versions of the same methods if there is a "diamond" in the inheritance graph - D will get two copies of any method in A: one from B and one from C. Which copy to use?
- ambiguity can be resolved by declaring
virtualinheritance in B and C definitions.
class Person {
public:
long getSSN() { return SSN; };
private:
long SSN;
};
class Employee : public virtual Person { };
class Student : public virtual Person { };
class GradAsst: public Employee, public Student { };
int main()
{
GradAsst ga;
ga.getSSN(); // not ambiguous with virtual above; remove virtual and get error
}
- With
virtualdeclared, it means C++ will make only one copy of super-superclassPersonfor theGradAsst- only oneSSNand only onegetSSN() - Sometimes "only one copy" is not the right meaning, be careful
- Fortunately you don't run into the diamond problem very often