Conformance Conformance and Class Invariants Same or Better Principle Access Conformance Contract Conformance Signature Conformance Co-, Contra- and No-Variance Overloading and Overriding Inheritance as an Operator What s Conformance? Overriding: replace method body in sub-class. Polymorphism: subclass is usable wherever superclass is usable. Dynamic Biding: consequence of overriding + polymorphism. Select right method body Conformance: overriding and overridden should be equivalent Superclass f(arg1, arg2) xxx Subclass f(arg1, arg2) yyy p->f(...) p->f(...) p yyy should "conform" to xxx Conformance and Class Invariants Question: Let v be the invariant of a class C. Let v be the invariant of a class C derived from C. Then, what should be the relationship between v and v? Answer: v v Rationale: a non-overridden method of C assumes v. This assumption must hold even if the method is applied to an object of class C. More generally: we would want to require also: v v but this is impossible to achieve since v refers to new attributes. Example: A Square inheriting from Rectangle. Addition to class invariant: sides are equal. Conformance and Overriding Thanks to the dynamic binding and polymorphism combination, a client cannot tell which version of a method will be invoked. Ideally, an overriding method should be semantically compatible with the overridden one. All versions of draw should draw the object. Not well-defined. Impossible to guarantee! We must content ourselves with conformance in the following aspects: Access. Contract: pre- and post-conditions, thrown exceptions. Signature: input arguments, output arguments, input-output arguments, function result.
Same or better Principle Same or better: An overriding method should have at least the same functionality as the overridden method. Overriding should not pose surprises to client. More realistically: All that can be checked is static typing. The type of the overriding method should be a super-type of the type of the overridden one. The overriding method should have the same or more calling patterns as the overridden one. Access Conformance All versions of a method should have the same visibility. Smalltalk: All methods are public. Eiffel: Inheritance and export are orthogonal. Sub-class is not strictly a sub-type. C++: Not enforced, may lead to breaking of encapsulation. class Base virtual void f(void); class Derived: public Base private: void f(void); y, *py = &y; Base *px = py; py->f(); // Error! Derived::f is private. px->f(); // OK! (but breaks encapsulation.) Access Control Revisited Friendship and Overriding The overriding method should be visible to all components to which the overridden one is visible. A friend of base class is not necessarily a friend of the derived class. Example: Overriding enhancing visibility in C++. class Base protected: virtual void f(void); class Derived: public Base void f(void); // Visibility is increased. y, *py = &y; class Derived; class Base friend void amigo(derived *); protected: virtual void f(void); class Derived: public Base void f(void); // Visibility is the same, or is it? y, *py = &y; Base *px = py; px->f(); // Error! Base::f is protected. py->f(); // OK! Derived::f is public. amigo(derived *p) p->f(); // Error! Derived::f is private. Base *px = p; // Simple up casting. px->f(); // OK! now the same Derived::f is accessible.
Contract Conformance Rules of contract conformance. Pre-condition: Overriding method must demand the same or less from its client. Post-condition: Overriding method must promise the same or more to its client. Exceptions: Overriding method must not throw any exceptions that the overridden doesn t. Rationale: Same or better principle. Contracts and Inheritance in Eiffel: Pre and post conditions are inherited. They can be refined, but never replaced. Pre-condition refinement: old_pre_condition or new_pre_condition Post-condition refinement: old_post_condition and new_post_condition Contracts and Inheritance in C++: The exception-specification (throw list) of an overriding function must be at least as restrictive as that of the overridden function. Signature Conformance Elements of signature Input arguments Output arguments Input-Output arguments Result No-variance: The type in signature cannot be changed. Co-variance: Change of type in the signature is in the same direction as that of the inheritance. Contra-variance: Change of type in the signature is in the opposite direction as that of the inheritance. Conformance: Contra-variant input arguments. Co-variant output arguments. No-variant input-output arguments. Co-variant return value. Type of Arguments For simplicity, we consider only input arguments. Suppose that: m is a method of a base class m takes an argument of class C m is overridden by m in a derived class What is the type of the corresponding argument of m in the derived class? Variance Type No-variance Must be C Argument Type Programming Language Example C++ Covariance is Natural & Essential Contra-variance Co-variance C or base class thereof C or derived class thereof Sather Eiffel More examples: Base: Graph and Node. Derived: MyGraph and MyNode Base: Animal and Food. Derived: Cat and Bones Base: Number and Number. Derived: Integer and Integer
Internal Co-variance class Employee //.. virtual bool operator ==(const Employee &e); //... class Manager: public Employee //.. virtual bool operator ==(const Manager &m); Co-variance is Unsafe f() Employee *e1 = new Employee; Employee *e2 = new Manager; *e2 == *e1 ; // Run time error! The error occurs since Manager::operator ==() is called Formal argument is of type Manager & Actual argument is of type Employee & Hence, run time type error! Conversely, it can be easily seen that contravariance is safe, but usually unnatural! Pointer to Singly Linked List Co-variance is Unsafe Co-variance and Strong Typing We have seen that co-variance is type unsafe. It may generate run-time type errors. How can co-variance exist in Eiffel, which is supposed to be a strongly-typed language? The simple answer: Break the tie between inheritance and subtyping. If an heir overrides co-variantly, then it is no longer a sub-type of the parent. Inclusion polymorphism doesn t work any more for this heir. However, inclusion polymorphism is essential for using inherited methods! So, the tie cannot be broken so lightheadedly. The real answer: A global system survey: What are all the types which may ever be assigned to each variable. For each co-variant message sent to a specific variable, check that the co-variant conditions are satisfied if the variable assumes any of its essential types. Same process enables the orthogonality and export.
As a rule: No Variance! Variance in C++ Exact match in signature required between: Overriding method Overridden method Relaxation: Co-variance is allowed in return value Must have reference semantics (pointer or reference). Relatively new addition (1992) Absolutely type safe Quite useful Conformance and # Arguments Suppose that: m is a method of a base class taking n arguments m is overridden by m in a derived class. Then, how many arguments should m have? Exactly n: Most current programming languages. n or less: It does not matter which arguments are omitted as long as naming is consistent. n or more: The BETA programming language (daughter of Simula). Note that adding arguments in an overriding method violates the same or better principle! More generally: how should the type of m relate to that of m? Define virtual constructors Virtual constructors are an effective technique for implementing cut and shape palettes in a GUI draw application. Variance in C++ (cont.) Using the virtual constructors class Employee virtual Employee *clone(void) return new Employee(*this); class Manager: public Employee virtual Manager *clone(void) return new Manager(*this); f(void) Employee *e1 = new Employee; Employee *e2 = new Manager; Employee *e3; e3 = e1->clone (); // e3 points to an Employee e3 = e2->clone (); // e3 points to a Manager Overriding and Overloading The type of an overriding function must be a supertype of that of the overridden function. In C++: The types of both functions must be the same. (Up to the co-variance of reference return type) An overriding function in C++ cannot overload a definition in the base class. If types were not the same, then the function in the derived class would hide all functions of the same name in the base class. Example: class Array virtual int& operator[](int i); int operator[](int i) const;... class CheckedArray: public Array int& operator[](int i); // Hides Array::operator[](int i) const
High-Level Methods in the Array Class Straightforward implementation of the accessor: int Array::operator[](int i) const return buff[i]; Implementation of the accessor as a high-level method: int Array::operator[](int i) const return (*const_cast<array *const>this)[i]; // Invoke the virtual function member // operator[](int) Deprecated technique: Unhiding class CheckedArray: protected Array Array::operator[];... Future compilers technique: class CheckedArray: protected Array using Array::operator[];...