Skip to Content
CSE332SObject-Oriented Programming Lab (Lecture 12)

CSE332S Object-Oriented Programming in C++ (Lecture 12)

Object-Oriented Programming (OOP) in C++

Today:

  1. Type vs. Class
  2. Subtypes and Substitution
  3. Polymorphism a. Parametric polymorphism (generic programming) b. Subtyping polymorphism (OOP)
  4. Inheritance and Polymorphism in C++ a. construction/destruction order b. Static vs. dynamic type c. Dynamic binding via virtual functions d. Declaring interfaces via pure virtual functions

Type vs. Class, substitution

Type (interface) vs. Class

Each function/operator declared by an object has a signature: name, parameter list, and return value

The set of all public signatures defined by an object makes up the interface to the object, or its type

  • An object’s type is known (what can we request of an object?)
  • Its implementation is not - different objects may implement an interface very differently
  • An object may have many types (think interfaces in Java)

An object’s class defines its implementation:

  • Specifies its state (internal data and its representation)
  • Implements the functions/operators it declares

Subtyping: Liskov Substitution Principle

An interface may contain other interfaces!

A type is a subtype if it contains the full interface of another type (its supertype) as a subset of its own interface. (subtype has more methods than supertype)

Substitutability: if S is a subtype of T, then objects of type T may be replaced with objects of type S

Substitutability leads to polymorphism: a single interface may have many different implementations

Polymorphism

Parametric (interface) polymorphism (substitution applied to generic programming)

  • Design algorithms or classes using parameterized types rather than specific concrete data types.
  • Any class that defines the full interface required of the parameterized type (is a subtype of the parameterized type) can be substituted in place of the type parameter at compile-time.
  • Allows substitution of unrelated types.

Polymorphism in OOP

Subtyping (inheritance) polymorphism: (substitution applied to OOP)

  • A derived class can inherit an interface from its parent (base) class
    • Creates a subtype/supertype relationship. (subclass/superclass)
    • All subclasses of a superclass inherit the superclass’s interface and its implementation of that interface.
    • Function overriding - subclasses may override the superclass’s implementation of an interface
      • Allows the implementation of an interface to be substituted at run-time via dynamic binding

Inheritance in C++ - syntax

Forms of Inheritance in C++

A derived class can inherit from a base class in one of 3 ways:

  • Public Inheritance (“is a”, creates a subtype)
    • Public part of base class remains public
    • Protected part of base class remains protected
  • Protected Inheritance (“contains a”, derived class is not a subtype)
    • Public part of base class becomes protected
    • Protected part of base class remains protected
  • Private Inheritance (“contains a”, derived class is not a subtype)
    • Public part of base class becomes private
    • Protected part of base class becomes private

So public inheritance is the only way to create a subtype.

class A { public: int i; protected: int j; private: int k; }; class B : public A { // ... }; class C : protected A { // ... }; class D : private A { // ... };

Class B uses public inheritance from A

  • i remains public to all users of class B
  • j remains protected. It can be used by methods in class B or its derived classes

Class C uses protected inheritance from A

  • i becomes protected in C, so the only users of class C that can access i are the methods of class C
  • j remains protected. It can be used by methods in class C or its derived classes

Class D uses private inheritance from A

  • i and j become private in D, so only methods of class D can access them.

Construction and Destruction Order of derived class objects

Class and Member Construction Order

class A { public: A(int i) : m_i(i) { cout << "A" << endl;} ~A() {cout<<"~A"<<endl;} private: int m_i; }; class B : public A { public: B(int i, int j) : A(i), m_j(j) { cout << "B" << endl;} ~B() {cout << "~B" << endl;} private: int m_j; }; int main (int, char *[]) { B b(2,3); return 0; };

In the main function, the B constructor is called on object b

  • Passes in integer values 2 and 3

B constructor calls A constructor

  • passes value 2 to A constructor via base/member initialization list

A constructor initializes m_i with the passed value 2

  • Body of A constructor runs
  • Outputs “A”

B constructor initializes m_j with passed value 3

  • Body of B constructor runs
  • outputs “B”

Class and Member Destruction Order

class A { public: A(int i) : m_i(i) { cout << "A" << endl;} ~A() {cout<<"~A"<<endl;} private: int m_i; }; class B : public A { public: B(int i, int j) : A(i), m_j(j) { cout << "B" << endl;} ~B() {cout << "~B" << endl;} private: int m_j; }; int main (int, char *[]) { B b(2,3); return 0; };

B destructor called on object b in main

  • Body of B destructor runs
  • outputs “~B”

B destructor calls “destructor” of m_j

  • int is a built-in type, so it’s a no-op

B destructor calls A destructor

  • Body of A destructor runs
  • outputs “~A”

A destructor calls “destructor” of m_i

  • again a no-op

At the level of each class, order of steps is reversed in constructor vs. destructor

  • ctor: base class, members, body
  • dtor: body, members, base class

In short, cascading order is called when constructor is called, and reverse cascading order is called when destructor is called.

Polymorphic function calls - function overriding

Static vs. Dynamic type

The type of a variable is known statically (at compile time), based on its declaration

int i; int * p; Fish f; Mammal m; Fish * fp = &f;

However, actual types of objects aliased by references & pointers to base classes vary dynamically (at run-time)

Fish f; Mammal m; Animal * ap = &f; // dynamic type is Fish ap = &m; // dynamic type is Mammal Animal & ar = get_animal(); // dynamic type is the type of the object returned by get_animal()

A base class and its derived classes form a set of types

type(*ap) \in {Animal, Fish, Mammal} typeset(*fp) \subset typeset(*ap)

Each type set is open

  • More subclasses can be added

Supporting Function Overriding in C++: Virtual Functions

Static binding: A function/operator call is bound to an implementation at compile-time

Dynamic binding: A function/operator call is bound to an implementation at run-time. When dynamic binding is used:

  1. Lookup the dynamic type of the object the function/operator is called on
  2. Bind the call to the implementation defined in that class

Function overriding requires dynamic binding!

In C++, virtual functions facilitate dynamic binding.

class A { public: A () {cout<<" A";} virtual ~A () {cout<<" ~A";} // tells compiler that this destructor might be overridden in a derived class (the destructor of the parent class is usually virtual) virtual void f(int); // tells compiler that this function might be overridden in a derived class }; class B : public A { public: B () :A() {cout<<" B";} virtual ~B() {cout<<" ~B";} virtual void f(int) override; // tells compiler that this function might be overridden in a derived class, the parent function is virtual otherwise it will be an error //C++11 }; int main (int, char *[]) { // prints "A B" A *ap = new B; // prints "~B ~A" : would only // print "~A" if non-virtual delete ap; return 0; };

Virtual functions:

  • Declared virtual in a base class

  • Can override in derived classes

  • Overriding only happens when signatures are the same

  • Otherwise it just overloads the function or operator name

When called through a pointer or reference to a base class:

  • function/operator calls are resolved dynamically

Use final (C++11) to prevent overriding of a virtual method

Use override (C++11) in derived class to ensure that the signatures match (error if not)

class A { public: void x() {cout<<"A::x";}; virtual void y() {cout<<"A::y";}; }; class B : public A { public: void x() {cout<<"B::x";}; virtual void y() {cout<<"B::y";}; }; int main () { B b; A *ap = &b; B *bp = &b; b.x (); // prints "B::x": static binding always calls the x() function of the class of the object b.y (); // prints "B::y": static binding always calls the y() function of the class of the object bp->x (); // prints "B::x": lookup the type of bp, which is B, and x() is non-virtual so it is statically bound bp->y (); // prints "B::y": lookup the dynamic type of bp, which is B (at run-time), and call the overridden y() function ap->x (); // prints "A::x": lookup the type of ap, which is A, and x() is non-virtual so it is statically bound ap->y (); // prints "B::y": lookup the dynamic type of ap, which is B (at run-time), and call the overridden y() function of class B return 0; };

Only matter with pointer or reference

  • Calls on object itself resolved statically
  • E.g., b.y();

Look first at pointer/reference type

  • If non-virtual there, resolve statically
  • E.g., ap->x();
  • If virtual there, resolve dynamically
  • E.g., ap->y();

Note that virtual keyword need not be repeated in derived classes

  • But it’s good style to do so

Caller can force static resolution of a virtual function via scope operator

  • E.g., ap->A::y(); prints “A::y”

Potential Problem: Class Slicing

When a derived type may be caught by a catch block, passed into a function, or returned out of a function that expects a base type:

  • Be sure to catch by reference
  • Pass by reference
  • Return by reference

Otherwise, a copy is made:

  • Loses original object’s “dynamic type”
  • Only the base parts of the object are copied, resulting in the class slicing problem

Class (implementation) Inheritance VS. Interface

Inheritance Class is the implementation of a type.

  • Class inheritance involves inheriting interface and implementation
    • Internal state and representation of an object

Interface is the set of operations that can be called on an object.

  • Interface inheritance involves inheriting only a common interface
    • What operations can be called on an object of the type?
    • Subclasses are related by a common interface
    • But may have very different implementations

In C++, pure virtual functions make interface inheritance possible.

class A { // the abstract base class public: virtual void x() = 0; // pure virtual function, no default implementation virtual void y() = 0; // pure virtual function, no default implementation }; class B : public A { // B is still an abstract class because it still has a pure virtual function y() that is not defined public: virtual void x(); }; class C : public B { // C is a concrete derived class because it has all the pure virtual functions defined public: virtual void y(); }; int main () { A * ap = new C; // ap is a pointer to an abstract class type, but it can point to a concrete derived class object, cannot create an object of an abstract class, for example, new A() will be an error. ap->x (); ap->y (); delete ap; return 0; };

Pure Virtual Functions and Abstract Base Classes:

A is an abstract (base) class

  • Similar to an interface in Java
  • Declares pure virtual functions (=0)
  • May also have non-virtual methods, as well as virtual methods that are not pure virtual

Derived classes override pure virtual methods

  • B overrides x(), C overrides y()

Can’t instantiate an abstract class

  • class that declares pure virtual functions
  • or inherits ones that are not overridden

A and B are abstract, can create a C

Can still have a pointer or reference to an abstract class type

  • Useful for polymorphism

Review of Inheritance and Subtyping Polymorphism in C++

Create related subclasses via public inheritance from a common superclass

  • All subclasses inherit the interface and its implementation from the superclass

Override superclass implementation via function overriding

  • Relies on virtual functions to support dynamic binding of function/operator calls

Use pure virtual functions to declare a common interface that related subclasses can implement

  • Client code uses the common interface, does not care how the interface is defined. Reduces complexity and dependencies between objects in a system.
Last updated on