460 likes | 564 Views
Object Oriented Programming. Egar 2008 Recitation 9. Private / Protected Inheritance. Why?. Unlike public inheritance (that means “is-a”), private and protected inheritance mean “has-a” or “is-implemented-in-terms-of”.
E N D
Object Oriented Programming Egar 2008 Recitation 9 OOP Etgar 2008 – Recitation 9
Private/Protected Inheritance OOP Etgar 2008 – Recitation 9
Why? • Unlike public inheritance (that means “is-a”), private and protected inheritance mean “has-a” or “is-implemented-in-terms-of”. • They are quite similar to composition (when an inner object is contained as data member in the outer object), but there are subtle differences. OOP Etgar 2008 – Recitation 9
Composition • Inner object is called data member. • Data member can be accessed from outside only if declared public. • Outer object can’t override data member’s methods or access protected members. • Outer object can contain several data members of one type. OOP Etgar 2008 – Recitation 9
Private/Protected Inheritance • Inner object is called base class subobject. • Subobject cannot be accessed from outside (and derived pointer cannot be converted to private/protected base pointer, unless by methods or friends of the derived). • Derived can access protected members of the subobject. • Derived can override subobject’s methods. • Outer object contains only one base class subobject. OOP Etgar 2008 – Recitation 9
Code Example • class B { • public: void pub(); • protected: void prot(); • private: void priv(); • }; • class pubD : public B {…}; • class protD : protected B {…}; • class privD : private B {…}; OOP Etgar 2008 – Recitation 9
Access Levels • public inheritance: • public parts of B remain public in pubD and protected remain protected in pubD. • protected inheritance: • public and protected parts of B become protected in protD. • private inheritance: • public and protected parts of B become private in privD. • B’s private remains inaccessible to derived. OOP Etgar 2008 – Recitation 9
When to Use? • Use composition whenever you can. • Use private/protected inheritance when you have to: • If you need to override inherited virtual method. • If you need access to protected members. OOP Etgar 2008 – Recitation 9
Multiple Inheritance OOP Etgar 2008 – Recitation 9
Why? • Recall the interface Shape from previous lecture and imagine we have class MyCircle that implements circles (but not with Shape’s interface). • We would like to implement a Circle with Shape’s interface while using existing code from MyCircle. • Usually this is done with composition, but what if private inheritance is required? OOP Etgar 2008 – Recitation 9
Multiple Inheritance • In C++ we can use multiple inheritance – make a class inherit from more than one base. • class Circle: public Shape, private MyCircle {…}; • Circle now has Shape’s interface and all features of private inheritance from MyCircle. • Class can inherit from any number of bases and in any combination of public/protected/private inheritance. OOP Etgar 2008 – Recitation 9
Points • Pointer or reference to derived can be implicitly converted to any of the bases. • Conversion to each base is considered equally good. • Subclass constructor initializes base classes in the order of their appearance from left to right (and destructor destroys in the reverse order). OOP Etgar 2008 – Recitation 9
Ambiguities • What if two bases define functions with the same name? • class Shape { public: virtual void draw(); }; • class MyCircle { public: void draw(); }; • class Circle: public Shape, private MyCircle {…}; • // Circle doesn't redefine draw() • Circle c; • c.draw(); // Ambiguous – Shape::draw() or • MyCircle::draw()? OOP Etgar 2008 – Recitation 9
Overload Resolution? • Even if draw()’s parameter list were different in bases, the call still would have been ambiguous. • This is because overload resolution is not applied across different class scopes. OOP Etgar 2008 – Recitation 9
Ambiguity Resolution • To resolve this ambiguity we can: • Use explicit qualification • c.Shape::draw(); • Use the using-declaration • class Shape…{ • using Shape::draw; • }; • c.draw(); // Calls c.Shape::draw() OOP Etgar 2008 – Recitation 9
Virtual Bases OOP Etgar 2008 – Recitation 9
The Dreaded Diamond • Continuing the Shape hierarchy: • class OnScreenObj {protected : Screen _scr…}; • class Shape : public OnScreenObj {…}; • class Text : public OnScreenObj {…}; • class TextArt : public Shape, public Text {…}; • This poses a problem – in multiple inheritance each base is represented by its own subobject. • But we don’t want the Shape part of TextArt to have different _scr than the Text part! OOP Etgar 2008 – Recitation 9
Another Ambiguity • Moreover, since TextArt has two OnScreenObj parts, we must specify which one we mean: • TextArt ta; • ta._scr; // Ambiguous – which _scr? • ta.Shape::_scr; // Ok • ta.Text::_scr; // Ok • ta.Text::OnScreenObj::_scr; // Ok OOP Etgar 2008 – Recitation 9
Virtual Inheritance • To specify that derived classes of Shape or Text can share one copy of the base OnScreenObj, we can define it as virtual base: • class Shape : public virtual OnScreenObj {…}; • class Text : virtual public OnScreenObj {…}; • class TextArt : public Shape, public Text {…}; • Now TextArt has only one OnScreenObj subobject. OOP Etgar 2008 – Recitation 9
Initialization • In regular inheritance, each class initializes its immediate base classes. • With virtual inheritance, the most derived class must initialize it. OOP Etgar 2008 – Recitation 9
Initialization Example • For example, A is a virtual base of B, D (regularly) derives from B, E from D and F from E.class B : virtual public Aclass D : public Bclass E : public Dclass F : public E • When object of type B is created, A is initialized from B’s constructor, when object of type D is created – from D’s, and so on. • Thus B, D, E, F‘s constructors must initialize A.A::A(int a=1):_a(a){}B::B(int a=1,b=2):A(a),_b(b){} D::D(int a=1,b=2,d=3):A(a),B(a,b){}E::E(int a=1,b=2,d=3,e=4):A(a),b(a,b),d(a,b,d){}… • Note that all virtual base classes are created before all other base classes. OOP Etgar 2008 – Recitation 9
Multiple Inheritance Alternatives • Consider the following situation:Suppose you have land vehicles, water vehicles, air vehicles, and space vehicles . • Suppose we also have different power sources: gas powered, wind powered, nuclear powered, pedal powered, etc. • We could use multiple inheritance to tie everything together, but before we do, we should ask a few tough questions: • Will the users of LandVehicle need to have a Vehicle& that refers to a LandVehicle object? In particular, will the users call methods on a Vehicle-reference and expect the actual implementation of those methods to be specific to LandVehicles? • The same question for for GasPoweredVehicles. OOP Etgar 2008 – Recitation 9
Multiple Inheritance Alternatives • if both answers are "yes," multiple inheritance is probably the best way to go. • If none or one of the answers are “no” consider the following alternatives. • Also there are some considerations which will make you choose an alternative even if both answers are “yes”. • The alternatives are the bridge pattern and nested generalization. OOP Etgar 2008 – Recitation 9
The Bridge Pattern • We create two distinct hierarchies: • ABCVehicle has derived classes LandVehicle, WaterVehicle, etc. • ABCEngine has derived classes GasPowered, NuclearPowered, etc. • Then the Vehicle has an Engine* (that is, an Engine-pointer), and users mix and match vehicles and engines at run-time. OOP Etgar 2008 – Recitation 9
Nested Generalization • We pick one of the hierarchies as primary and the other as secondary, and you have a nested hierarchy. • For example, if you choose geography as primary, Vehicle would have derived classes LandVehicle, WaterVehicle, etc. • Those would each have further derived classes, one per power source type. E.g., LandVehicle would have derived classes GasPoweredLandVehicle, PedalPoweredLandVehicle, NuclearPoweredLandVehicle, etc. OOP Etgar 2008 – Recitation 9
Exceptions Error-handling mechanism OOP Etgar 2008 – Recitation 9
Why? • Consider what happens when an error occurs in a program that consists of separate modules. • Often the action that needs to be taken depends on the calling module rather than on the module that found the error. • A string doesn’t know what to do if it cannot allocate memory. The user of the string does. • And what if the caller of the caller knows how to fix the error? OOP Etgar 2008 – Recitation 9
Exceptions • Exceptions are program anomalies that occur during runtime (out-of-memory, pop-on-empty-stack, division-by-zero, etc.). • Exception handling is a mechanism that allows two separately developed modules to communicate when exception occurs. OOP Etgar 2008 – Recitation 9
Throwing and Catching • A module that detects an error has occurred signals it by throwing an exception of appropriate type. • The module in the calling chain that knows how to handle the exception declares that by catching an exception of that type. • A pop() in Stack can throw popOnEmpty, and Stack’s user can catch it and know that pop() didn’t succeed because it’s empty. OOP Etgar 2008 – Recitation 9
Why Exceptions? • Exceptions: • Separate exceptional logic from normal logic. • Supported by the compiler. • Can carry any amount of information from thrower to the catcher. • Can be used in constructors. • But impose runtime overhead. • Returning error codes is still used. OOP Etgar 2008 – Recitation 9
throw • An exception is an object of some type. • The program part that has detected an error that it cannot handle, throws an exception (which is an object) of some type. • This is done using the throw expression. OOP Etgar 2008 – Recitation 9
Example • class popOnEmpty {}; // Exceptions are objects, • class pushOnFull {}; // need to define the classes • int Stack::pop() { // Stack from recitation 1 • if (_top_index == 0) • throw popOnEmpty(); // Used to be "return 0" • return _contents[--_top_index]; • } • void Stack::push(int el) { • if (_top_index == _size) • throw pushOnFull(); // Used to be "return false" • _contents[_top_index++] = el; • return true; • } OOP Etgar 2008 – Recitation 9
Example Notes • Notice that now push() doesn’t return a value, and pop() returns only elements of the stack. • Since throw throws an object, the corresponding class needs to be defined beforehand. • It’s best to use separate classes for exceptions. OOP Etgar 2008 – Recitation 9
throw Creates an Object • throw creates a temporary object of the given type and “passes it on”. • Note the () in • throw pushOnFull(); • This creates a pushOnFull object using the default constructor. • Had class pushOnFull had a constructor with int parameter (for example), we could write: • throw pushOnFull(el); • The handler would then know what element wasn’t pushed. OOP Etgar 2008 – Recitation 9
Catching • To be handled, an exception needs to be caught. • This is done using try-catch construct. • Operations that can throw exception are enclosed in a try block, and following that block a series of catch clauses, each defining how to handle specific type of exception. OOP Etgar 2008 – Recitation 9
Example • int main() { • Stack s(10); • try { • s.push(1); • // More pops, pushes, printouts, etc. • } catch (popOnEmpty) { • cout << "Caught popOnEmpty\n"; • } catch (pushOnFull) { • cout << "Caught pushOnFull\n"; • } • cout << "Done\n"; • return 0; • } OOP Etgar 2008 – Recitation 9
catch Clauses • Each catch clause defines how to handle a specific type of exception that might have occurred in the preceding try block. • The clauses are examined top down until a matching catch is found. • If none is found, the exception propagates to the caller. • If a matching catch clause is found, its body is executed and execution resumes after the last catch (for that try block). OOP Etgar 2008 – Recitation 9
Exception Propagation • Notice that a try-catch block only needs to handle exceptions it knows how to “fix”. • Anything is doesn’t handle propagates to the caller of that function. • When exception tries to propagate out of main(), the STL terminate() function is called, which by default calls abort(). OOP Etgar 2008 – Recitation 9
Exception Declaration • The () part in catch is called exception declaration. • It can be either type declaration or an object declaration. • …catch (popOnEmpty) { // Type declaration • cout << "Caught popOnEmpty\n"; • } catch (pushOnFull e) { // Object declaration • cout << e.val() << " cannot be pushed\n"; • } • In object declaration the exception object that was thrown is given a name and can be accessed. • This allows the exception object to carry additional information about the error. OOP Etgar 2008 – Recitation 9
Catching Everything • If someone (main(), for example) wants to handle any exception that can be thrown, it can use catch(...): • try { • //… • } catch (...) { • cout << "Unknown exception occurred\n"; • } • Obviously, it should come last in the list of catches. OOP Etgar 2008 – Recitation 9
Catching by Reference • Exception declaration is much like function parameter list. • In particular, we can catch exception objects by reference: • …catch (pushOnFull& e) {…} • Otherwise the exception object is passed by-value into the catch clause. • Using “catch-by-reference”: • Prevents unnecessary copying; • Allows changing the exception object; • Permits polymorphism. OOP Etgar 2008 – Recitation 9
Exception Specification • How do we know which function throws exceptions and what types? • Function exception specification is a comma-separated list of exceptions the function might throw. • int Stack::pop() throw(popOnEmpty); • void Stack::push(int) throw(pushOnFull); • This list is checked at runtime – if the function throws unspecified exception, the STL unexpected() function is called (which by default calls terminate()). • Exception specification is part of functions interface: • int Stack::pop() { // Err, must match declaration OOP Etgar 2008 – Recitation 9
Stack Unwinding • When an exception is thrown, the calling functions exit one after another until a handler is found. • During this process all the objects that were defined on stack (local – also called automatic – variables in these functions) are properly destroyed. • This is called stack unwinding. OOP Etgar 2008 – Recitation 9
Rethrow • If a handler doesn’t know how to fully recover from an exception, it can rethrow it (after doing what it wanted). • This is done by throw; • …catch (popOnEmpty e) { • cout << "Can't pop"; • throw; • } • The object rethrown is the original exception object (not the copy in catch). OOP Etgar 2008 – Recitation 9
Exception Hierarchies • Since exceptions are objects, we can build exceptions hierarchies. • For example, a base class Exception, subclasses StackException and MathException, and two subclasses for StackExceptions – popOnEmpty and pushOnFull. • All rules applying to base and derived apply here (catching derived instead of base, virtual functions with “catch-by-reference”, etc.) OOP Etgar 2008 – Recitation 9
Constructors and Destructors • Constructor can (and should) signal an error by throwing an exception. • In this case all data members and base class subobjects constructed so far are properly destroyed. • Destructor should never throw (or let out) an exception. • Destructors are called during stack unwinding, and if it throws another exception (thus two exceptions will be active simultaneously), terminate() is called. OOP Etgar 2008 – Recitation 9