CIS-62 Home http://www.c-jump.com/CIS62/CIS62syllabus.htm

Class Inheritance and Object-Oriented Design


  1. C++ and OO design
  2. Problems to solve
  3. Structured programming
  4. Encapsulation
  5. Inheritance
  6. Encapsulation + inheritance + polymorphism
  7. Run-time polymorphism vs. parametric polymorphism
  8. Inheritance Goals
  9. What is inheritance?
  10. Inheritance example
  11. Inheritance interactions
  12. protected class members
  13. Constructing a derived class
  14. Constructing a derived class example
  15. Access levels for inheritance
  16. Inheritance and friendship
  17. Inheritance vs. encapsulation
  18. Inheritance vs. templates
  19. Things to watch out for
  20. Implicit conversion, upcast
  21. More things to watch out for
  22. Ways to use inheritance
  23. Inheritance and Polymorphism
  24. The basic idea: redirecting calls
  25. Virtual functions
  26. Possible alternative: Faking it
  27. Using virtual functions
  28. Pure virtual functions
  29. Under the covers
  30. Virtual destructors
  31. Two forms of inheritance
  32. Pure interfaces
  33. C++ way of conversions between types
  34. Safer casting with static_cast
  35. Safer casting with dynamic_cast
  36. Unrelated types conversion: reinterpret_cast
  37. const cast away: const_cast
  38. Mistakes and pain caused by careless overriding of virtual and non-virtual functions
  39. Inheritance advice

1. C++ and OO design



2. Problems to solve

  • 
    void print( Printer* pr )
    {
        if ( pr->type == 'L' ) {
            // ...
        } else if ( pr->type == 'I' ) {
            // ...
        } else if ( pr->type == 'F' ) {
            // ...
        }
    }
    
    
  1. Print function becomes very complex over time.

  2. Multiple flag variables introduced to control the if-else logic.

  3. Only experienced veteran programmers understand how it works.

  4. New programmers become frustrated when assigned to make changes in print( )

  5. . . .

  • Solutions?

    • C++ OO design.

3. Structured programming

  • Create functions for each individual case:

    
    void print_Lazer( Printer* pr /*etc.*/ );
    
    void print_InkjetPrinter( Printer* pr /*etc.*/ );
    
    void print_FaxMachine( Printer* pr /*etc.*/ );
    
    void print( Printer* pr )
    {
        if ( pr->type == 'L' ) {
            print_Lazer( Printer* pr /*etc.*/ );
    
        } else if ( pr->type == 'I' ) {
            print_InkjetPrinter( Printer* pr /*etc.*/ );
    
        } else if ( pr->type == 'F' ) {
            print_FaxMachine( Printer* pr /*etc.*/ );
        }
    }
    
    
  • Problems yet to be solved:

    • if-else still remains a complexity!

    • boolean flag variables still haunt programmers!

    • functions still have too many input parameters!

4. Encapsulation

  • Create classes for each printer type:

    
    class LazerPrinter { /*etc.*/ };
    
    class InkjetPrinter { /*etc.*/ };
    
    class FaxMachine { /*etc.*/ };
    
    void print( Printer* pr )
    {
        if ( pr->type == 'L' ) {
            LazerPrinter.print( /*params*/ );
    
        } else if ( pr->type == 'I' ) {
            InkjetPrinter.print( /*params*/ );
    
        } else if ( pr->type == 'F' ) {
            FaxMachine.print( /*params*/ );
        }
    }
    
    
  • Better!

    However...

    • if-else continues to control the logic in the original print( ) function and that adds to the complexity of the implementation.

5. Inheritance


  • Consider class hierarchy:

      class hierarchy

  • Finally:

    
    class Printer;
    
    class LazerPrinter;
    
    class InkjetPrinter;
    
    class FaxMachine;
    
    
    void print( Printer* pr )
    {
        pr->print( /*params*/ );
    }
    
    

6. Encapsulation + inheritance + polymorphism



7. Run-time polymorphism vs. parametric polymorphism

  • C++ also supports parametric polymorphism, also known as compile-time polymorphism:

    • Unlimited number of polymorphic behaviors

    • Programmer creates a template

    • Compiler determines concrete implementation at compile-time.

  • For example,

    
    template< typename ValueT >
    void swap( ValueT& one, ValueT& another )
    {
        ValueT temp = one;
        one = another;
        another = temp;
    }
    
    #include <cassert>
    
    int main()
    {
        double dbl1 = 0.1;
        double dbl2 = 0.2;
        swap( dbl1, dbl2 ); // swap two doubles
        assert( dbl1 == 0.2 && dbl2 == 0.1 );
    
        int int1 = 1;
        int int2 = 2;
        swap( int1, int2 ); // swap two integers
        assert( int1 == 2 && int2 == 1 );
    
        return 0;
    }
    
    

8. Inheritance Goals



9. What is inheritance?



10. Inheritance example

  • Base class:

    
    class Account {
    public:
        Account();
        bool sell( string symbol, int shares );
        bool buy( string symbol, int shares );
        bool deposit( double amount );
        bool withdraw( double amount );
    
    private:
        double m_cash_balance;
        static const int SIZE = 100;
        string m_holding_symbols[ SIZE ];
        int m_holding_shares[ SIZE ];
    
    };//class Account
    
    
  • Derived class:

    
    class GoldAccount : public Account {
    public:
        GoldAccount();
        virtual ~GoldAccount();
        bool credit_interest();
    
    private:
        int m_margin_balance;
        std::string m_person_name;
    
    };// class GoldAccount
    
    

11. Inheritance interactions



12. protected class members



13. Constructing a derived class



14. Constructing a derived class example



15. Access levels for inheritance


  • 
    class Derived : public Base
    
        // same as
        struct Derived : Base
    
    
  • Most common: all public members of Base are also public in Derived, so they can be used by everyone.

  • 
    class Derived : private Base
    
        // same as
        class Derived : Base
    
    
  • All public and protected members of Base become private within Derived, so they can be used only by members of Derived.

  • 
    class Derived : protected Base
    
    
  • All public and protected members of Base become protected within Derived, so they can only be used by members of Derived and members of classes derived from Derived.

  • All non-public inheritance is a suspect - have a good reason before doing it.


16. Inheritance and friendship



17. Inheritance vs. encapsulation

  • There are two ways to be a client of an existing class A:

    • Inherit from type A

    • Have a member of type A

  • Inheritance automatically provides interface of Base class.

  • Encapsulation allows restricting the interface, but requires republication of needed parts.

  • Usually, it's pretty obvious which is correct.

  • Class libraries often rely on inheritance (recall Printer example.)

  • This is known as Is-a vs. Has-a:

      class Employee (Base)
               | |
               | +--has-a--> int Salary;
               | `--has-a--> string Name;
               |
               |
               |
      class Manager (Derived) : (is-a) public Employee
                 |
                 `--has-a--> Employee subordinates[ 10 ];
    

18. Inheritance vs. templates



19. Things to watch out for



20. Implicit conversion, upcast



21. More things to watch out for



22. Ways to use inheritance


23. Inheritance and Polymorphism



24. The basic idea: redirecting calls



25. Virtual functions

  • 
    // Shapes.h
    class Shape {
    public:
        Shape();
        virtual void draw();
    };
    
    
    class Circle : public Shape {
    public:
        Circle( int radius );
        virtual void draw();
    };
    
    
    class Square : public Shape {
    public:
        Square( int side );
        virtual void draw();
    };
    
    
  • 
    //main.cpp
    #include "Shapes.h"
    
    void push_shape( Shape* ps, int& cnt, Shape* shapes[] );
    
    int main()
    {
        // Array of pointers to shapes:
        Shape* shapes[ 100 ] = { 0 };
    
        int shape_cnt = 0;
        push_shape( new Circle(10), shape_cnt, shapes );
        push_shape( new Square(5), shape_cnt, shapes );
    
        for ( int idx = 0; idx < shape_cnt; ++idx ) {
            // Call virtual function:
            shapes[ idx ]->draw();
        }
    }
    
    void push_shape( Shape* ps, int& cnt, Shape* shapes[] )
    {
        shapes[ cnt++ ] = ps;
    }
    
    

26. Possible alternative: Faking it

  • 
    class Shape {
    public:
        enum EnumType {
            Circle, Square
        };
    
        Shape( EnumType type );
        void draw();
    
    private:
        EnumType m_shape_type;
    
    };//class Shape
    
    
    void Shape::draw()
    {
        switch ( m_shape_type ) {
        case Circle:
            //draw a circle...
    
        case Square:
            //draw a square...
        }
    }
    
    
  • So why not fake it?

    1. Can't extend hierarchy without disturbing the base class.

      • This makes maintenance difficult.

    2. All functionality is in one type, so specific functionality has no home:

      • e.g. where do we put the radius( ) function?

    3. Faking is almost guaranteed to be slower!

27. Using virtual functions



28. Pure virtual functions



29. Under the covers



30. Virtual destructors



31. Two forms of inheritance



32. Pure interfaces



33. C++ way of conversions between types



34. Safer casting with static_cast



35. Safer casting with dynamic_cast



36. Unrelated types conversion: reinterpret_cast



37. const cast away: const_cast



38. Mistakes and pain caused by careless overriding of virtual and non-virtual functions

  • 
    #include <iostream>
    
    class Base {
    public:
        virtual void vf() const
        {
            std::cout << "Base::vf\n";
        }
    
        void f() const
        {
            std::cout << "Base::f\n";
        }
    };//class Base
    
    class Derived : public Base {
    public:
        // Attempt to "override" virtual:
        virtual void vf()
        {
            std::cout << "Derived::vf\n";
        }
    
        // Override non-virtual:
        void f() const
        {
            std::cout << "Derived::f\n";
        }
    };//class Derived
    
    
  • Functions in derived classes override virtual functions in base classes only if their type is the same .

  • Redefinition of non-virtual functions may also cause confusion,
    especially in a mix-in of virtual and non-virtual functions in one interface.

    
    int main()
    {
        Derived d;
        Derived* dp = &d;
    
        Base* bp = &d;
        dp->vf(); // expected Derived::vf
        dp->f();  // expected Derived::f
        bp->vf(); // expected Derived::vf (it's virtual!)
        bp->f();  // expected Derived::f (confusing for virtual)
        return 0;
    }
    /*Program output:
    Derived::vf
    Derived::f
    Base::vf
    Base::f
    */
    
    

39. Inheritance advice