1 / 33

Advanced Program Design with C++

Advanced Program Design with C++. Polymorphism Virtual functions Abstract classes Slicing problem Value and reference semantics. Function pointers Virtual functions tables Diamond problem Virtual inheritance. Polymorphism. Polymorphism Associating many meanings to one function.

Download Presentation

Advanced Program Design with C++

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Advanced Program Design with C++ Polymorphism Virtual functions Abstract classes Slicing problem Value and reference semantics Function pointers Virtual functions tables Diamond problem Virtual inheritance Joey Paquet, 2007-2014

  2. Polymorphism • Polymorphism • Associating many meanings to one function. • Virtual functions provide this capability. • Fundamental principle of object-oriented programming. • Virtual • Existing in "essence" though not in fact. • Virtual functions. • Virtual (abstract) classes. Joey Paquet, 2007-2014

  3. Polymorphism • Best explained by example: • Classes for several kinds of figures • Rectangles, circles, etc. • Each figure is an object of a different class • Each have different data elements and formulas to compute them • Rectangle data: name, area, perimeter, height, width • Circle data: name, area, perimeter, radius • All derive from one parent-class: CShape • Requires a method getArea() • Different formula to compute different figures’ area, requires different implementation for each kind of figure Joey Paquet, 2007-2014

  4. Polymorphism • Each class needs a different getArea()function • Can define a member getArea()in each class, so: • Nothing new here yet… CRectangle r; CCircle c; r.getArea(); //Calls Rectangle’s getArea c.getArea(); //Calls Circle’s getArea Joey Paquet, 2007-2014

  5. Polymorphism • Problem! • Parent class CShape may contain functions that applies to all kinds of figures. • Consider: print() : prints out all characteristics common to all kinds of figures • Fine for getName(), as it has common behavior. • However, getArea()has subclass-specific behavior • Complications! • Which getArea() function to call? • From which class? • Here, a function that exposes common behavior uses another function that exposes subclass-specific behavior. • May hardcode a function that calls the right getArea()depending on which subclass the figure in question is. • Need a mechanism to call subclass-specific behavior automatically Joey Paquet, 2007-2014

  6. Polymorphism • Problem! • Consider a new kind of figure later coming along: CTriangle class derived from CShape class • Function print() inherited from CShape • Will it work for triangles? • It uses getArea(), which is different for each subclass of figure • It will use CShape::getArea(), which was not made to work for triangles • We want the inherited function print() to use the functionTriangle::getArea()and not the function CShape::getArea() • But class CTrianglewasn’t even written when CShape::print() was written. It does not “know” about triangles. • If print()function is hard-coded to call the right getArea() depending on the type of CShape, we need to change the implementation of print()every time we add a new subclass of Cshape. • Again, we need a mechanism to automatically call subtype-specific behavior. Joey Paquet, 2007-2014

  7. Polymorphism voiddisplayCShapeDynamicCastDemo(CShape& shape) { CShape* p = &shape; CCircle* p1 = dynamic_cast<CCircle*>(p); CRectangle* p2 = dynamic_cast<CRectangle*>(p); CTriangle* p3 = dynamic_cast<CTriangle*>(p); if (p1 != NULL){ cout << "Circle's area is " << p1->getArea() << endl; cout << "Circle's radius is " << p1->getRadius() << endl; } if (p2 != NULL){ cout << "Rectangle's area is " << p2->getArea() << endl; cout << "Rectangle's width is " << p2->getWidth() << endl; cout << "Rectangle's height is " << p2->getLength() << endl; } if (p3 != NULL){ cout << "Triangle's area is " << p3->getArea() << endl; cout << "Triangle's width is " << p3->getHeight() << endl; cout << "Triangle's height is " << p3->getBase() << endl; } } Joey Paquet, 2007-2014

  8. Virtual methods Joey Paquet, 2007-2014

  9. Polymorphism: virtual methods • Virtual methods allow to declare such a subtype-specific behavior. • If an object of type CShape is declared and instantiated with an object of type e.g. CRectangle, calling any of its virtual methods will result in using CRectangle’s behavior, even though its type is declared as CShape. • This requires a dynamic run-time binding mechanism. In C++ implementation, it is called “dynamic binding” or “late binding”. classCShape{ protected: double area; string name; public: CShape() : name("anonymous"){ } CShape(stringnewName) : name(newName){ } stringgetName(){ return name; } virtualdoublegetArea()= 0; virtualvoid print(){ cout << "Shape:" << name << endl; cout << "Area: " << getArea() << endl; } }; bool operator < (CShape &s1, CShape &s2){ return (s1.getArea() < s2.getArea()); } Joey Paquet, 2007-2014

  10. Polymorphism: virtual methods • Polymorphic behavior comes when you declare an object of a base class type and then instantiate it with an instance of a derived type. • In this case, the type of the object can only be determined dynamically at run-time. • If a method is declared as virtual, its subtype behavior is branched upon. • If a method is not declared as virtual, the base type behavior is branched upon. void main() { CRectangle *shapeRect = newCRectangle("shapeRect", 16.8, 8.8); CCircle *shapeCircle = newCCircle("shapeCircle", 13.8); CTriangle *shapeTriangle = newCTriangle("shapeTriangle", 2.5, 3.8); CShape *shapes[] = { shapeRect, shapeCircle, shapeTriangle }; for (inti = 0; i < sizeof(shapes) / sizeof(shapes[0]); i++){ shapes[i]->getArea(); shapes[i]->print(); if (i>0) cout << "Shape[" << i << "] area (" << shapes[i]->getArea() << ") less than Shape[" << i - 1 << "] area (" << shapes[i-1]->getArea() << ") : " << (*shapes[i] < *shapes[i-1]) << endl; } deleteshapeRect; deleteshapeCircle; deleteshapeTriangle; } Joey Paquet, 2007-2014

  11. Polymorphism: virtual methods classCCircle : publicCShape{ double radius; public: CCircle() : CShape(), radius(1){ }; CCircle(stringn, doubler) : CShape(n), radius(r) { }; voidsetRadius(doubler) { radius = r; } doublegetRadius() const { return radius; } doublegetArea(){ area = 3.14159265 * radius * radius; return area; } void print(){ CShape::print(); cout << "Circle:" << endl; cout << "Radius: " << radius << endl; } }; classCRectangle : publicCShape{ double length; double width; public: CRectangle() : CShape(), length(1), width(1){ } CRectangle(stringn, doublel, doublew) : CShape(n), length(l), width(w){ } voidsetLength(doublel) { length = l; } voidsetWidth(doublew) { width = w; } doublegetLength() const { return length; } doublegetWidth() const { return width; } doublegetArea(){ area = length * width; return area; } void print(){ CShape::print(); cout << "Rectangle:" << endl; cout << "Height: " << length << " Width: " << width << endl; } }; Joey Paquet, 2007-2014

  12. Polymorphism: virtual destructors • If you implement a class to be used polymorphically, you probably need to declare its destructor as virtual. • If an object of a derived class is assigned to a pointer to a base class, and then later deleted using the base class pointer, the base class destructor will be used to delete the object. • The derived class’ destructor will thus never be called, possibly leading to a resource leak. • Rule of thumb: always declare the destructor as virtual if a class is to be use polymorphically. classBase { public: virtual ~Base(){ cout << "Base::~Base()" << endl; } }; classDerived : publicBase { public: ~Derived(){ cout << "Derived::~Derived()" << endl; } }; int main(){ Base *b = newDerived(); delete b; inti; cin >> i; } Joey Paquet, 2007-2014

  13. Pure virtual functions Abstract classes Joey Paquet, 2007-2014

  14. Polymorphism: pure virtual methods and abstract classes • In C++, some virtual methods can be declared as “pure virtual”. • Any class with at least one pure virtual method is an abstract class. • No instance of an abstract class can be created. • However, an abstract class still represents a data type. • To be used, abstract classes need to be derived by subclasses that provide an implementation to all their pure virtual methods. • A class that derives an abstract class and does not provide an implementation for all the pure virtual functions it inherits is itself still an abstract class. Joey Paquet, 2007-2014

  15. Polymorphism: pure virtual methods and abstract classes • There are two main uses for pure virtual functions/abstract classes: • Classes that are to be used polymorphically with different subclass behaviors. • Interfaces, i.e. classes that contain only pure virtual functions. This is equivalent to Java’s interfaces. • Contrary to Java interfaces, C++ abstract classes can provide a definition for some of their methods, and can have non-constant data members. classCShape{ protected: double area; string name; public: CShape() : name("anonymous"){ } CShape(stringnewName) : name(newName){ } stringgetName(){ return name; } virtualdoublegetArea()= 0; virtualvoid print(){ cout << "Shape:" << name << endl; cout << "Area: " << getArea() << endl; } }; classIserialisable { public: virtualvoid load(istream& in) = 0; virtualvoid save(ostream& out) = 0; virtual ~serialisable(); } Joey Paquet, 2007-2014

  16. Object slicing: value semantics and reference semantics Joey Paquet, 2007-2014

  17. Object slicing classPet { public: string name; Pet() {} Pet(constPet& aPet) : name(aPet.name) { cout << "In Pet's copy constructor" << endl; } Pet(stringnewName) : name(newName){} virtual ~Pet(){} virtualvoid print() const { cout << "In Pet::print()" << endl; cout << "name: " << name << endl << endl; } Pet & operator= (constPet & otherPet){ cout << "In Pet's assignment operator" << endl; name = otherPet.name; return *this; } }; classDog : publicPet { public: string breed; Dog() : Pet() {} Dog(constDog& aDog) : Pet(aDog), breed(aDog.breed) { cout << "In Dog's copy constructor" << endl; } Dog(stringnewName, stringnewBreed) : Pet(newName), breed(newBreed){} virtualvoid print() const { cout << "In Dog::print(): " << endl; cout << "name: " << name << endl; cout << "breed: " << breed << endl << endl; } Dog & operator= (constDog & otherDog){ cout << "In Dog's assignment operator" << endl; Pet::operator=(otherDog); breed = otherDog.breed; return *this; } }; Joey Paquet, 2007-2014

  18. Object slicing • Anything that is a Dogis also a Pet: • Can assign values to parent-types, but not inversely • A Petis not necessarily a Dog • However, the values assigned to vpet1 and vpet2 lose their breed field • Called the slicing problem • This is due to the fact that value semantics was used in the operations to assign/copy the object in the operation. The resulting copy is a Pet that has lost its identity as a Dog. • When using pointers or references, reference semantics is used, which does not result in slicing and enables polymorphism. Dog vdog1("Tiny", "GreatDane"); Pet vpet1; cout << "Use of Pet's copy constructor:"; Pet vpet2(vdog1); cout << "Use of Pet's assignment operator:"; vpet1 = vdog1; Joey Paquet, 2007-2014

  19. Value semantics: object slicing cout << "\nUsing value semantics results in the slicing problem\n"; cout << "and is not very interesting as it does not enable polymorphism.\n\n"; Dog vdog1("Tiny", "GreatDane"); Pet vpet1; cout << "Use of Pet's copy constructor:"; Pet vpet2(vdog1); cout << "Use of Pet's assignment operator:"; vpet1 = vdog1; cout << "The Dog has been sliced into a Pet\n"; cout << "due to the value semantics used in\n"; cout << "the assignment operation.\n"; cout << "vdog1.print() uses Dog::print():\n"; vdog1.print(); cout << "vpet1.print() uses Pet::print():\n"; vpet1.print(); cout << "vpet2.print() uses Pet::print():\n"; vpet2.print(); cout << endl << "Passing a Dog received as a Pet "; cout << "using value semantics:" << endl; valueSemantics(vdog1); voidvalueSemantics(constPetaPet) { // As this function receives its parameter as value, // it is copied into a Pet value, and then always // calls Pet::print(). aPet.print(); } Joey Paquet, 2007-2014

  20. Reference semantics: polymorphism cout << "\nUsing reference semantics does not result in the slicing problem\n"; cout << "and it allows to use polymorphism through late binding.\n\n"; Dog *pdog1 = newDog("Tiny", "GreatDane"); Pet *ppet1 = newPet; cout << "Use of Dog and Pet's copy constructors:\n"; Pet *ppet2 = newDog(*pdog1); cout << "Use of Pet's copy constructor:\n"; Pet *ppet3 = newPet(*pdog1); cout << "ppet1 = pdog1 does not use "; cout << "Pet`s assignment operator\n"; ppet1 = pdog1; cout << "pdog1.print() uses Dog::print():\n"; pdog1->print(); cout << "ppet1.print() uses Dog::print():\n"; ppet1->print(); cout << "ppet2.print() uses Dog::print():\n"; ppet2->print(); cout << "ppet3.print() uses Pet::print():\n"; ppet3->print(); cout << "Passing a Dog received as a Pet using"; cout << " reference semantics" << endl; referenceSemantics(vdog1); void referenceSemantics(constPet& aPet) { // As this function receives its parameter // as a reference, its original type is kept // in the value the reference parameter // refers to and thus can polymorphically // call print(). aPet.print(); } Joey Paquet, 2007-2014

  21. Function pointers Joey Paquet, 2007-2014

  22. Function pointers • Function pointers are pointers, i.e. variables, which point to the starting address of a function’s code. • Enables the call to different functions, depending on the function pointer value, which can be pointing to different functions’ code in time. • In C++, a function pointer can only point to different functions with the same signature (single dispatch mechanism). Single dispatch assumes that the same number/types of values are passed/returned during a function call. • multiple dispatch – dispatch to any function (independent of signature), with a an elaborated mechanism for mapping parameters/return value. Joey Paquet, 2007-2014

  23. Function pointers float Plus(floata, floatb) {returna + b;} float Minus(floata, floatb) {returna - b;} float Multiply(floata, floatb) {returna * b;} float Divide(floata, floatb) {returna / b;} float Switch(floata, floatb, charopCode) { float result; // execute operation switch (opCode) { case'+': result = Plus(a, b); break; case'-': result = Minus(a, b); break; case'*': result = Multiply(a, b); break; case'/': result = Divide(a, b); break; } return result; } floatSwitch_With_Function_Pointer(floata, floatb, float(*ptFunc)(float, float)) { // call using function pointer float result = ptFunc(a, b); return result; } int main(){ //function pointer declaration float(*op)(float, float); //assign the address of a particular function op = &Plus; cout << "Switch: 1+2="; cout << Switch(1, 2, '+') << endl; cout << "Switch_With_Function_Pointer: 1+2="; cout << Switch_With_Function_Pointer(1, 2, op) << endl; int i; cin >> i; } Joey Paquet, 2007-2014

  24. Virtual function tables Joey Paquet, 2007-2014

  25. Polymorphism: virtual function tables • C++ implements virtual functions through a form of late binding using virtual function tables, also known as virtual method tables, or vtables. • Every class that includes or inherits at least one virtual function is assigned a virtual function table. • A vtable is a static array of function pointers. • There is one function pointer for each virtual function declared or inherited in the class. • Each pointer points to the function belonging to the most derived class that implements this function. • For pure virtual functions, one can assume that the vtable contains a NULL pointer. Some implementations implement a pointer to a special function that may throw an exception upon call. • Every object created that belongs to a class hierarchy having virtual functions keeps its own vtable that is used to resolve its function calls at run time. • When this object is assigned to a variable of one of its supertypes, its own vtable is used to resolve the function calls. Joey Paquet, 2007-2014

  26. Polymorphism: virtual function tables classBase { public: virtualvoid function1(); virtualvoid function2(); virtualvoid function3() = 0; void function4(); }; classDerived1 : publicBase { public: void function1(); virtualvoid function5(); void function6(); }; classDerived2 : publicBase { public: void function2(); void function3(); void function4(); void function5(); }; classDerived3 : publicDerived1 { public: void function3(); }; Joey Paquet, 2007-2014

  27. Polymorphism: virtual function tables int main(){ Base *baseArray[] = { newDerived2, newDerived3 }; cout << "baseArray[0]->function1(): " << endl; baseArray[0]->function1(); cout << "baseArray[0]->function2(): " << endl; baseArray[0]->function2(); cout << "baseArray[0]->function3(): " << endl; baseArray[0]->function3(); cout << "baseArray[0]->function4(): " << endl; baseArray[0]->function4(); //baseArray[0]->function5(); //no function5 in Base cout << "dynamic_cast<Derived2*>(baseArray[0])"; cout << "->function5(): " << endl; dynamic_cast<Derived2*>(baseArray[0])->function5(); cout << "baseArray[1]->function1(): " << endl; baseArray[1]->function1(); cout << "baseArray[1]->function2(): " << endl; baseArray[1]->function2(); cout << "baseArray[1]->function3(): " << endl; baseArray[1]->function3(); cout << "baseArray[1]->function4(): " << endl; baseArray[1]->function4(); //baseArray[1]->function5(); cout << "dynamic_cast<Derived3*>(baseArray[1])->function5(): " << endl; dynamic_cast<Derived3*>(baseArray[1])->function5(); //baseArray[1]->function6(); cout << "dynamic_cast<Derived3*>(baseArray[1])->function6(): " << endl; dynamic_cast<Derived3*>(baseArray[1])->function6(); } Joey Paquet, 2007-2014

  28. Virtual inheritance and the diamond problem Joey Paquet, 2007-2014

  29. Multiple use of a class in a hierarchy • Multiple inheritance allows you to use more than once the same abstract class in a class hierarchy, similarly to Java’s Interfaces. • Here, IComm is an abstract class having only pure virtual methods and no data members and will not be instantiated. • You may also want to use the same non-abstract class more than once in a hierarchy. • Here, Recorder is a class that is used to record all the communications emitted or received. There is one store for emission, and another store for reception. Joey Paquet, 2007-2014

  30. Diamond problem • But what if what you really want is a single Store that records all received/emitted messages? • In C++, if you set both Transmitter and Receiver to inherit from Recorder, both of them will by default have their own copy of a different Recorder object in memory. • This is going to also lead to ambiguities when, e.g. a WalkieTalkie object refers to a member of Recorder. As there are in fact two separate stores. • This is called the diamond problem. • In order to solve both of these problems, C++ offers what is called virtual inheritance. Joey Paquet, 2007-2014

  31. Virtual inheritance #include<iostream> usingnamespacestd; classA1 { inti; public: A1() { cout << "in A1::A1()\n"; }; A1(intk) : i(k){ cout << "in A1::A1(int)\n"; } }; classA :virtualpublicA1 { public: A() {cout<<"in A::A()\n";}; A(intk) : A1(k){ cout << "in A::A(int)\n";} }; classB : publicA { public: B(){cout<<"in B::B()\n";}; B(inti) : A1(), A(i) { cout << "in B::B(int)\n"; } }; classC : publicA { public: C(){ cout << "in C::C()\n"; }; C(inti) : A(i) { cout << "in C::C(int)\n"; } }; classD : publicB, C{ public: D() { cout << "in D::D()\n"; } D(inti) : A1(i), B(i), C(i) { cout << "in D::D(int)\n"; } }; int main() { D d1(2); D d2; inti; cin >> i; return 0; } • To signify virtual inheritance, the classes that are to share a same instance of a class in a hierarchy have to use the virtual keyword in their inheritance specification. • This changes how the objects of the most derived class are created, and how their constructors call each other implicitly or explicitly. • The class responsible to call the constructor on the virtually inherited class is the first derived class that includes all instances of a virtually inherited class. • In the example here, it is the class D. • Any other call to a constructor of a virtually inherited class up the hierarchy is ignored. Joey Paquet, 2007-2014

  32. Virtual inheritance #include<iostream> usingnamespacestd; classV { inti; public: V() { cout << "in V::V()\n"; }; V(inti) : i(i){ cout << "in V::V(int)\n"; } }; classA : virtualpublicV { public: A() { cout << "in A::A()\n"; }; A(inti) : V(i) { cout << "in A::A(int)\n"; } }; classB : virtualpublicV { public: B(){ cout << "in B::B()\n"; }; B(inti) : V(i) { cout << "in B::B(int)\n"; } }; classC : publicA, B { public: C(){ cout << "in C::C()\n"; }; C(inti) : A(i), B(i) { cout << "in C::C(int)\n"; } }; classD : virtualpublicV { public: D() { cout << "in D::D()\n"; } D(inti) : V(i) { cout << "in D::D(int)\n"; } }; classE : publicC, D { public: E() { cout << "in E::E()\n"; } E(inti) : V(i), C(i), D(i) { cout << "in E::E(int)\n"; } }; int main() { E e1(2); E e2; inti; cin >> i; return 0; } Joey Paquet, 2007-2014

  33. References • Mark Radford. C++ Interface Classes - An Introduction. Overload Journal #62 - Aug 2004. • LearnCPP.com The virtual table. • Andrei Milea. Solving the Diamond Problem with Virtual Inheritance. Cprogramming.com. • Walter Savitch. Polymorphic and Virtual Functions in C++. Informit, Pearson. March 2002. • Robert C. Martin. Java and C++, A critical comparison. March 1997. • Andrzej's C++ blog. Value semantics. February 2013. Joey Paquet, 2007-2014

More Related