C++ TrainingC++ TrainingDatascopeDatascope
Lawrence D’AntonioLawrence D’AntonioLecture 8Lecture 8
An Overview of C++:An Overview of C++:What is Polymorphism? – What is Polymorphism? –
Subtype Polymorphism and InheritanceSubtype Polymorphism and Inheritance
Do not multiply objects without necessity
W. Occam
What is polymorphism?What is polymorphism?
Different types of objects respond to the Different types of objects respond to the same message and use the appropriate same message and use the appropriate method.method.
Polymorphism
Universal
Ad-hoc
Parametric
Subtype
Overloading
Coercion
Subtype polymorphism
Subtype (or inclusion) polymorphism Subtype (or inclusion) polymorphism allows objects of a given type to be allows objects of a given type to be substituted for by objects of a subtype.substituted for by objects of a subtype.
What is inheritance?What is inheritance?
One class (derived/child) relies on the definition of another class (base/parent).
Single vs. multiple inheritance A method of sharing code or sharing
interface among classes. Language may define a class tree (with
single root): Java, Smalltalk Language may define a class forest: C++
What does a child inherit?
A child class inherits all of the features of the parent except for the following:
Constructors/destructor The assignment operator Friend functions and classes
Access and Inheritance
The following table gives for each type of inheritance, for a member of given access in the parent class what access that member has in the child class.
Inheritance Type
Public Protected Private
Access Type
Public Public Protected Private
Protected Protected Protected Private
Private NA NA NA
Forms of inheritance
Single: Derived class has one parent Multiple: Derived class has more than one
parent. Interface: Defines how a class may be
implemented. Mixin: A fragment of a class that is meant
to be composed with another.
Three views of inheritance
Code sharing – one defined structure can be incorporated into another.
Logical relationship – one class has the relationship of generalization or specialization with respect to the other.
Type – a subtype can be created given the definition of a supertype.
Reasons for using inheritance
Code reuse – The child inherits features from the parent, hence the code doesn’t need to be rewritten.
Concept reuse – The child overrides features of the parent. No code is shared, but the semantics of the method is maintained.
Inheritance in different languages
C++class B: public A {};
C#class B: A {}
CLOS(defclass B (A) () )
Inheritance part 2
Javaclass B extends A {}
Object Pascaltype
B = object(A)…end;
Inheritance part 3
Pythonclass B(A):
def __init__ (self):…
Rubyclass B < A…end
Inheritance part 4
Eiffel
class B inherit
A
…
end Ada
type B is new A with …
Inheritance part 5
Objective C@interface B: A…
@end Dylandefine class <B> (<A>)…
end class <B>
Direct vs. Indirect Base
A base class is direct if it is explicitly mentioned as a base class in the declaration of the child class.
An indirect base class is a base class that is not mentioned explicitly in the child class declaration, but is available to the child class through one of its base classes.
Example
Direct base: class A is a direct base of class B
class A {};
class B: public A {};
Example
Indirect base: class A is an indirect base of class C.
class A {};
class B: public A {};
class C: public B {};
Example
In this example, class A is both a direct and indirect base of class C.
class A {};
class B: public A {};
class C: public A, public B {};
Constructors/Destructors
The child class either must directly call the parent’s constructor or else the parent must have a default constructor.
Base classes are initialized in the order of declaration.
Destructors are called in reverse order of the constructors.
Example
class A {public:
A() {}};
class B: public A {public:
B() {}};
This example is valid because class A has a default constructor. The constructor for class B will, in effect, call this constructor.
Example
class A {public:
A(int) {}};
class B: public A {public:
B() {}};
This is illegal.
Since class A has a constructor A(int), this means that A will not have a default constructor written for it.
Thus A’s constructor is not called by B’s constructor. This is then an error.
Example
class A {public:
A(int) {}};
class B: public A {public:
B(): A(5) {}};
This is legal.
B’s constructor explicitly calls A’s constructor.
Order of Initialization
Virtual base classes in the order of declaration.
Nonvirtual base classes in the order of declaration.
Class members are initialized in order of declaration.
The body of the constructor is then executed.
Order of Destruction
The destructor for a class object is called before destructors for members and bases are called.
Destructors for nonstatic members are called before destructors for base classes are called.
Destructors for nonvirtual bases are called before destructors for virtual base classes.
Example
class Employee {std::string first_name, last_name,
ssn;
public://…
};
class Manager: public Employee {std::set<Employee*> group;
public:short level;
};
Employee:
first name:
last name:
ssn:
Manager:
first name:
last name:
ssn:
group:
level:
Is-A Relationship
A Manager is a type of Employee. So any expression accepting a valid pointer or reference to a base object may accept a pointer or reference to a child object.
Example
void f(const Employee & re,
const Manager &rm)
{
std::list<Employee *> le;
le.push_front(&re);
le.push_front(&rm);
}
Explanation
Manager “is-an” Employee. Manager* can be used as an Employee*
(likewise for references). Employee* cannot be used as a Manager*
without a cast.
Is this legal?class Employee{ std::string first_name, last_name, ssn; };
class Manager: public Employee { std::set<Employee*> group;public: short level;};
void foo(Manager &m, Employee &e){ Employee *pe = &m; Manager *pm = &e;
pm->level = 2; pm = static_cast<Manager*>(pe); pm->level = 2;}
main() { Manager m; Employee e; foo(m,e);};
Yes and no.
Employee *pe = &m; //OK, Manager is Employee
Manager *pm = &e; //Not legal, Employee is not a Manager pm->level = 2;//Not legal, e doesn’t have a level
pm = static_cast<Manager*>(pe); //Works because pe points to Manager m
pm->level = 2;//Works because pm points to Manager m that has a//level.
Overriding Base class
class Employee { std::string first_name, last_name;public: void print() const;};
void Employee::print() const{ std::cout << "Employee\n";}
• Derived class
class Manager: public Employee { std::set<Employee*> group; short level;public: void print() const;}; void Manager::print() const{ Employee::print(); std::cout << "Manager\n";}
What’s the output?
Employee e; Manager m; Employee *pe1 = &e; Employee *pe2 = &m; Manager *pm = &m; pe1->print(); pe2->print(); pm->print(); pm->Employee::print();
Output
Employee //pe1->print()
Employee //pe2->print()
Employee //pm->print()
Manager //pm->print()
Employee //pm->Employee::print()
What’s the output?class A {public: A(int x) { std::cout << "A::x = " << x << '\n'; }};
class B: public A {public: B(int x): A(x-1)
{ std::cout << "B::x = " << x << '\n'; }};
class C: public A, public B {public: C(int x): A(x+1), B(x-1) { std::cout << "C::x = " << x << '\n'; }};
C c(5);
Output
A::x = 6 //C::A
A::x = 3 //C::B::A
B::x = 4 //C::B
C::x = 5 //C
Slicing
In copying from a child to a parent object, only the data of the parent subobject of the child is copied.
Slicing Exampleclass A { int x;public: A(int a): x(a) {}};
class B: public A { int y;public: B(int a, int b): A(a), y(b) {}};
main () { B b(3,4); A a = b; a = b;}
What happened?
A a = b;
//Calls A(const A&),
//only copies b.x
a = b;
//Calls A::operator=(const A&)
//only copies b.x
Class Hierarchy
class Employee {}; //Base classclass Manager: public Employee {};class Director: public Manager {};
class Temporary {}; //Base classclass Secretary: public Employee {};class TempSec: public Temporary, public Secretary {};class Consultant: public Temporary,
public Manager {};
Temporary Employee
Secretary Manager
TempSec
Consultant Director
Class Hierarchy Structure
A class hierarchy forms a DAG (directed acyclic graph).
The graph need not be a tree. For example, in the graph below there are two paths from D to A.
A
B C
D
Type fields
A simple, but usually inadequate method to create polymorphism is to use type fields.
A type field is a data type (usually an enum) that is placed in the base class for functions to use.
Type field example
struct Employee { enum E_type {E,M}; E_type type;
Employee(): type(E) {}};
struct Manager: public Employee { Manager() { type = M; }};
Example continuedvoid print_employee(const Employee *pe){ switch(pe->type) { case Employee::E: //print out Employee data break; case Employee::M: Manager *pm =
static_cast<const Manager*>(pe);
//print out Manager data break; ]}
Example continued
void print_employee_list(const list<Employee*> & emp_list)
{ for(list<Employee*>::const_iterator p =
emp_list.begin(); p != empl_list.end(); ++p) print_employee(*p);}
Virtual Functions
Virtual functions avoid the problems associated with type fields. The base class defines functions that the derive classes may redefine.
The base class doesn’t need to anticipate all the different versions of the function that may be needed.
Virtual Functions 2
The compiler guarantees the correspondence between objects and the appropriate version of the function.
Virtual functions use what is known as dynamic binding. The resolution of which version of the function is called will occur at run-time.
Virtual Functions 3
Binding is the association of an object with its member.
Dynamic binding gives a program flexibility.
Virtual Functions 4
The advantage for clients is the ability to request an operation without explicitly selecting one of its variants.
In large systems, where many variants may be possible; dynamic binding protects each component against changes in other components.
Virtual Functions 5
Polymorphism is invoked through virtual functions. In a function call the correct version of the function is selected based on the actual type of the calling object.
Virtual functions are implemented in C++ through the mechanism called the virtual function table.
Virtual Functions 6
A derived class may or may not redefine a virtual function inherited from the parent.
If a function is virtual in the parent, then it remains virtual in any class that has the parent as a direct or indirect base (namely, you can’t undo the virtual property).
Virtual Functions 7
The word virtual need only be used for the parent class function.
A virtual function must be defined for the class in which it is first declared (except for pure virtual functions).
Virtual Functions 8
Any member function may be declared virtual except for constructors. Overloaded operators may be declared virtual.
A virtual function must have the same argument types in the base and derived classes.
Furthermore, only very slight changes are allowed in the return type.
Exampleclass Employee { std::string first_name, last_name;public: Employee(const std::string &f,
const std::string &l): first_name(f),last_name(l)
{} virtual void print() const;};
void Employee::print() const{
std::cout << last_name << ", " << first_name << '\n';
}
Example Part 2class Manager: public Employee { std::set<Employee*> group; short level;public: Manager(const std::string &f, const std::string &l, short le):
Employee(f,l), level(le) {} void print() const;};
void Manager::print() const{ Employee::print(); std::cout << "\tlevel " << level << '\n';}
Example Part 3
void print_list(std::set<Employee*>&s)
{
std::for_each(s.begin(),s.end(),
std::mem_fun(&Employee::print));
}
Example Part 4main() { Employee e1("Bob", "Jones"); Employee e2("Joe", "Smith"); Manager m("Mary", "Brown", 3); std::set<Employee*> s; s.insert(&e1); s.insert(&e2); s.insert(&m); print_list(s);
return 0;}
Output
Brown, Mary
level 3
Smith, Joe
Jones, Bob
What is mem_fun()?
It takes a pointer to member function argument and returns a function object.
Basically this is a function to convert a member function into a global function.
Code for mem_fun()
template<class R, class T>
mem_fun_t<R,T> mem_fun(R (T::*pm)())
{
return mem_fun_t<R,T>(pm);
}
Code for mem_fun_t()
template<class R, class T>class mem_fun_t: public unary_function<T*,R>
{ R (T::*ptr) ();public: explicit mem_fun_t(R (T::*pm) ()):
ptr(pm) {} R operator()(T *p) const
{ return (p->*ptr)(); }};
mem_fun_ref()
What if you wanted to take a member function, convert it into a global function that is passed a reference to the calling object?
mem_fun() is passed a pointer, uses the -> operator to call the function.
To use a reference to the calling object, use mem_ref_fun().
Code for mem_ref_fun()
template<class R, class T>
mem_ref_fun_t<R,T>
mem_ref_fun(R (T::*pm)())
{
return mem_ref_fun_t<R,T>(pm);
}
Code for mem_ref_fun_t()
template<class R, class T>class mem_fun_t: public unary_function<T*,R>
{ R (T::*ptr) ();public: explicit mem_fun_t(R (T::*pm) ()):
ptr(pm) {} R operator()(T &t) const
{ return (t.*ptr)(); }};
Is this legal?//Employee, Manager as before
void print_list2(std::set<Employee&>&s){ for_each(s.begin(),s.end(), std::mem_fun_ref(&Employee::print)); }main() { std::set<Employee> t; t.insert(e1); t.insert(e2); t.insert(m); print_list2(t);
Not legal.
The Employee class is not sortable. So it cannot be used in a set container. We must overload operator< for the Employee class or give the set a sorting criterion.
Suppose we overload operator< for the Employee class. Will the code be legal then?
Sorry. It’s still not legal.
You receive an error message:
“Forming reference to reference”
C++ doesn’t allow a reference to a reference.
Can we duplicate this error?
template<class T>class Why {public:
void foo(T &) { }};
main() {Why<int &> w;w.foo();
}
Is this legal?
main() {
int x = 4;
int &r = x;
r = 5;
int &rr = r;
return 0;
}
Yes, this is legal.
int &r = x; int &rr = r;
doesn’t create a reference to a reference.
Instead, both r and rr are references to x.
Is this legal?
void foo(int &a) { a++; }
main() { int x = 4; int &r = x; int &rr = r; foo(r); return 0;}
Yes, this is legal.
As before, you are not creating a reference to a reference with the call foo(r).
In foo(int &a), the a will not store a reference to the r, but a reference to the x (which the r references).
Is this legal?
main() {
int x = 4;
int &r = x;
int &rr = r;
pair<int,int> p(r,rr);
pair<int &,int &> q(x,x);
pair<int &, int &> s(r,rr);
//...
pair<int,int> p(r,rr);This is legal (two ints are created by value and stored in the pair).
pair<int &,int &> q(x,x);Not legal! Reference to reference.
pair<int &, int &> s(r,rr);Not legal! Reference to reference.
Is this legal?class A {public: virtual A operator+() { std::cout << "In class A\n"; return *this; }};
class B: public A {public: B operator+() { std::cout << "In class B\n"; return *this; }};
main() { A *p = new A; +(*p); p = new B; +(*p); }
No, this is not legal.
A A::operator+() and B B::operator+() are invalid covariant return types.
Valid version of exampleclass A {public: virtual A& operator+() { std::cout << "In class A\n";
return *this; }};
class B: public A {public: B& operator+() { std::cout << "In class B\n";
return *this; }};
Virtual destructors
Rule of thumb. If a class has any virtual methods then the destructor should be declared virtual.
Exampleclass A {public: virtual void f() { std::cout << "A::f\n"; } ~A() { std::cout << "A dies\n"; }};
class B: public A {public: void f() {std::cout << "B::f\n"; } ~B() { std::cout << "B dies\n"; }};
main() { A *p = new B; p->f(); delete p;
Output of example
The output is
B::f
A dies This is not correct. The correct version of f() was called (since it is a virtual function). Only the A dtor was called. This is because A’s dtor was not declared virtual.
Correct versionclass A {public: virtual void f() { std::cout << "A::f\n"; } virtual ~A() { std::cout << "A dies\n"; }};
class B: public A {public: void f() {std::cout << "B::f\n"; } ~B() { std::cout << "B dies\n"; }};
main() { A *p = new B; p->f(); delete p;
This has the correct output, delete p calls the dtor for B then A.
B::fB diesA dies
Note, some programmers recommend that all destructors be virtual. Why not?
Not all classes are meant to be base classes.
Virtual constructors
But aren’t virtual constructors illegal? Constructors are unique. For example, you
can’t have a pointer to a constructor. Also, constructors interact with memory
management. So it would seem that a constructor cannot be virtual.
Illegality is in the eye of the offender.
“Virtual constructor” example//Base classclass A {public: //Actual constructors A() { std::cout << "A default ctor\n"; } A(const A& a) { std::cout << "A copy ctor\n"; }
//Virtual constructors virtual A* default_ctor() { std::cout << "A virtual ctor\n"; return new A; } virtual A* clone() { std::cout << "A clone\n"; return new A(*this); }
virtual ~A() { std::cout << "A is dead\n"; }};
Example part 2//Derived classclass B: public A {public: //Actual constructors B() { std::cout << "B default ctor\n"; } B(const B& b) { std::cout << "B copy ctor\n"; }
//Virtual constructors virtual B* default_ctor() { std::cout << "B virtual ctor\n"; return new B; } virtual B* clone() { std::cout << "B clone\n"; return new B(*this); }
virtual ~B() { std::cout << "B is dead\n"; }};
Example part 3void test(A *pa) { A* pa2 = pa->default_ctor(); A *pa3 = pa->clone();} main() { A a; B b; test(&a); test(&b); return 0;}
Example outputA default ctorA default ctorB default ctorA virtual ctorA default ctorA cloneA copy ctorB virtual ctorA default ctorB default ctorB cloneA default ctorB copy ctorB is deadA is deadA is dead
Virtual function table
How are virtual functions implemented in C++?
For each virtual function in a class, the compiler takes the name of the function and converts it into an index into a table, called the virtual function table (or vtable).
Each class with virtual functions has its own vtable.
Virtual function table 2
The vtable has pointers to the virtual functions for that class.
If a derived class has overridden an inherited virtual function then the derived class vtable replaces the base class function with that of the derived class.
Vtable example
class B1{ public: void f0() {} virtual void f1() {}}; class B2{ public: virtual void f2() {}};
Vtable example part 2
class D : public B1, public B2
{
public:
virtual void d() {}
virtual void f2() {}
};
Vtable
Using the g++ command
–fdump-class-hierarchy We get the following vtables
B1
Class B1
size=4 align=4
base size=4 base align=4
B1 (0xfefe26c0) 0 nearly-empty
vptr=((&B1::_ZTV2B1) + 8u)
Vtable for B1
Vtable for B1
B1::_ZTV2B1: 3u entries
0 0u
4 (int (*)(...))(&_ZTI2B1)
8 B1::f1
B2
Class B2
size=4 align=4
base size=4 base align=4
B2 (0xfefe2d40) 0 nearly-empty
vptr=((&B2::_ZTV2B2) + 8u)
Vtable for B2
Vtable for B2
B2::_ZTV2B2: 3u entries
0 0u
4 (int (*)(...))(&_ZTI2B2)
8 B2::f2
D
Class D size=8 align=4 base size=8 base align=4D (0xfefe31c0) 0 vptr=((&D::_ZTV1D) + 8u) B1 (0xfefe3200) 0 nearly-empty primary-for D (0xfefe31c0) B2 (0xfefe3240) 4 nearly-empty vptr=((&D::_ZTV1D) + 28u)
Vtable for D
Vtable for DD::_ZTV1D: 8u entries0 0u4 (int (*)(...))(&_ZTI1D) 8 B1::f112 D::d 16 D::f220 4294967292u24 (int (*)(...))(&_ZTI1D) 28 D::_ZThn4_N1D2f2Ev
What’s the output?
class A {public: virtual int foo() const {return 0; }};
class B: public A { int b;public: B(int x): b(x) {} int foo() const { return b; }};
bool bar(A a){ return a.foo() > 100;}
main() { B y(200); if (bar(y))
std::cout << "Hello\n"; else std::cout << "Goodbye\n";
return 0;}
In the call bar(y),
y.foo() would return 200, so giving output “Hello”, but instead the output I “Goodbye”. Why is that?
The reason is that function bar() is passed an A by value.
Is this legal?class A {public:
A() { val = 0; }virtual ~A() {}
protected:A(int x) { val = x; }
private:int val;
};
class B: public A {public:
B(): A(0) {}A *createA(int x = 0) { return new A(x); }
};
This is not legal.
A::A(int) is protected, so it’s not accessible in the function createA().
A child can only access protected members in the parent through a pointer or reference to the child.
Protected access rules
A protected nonstatic base class member can be accessed by members and friends of any classes derived from that base class by using one of the following:
A pointer to a directly or indirectly derived class. A reference to a directly or indirectly derived
class. An object of a directly or indirectly derived class
Example Given the following classes
class A { protected:
int i; };
class B : public A { friend void f(A*, B*); void g(A*);
};
Is this legal?
void f(A* pa, B* pb) {
pa->i = 1;
pb->i = 2;
int A::* point_i = &A::i;
int A::* point_i2 = &B::i;
}
pa->i = 1;//Illegal, not accessing protected member //through pointer or reference to derived
pb->i = 2;//Legal, accessing i through a pointer//to a B object
int A::* point_i = &A::i;//Illegal, not accessing protected member //through pointer or reference to derived
int A::* point_i2 = &B::i;//Legal, accessing i through a pointer to //a B
Is this legal?
void B::g(A* pa) {
pa->i = 1;
i = 2;
int A::* point_i = &A::i;
int A::* point_i2 = &B::i;
}
pa->i = 1;//Illegal, pa is not a pointer to a B
i = 2;//Legal, uses B::this pointer int A::* point_i = &A::i;//Illegal, because i is being used as an//A member
int A::* point_i2 = &B::i; //Legal, because i is being used as a //B member
Dynamic binding in Java
In Java there are two kinds of methods. Class (or static) methods use static binding. Instance methods (called by an object) use dynamic binding.
When the Java Virtual Machine (JVM) invokes a class method, it uses the type of the object reference.
Dynamic binding in Java 2
When the JVM invokes an instance method, it selects the method based on the actual class of the calling object.
JVM uses instructions invokestatic, invokevirtual to invoke these methods.
Java is dynamically linked, so that references to methods are initially symbolic.
Dynamic binding in Java 3
Symbolic references include the class name, method name, and method descriptor (the return type and number and types of arguments).
To resolve a symbolic reference, JVM locates the method and replaces the symbolic reference with a direct reference.
Dynamic binding in Java 4
During the resolution process the JVM undertakes verification. It checks that the method actually exists, that the associated class can legally access the method (e.g., is it private?).
If the verification fails, the JVM throws an exception.
Dynamic binding in Java 5
After the method is resolved the JVM invokes it. For instance methods, it expects the object reference to be on the stack, together with the method’s arguments.
The JVM then creates a new stack frame for the method.
Java Examplepublic class Animal {
public void eat() { System.out.println("I eat like a generic Animal."); }
public static void main(String[] args) { Animal[] anAnimal = new Animal[4];
anAnimal[0] = new Animal(); anAnimal[1] = new Wolf(); anAnimal[2] = new Fish(); anAnimal[3] = new OtherAnimal(); for (int i = 0; i < 4; i++)
anAnimal[i].eat(); }
}
Java Example 2public class Wolf extends Animal {
public void eat() { System.out.println("I eat like a wolf!"); }
}
public class Fish extends Animal { public void eat() { System.out.println("I eat like a fish!"); }
}
public class OtherAnimal extends Animal { }
Output
I eat like a generic animal.
I eat like a wolf!
I eat like a fish!
Abstract Classes
An abstract class is one that cannot be instantiated (i.e., you cannot declare variables of this type). This is in contrast to concrete classes.
Abstract classes can be seen as defining a protocol or interface for derived classes.
The protocol consists of a set of operations that derived classes must support.
Abstract classes 2
In C++, pure virtual functions define the interface that derived classes must support.
A derived class must implement the pure virtual functions it inherits, or else the derived class itself is abstract.
Abstract classes 3
So there are two ways that a class may be abstract. It is a base class with pure virtual functions It is a derived class that inherits pure virtual
functions but doesn’t implement all of them.
Traits are an alternative to abstract classes. Traits are building blocks for classes.
Abstract classes 4
Terminology problem. In C++ an abstract class can have data
and defined methods. But pure virtual functions cannot have a definition.
An interface class does not have any concrete features.
Example of interface class
class Character_device {
public:
virtual int open(int opt) = 0;
virtual int close(int opt) = 0;
virtual int read(char *p, int n) = 0;
virtual int ioctl(int filedes, int request, ...) = 0;
virtual ~Character_device() {}
};
Comments on example
In this example, particular drivers will be classes derived from Character_device.
The abstract base class creates a protocol for all drivers.
Other code will manipulate drivers through that interface.
Is this legal?#include <iostream>
class A {public: virtual ~A()=0; virtual void f()=0;};
class B: public A {public: void f() { std::cout << "Concrete\n"; }};
main() { A *p = new B; p->f();}
Not legal!
You get a linker error. Why?
The linker complains that there is no definition for A::~A (which is a pure virtual!).
So is this legal?#include <iostream>
class A {public: virtual ~A()=0 {}; virtual void f()=0;};
class B: public A {public: void f() { std::cout << "Concrete\n"; }};
main() { A *p = new B; p->f();}
No, this is not legal!
This is a compiler error. You can’t define a pure virtual function!
Correct version#include <iostream>
class A {public: virtual ~A()=0; virtual void f()=0;};
A::~A() {}
class B: public A {public: void f() { std::cout << "Concrete\n"; }};
Pure virtual constructors
Suppose we have an abstract Shape class
class Shape {
public:
//virtual copy constructor
virtual Shape *clone() const = 0;
virtual ~Shape() { };
};
Pure virtual constructors 2
In derived classes,
class Circle : public Shape {
public:
Circle *clone() const
{ return new Circle(*this); }
};
Pure virtual constructors 3
class Triangle : public Shape {
public:
Triangle *clone() const
{ return new Triangle(*this); }
};
Pure virtual constructors 4class Bob {
Shape * p_;public:
Bob(Shape *p): p_(p) { //Check p != 0 }~Bob() { delete p_; }
Bob(const Bob &b): p_(b.p_->clone()) { }Bob &operator=(const Bob &b) {
if (this != &b) {delete p_;p_ = b.p_->clone();
}return *this;
}};
Design problem
This is an example in Stroustrup’s The C++ Programming Language.
Problem: provide a way for a program to get an integer value from a user.
Create a base class Ival_box that defines a range of allowed input values.
Ival_box can be used by the program to get and set the value and prompt the user.
Design problem 2
The design must account for the fact that there will be very many different types of Ival_boxes.
Stroustrup recommends a “virtual user-interface system”.
The application is isolated from particular user-interface systems.
Design problem 3class Ival_box {protected: int val; int high low; bool changed;public: Ival_box(int ll, int hh) { changed = false; val = low = ll; high = hh; } virtual int get_value() { changed = false; return val; } virtual void set_value (int i) { changed = true; val = i; } virtual void prompt() { } virtual bool was_changed() const { return changed; }};
Design problem 4 Polymorphism using Ival_box
void interact(Ival_box *p) { p->prompt(); int i = pb->get_value(); if (pb->was_changed()) { //New value, do something } else { //Old value, do something }}
Types of Ival_boxes
Ival_box
Ival_slider Ival_dial
Popup_Ival_slider Flashing_Ival_slider
How do we get the graphics?
User-interface systems provide class(es) which define what it means to be an object on the screen.
Suppose we use the user-interface class BB_Window (bought from Big Bucks Inc.).
We could make each of our Ival_boxes a kind of BB_Window.
Implementation
BB_Window
Ival_box
Ival_slider Ival_dial
Popup_Ival_slider Flashing_Ival_slider
Is this a good design?
The concept of an Ival_box doesn’t depend on BB_Window.
The specific user-interface is an implementation detail. Should it be part of the overall design?
One of the mantras of OOP is “Separate interface from implementation.”
Is this a good design? Part 2
Deriving from BB_Window allows our code to use the facilities of BB_Window (which might be a good thing).
But, future changes to BB_Window may force us to recompile (or even rewrite) our code (which is a bad thing).
Is this a good design? Part 3
What if the program must run in a mixed environment which uses several different types of user interface systems? Our design is wired in to the BB-Window system?
We might want to use CW_Window (Compiler Whizzes), IB_Window (Imperial Bananas), or LS_Window (Liberated Software).
Is this a good design? Part 4
Another problem: every derived class inherits the data declared in Ival_box.
But do the slider or dial controls really need to store a value? The function get_value() calculate the value from the position of the control.
The problem is that you cannot disinherit features of the parent.
New Design
The user-interface system should be an implementation detail hidden from users.
No recompilation of the Ival_box family should be required after a change in the user-interface system.
Ival_boxes for different user interface systems should be able to coexist.
How do we implement?
Where do we include BB_Window? Making BB_Window a base for Ival_box
has problems mentioned before. Making BB_Window a data member of Ival_box will not work. You cannot override member functions of a data member.
Could have a data member BB_Window* in Ival_box.
Better Solution Make Ival_box an abstract base class (ABC).
class Ival_box {public:virtual int get_value() = 0;virtual void set_value (int i) = 0;virtual void prompt() = 0;virtual bool was_changed() const = 0;virtual ~Ival_box {}
};
Types of Ival_boxes
class Ival_slider: public Ival_box, protected BB_Window {
public:
//Override functions from Ival_box
protected:
//Override functions from BB_Window
private:
//Data members specific to slider
};
New Hierarchy
BB_Window Ival_box LS_Window
Ival_slider Ival_dial
Popup_Ival_slider Flashing_Ival_slider
Does this solve all our problems?
No, we still have problems trying to have different user-interface systems coexist in the design.
In other words, one cannot simultaneously have an Ival_slider for BB_Window and LS_Window.
For example
The following is not legal.
class Ival_slider: public Ival_box, protected BB_Window {};
class Ival_slider: public Ival_box, protected LS_Window {};
A solution
Have each implementation of Ival_slider define a different class.
class BB_ival_slider: public Ival_box, protected BB_Window {};
class LS_ival_slider: public Ival_box, protected LS_Window {};
Class hierarchy
BB_Window Ival_box LS_Window
BB_ival_slider LS_ival_slider
Better solution
We can further insulate our design from implementation details.
We can make Ival_slider an abstract class.
Then we can derive specific sliders from the abstract class.
New class design
class Ival_box {}; //ABCclass Ival_slider: public Ival_box {}; //ABC
//Implemetation classesclass BB_ival_slider: public Ival_slider,
protected BB_window {};
class LS_ival_slider: public Ival_slider, protected LS_Window {};
New class hierachy
Ival_box
BB_Window Ival_slider LS_Window
BB_ival_slider LS_ival_slider
Even better hierarchy
BB_Window Ival_box LS_Window
BB_slider Ival_slider LS_slider
BB_ival_slider LS_ival_slider
Bridge Pattern
GoF definition The purpose of the Bridge Pattern is to
“De-couple an abstraction from its implementation so that the two can vary independently.”
Bridge Pattern Diagram
Bridge and the OAOO rule
The bridge pattern exemplifies the once and only once rule.
This rule, stated by Kent Beck, asserts that “each and every declaration of behavior should appear once and only once.”
More on OAOO
One should eliminate duplicated behavior.
If duplicate behavior appears, merge them or replace multiple implementations with a unifying abstraction.
This reflects the goal of making code as simple as possible.
Bridge Pattern Analysis
Look for variations in abstractions of a concept
Look for variations in implementations of these concepts
Commonality vs. Variability
From Jim Coplien, Multi-Paradigm Design for C++
“Commonality analysis is the search for common elements that helps us understand how family elements are the same.”
“Commonality analysis seeks structure that is unlikely to change over time.”
Commonality vs. Variability 2
“ Variability analysis captures structure that is likely to change.”
“Variability analysis makes sense only in terms of the context defined by the associated commonality analysis.”
“From an architectural perspective, commonality analysis gives the architecture its longevity; variability analysis drives its fitness for use.”
Bridge Pattern Example
Suppose we are writing a program that will allow the user to draw basic shapes. There are two different drawing programs that may be used, DP1 and DP2. Each provide the necessary draw functions.
We want a design which connects the shape concepts to the drawing programs.
Class Hierarchy
Rectangle
+draw()
#drawline() = 0
V1_Rectangle
#drawline()
V2_Rectangle
#drawline()
DP1
+draw()
DP2
+draw()
Class Definition
class Rectangle {public:
void draw();protected:
virtual voiddrawline(Point,Point)= 0;
private:Point _ne, _se, _nw, _sw;
};
Class Definition 2
void Rectangle::draw() {
drawline(_ne,_se);
drawline(_se,_sw);
drawline(_sw,_nw);
drawline(_nw,_ne);
};
Class Definition 3
class V1_Rectangle {
protected:
void drawline(Point a, Point b)
{ _imp->draw_line(a,b); }
private:
DP1* _imp;
};
Class Definition 4
class V2_Rectangle {
protected:
void drawline(Point a, Point b)
{ _imp->draw_line(a,b); }
private:
DP2* _imp;
};
Is this a good design?
This design illustrates combinatorial explosion.
If there are m different types of Shapes and n drawing programs this design will require m + n + mn different classes to be defined.
The ideal design, which completely separates abstraction from implementation would only require m + n different classes.
More design flaws
The abstraction and implementation are tightly coupled.
Each type of shape must know which drawing program it’s using.
There is redundancy in the design. The code for V1_Rectangle and V2_Rectangle is essentially the same.
Alternative design
Try to reduce the coupling between abstraction and design.
Have each implementation define its own inheritance scheme.
New Class Hierarchy
+draw()
Shape
#drawLine()#drawCircle()
V1_Shape
#drawLine()#drawCircle()
V2_Shape
+draw()
V1_Rectangle
+draw()
V1_Circle
+draw()
V2_Rectangle
+draw()
V2_Circle
+draw_line()
DP1
+draw_line()
DP2
-Implementor
11
-Implementor
1 1
How good is this design?
There is no longer as much coupling in the design. Each drawing program has it’s own shapes.
There is still redundancy. V1_Rectangle and V2_Rectangle will have the same draw() method.
There is still combinatorial explosion in classes when new shapes or drawing programs are added.
How can the design be improved?
Use the following principles.
Find what varies and encapsulate it.
Favor composition over inheritance.
How to apply these principles
What varies in this situation? There are different shapes and different
drawing programs. Encapsulate shapes, who know how to
draw themselves. Encapsulate drawing methods for lines
and circles, all draw() functions will use these basic methods.
What varies?
There are two types that vary in this system. The type of Shape and the type of drawing program.
Drawing
drawLine( )drawCircle( )
Shape
draw( )
Represent variations
Particular variations in each type.
Shape
draw( )
Drawing
drawLine( )drawCircle( )
Rectangle
draw( )
Circle
draw( ) V1Drawing
drawLine( )drawCircle( )
V2Drawing
drawLine( )drawCircle( )
Tie classes together
How should the Shape and Drawing types be connected?
Prefer composition over inheritance.
Should Shape use Drawing or Drawing use Shape?
Tie classes together 2
What if Drawing used Shape? Then the drawing program would need to
know about the different types of Shapes. They would need to know specific details
of the Shapes. This would break encapsulation for the
Shape class.
Tie classes together 3
What if Shape used Drawing? Shape classes would not need to know
details of the drawing programs. Shape classes only need to use the
interface of the drawing program. They don’t need to know the specific program being used.
Tie classes together 4
Rectangle
draw( )
Circle
draw( )
V1Drawing
drawLine( )drawCircle( )
V2Drawing
drawLine( )drawCircle( )
Drawing
drawLine( )drawCircle( )
Shape
draw( )
Fill in the details
Each concrete drawing class will be associated to a particular drawing program.
The Shape class will have protected member functions drawLine() and drawCircle().
Final Design
Rectangle
draw( )
Circle
draw( )
V1Drawing
drawLine( )drawCircle( )
V2Drawing
drawLine( )drawCircle( )
Drawing
drawLine( )drawCircle( )
Shape
draw( )drawLine( )drawCircle( )
DP2
draw_a_Line( )draw_a_Circle( )
DP2
draw_a_Line( )draw_a_Circle( )
Conclusions
There are only 3 objects being used at a time.
A Shape object (all of which look identical in operation to the client).
A Drawing object (all of which look identical in operation to the Shape).
An actual drawing program (the Drawing object must use a specific drawing program).
Sample Codeclass Shape {private: Drawing *_dp;protected: void drawLine(double x1, double y1,
double x2, double y2) const { _dp->drawLine(x1,y1,x2,y2); }
void drawCircle(double x, double y, double r) const
{ _dp->drawCircle(x,y,r); }public: Shape(Drawing *dp): _dp(dp) {} virtual void draw() const = 0;
virtual ~Shape() { delete _dp; }};
Sample Code 2class Rectangle: public Shape {private: double _x1, _y1, _x2, _y2;public: Rectangle(Drawing *dp, double x1, double y1, double x2, double y2): Shape(dp), _x1(x1), _y1(y1),
_x2(x2), _y2(y2) {}
void draw() const { drawLine(x1,y1,x2,y1); drawLine(x2,y1,x2,y2); drawLine(x2,y2,x1,y2); drawLine(x1,y2,x1,y1); }};
Sample Code 3
class Circle: public Shape {private: double _x, _y, _r;public: Circle(Drawing *dp,
double x, double y, double r): Shape(dp), _x(x), _y(y), _r(r) {} void draw() const { drawCircle(_x,_y,_r); }};
Sample Code 4
class Drawing {public: virtual void drawLine( double x1, double y1, double x2, double y2) const = 0;
virtual void drawCircle( double x, double y, double r)
const = 0;};
Sample Code 5
class V1Drawing: public Drawing {public: void drawLine(double x1, double y1, double x2, double y2) const { DP1::draw_a_line(x1,y1,x2,y2); } void drawCircle(double x, double y,
double r) const { DP1::draw_a_circle(x,y,r); }};
Sample Code 6
class V2Drawing: public Drawing {public: void drawLine(double x1, double y1, double x2, double y2) const { DP2::draw_a_line(x1,y1,x2,y2); } void drawCircle(double x, double y,
double r) const { DP2::draw_a_circle(x,y,r); }};
Sample Code 7
class DP1 {public: static void draw_a_line( double x1, double y1, double x2, double y2) const;
static void draw_a_circle( double x, double y, double r) const;};
Sample Code 8
class DP2 {public: static void draw_a_line( double x1, double y1, double x2, double y2) const;
static void draw_a_circle( double x, double y, double r) const;};
Bjarne’s advice column
Avoid type fields. Use pointers and references to avoid
slicing. Use abstract classes to focus design on
the provision of clean interfaces. Use abstract classes to minimize
interfaces.
Bjarne’s advice column 2
Use abstract classes to keep implementation details out of interfaces.
Use virtual functions to allow new implementations to be added without affecting user code.
Use abstract classes to minimize recompilation of user code.
Bjarne’s advice column 3
Use abstract classes to allow alternative implementations to coexist.
A class with a virtual function should have a virtual destructor.
An abstract class typically doesn’t need a constructor.
Keep the representations of distinct concepts distinct.
Multiple Inheritance
Multiple inheritance consists in a class having more than one direct base class.
MI allows the programmer to combine independent concepts represented by different classes into a composite concept represented by the derived class.
A class with multiple parents is called a join class.
Multiple Inheritance 2
A class may only be used once as a direct base class but may be used multiple times as an indirect base class.
If there is more than one path from an ancestor class down to a descendent then there are possible problems.
This is called the common root problem.
Multiple Inheritance 3
What are the issues associated with the common root problem?
Duplicate data. Any data inherited from a common root is stored as many times in the child as there paths from the root to the child.
Ambiguities. Multiple versions of the same function can be inherited.
Why use multiple inheritance?
Feature Intersection: A child has features of two parents. The child class may be considered the intersection of the parents.
For example. The class iostream combines features of istream and ostream.
Mixin Classes
A mixin classes combines a specific type of feature with a subclass of some inheritance scheme.
Mixin classes are generally not intended to be standalone.
For example, take any type of ice cream and combine it with some garnish (nuts, sprinkles, sundae sauce). The garnish is a mixin (not meant to be eaten by itself).
Mixin Classes 2
Mixin classes define another form of multiple inheritance.
A class that is already part of an inheritance scheme uses the mixin class as another parent in order to add some specific feature.
Flavors was the first OOPL that used multiple inheritance and mixin classes.
MI Memory Layout
Suppose we have the class hierarchy
A A A
B C
D
MI Memory Layout
A part of D
A part of B
B part of D
A part of C
C part of D
D itself
Is this legal?
class A {public: virtual void foo()
{ std::cout << "A::foo()\n"; }};
class B: public A {};
class C: public A {public: void foo() { std::cout << "C::foo()\n"; }};
Example part 2
class D: public B, public C { };
main() { A *p;
p = new A; p->foo();
p = new B;p->foo();
Example part 3
p = new C;p->foo();
p = new D;p->foo();
B *q = new D;q->foo();
p = new A;
p->foo();
// Legal, p is an A pointer.
// Output is: A::foo()
p = new B;
p->foo();
// Legal, p points at the A part of B
// Output is: A::foo()
p = new C;
p->foo();
// Legal, p points at the A part of C
// Output is: C::foo()
p = new D;
p->foo();
// Illegal, A is an ambiguous base of D
B *q = new D;
q->foo();
// Legal, q points at the B part of D
// Output is: A::foo()
Is this legal?
In the previous example
D d;
d.foo();
D d;
// Legal, A is an ambiguous base
// but this is ok as long as A
// is not used
d.foo();
// Illegal, this is ambiguous
// Could be A::foo() or C::foo()
Is this legal?
class A {public: virtual void foo()
{ std::cout << "A::foo()\n"; }};
class B: public A {};
class C: public A {public: void foo() { std::cout << "C::foo()\n"; }};
Example part 2
class D: public A, public B, public C { };
main() {
D d;
d.A::foo();
}
First, the declaration
D d;
Gets a warning: direct base 'A' inaccessible in 'D' due to ambiguity.
Next,
d.A::foo()
Is ambiguous. Compiler states that 'A‘ is an ambiguous base of 'D‘.
Is this legal?
class A {public: virtual void foo()
{ std::cout << "A::foo()\n"; }};
class B: public virtual A {};
class C: public virtual A {public: void foo() { std::cout << "C::foo()\n"; }};
Example part 2
class D: public A, public B, public C { };
main() {
D d;
d.A::foo();
}
First, the declaration
D d;
Gets a warning: direct base 'A' inaccessible in 'D' due to ambiguity.
Next,
d.A::foo()
Is ambiguous. Compiler states that 'A‘ is an ambiguous base of 'D‘.
Is this legal?
In the previous example, if we replace:
p = new D;
p->foo();
byp = static_cast<C*>(new D);
p->foo();
This is legal.
The assignment
p = static_cast<A*>(static_cast<C*>(new D));
makes p point at the A part of the C part of D.
The call p->foo() has output
C::foo()
Is this legal?class A {public: void f(int) { std::cout << "A::f(int)\n"; } void f(char) { std::cout << "A::f(char)\n"; }};
class B {public: void f(double) { std::cout << "B::f(double)\n"; }};
class AB: public A, public B {public: void f(char) { std::cout << "AB::f(char)\n"; } void f(AB) { std::cout << "AB::f(AB)\n"; }};
Example part 2
void g(AB &ab){ ab.f(1); ab.f('a'); ab.f(2.0); ab.f(ab);} main(){ AB ab; g(ab); }
void g(AB &ab)
{
ab.f(1);
//Legal, but calls AB::f(char)
ab.f('a');
//Legal, but calls AB::f(char)
ab.f(2.0);
//Legal, but calls AB::f(char)
ab.f(ab);
//Legal, calls AB::f(AB)
}
Output of example
AB::f(char)
AB::f(char)
AB::f(char)
AB::f(AB)
What’s the output?
class A {public: void f(int) { std::cout << "A::f(int)\n"; } void f(char) { std::cout << "A::f(char)\n"; }};
class B {public: void f(double) { std::cout << "B::f(double)\n"; }};
Example part 2
class AB: public A, public B {public:
using A::f;using B::f;
void f(char) { std::cout << "AB::f(char)\n"; }
void f(AB) { std::cout << "AB::f(AB)\n"; }
};
Example part 3
void g(AB &ab){ ab.f(1); ab.f('a'); ab.f(2.0); ab.f(ab);} main(){ AB ab; g(ab); }
Output of example
A::f(int)
AB::f(char)
B::f(double)
AB::f(AB)
Common root problem
What’s the problem with multiple inheritance?
Common root problem
A
B C
D
Common root problem 2
As we saw in the previous example, class D inherits two copies of A.
Hence D has the problem of having A as an ambiguous base.
How to solve the common root problem? The virtual base class mechanism insures
that D will only have one copy of A.
Inheritance Lattice
Lattice search
How does one search the inheritance lattice in order to find some attribute or method not defined locally?
There may be several paths in the lattice that must be searched to locate a particular feature.
Lattice search 2
What search algorithm should be used?
The search method may uncover features of the same name in different classes in the lattice. This is a name clash.
Name clash resolution
One alternative. When searching the inheritance lattice, if a name clash occurs then flag that as an error and terminate the search.
Second alternative. Permit qualified message passing. Inherited methods are qualified with the name of the class in which it is defined. This is a type of renaming.
Approaches to multiple inheritance
There are four approaches Tree inheritance Graph inheritance Linearized inheritance Prohibition
Tree inheritance
Tree inheritance involves labeling nodes when conflicts arise.
A class may inherit more than one copy of a slot, but with different names.
If a slot can be reached via a number of paths in the inheritance lattice, there will be a separate set of slots for each path.
Tree inheritance 2
For example, take the diamond diagram.
A
B C
D
Tree inheritance 3
Tree inheritance transforms the diamond into the following diagram.
A A
B C
D
Tree inheritance 4
This is now a tree.
Unfortunately there still exists the problem of duplicated data.
There are examples where tree inheritance works poorly.
Tree inheritance 5
Example. Suppose there is a Point class. Points can be moved (they store x and y
coordinates). Derived from the Point class are classes HistoryPoint and BoundedPoint.
HistoryPoint records all movements of the Point.
BoundedPoint only allows the point to move within fixed boundaries.
Tree inheritance 6
What if we want a BoundedHistoryPoint?
This class will inherit a move method from each parent. Neither move method is appropriate for BoundedHistoryPoint.
The two parents cannot be combined using tree inheritance. So this type of inheritance is insufficient.
Graph inheritance
Here the inheritance lattice is treated as a graph.
For example, to invoke all the definitions of a certain operation in the inheritance graph, each class can define an operation on each of its parents and then perform any local computation.
Graph inheritance 2
In many languages supporting graph inheritance there is one set of instance variables for each ancestor class, regardless of how many paths by which the class can be reached.
There is a problem with this approach. It exposes the inheritance structure of the graph.
Graph inheritance 3
Namely, encapsulation implies that the way a class is derived should not be visible.
Only immediate parents should be visible to a class.
But graph inheritance typically means that the structure of inheritance is exposed to subclasses.
Graph inheritance 4
Also, in using graph inheritance, changing the inheritance of an intermediate class may break a descendant class.
For example, suppose we have:
Z
Y1 Y2
X
Graph inheritance 5
Suppose there is a function foo() defined in each of the classes Z, Y2, and X. Suppose that the foo() in X invokes the function foo() in its parents.
Now suppose that the class Y2 is modified so that it inherits from Z and uses the function foo() defined in Z.
Graph inheritance 6
The function foo() in X then invokes foo() in Z twice.
So a change in the inheritance structure of one class (Y2) breaks one of its child classes (X).
Linearized inheritance
Linearized inheritance flattens the inheritance lattice into a linear chain and then searches the chain in order to find slots.
The simplest linearization is to do a depth first transversal.
The problem is that the order of classes in the linear chain is not natural.
Linearized inheritance
Problems with linearized inheritance: The linearization process may insert
unrelated classes between related classes in the sorted superclass chain.
The method that is invoked through the linear chain may be sub-optimal. The optimal method may be farther back in the chain and hence hidden by the sub-optimal.
Alternatives to MI
Perspectives
Interfaces
Prototypes
Aggregation
Perspectives
In languages that implement perspectives, a class may have, in addition to data and methods, a list of perspectives. These are additional ways of looking at objects of a class.
An object may use particular perspectives for its class.
Perspectives 2
For example, I am an object of the Professor class.
Some of the perspectives that apply to me: Husband, Traveler, Punk Rock fan
Interfaces
In Java and other OOPLs, one can inherit from a single superclass and implementing one or more interfaces.
An interface is not a class, but the specification of data and operations that, when implemented, will achieve the intended meaning of the interface.
Interfaces 2 Example
public interface Predator { boolean chasePrey(Prey p); void eatPrey(Prey p);
}
public class Cat implements Predator {public boolean chasePrey(Prey p)
{ // cat behavior } public void eatPrey (Prey p) { // cat behavior }
}
Prototypes
In most OOP languages there is a distinction between a class and its objects.
In a prototype language there is no such distinction. Instead everything is a prototype.
SELF, Kevo, and Omega are examples of prototype languages.
Prototypes 2
A prototype is a representative example of a concept.
Prototypes are examples of at least one concept.
What is a Datascope programmer? My prototype of a Datascope programmer
is Ralph Ebler.
Prototypes 3
Your concept of a Datascope programmer may look a lot different from how Ralph looks.
What do you do? You clone Ralph. I know, this sounds even worse than
having one Ralph.
Prototypes 4
The different features of an object (attributes and methods) are known as slots.
The clone can be modified in several ways.
Yes, you can make a new, better version of Ralph.
Prototypes 5
Ways of modifying a clone. Add a slot. Remove a slot. Rename a slot. Hide a slot. Show a slot. Redefine a slot.
Delegation
When cloning one doesn’t always need to copy methods from the parent to the clone.
Access to the parent’s slots is created through delegation.
Delegation replaces inheritance in prototype languages.
Delegation 2
Requests made to objects contain references to slots.
If the slot is not present in an object, the request can be delegated to another object.
Typically requests are delegated to the parents of the object to whom the initial request was made.
Delegation 3
If the immediate parent of the object does not contain the slot, the request is passed up the chain of parents.
When the slot is first encountered, the value stored there is returned or the method stored there is invoked.
Delegation 4
Prototypes will generally have a slot containing a pointer to its parent.
In some languages the value stored in this slot may be changed, thus dynamically changing an object’s parent.
This is called dynamic inheritance or computed delegation.
Delegation 5
Delegation is different from multiple inheritance.
There is a specific chain of parents that is followed in delegation (although that chain may be modified during program execution).
This message passing up the chain of parents acts like single inheritance.
Shared behavior
One problem of prototype languages is how to represent shared behavior.
But prototypes are all individuals. Since individuals may share properties in the real world, the representation problem is significant.
SELF uses traits to solve this problem.
Traits
Traits are parent objects containing shared behavior.
They act like classes do in a class-based system.
For example, there may be many different Point objects in a system.
A Point traits object contains the common properties of points.
Traits 2
For example, each Point object has mutable slots for the x and y coordinates of the Point.
The Point traits object is the parent of each Point. The traits objects contains common methods for handling points (e.g., adding and multiplying points).
Aggregation
Complex objects are built out of simpler objects. The complex object is an aggregate.
This is an alternative to inheritance. Objects share code through composition.
Aggregation is based on the idea that components of an object are used either by naming or message passing.
Aggregation 2
Naming occurs when one object names the components of another object and refers to these remote components by use of the names.
In order to accomplish this, the aggregate must have access to at least the interface of the component.
Aggregation 3
Aggregation by message passing is very similar to delegation.
If the aggregating object wants a component, it sends a message to the object which contains it. The receiving object then returns a message with the desired component.
Aggregation vs. Inheritance
Suppose we want a stack class based on a deque.
Suppose the stack inherits from a deque?
class Stack: public Deque {};
Problem: this exposes Stack to potentially harmful public member functions of Deque.
Aggregation vs. Inheritance 2
Suppose the stack contains a deque? In Beta,
Stack : (# d: Dequepush : d.enterInFront;pop : d.removeInFront;(* … *)
#)
Aggregation vs. Inheritance 3
In this example, aggregation is clearly more natural than inheritance.
In using aggregation the Deque class is not exposed. It is used internally in the Stack methods.
Virtual base classes
Notation:
class A {};
class B: public virtual A {};
class C: public virtual A { };
class D: public B, public C { };
Virtual base classes 2
Note: One can write either
class B: public virtual A {};
or
class B: virtual public A {};
Virtual base classes 3
The effect of declaring A as a virtual base class is that D will only contain one copy of A.
In a sense, it’s as if A has become a direct base class of D.
D is responsible for constructing its A subobject (usually a child is only responsible for constructing its direct base classes).
Virtual base classes 4
Since D has only one copy of A, the data of A is not duplicated in D.
When using virtual functions, there must be one function that overrides all others.
There must be an overriding class that is derived from every other overriding class.
MI Memory Layout (VBC)
A part of D
B part of D
C part of D
D itself
What’s the output?
class A {public: virtual void foo()
{ std::cout << "A::foo()\n"; }};
class B: public virtual A {};
class C: public virtual A {public: void foo() { std::cout << "C::foo()\n"; }};
Example part 2
class D: public B, public C { };
main() { A *p;
p = new A; p->foo();
p = new B;p->foo();
Example part 3
p = new C;p->foo();
p = new D;p->foo();
D d;d.foo();
Example part 4
B *q = new D;
q->foo();
Output
A::foo()
A::foo()
C::foo()
C::foo()
C::foo()
C::foo()
What’s the output?
Suppose we make the following change.
class A {
public:
void foo()
{ std::cout << "A::foo()\n"; }
};
Output
A::foo()
A::foo()
A::foo()
A::foo()
C::foo()
A::foo()
Is this legal?
class A { private: int x;public: A(int a): x(a) {}};
class B: public virtual A {public: B(int b): A(b+1) {}};
Example part 2
class C: public virtual A {public: C(int c): A(c+2) {}};
class D: public B, public C {public: D(int d): B(d-1), C(d+1) {};
This is illegal.
Class D is responsible for constructing its virtual base class A.
Since A has no default constructor, D’s constructor fails to construct the A subobject.
What’s the output?
class A { private: int x;public: A(int a): x(a) {} void print() const { std::cout << “A::x = “ << x << ‘\n’; }};
class B: public virtual A {public: B(int b): A(b+1) {}};
Example Part 2
class C: public virtual A {public: C(int c): A(c+2) {}};
class D: public B, public C {public: D(int d): A(d), B(d-1), C(d+1) {}};
Example Part 3
main() {
D d(3);
d.print();
return 0;
}
The output is:
A::x = 3
This is because the calls to the A constructor in B’s and C’s constructors are ignored.
Is this legal?
class A { private: int x;public: A(int a): x(a) {} void print() const { std::cout << “A::x = “ << x << ‘\n’; } void set_x(int a) { x = a; }};
Example Part 2
class B: public virtual A {
public:
B(int b): A(b+1) {}
void set_x(int a) {
A::set_x(a);
std::cout << “Hello\n”;
}
};
Example Part 3
class C: public virtual A {public: C(int c): A(c+2) {}};
class D: public B, public C {public: D(int d): A(d), B(d-1), C(d+1) {}};
Example Part 4
main() { D d(3);
d.print(); d.set_x(5); d.print();
return 0;}
Yes, this example is legal.
It is legal for B::set_x() to call A::set_x().
d.print() calls d.A::print()
d.set_x() calls d.B::set_x()
Superclass methods
A child class may specialize a method inherited from some superclass, but still need to invoke the inherited method.
In C++ one uses the scope resolution operator :: to access superclass methods.
In Smalltalk and Java, one uses the keyword super as a generic name for the superclass.
Superclass methods 2
Java example:
public class Superclass {
public void printMethod() {
System.out.println
("Printed in Superclass.");
}
}
Superclass methods 3
public class Subclass extends Superclasspublic void printMethod() { //overrides printMethod in Superclass
super.printMethod(); System.out.println("Printed in Subclass"); }
public static void main(String[] args) { Subclass s = new Subclass();
s.printMethod(); }
}
Superclass methods 4
Some languages, such as Dylan and CLOS, use a bottom-up method called next-method (or call-next-method).
The arguments to the method that calls next-method are supplied to next-method.
Superclass methods 5
The next-method operation searches the inheritance chain to find another method with the same name and parameters at a point higher in the chain.
So next-method searches for a more general version of itself.
Superclass methods 6
If, in the search conducted by next-method, no relevant method is found then an error results.
If a method is located then it is invoked using the same arguments as in the original method.
If two such methods are found then the more specific is called.
next-method example
The following example defines a class for a FIFO queue (it uses the Dylan language).
define class <FIFO>
slot elems :: LIST;
end class <FIFO>;
next-method example 2
Here is a method for inserting elements into the queue
define method add_element(q :: FIFO, x)
elems(q) := cons(x, elems(q))
end method;
next-method example 3
Now suppose we want to define a class LIFO that inherits from FIFO.
define class <LIFO> (<FIFO>)
end class <LIFO>
next-method example 4
We can use next-method to specialize the add_element for the LIFO class.
define method add_element(q :: FIFO, x)
elems(q) := reverse(elems(q));
next-method;
elems(q) := reverse(elems(q));
end method;
C++ vs. Dylan
Here is a Dylan mixin class example. This defines a Windows class and related classes for drawing.
define class <window> (<object>)
slot width :: <integer>;
slot height :: <integer>;
end class <window>;
C++ vs. Dylan 2
define class <border-window> (<window>)slot border-width :: <integer>;
end class <border-window>;
define method width(window :: <border-window>)
next-method() - 2 * window.border-width; end method width;
define method height(window :: <border-window>)
next-method() - 2 * window.border-width; end method height;
C++ vs. Dylan 3
define class <label-window> (<window>) slot label-height :: <integer>; slot label-text :: <string>;
end class <label-window>;
define method height(window :: <label-window>)
next-method() - window.label-height; end method height;
C++ vs. Dylan 4
The following class uses multiple inheritance to create a Window class with features of all previously defined classes.
define class <border-label-window> (<border-window>, <label-window>,
<window>)
end class <border-label-window>;
C++ vs. Dylan 5
Here is the same example in C++
class Window { private:
int _width; int _height; public:
virtual int width() { return _width; } virtual int height() { return _height; }
};
C++ vs. Dylan 6
class BorderWindow : public virtual Windowclass BorderWindow : public virtual Window{ { private: private:
int _border_width; int _border_width;
public: public: virtual int border_width() virtual int border_width() { return _border_width; } { return _border_width; }
virtual int width(); virtual int width(); virtual int height(); virtual int height();
}; };
C++ vs. Dylan 7
int BorderWindow::width() {int BorderWindow::width() { return return
Window::width() - 2*border_width();Window::width() - 2*border_width();} }
int BorderWindow::height() int BorderWindow::height() { { return return
Window::height() - 2*border_width(); Window::height() - 2*border_width(); }}
C++ vs. Dylan 8
class LabelWindow : public virtual Window { private:
int _label_height; char *_label_text;
public: virtual int label_height() { return _label_height; }
virtual char* label_text() { return _label_text; } virtual int height();
};
C++ vs. Dylan 9int LabelWindow::height() { return Window::height() - label_height(); }
class BorderLabelWindow : public virtual BorderWindow, public virtual LabelWindow, public virtual Window
{ public:
virtual int height(); };
int BorderLabelWindow::height() {
return Window::height() - 2*border_width() – label_height();
}