SlideShare a Scribd company logo
1 
(7) C++ Abstractions 
Nico Ludwig (@ersatzteilchen)
2 
TOC 
● (7) C++ Abstractions 
– C++11: Explicit Overrides and final Methods 
– Run Time Type Identification (RTTI) and dynamic_cast 
– protected Members 
– Abstract Types with pure virtual Member Functions 
– Virtual Destructors 
– Inheritance and Operators 
● Sources: 
– Bjarne Stroustrup, The C++ Programming Language 
– John Lakos, Large-Scale C++ Software Design
3 
// Non-virtual member function: 
void StartEngine() { /* pass */ } 
C++11 – explicitly overridden methods 
class DieselCar : public Car { 
public: 
// Invalid! Results in compiler error: doesn't override an inherited method! 
void StartEngine() override { /* pass */ } 
}; 
C++11: Explicit Overrides 
● In C++ an "overriding method" is not marked as overriding a base type's method. 
– The problem: sometimes non-virtual functions are meant to be overridden by accident. 
class Car { 
public: 
}; 
● C++11 allows explicit specification of overriding methods. 
– Just add the "override"-specifier to the method: 
– The override specifier can only be used on inherited methods. 
class DieselCar : public Car { 
public: 
// Oops! Doesn't override Car::StartEngine()! 
void StartEngine() { /* pass */ } 
}; 
● Sometimes the keyword virtual is also written on the 
overriding methods (in this example it could be 
written on the method DieselCar::StartEngine()). It's 
done because of tradition and in order to mark that 
the method overrides another method. - The new 
C++11-way to mark overrides is the override-specifier, 
this mark is also checked by the compiler. 
● The override keyword must not be repeated on a 
non-inline definition.
4 
C++11: Explicit Final Methods 
● Sometimes it is not desired to let a method be overridden in subtypes. 
● In C++11 we can mark methods as being non-overridable in a base type. 
– Just add the "final"-specifier on the method in the "leaf type". 
class Car { 
public: 
// Method in the base type: 
virtual void StartEngine() { /* pass */ } 
}; 
– The final specifier can only be used on inherited methods. 
C++11 – non-overridable methods 
class DieselCar : public Car { 
public: 
// Mark a method as being non-overridable: 
void StartEngine() final { /* pass */ } 
}; 
class SpecialDieselCar : public DieselCar { 
// Invalid! Results in compiler error: can't override a non-overridable method! 
void StartEngine() override { /* pass */ } 
}; 
● The final keyword must not be repeated on a non-inline 
definition.
aBus.???? nOccupiedSeatBenches 
5 
The Size of inherited Types 
● When a UDT inherits from another UDT, their sizes sum up. 
– I.e. the sizes of all fields (incl. private fields) contribute to the final sum. 
class Car { // UDTs Engine and Tyre elided. 
Engine* theEngine; 
Tyre* spareTyre; 
public: 
void StartEngine() { /* pass */ } 
}; 
sizeof(Car): 8 
// Bus inherits from Car: 
class Bus : public Car { // (members hidden) 
int nOccupiedSeatBenches; 
public: 
/* pass */ 
}; 
sizeof(Bus): 12 // sizeof(int) + sizeof(Car) 
aCar 
● The sizes of polymorphic types could be different! Let's inspect this effect... 
aCar.spareTyre 
aCar.theEngine 
4B 
8B 
???? 
???? 
aBus 
aBus.spareTyre 
aBus.theEngine 
12B ???? 
????
The Size of polymorphic Types w/ virtual Member Function Tables 
6 
● Interestingly, the "sum of field sizes" can be different for polymorphic types. 
class PolyCar { // In PolyCar StartEngine() is virtual. 
Engine* theEngine; 
Tyre* spareTyre; 
public: 
virtual void StartEngine() { /* pass */ } 
}; 
sizeof(PolyCar): 12 // Huh? (LLVM GCC 4.2) 
● Some C++ distributions implement methods with virtual (member) function tables. 
– A virtual function table (vtable) is accessible as pointer field in a polymorphic type. 
– Vtable and pointer are added by the compiler. (They're not accessible programatically.) 
class PolyCar { // … what the compiler generated 
ImplementationSpecific* __vtptr; 
Engine* theEngine; 
Tyre* spareTyre; 
public: 
virtual void StartEngine() { /* pass */ } 
0xbffff918 aPolyCar.__vtptr 
}; 
sizeof(PolyCar): 12 // Aha! (LLVM GCC 4.2) aPolyCar 
aPolyCar.spareTyre 
aPolyCar.theEngine 
4B 
12B ???? 
???? 
● The implementation of methods is compiler 
specific. The vtable implementation is used very 
often, it is the basis for the object system in the 
Microsoft technologies COM and .Net. 
● Polymorphic types are no longer PODs, as their 
sizes and layouts are no longer predictable. The C+ 
+11 type trait std::is_pod() will return false for 
polymorphic types.
7 
Virtual Member Function Tables – Oversimplification! 
class PolyCar { // UDTs Engine and Tyre elided. 
Engine* theEngine; 
Tyre* spareTyre; 
● For each polymorphic type a vtable is created. 
– It lists all methods of that type as function pointers. 
PolyCar* polyCar = new PolyCar; 
polyCar->StartEngine(); 
– Each instance of the polymorphic type has a pointer (__vtptr) to the vtable. 
● A derived type inherits the vtables well. 
PolyCar::StartEngine() 
PolyCar* bus = new Bus; 
bus->StartEngine(); 
– The compiler replaces inherited function pointers by pointers to overridden methods. 
– Non-overridden methods are left as inherited function pointers in the vtable. 
public: 
virtual void StartEngine() { /* pass */ } 
}; 
// Bus overrides StartEngine(): 
class Bus : public PolyCar { // (members hidden) 
int nOccupiedSeatBenches; 
public: 
void StartEngine() { /* pass */ } 
}; 
PolyCar's virtual function table 
polyCar->__vtptr 
Bus::StartEngine() 
Bus' virtual function table 
bus->__vtptr
8 
Late Binding – Polymorphism – and a Layer of Indirection 
● The procedure of selecting the correct vtable entry during run time is often called (dynamic) dispatch or late binding. 
– We have to clarify another aspect of this procedure: the variables. 
● After we've an idea of how polymorphism works, let's review these lines of code: 
– The difference between static and dynamic type is to be strengthened here. 
PolyCar* bus = new Bus; 
bus->StartEngine(); 
● Polymorphism only works on pointers or references of polymorphic types! 
– Polymorphism: ability to call methods of a dynamic type through a static type. 
– "Through a static type" means through a pointer or reference of static type. 
● To make polymorphism work respective variables must be pointers or references. 
– It is often said "an extra layer of indirection" is required for polymorphism to work. 
● Polymorphism happens during run time and 
information like the vtables need to be evaluated 
during run time to make method dispatching work. 
In other words: calling methods is more costly than 
calling non-virtual member functions.
9 
Run Time Type Identification (RTTI) with the typeid-Operator 
● Concerning polymorphism we've discussed objects' static and dynamic types. 
– In a former example we've used type flags to get information about dynamic types. 
– (But then we learned about virtual member functions, which are the better alternative.) 
● In C++ we can directly get the dynamic type of an object of polymorphic type. 
– The dynamic type of an object can be retrieved with Run Time Type Identification (RTTI): 
Car* car = new VintageCar; // Let's car point to a VintageCar. 
std::cout<<std::boolalpha<<"car points to a VintageCar: "<<(typeid(VintageCar) == typeid(*car))<<std::endl; 
// >car points to a VintageCar: true 
car = new Bus; // Let's car point to a Bus. 
std::cout<<std::boolalpha<<"car points to a Bus: "<<(typeid(Bus) == typeid(*car)) 
<<"; car points to a VintageCar: "<<(typeid(VintageCar) == typeid(*car))<<std::endl; 
// >car points to a Bus: true; car points to a VintageCar: false 
– The operator typeid retrieves information about the dynamic type of an object. 
– Here we use typeid to make the difference between static and dynamic type visible: 
● The pointer car can point to an object of any subtype of Car. Here: Bus, then VintageCar. 
– typeid can only be used on polymorphic types! 
● The operator typeid returns an object of type 
std::type_info (declared in <typeinfo>). 
● std::type_info objects can not be created or 
assigned to from using typeid (std::type_info's 
copy assignment and the cctor are both non-public). 
● The only interesting thing we can do with 
std::type_info is directly ==/!=-comparing the 
results of two typeid calls. 
● It is typically an error to use typeid to compare 
pointer types! 
● There is also a member function 
std::type_info::name() that returns a (arbitrary and 
non-portable) string representation of a type. This 
function must also be directly called on the result 
of the typeid operator.
10 
Garage::TryToStartCar() with the typeid-Operator 
● It's possible to reimplement Garage::TryToStartCar() with RTTI: 
void Garage::TryToStartCar(Car* car) const { 
if (typeid(VintageCar) == typeid(*car)) { // If car's dynamic type is VintageCar start it specially. 
VintageCar* vintageCar = static_cast<VintageCar*>(car); // Downcasting! 
if (!vintageCar->HasStarter()) { 
vintageCar->CrankUntilStarted(); 
} else { 
vintageCar->StartEngine(); 
} 
} else { 
car->StartEngine(); // Start other cars just by calling StartEngine(). 
} 
} 
– This implementation neglects the presence of methods, but uses RTTI and downcasts! 
● (We already learned that this is really bad: "pasta-object-orientation"!) 
● With this implementation of Garage::TryToStartCar() we can start fordThinLizzie: 
VintageCar* fordThinLizzie = new VintageCar(1909); 
joesStation.TryToStartCar(fordThinLizzie); // Ok! TryToStartCar() will pick the correct start-algorithm 
// depending on the run time type!
11 
Garage::TryToStartCar() with the dynamic_cast-Operator 
● Instead of RTTI (i.e. typeid), we can use dynamic_cast for polymorphic UDTs: 
void Garage::TryToStartCar(Car* car) const { 
const VintageCar* vintageCar = dynamic_cast<VintageCar*>(car); 
if (0 != vintageCar) { 
/* pass */ 
} 
/* pass */ 
} 
● dynamic_cast tries to cast the passed pointer or reference to the specified type. 
– Basically it tries to "cast the dynamic type out of the pointer or reference". 
– It works like a combination of typeid comparison and static_cast-based downcasting. 
– It returns a valid pointer or reference to the specified type if the cast worked. 
– It returns 0 for pointers or throws std::bad_cast for references if the cast didn't work. 
– The operator only works with polymorphic types, else we'll get a compile time error. 
● But using dynamic_cast is basically the same as the typeid-else if-procedure. 
– Better use virtual member functions (methods)! RTTI and dynamic_cast should be avoided! 
● In opposite to static_cast, dynamic_cast is type-checked 
at run time! It's type safe to use 
dynamic_cast for downcasting! 
● dynamic_cast is the only cast that can't be 
expressed with the older cast syntaxes. 
● The explanation why we didn't use const& as 
parameter types here (kind of breaking the rule 
we've defined lately) is, that we'd have to deal with 
std::bad_cast exceptions in this case (instead of 0- 
pointers). - And we didn't discuss exceptions yet. 
● There is the saying "polymorphism is always better 
than branching". In this example the usage of 
dynamic type analysis (with RTTI or dynamic_cast) 
and if-else cascades would be the "branching". - 
This approach requires to add new dynamic types 
to be potentially handled in Garage::TryToStartCar() 
and this is a disapproving approach; always prefer 
polymorphism! - Design patterns, which are often 
based on polymorphism, help to implement even 
complex problems w/o dynamic type analysis.
12 
Protected Family Secrets 
● Let's review the type VintageCar: 
class VintageCar : public Car { // (members hidden) 
public: 
bool CrankUntilStarted() { /* pass */ } 
bool HasStarter() const { /* pass */ } 
void StartEngine(); 
}; 
● There are some points to be thought of: 
– HasStarter() and CrankUntilStarted() are only used in VintageCar::StartEngine(). 
– HasStarter() could be defined in the base type Car, as each Car could have a starter. 
– CrankUntilStarted() should be declared private, being encapsulated by VintageCar. 
● But some problems arise with the proposed modifications: 
– Car::HasStarter() should not be visible to the public, but to its derived types ("family"). 
– Maybe VintageCar::CrankUntilStarted() should be accessible by derived types as well. 
– To solve these issues we can use the access specifier "protected". 
void VintageCar::StartEngine() const { 
if (HasStarter()) { /* pass */ 
} else { 
while (CrankUntilStarted()) { /* pass */ } 
} 
} 
● We can also apply protected inheritance in C++.
13 
Protected Member Functions 
● Let's redesign Car/VintageCar: 
– Move HasStarter() to the type Car and mark it as being protected. 
– Make VintageCar::CrankUntilStarted() protected as well. 
– Then both member functions are only visible in the "family", not to the public. 
class Car { // (members hidden) 
protected: 
bool HasStarter() const { /* pass */ } 
public: 
virtual void StartEngine() { /* pass */ } 
}; 
class VintageCar : public Car { // (members hidden) 
protected: 
bool CrankUntilStarted() { /* pass */ } 
public: 
void StartEngine() { 
if (HasStarter()) { /* pass */ 
} else { 
while (CrankUntilStarted()) { /* pass */ } 
} 
} 
}; 
Car 
# HasStarter() : bool 
+ StartEngine() 
VintageCar 
# CrankUntilStarted() : bool 
+ StartEngine() 
The member functions 
HasStarter() and 
CrankUntilStarted() are 
protected, mind the "#" 
notation.
14 
Intermezzo: Inheritance for white-box Reuse 
● Aggregation: Using a Car's public interface is black-box reuse. 
– All the critical aggregated stuff is encapsulated, declared private or protected. 
● Inheritance: Using a Car's protected interface is white-box reuse. 
– Subtyping is needed to access the protected stuff. 
– Subtypes have to know how to work with protected members! 
– Subtyping breaks encapsulation to certain degree! 
– Never ever use inheritance for plain reuse, this is an antipattern! 
● Inherited/derived types have access to public and protected members of the base types. 
– protected members are only accessible within the "family". 
● E.g. accessing or handling the start-system of Cars (e.g. HasStarter()) is too critical to be public. 
● Car's subtypes must know how to use the start-system (e.g. the subtype VintageCar needed to handle the start-system in a different way w/ cranking). 
● In fact the protected members of our types should 
be as good documented (e.g. via comments) as the 
public members to make white-box reuse possible!
15 
Abstract Types 
● There are types that are too abstract to have instances. 
– This means that some methods can not provide suitable (default) implementations. 
● E.g. a new UDT Vehicle is more abstract than Car: 
– Vehicle is too abstract of its own, we should not assume that it has an Engine, or even a SpareTyre... it could be left away. 
(StartEngine() also makes no sense!) 
– More concrete subtypes (as Car) could fill, what we left away in Vehicle. 
– Vehicle is even too abstract to provide a default implementation for e.g. Drive(). 
● Abstract types in C++: 
– UDTs can provide methods without implementation. 
– These methods are called abstract methods. 
– Abstract methods are defined as pure virtual member functions in C++. 
– Abstract types allow to abstract a type's usage from its implementation. 
– Now let's implement the abstract type Vehicle...
16 
The abstract Type Vehicle 
● A C++ implementation of the abstract type Vehicle might look like this: 
● An inheriting type can override/implement abstract methods. 
– Inheriting UDTs that don't implement all abstract methods become itself abstract UDTs! 
● I.e. Bike is an abstract UDT! 
// The abstract UDT Vehicle: 
class Vehicle { 
public: 
// A pure virtual member function: 
virtual void Drive() = 0; 
}; 
// The concrete UDT Car: 
class Car : public Vehicle { // (members hidden) 
public: 
// Override Drive() const: 
void Drive() { 
std::cout<<"zooming off..."<<std::endl; 
} 
}; 
Vehicle 
+ Drive() 
Vehicle 
{abstract} 
+ Drive() {abstract} 
Abstract classes' and 
operations' titles are 
written in italics. 
Alternatively abstract 
classes and operations 
can also be marked with 
the abstract constraint. 
// The abstract UDT Bike: 
class Bike : public Vehicle { 
public: 
// Doesn't override Drive() const: 
void ChangeUp() { /* pass */ } 
};
17 
Implementation and Usage of abstract Types 
● Implementation side of abstract types: 
– Assign 0 to the method declaration in the top level abstract type. 
● Inherited abstract methods must be overridden in concrete types. 
● Inherited abstract methods need not to be overridden in abstract types. 
● Usage side of abstract types: 
– No instances of abstract UDTs can be created; Vehicle is just a too abstract type. 
Vehicle* vehicle = new Vehicle; // Invalid! Instance of abstract UDT can't be created. 
– The only idea behind abstract types is to support polymorphism. 
– The is-a relationship holds true: A Car is a Vehicle and a RacingBicycle is a Vehicle. 
● The substitution works nicely with abstract types! - We're going to revisit this idea! 
void Move(Vehicle* vehicle) { 
vehicle->Drive(); 
} 
Move(new Car); 
// >zooming off... 
Move(new RacingBicycle); 
// >woosh... 
// The concrete UDT RacingBicycle: 
class RacingBicycle : public Bike { 
public: 
void Drive() { 
std::cout<<"woosh..."<<std::endl; 
} 
};
18 
Abstract default Implementations 
● Abstract methods are allowed to provide a default implementation. 
– The implementation must be provided as non-inline member function definition. 
// The abstract UDT Vehicle: 
class Vehicle { 
public: 
// A pure virtual member function: 
virtual void Drive() = 0; 
}; 
// A default implementation for inheriting types: 
void Vehicle::Drive() { // Mind: it must be a non-inline member function! 
std::cout<<"driving..."<<std::endl; 
} 
● Inheriting types are allowed to call this default implementation like so: 
// Override Drive() const: 
void Car::Drive() { 
Vehicle::Drive(); // Calls inherited default implementation: 
std::cout<<"zooming off..."<<std::endl; 
} 
Car car; 
car.Drive(); 
// >driving... 
// >zooming off... 
– Calling Car::Drive() shows that it works:
19 
Why is Abstraction needed? 
● OO programmers design models that simulate reality. 
– Abstraction means to leave out irrelevant details from the model. 
– Some degree of detail is sufficient. Capturing "al" details is impossible (computer memory, time and expert knowledge is limited). 
– OO seems to be appropriate for big projects to get rid of the "academic touch". 
● A vendor's framework only abstracts a certain (the vendor's) view of a model. 
– Multiple abstraction solutions can be correct for a certain task. 
● Delegation, information hiding, encapsulation and substitution help to abstract. 
– This means do also help to postpone or defer details. 
● A core idea of oo development is to accept types as incomplete. 
– These types could be extended incrementally and iteratively. 
● This is called incremental and iterative software engineering. 
● Abstraction from lat. abstrahere: "to remove 
something". 
● What does "incremental and iterative" mean? 
● Incremental: the engineering will be performed in 
steps. 
● Iterative: some parts of the engineering process 
will be repeated to improve these aspects.
20 
Calling the Ctors of base Classes 
● If the base types have dctors they'll be called by the ctors of subtypes: 
class Car { // (members hidden) 
public: 
Car() { 
std::cout<<"Car::Car()"<<std::endl; 
} 
● If a base type doesn't provide a dctor, nothing can be implicitly called of course! 
– Instead we have to call another ctor of the base type in the subtype's ctor explicitly. 
● Syntactically we have to call ctors of the base types in the initializer list of a subtype's ctor: 
}; 
class Bus : public Car { // (members hidden) 
public: 
/* pass */ 
}; 
Bus bus; // Invalid! Base type class Car { // Car w/o dctor! Car provides no dctor. 
public: 
Car(double power) { /* pass */ } 
}; 
class Bus : public Car { 
public: 
// Ctor, which calls one of Car's ctors. 
Bus(double power) : Car(power) { /* pass */ } 
}; 
Bus bus; 
// >Car::Car() // Calls Car's dctor implicitly. 
Bus bus(320); // Fine!
21 
Inheritance and Destructors 
● A derived UDT accesses the dtors of its base types on destruction. 
class A { 
public: 
~A() { 
std::cout<<"~A"<<std::endl; 
} 
}; 
class B : public A { 
public: 
~B() { 
} 
}; 
– When the dtor of an object is called, the base types' dtors are also called: 
{ // With RAII: 
B b; 
} 
// >~B // The dtors are called from the most 
// >~A // special to the most general type. 
● But there is a problem: it doesn't work on dynamic types! 
// With dynamic allocation: 
B* b = new B; 
delete b; 
// >~B 
// >~A 
A* a = new B; // a points to a B-instance that must be destroyed. 
delete a; 
// >~A // Oops! Only the dtor of the static type is called! 
– Obviously dynamic dispatch doesn't work on dtors! 
std::cout<<"~B"<<std::endl; 
– But the solution is simple: we have to make the base types' dtors polymorphic!
22 
Virtual Destructors 
● The solution is to make the base types' dtors virtual: 
class A { 
public: 
virtual ~A() { 
std::cout<<"~A"<<std::endl; 
} 
}; 
– Then the destruction works as it should: 
● Virtual dtors: 
class B : public A { 
public: 
– C++ expresses polymorphism by the separation of static and dynamic types. 
~B() { 
– virtual member functions are called on static types being dispatched at run time. 
– Let's stipulate that we await polymorphic calls to be effective on the dynamic type. 
– This should also the case for dtors: a polymorphic type should have a virtual dtor! 
std::cout<<"~B"<<std::endl; 
} 
}; 
A* a = new B; // a points to a B-instance that must be destroyed. 
delete a; 
// >~B // Fine! The base type's dtor is called. 
// >~A 
● We can also have pure virtual dtors.
23 
Inheritance and Operators 
● Derived types don't inherit all members of its base types, because it would introduce inconsistencies. Not inherited are: 
– ctors and the non-virtual dtor, 
– assignment operators and 
– friends. 
● Not inherited just means, that those members are no part of the public interface of the derived type! 
– But the implementations of those members are still "callable" in a derived type! - So we can reuse them! 
● However, usually the operators of base types are called in the implementation of those in the derived type. 
– This example shows the idiomatic "delegation" of operator calls to reuse the functionality of base types. 
class SpecialPerson : public Person { // (types hidden) 
public: 
SpecialPerson(const SpecialPerson& original) : Person(original) { // Just forward to the cctor of the base type. 
} 
SpecialPerson& operator=(const SpecialPerson& rhs) { 
if (this != &rhs) { 
Person::operator=(rhs); // Just call copy-operator= of the base type. 
} // Mind, that the result of Person::operator= can't be returned, because the 
return *this; // return type is too general! We call Person::operator= only for the side effect 
} // on this! 
};
24 
The SOLID Principle 
● Robert Cecil Martin ("Uncle Bob") et al. collected five principles for oo design. 
– These principles guide us through the design of "good" oo code. 
● Single Responsibility Principle (SRP) 
● Open-Close Principle (OCP) 
● Liskov Substitution Principle (LSP) 
● Interface Segregation Principle (ISP) 
● Dependency Inversion Principle (DIP) 
● SRP: Types should have only one reason to change. This 
requirement also applies to methods. 
● OCP: Open types for extension, but close them for 
modification, i.e. extend them w/o modification of the core 
code, rather code will be added (e.g. via polymorphism). 
If we need to modify a type's interface, the calling code 
needs to be modified (ripple effect), which is expensive. 
Separate things that change, from others that don't (i.e. 
find the vector of change to drive design patterns). 
● LSP: Exploit subtyping to put the substitution principle 
into effect. 
● ISP: Use small type interfaces to which callers depend 
on, so that changes in other type interfaces don't bother 
them. 
● DIP: Let types only depend on abstract types (the 
contracts), never on concrete types (the implementation) 
to reduce coupling. Layers of lower abstraction (more 
concrete type) depend on layers of higher abstraction 
(more abstract types), never vice versa. 
● The SOLID principle enumerates a set of guidelines, not 
laws.
25 
Thank you!

More Related Content

PPTX
P/Invoke - Interoperability of C++ and C#
PDF
Le langage rust
PDF
Part II: LLVM Intermediate Representation
PDF
Implementing one feature set in two JavaScript engines (Web Engines Hackfest ...
PDF
are Code Katas always Agile ?
PPT
Introduction to llvm
ODP
OOP in C - Before GObject (Chinese Version)
PDF
Cling the llvm based interpreter
P/Invoke - Interoperability of C++ and C#
Le langage rust
Part II: LLVM Intermediate Representation
Implementing one feature set in two JavaScript engines (Web Engines Hackfest ...
are Code Katas always Agile ?
Introduction to llvm
OOP in C - Before GObject (Chinese Version)
Cling the llvm based interpreter

What's hot (20)

PPTX
C Programming Language Step by Step Part 1
PDF
C++11 concurrency
PDF
Objective-C to Swift - Swift Cloud Workshop 3
PPTX
Kotlin For Android - Properties (part 4 of 7)
PDF
OOP in C - Inherit (Chinese Version)
PPTX
PDF
[Apostila] programação arduíno brian w. evans
PPT
C++ polymorphism
PPTX
Review: Apitrace and Vogl
PDF
Comparing IDL to C++ with IDL to C++11
PDF
Operator overloading
PDF
CORBA Programming with TAOX11/C++11 tutorial
PPTX
Pure virtual function and abstract class
PPTX
C programming-apurbo datta
PPT
Verilog tutorial
PPT
C++ Introduction
PDF
VHDL- gate level modelling
PDF
Virtual Functions
PDF
김재석, C++ 프로그래머를 위한 C#, NDC2011
PPTX
Inline assembly language programs in c
C Programming Language Step by Step Part 1
C++11 concurrency
Objective-C to Swift - Swift Cloud Workshop 3
Kotlin For Android - Properties (part 4 of 7)
OOP in C - Inherit (Chinese Version)
[Apostila] programação arduíno brian w. evans
C++ polymorphism
Review: Apitrace and Vogl
Comparing IDL to C++ with IDL to C++11
Operator overloading
CORBA Programming with TAOX11/C++11 tutorial
Pure virtual function and abstract class
C programming-apurbo datta
Verilog tutorial
C++ Introduction
VHDL- gate level modelling
Virtual Functions
김재석, C++ 프로그래머를 위한 C#, NDC2011
Inline assembly language programs in c
Ad

Viewers also liked (15)

PPT
Basic c#
PPTX
Inheritance in C++
PPT
Inheritance
PPT
Inheritance C#
PPSX
PPS
Inheritance
PPTX
Inheritance in C++
PPT
C++: inheritance, composition, polymorphism
PPTX
Inheritance in oops
PPSX
PPT
Inheritance in c++ ppt (Powerpoint) | inheritance in c++ ppt presentation | i...
PPTX
Inheritance
PPT
Oops ppt
PPT
C++ Inheritance
PPTX
Oop c++class(final).ppt
Basic c#
Inheritance in C++
Inheritance
Inheritance C#
Inheritance
Inheritance in C++
C++: inheritance, composition, polymorphism
Inheritance in oops
Inheritance in c++ ppt (Powerpoint) | inheritance in c++ ppt presentation | i...
Inheritance
Oops ppt
C++ Inheritance
Oop c++class(final).ppt
Ad

Similar to (7) cpp abstractions inheritance_part_ii (20)

PDF
(6) cpp abstractions inheritance_part_i
PPT
UNIT IV (1).ppt
PPTX
Virtual function
PPT
Lecture6.ppt
PDF
polymorphism for b.tech iii year students
PPT
Polymorphism in C++ for beginners reference
PPTX
C++ Object Oriented Programming
PPTX
CMSC 202 - Lec16 - Polymorphisms(1).pptx
PPT
Polymorphism.pptthis is oops one of the most important feature polymorphism
PDF
Vladymyr Bahrii Understanding polymorphism in C++ 16.11.17
PPTX
07. Virtual Functions
PPT
Virtual Function
PDF
Classes-and-Objects-in-C++.pdf
PPTX
11.C++Polymorphism [Autosaved].pptx
PDF
L9
PPT
this is the concept in C++ under object oriented programming language "POLYMO...
PPT
C++ Interview Questions
PDF
polymorpisum-140106223024-phpapp01.pdf
PPTX
full defination of final opp.pptx
PPT
Unit iv
(6) cpp abstractions inheritance_part_i
UNIT IV (1).ppt
Virtual function
Lecture6.ppt
polymorphism for b.tech iii year students
Polymorphism in C++ for beginners reference
C++ Object Oriented Programming
CMSC 202 - Lec16 - Polymorphisms(1).pptx
Polymorphism.pptthis is oops one of the most important feature polymorphism
Vladymyr Bahrii Understanding polymorphism in C++ 16.11.17
07. Virtual Functions
Virtual Function
Classes-and-Objects-in-C++.pdf
11.C++Polymorphism [Autosaved].pptx
L9
this is the concept in C++ under object oriented programming language "POLYMO...
C++ Interview Questions
polymorpisum-140106223024-phpapp01.pdf
full defination of final opp.pptx
Unit iv

More from Nico Ludwig (20)

PPTX
Grundkurs fuer excel_part_v
PPTX
Grundkurs fuer excel_part_iv
PPTX
Grundkurs fuer excel_part_iii
PPTX
Grundkurs fuer excel_part_ii
PPTX
Grundkurs fuer excel_part_i
PDF
(2) gui drawing
ODP
(2) gui drawing
PDF
(1) gui history_of_interactivity
ODP
(1) gui history_of_interactivity
PDF
New c sharp4_features_part_vi
PDF
New c sharp4_features_part_v
PDF
New c sharp4_features_part_iv
ODP
New c sharp4_features_part_iii
PDF
New c sharp4_features_part_ii
PDF
New c sharp4_features_part_i
PDF
New c sharp3_features_(linq)_part_v
PDF
New c sharp3_features_(linq)_part_iv
ODP
New c sharp3_features_(linq)_part_iv
PDF
New c sharp3_features_(linq)_part_iii
PDF
New c sharp3_features_(linq)_part_ii
Grundkurs fuer excel_part_v
Grundkurs fuer excel_part_iv
Grundkurs fuer excel_part_iii
Grundkurs fuer excel_part_ii
Grundkurs fuer excel_part_i
(2) gui drawing
(2) gui drawing
(1) gui history_of_interactivity
(1) gui history_of_interactivity
New c sharp4_features_part_vi
New c sharp4_features_part_v
New c sharp4_features_part_iv
New c sharp4_features_part_iii
New c sharp4_features_part_ii
New c sharp4_features_part_i
New c sharp3_features_(linq)_part_v
New c sharp3_features_(linq)_part_iv
New c sharp3_features_(linq)_part_iv
New c sharp3_features_(linq)_part_iii
New c sharp3_features_(linq)_part_ii

Recently uploaded (20)

PPTX
MYSQL Presentation for SQL database connectivity
PPTX
KOM of Painting work and Equipment Insulation REV00 update 25-dec.pptx
PPTX
Machine Learning_overview_presentation.pptx
PPTX
Big Data Technologies - Introduction.pptx
PDF
The Rise and Fall of 3GPP – Time for a Sabbatical?
PDF
Approach and Philosophy of On baking technology
PDF
Network Security Unit 5.pdf for BCA BBA.
PDF
Blue Purple Modern Animated Computer Science Presentation.pdf.pdf
PDF
Dropbox Q2 2025 Financial Results & Investor Presentation
PDF
Profit Center Accounting in SAP S/4HANA, S4F28 Col11
PDF
cuic standard and advanced reporting.pdf
PDF
A comparative analysis of optical character recognition models for extracting...
PDF
MIND Revenue Release Quarter 2 2025 Press Release
PDF
Mobile App Security Testing_ A Comprehensive Guide.pdf
PDF
Review of recent advances in non-invasive hemoglobin estimation
PDF
Machine learning based COVID-19 study performance prediction
PPTX
ACSFv1EN-58255 AWS Academy Cloud Security Foundations.pptx
PDF
Peak of Data & AI Encore- AI for Metadata and Smarter Workflows
PPTX
sap open course for s4hana steps from ECC to s4
PPTX
20250228 LYD VKU AI Blended-Learning.pptx
MYSQL Presentation for SQL database connectivity
KOM of Painting work and Equipment Insulation REV00 update 25-dec.pptx
Machine Learning_overview_presentation.pptx
Big Data Technologies - Introduction.pptx
The Rise and Fall of 3GPP – Time for a Sabbatical?
Approach and Philosophy of On baking technology
Network Security Unit 5.pdf for BCA BBA.
Blue Purple Modern Animated Computer Science Presentation.pdf.pdf
Dropbox Q2 2025 Financial Results & Investor Presentation
Profit Center Accounting in SAP S/4HANA, S4F28 Col11
cuic standard and advanced reporting.pdf
A comparative analysis of optical character recognition models for extracting...
MIND Revenue Release Quarter 2 2025 Press Release
Mobile App Security Testing_ A Comprehensive Guide.pdf
Review of recent advances in non-invasive hemoglobin estimation
Machine learning based COVID-19 study performance prediction
ACSFv1EN-58255 AWS Academy Cloud Security Foundations.pptx
Peak of Data & AI Encore- AI for Metadata and Smarter Workflows
sap open course for s4hana steps from ECC to s4
20250228 LYD VKU AI Blended-Learning.pptx

(7) cpp abstractions inheritance_part_ii

  • 1. 1 (7) C++ Abstractions Nico Ludwig (@ersatzteilchen)
  • 2. 2 TOC ● (7) C++ Abstractions – C++11: Explicit Overrides and final Methods – Run Time Type Identification (RTTI) and dynamic_cast – protected Members – Abstract Types with pure virtual Member Functions – Virtual Destructors – Inheritance and Operators ● Sources: – Bjarne Stroustrup, The C++ Programming Language – John Lakos, Large-Scale C++ Software Design
  • 3. 3 // Non-virtual member function: void StartEngine() { /* pass */ } C++11 – explicitly overridden methods class DieselCar : public Car { public: // Invalid! Results in compiler error: doesn't override an inherited method! void StartEngine() override { /* pass */ } }; C++11: Explicit Overrides ● In C++ an "overriding method" is not marked as overriding a base type's method. – The problem: sometimes non-virtual functions are meant to be overridden by accident. class Car { public: }; ● C++11 allows explicit specification of overriding methods. – Just add the "override"-specifier to the method: – The override specifier can only be used on inherited methods. class DieselCar : public Car { public: // Oops! Doesn't override Car::StartEngine()! void StartEngine() { /* pass */ } }; ● Sometimes the keyword virtual is also written on the overriding methods (in this example it could be written on the method DieselCar::StartEngine()). It's done because of tradition and in order to mark that the method overrides another method. - The new C++11-way to mark overrides is the override-specifier, this mark is also checked by the compiler. ● The override keyword must not be repeated on a non-inline definition.
  • 4. 4 C++11: Explicit Final Methods ● Sometimes it is not desired to let a method be overridden in subtypes. ● In C++11 we can mark methods as being non-overridable in a base type. – Just add the "final"-specifier on the method in the "leaf type". class Car { public: // Method in the base type: virtual void StartEngine() { /* pass */ } }; – The final specifier can only be used on inherited methods. C++11 – non-overridable methods class DieselCar : public Car { public: // Mark a method as being non-overridable: void StartEngine() final { /* pass */ } }; class SpecialDieselCar : public DieselCar { // Invalid! Results in compiler error: can't override a non-overridable method! void StartEngine() override { /* pass */ } }; ● The final keyword must not be repeated on a non-inline definition.
  • 5. aBus.???? nOccupiedSeatBenches 5 The Size of inherited Types ● When a UDT inherits from another UDT, their sizes sum up. – I.e. the sizes of all fields (incl. private fields) contribute to the final sum. class Car { // UDTs Engine and Tyre elided. Engine* theEngine; Tyre* spareTyre; public: void StartEngine() { /* pass */ } }; sizeof(Car): 8 // Bus inherits from Car: class Bus : public Car { // (members hidden) int nOccupiedSeatBenches; public: /* pass */ }; sizeof(Bus): 12 // sizeof(int) + sizeof(Car) aCar ● The sizes of polymorphic types could be different! Let's inspect this effect... aCar.spareTyre aCar.theEngine 4B 8B ???? ???? aBus aBus.spareTyre aBus.theEngine 12B ???? ????
  • 6. The Size of polymorphic Types w/ virtual Member Function Tables 6 ● Interestingly, the "sum of field sizes" can be different for polymorphic types. class PolyCar { // In PolyCar StartEngine() is virtual. Engine* theEngine; Tyre* spareTyre; public: virtual void StartEngine() { /* pass */ } }; sizeof(PolyCar): 12 // Huh? (LLVM GCC 4.2) ● Some C++ distributions implement methods with virtual (member) function tables. – A virtual function table (vtable) is accessible as pointer field in a polymorphic type. – Vtable and pointer are added by the compiler. (They're not accessible programatically.) class PolyCar { // … what the compiler generated ImplementationSpecific* __vtptr; Engine* theEngine; Tyre* spareTyre; public: virtual void StartEngine() { /* pass */ } 0xbffff918 aPolyCar.__vtptr }; sizeof(PolyCar): 12 // Aha! (LLVM GCC 4.2) aPolyCar aPolyCar.spareTyre aPolyCar.theEngine 4B 12B ???? ???? ● The implementation of methods is compiler specific. The vtable implementation is used very often, it is the basis for the object system in the Microsoft technologies COM and .Net. ● Polymorphic types are no longer PODs, as their sizes and layouts are no longer predictable. The C+ +11 type trait std::is_pod() will return false for polymorphic types.
  • 7. 7 Virtual Member Function Tables – Oversimplification! class PolyCar { // UDTs Engine and Tyre elided. Engine* theEngine; Tyre* spareTyre; ● For each polymorphic type a vtable is created. – It lists all methods of that type as function pointers. PolyCar* polyCar = new PolyCar; polyCar->StartEngine(); – Each instance of the polymorphic type has a pointer (__vtptr) to the vtable. ● A derived type inherits the vtables well. PolyCar::StartEngine() PolyCar* bus = new Bus; bus->StartEngine(); – The compiler replaces inherited function pointers by pointers to overridden methods. – Non-overridden methods are left as inherited function pointers in the vtable. public: virtual void StartEngine() { /* pass */ } }; // Bus overrides StartEngine(): class Bus : public PolyCar { // (members hidden) int nOccupiedSeatBenches; public: void StartEngine() { /* pass */ } }; PolyCar's virtual function table polyCar->__vtptr Bus::StartEngine() Bus' virtual function table bus->__vtptr
  • 8. 8 Late Binding – Polymorphism – and a Layer of Indirection ● The procedure of selecting the correct vtable entry during run time is often called (dynamic) dispatch or late binding. – We have to clarify another aspect of this procedure: the variables. ● After we've an idea of how polymorphism works, let's review these lines of code: – The difference between static and dynamic type is to be strengthened here. PolyCar* bus = new Bus; bus->StartEngine(); ● Polymorphism only works on pointers or references of polymorphic types! – Polymorphism: ability to call methods of a dynamic type through a static type. – "Through a static type" means through a pointer or reference of static type. ● To make polymorphism work respective variables must be pointers or references. – It is often said "an extra layer of indirection" is required for polymorphism to work. ● Polymorphism happens during run time and information like the vtables need to be evaluated during run time to make method dispatching work. In other words: calling methods is more costly than calling non-virtual member functions.
  • 9. 9 Run Time Type Identification (RTTI) with the typeid-Operator ● Concerning polymorphism we've discussed objects' static and dynamic types. – In a former example we've used type flags to get information about dynamic types. – (But then we learned about virtual member functions, which are the better alternative.) ● In C++ we can directly get the dynamic type of an object of polymorphic type. – The dynamic type of an object can be retrieved with Run Time Type Identification (RTTI): Car* car = new VintageCar; // Let's car point to a VintageCar. std::cout<<std::boolalpha<<"car points to a VintageCar: "<<(typeid(VintageCar) == typeid(*car))<<std::endl; // >car points to a VintageCar: true car = new Bus; // Let's car point to a Bus. std::cout<<std::boolalpha<<"car points to a Bus: "<<(typeid(Bus) == typeid(*car)) <<"; car points to a VintageCar: "<<(typeid(VintageCar) == typeid(*car))<<std::endl; // >car points to a Bus: true; car points to a VintageCar: false – The operator typeid retrieves information about the dynamic type of an object. – Here we use typeid to make the difference between static and dynamic type visible: ● The pointer car can point to an object of any subtype of Car. Here: Bus, then VintageCar. – typeid can only be used on polymorphic types! ● The operator typeid returns an object of type std::type_info (declared in <typeinfo>). ● std::type_info objects can not be created or assigned to from using typeid (std::type_info's copy assignment and the cctor are both non-public). ● The only interesting thing we can do with std::type_info is directly ==/!=-comparing the results of two typeid calls. ● It is typically an error to use typeid to compare pointer types! ● There is also a member function std::type_info::name() that returns a (arbitrary and non-portable) string representation of a type. This function must also be directly called on the result of the typeid operator.
  • 10. 10 Garage::TryToStartCar() with the typeid-Operator ● It's possible to reimplement Garage::TryToStartCar() with RTTI: void Garage::TryToStartCar(Car* car) const { if (typeid(VintageCar) == typeid(*car)) { // If car's dynamic type is VintageCar start it specially. VintageCar* vintageCar = static_cast<VintageCar*>(car); // Downcasting! if (!vintageCar->HasStarter()) { vintageCar->CrankUntilStarted(); } else { vintageCar->StartEngine(); } } else { car->StartEngine(); // Start other cars just by calling StartEngine(). } } – This implementation neglects the presence of methods, but uses RTTI and downcasts! ● (We already learned that this is really bad: "pasta-object-orientation"!) ● With this implementation of Garage::TryToStartCar() we can start fordThinLizzie: VintageCar* fordThinLizzie = new VintageCar(1909); joesStation.TryToStartCar(fordThinLizzie); // Ok! TryToStartCar() will pick the correct start-algorithm // depending on the run time type!
  • 11. 11 Garage::TryToStartCar() with the dynamic_cast-Operator ● Instead of RTTI (i.e. typeid), we can use dynamic_cast for polymorphic UDTs: void Garage::TryToStartCar(Car* car) const { const VintageCar* vintageCar = dynamic_cast<VintageCar*>(car); if (0 != vintageCar) { /* pass */ } /* pass */ } ● dynamic_cast tries to cast the passed pointer or reference to the specified type. – Basically it tries to "cast the dynamic type out of the pointer or reference". – It works like a combination of typeid comparison and static_cast-based downcasting. – It returns a valid pointer or reference to the specified type if the cast worked. – It returns 0 for pointers or throws std::bad_cast for references if the cast didn't work. – The operator only works with polymorphic types, else we'll get a compile time error. ● But using dynamic_cast is basically the same as the typeid-else if-procedure. – Better use virtual member functions (methods)! RTTI and dynamic_cast should be avoided! ● In opposite to static_cast, dynamic_cast is type-checked at run time! It's type safe to use dynamic_cast for downcasting! ● dynamic_cast is the only cast that can't be expressed with the older cast syntaxes. ● The explanation why we didn't use const& as parameter types here (kind of breaking the rule we've defined lately) is, that we'd have to deal with std::bad_cast exceptions in this case (instead of 0- pointers). - And we didn't discuss exceptions yet. ● There is the saying "polymorphism is always better than branching". In this example the usage of dynamic type analysis (with RTTI or dynamic_cast) and if-else cascades would be the "branching". - This approach requires to add new dynamic types to be potentially handled in Garage::TryToStartCar() and this is a disapproving approach; always prefer polymorphism! - Design patterns, which are often based on polymorphism, help to implement even complex problems w/o dynamic type analysis.
  • 12. 12 Protected Family Secrets ● Let's review the type VintageCar: class VintageCar : public Car { // (members hidden) public: bool CrankUntilStarted() { /* pass */ } bool HasStarter() const { /* pass */ } void StartEngine(); }; ● There are some points to be thought of: – HasStarter() and CrankUntilStarted() are only used in VintageCar::StartEngine(). – HasStarter() could be defined in the base type Car, as each Car could have a starter. – CrankUntilStarted() should be declared private, being encapsulated by VintageCar. ● But some problems arise with the proposed modifications: – Car::HasStarter() should not be visible to the public, but to its derived types ("family"). – Maybe VintageCar::CrankUntilStarted() should be accessible by derived types as well. – To solve these issues we can use the access specifier "protected". void VintageCar::StartEngine() const { if (HasStarter()) { /* pass */ } else { while (CrankUntilStarted()) { /* pass */ } } } ● We can also apply protected inheritance in C++.
  • 13. 13 Protected Member Functions ● Let's redesign Car/VintageCar: – Move HasStarter() to the type Car and mark it as being protected. – Make VintageCar::CrankUntilStarted() protected as well. – Then both member functions are only visible in the "family", not to the public. class Car { // (members hidden) protected: bool HasStarter() const { /* pass */ } public: virtual void StartEngine() { /* pass */ } }; class VintageCar : public Car { // (members hidden) protected: bool CrankUntilStarted() { /* pass */ } public: void StartEngine() { if (HasStarter()) { /* pass */ } else { while (CrankUntilStarted()) { /* pass */ } } } }; Car # HasStarter() : bool + StartEngine() VintageCar # CrankUntilStarted() : bool + StartEngine() The member functions HasStarter() and CrankUntilStarted() are protected, mind the "#" notation.
  • 14. 14 Intermezzo: Inheritance for white-box Reuse ● Aggregation: Using a Car's public interface is black-box reuse. – All the critical aggregated stuff is encapsulated, declared private or protected. ● Inheritance: Using a Car's protected interface is white-box reuse. – Subtyping is needed to access the protected stuff. – Subtypes have to know how to work with protected members! – Subtyping breaks encapsulation to certain degree! – Never ever use inheritance for plain reuse, this is an antipattern! ● Inherited/derived types have access to public and protected members of the base types. – protected members are only accessible within the "family". ● E.g. accessing or handling the start-system of Cars (e.g. HasStarter()) is too critical to be public. ● Car's subtypes must know how to use the start-system (e.g. the subtype VintageCar needed to handle the start-system in a different way w/ cranking). ● In fact the protected members of our types should be as good documented (e.g. via comments) as the public members to make white-box reuse possible!
  • 15. 15 Abstract Types ● There are types that are too abstract to have instances. – This means that some methods can not provide suitable (default) implementations. ● E.g. a new UDT Vehicle is more abstract than Car: – Vehicle is too abstract of its own, we should not assume that it has an Engine, or even a SpareTyre... it could be left away. (StartEngine() also makes no sense!) – More concrete subtypes (as Car) could fill, what we left away in Vehicle. – Vehicle is even too abstract to provide a default implementation for e.g. Drive(). ● Abstract types in C++: – UDTs can provide methods without implementation. – These methods are called abstract methods. – Abstract methods are defined as pure virtual member functions in C++. – Abstract types allow to abstract a type's usage from its implementation. – Now let's implement the abstract type Vehicle...
  • 16. 16 The abstract Type Vehicle ● A C++ implementation of the abstract type Vehicle might look like this: ● An inheriting type can override/implement abstract methods. – Inheriting UDTs that don't implement all abstract methods become itself abstract UDTs! ● I.e. Bike is an abstract UDT! // The abstract UDT Vehicle: class Vehicle { public: // A pure virtual member function: virtual void Drive() = 0; }; // The concrete UDT Car: class Car : public Vehicle { // (members hidden) public: // Override Drive() const: void Drive() { std::cout<<"zooming off..."<<std::endl; } }; Vehicle + Drive() Vehicle {abstract} + Drive() {abstract} Abstract classes' and operations' titles are written in italics. Alternatively abstract classes and operations can also be marked with the abstract constraint. // The abstract UDT Bike: class Bike : public Vehicle { public: // Doesn't override Drive() const: void ChangeUp() { /* pass */ } };
  • 17. 17 Implementation and Usage of abstract Types ● Implementation side of abstract types: – Assign 0 to the method declaration in the top level abstract type. ● Inherited abstract methods must be overridden in concrete types. ● Inherited abstract methods need not to be overridden in abstract types. ● Usage side of abstract types: – No instances of abstract UDTs can be created; Vehicle is just a too abstract type. Vehicle* vehicle = new Vehicle; // Invalid! Instance of abstract UDT can't be created. – The only idea behind abstract types is to support polymorphism. – The is-a relationship holds true: A Car is a Vehicle and a RacingBicycle is a Vehicle. ● The substitution works nicely with abstract types! - We're going to revisit this idea! void Move(Vehicle* vehicle) { vehicle->Drive(); } Move(new Car); // >zooming off... Move(new RacingBicycle); // >woosh... // The concrete UDT RacingBicycle: class RacingBicycle : public Bike { public: void Drive() { std::cout<<"woosh..."<<std::endl; } };
  • 18. 18 Abstract default Implementations ● Abstract methods are allowed to provide a default implementation. – The implementation must be provided as non-inline member function definition. // The abstract UDT Vehicle: class Vehicle { public: // A pure virtual member function: virtual void Drive() = 0; }; // A default implementation for inheriting types: void Vehicle::Drive() { // Mind: it must be a non-inline member function! std::cout<<"driving..."<<std::endl; } ● Inheriting types are allowed to call this default implementation like so: // Override Drive() const: void Car::Drive() { Vehicle::Drive(); // Calls inherited default implementation: std::cout<<"zooming off..."<<std::endl; } Car car; car.Drive(); // >driving... // >zooming off... – Calling Car::Drive() shows that it works:
  • 19. 19 Why is Abstraction needed? ● OO programmers design models that simulate reality. – Abstraction means to leave out irrelevant details from the model. – Some degree of detail is sufficient. Capturing "al" details is impossible (computer memory, time and expert knowledge is limited). – OO seems to be appropriate for big projects to get rid of the "academic touch". ● A vendor's framework only abstracts a certain (the vendor's) view of a model. – Multiple abstraction solutions can be correct for a certain task. ● Delegation, information hiding, encapsulation and substitution help to abstract. – This means do also help to postpone or defer details. ● A core idea of oo development is to accept types as incomplete. – These types could be extended incrementally and iteratively. ● This is called incremental and iterative software engineering. ● Abstraction from lat. abstrahere: "to remove something". ● What does "incremental and iterative" mean? ● Incremental: the engineering will be performed in steps. ● Iterative: some parts of the engineering process will be repeated to improve these aspects.
  • 20. 20 Calling the Ctors of base Classes ● If the base types have dctors they'll be called by the ctors of subtypes: class Car { // (members hidden) public: Car() { std::cout<<"Car::Car()"<<std::endl; } ● If a base type doesn't provide a dctor, nothing can be implicitly called of course! – Instead we have to call another ctor of the base type in the subtype's ctor explicitly. ● Syntactically we have to call ctors of the base types in the initializer list of a subtype's ctor: }; class Bus : public Car { // (members hidden) public: /* pass */ }; Bus bus; // Invalid! Base type class Car { // Car w/o dctor! Car provides no dctor. public: Car(double power) { /* pass */ } }; class Bus : public Car { public: // Ctor, which calls one of Car's ctors. Bus(double power) : Car(power) { /* pass */ } }; Bus bus; // >Car::Car() // Calls Car's dctor implicitly. Bus bus(320); // Fine!
  • 21. 21 Inheritance and Destructors ● A derived UDT accesses the dtors of its base types on destruction. class A { public: ~A() { std::cout<<"~A"<<std::endl; } }; class B : public A { public: ~B() { } }; – When the dtor of an object is called, the base types' dtors are also called: { // With RAII: B b; } // >~B // The dtors are called from the most // >~A // special to the most general type. ● But there is a problem: it doesn't work on dynamic types! // With dynamic allocation: B* b = new B; delete b; // >~B // >~A A* a = new B; // a points to a B-instance that must be destroyed. delete a; // >~A // Oops! Only the dtor of the static type is called! – Obviously dynamic dispatch doesn't work on dtors! std::cout<<"~B"<<std::endl; – But the solution is simple: we have to make the base types' dtors polymorphic!
  • 22. 22 Virtual Destructors ● The solution is to make the base types' dtors virtual: class A { public: virtual ~A() { std::cout<<"~A"<<std::endl; } }; – Then the destruction works as it should: ● Virtual dtors: class B : public A { public: – C++ expresses polymorphism by the separation of static and dynamic types. ~B() { – virtual member functions are called on static types being dispatched at run time. – Let's stipulate that we await polymorphic calls to be effective on the dynamic type. – This should also the case for dtors: a polymorphic type should have a virtual dtor! std::cout<<"~B"<<std::endl; } }; A* a = new B; // a points to a B-instance that must be destroyed. delete a; // >~B // Fine! The base type's dtor is called. // >~A ● We can also have pure virtual dtors.
  • 23. 23 Inheritance and Operators ● Derived types don't inherit all members of its base types, because it would introduce inconsistencies. Not inherited are: – ctors and the non-virtual dtor, – assignment operators and – friends. ● Not inherited just means, that those members are no part of the public interface of the derived type! – But the implementations of those members are still "callable" in a derived type! - So we can reuse them! ● However, usually the operators of base types are called in the implementation of those in the derived type. – This example shows the idiomatic "delegation" of operator calls to reuse the functionality of base types. class SpecialPerson : public Person { // (types hidden) public: SpecialPerson(const SpecialPerson& original) : Person(original) { // Just forward to the cctor of the base type. } SpecialPerson& operator=(const SpecialPerson& rhs) { if (this != &rhs) { Person::operator=(rhs); // Just call copy-operator= of the base type. } // Mind, that the result of Person::operator= can't be returned, because the return *this; // return type is too general! We call Person::operator= only for the side effect } // on this! };
  • 24. 24 The SOLID Principle ● Robert Cecil Martin ("Uncle Bob") et al. collected five principles for oo design. – These principles guide us through the design of "good" oo code. ● Single Responsibility Principle (SRP) ● Open-Close Principle (OCP) ● Liskov Substitution Principle (LSP) ● Interface Segregation Principle (ISP) ● Dependency Inversion Principle (DIP) ● SRP: Types should have only one reason to change. This requirement also applies to methods. ● OCP: Open types for extension, but close them for modification, i.e. extend them w/o modification of the core code, rather code will be added (e.g. via polymorphism). If we need to modify a type's interface, the calling code needs to be modified (ripple effect), which is expensive. Separate things that change, from others that don't (i.e. find the vector of change to drive design patterns). ● LSP: Exploit subtyping to put the substitution principle into effect. ● ISP: Use small type interfaces to which callers depend on, so that changes in other type interfaces don't bother them. ● DIP: Let types only depend on abstract types (the contracts), never on concrete types (the implementation) to reduce coupling. Layers of lower abstraction (more concrete type) depend on layers of higher abstraction (more abstract types), never vice versa. ● The SOLID principle enumerates a set of guidelines, not laws.

Editor's Notes

  • #4: Sometimes the keyword virtual is also written on the overriding methods (in this example it could be written on the method DieselCar::StartEngine()). It&amp;apos;s done because of tradition and in order to mark that the method overrides another method. - The new C++11-way to mark overrides is the override-specifier, this mark is also checked by the compiler. The override keyword must not be repeated on a non-inline definition.
  • #5: The final keyword must not be repeated on a non-inline definition.
  • #7: The implementation of methods is compiler specific. The vtable implementation is used very often, it is the basis for the object system in the Microsoft technologies COM and .Net. Polymorphic types are no longer PODs, as their sizes and layouts are no longer predictable. The C++11 type trait std::is_pod() will return false for polymorphic types.
  • #9: Polymorphism happens during run time and information like the vtables need to be evaluated during run time to make method dispatching work. In other words: calling methods is more costly than calling non-virtual member functions.
  • #10: The operator typeid returns an object of type std::type_info (declared in &amp;lt;typeinfo&amp;gt;). std::type_info objects can not be created or assigned to from using typeid (std::type_info&amp;apos;s copy assignment and the cctor are both non-public). The only interesting thing we can do with std::type_info is directly ==/!=-comparing the results of two typeid calls. It is typically an error to use typeid to compare pointer types! There is also a member function std::type_info::name() that returns a (arbitrary and non-portable) string representation of a type. This function must also be directly called on the result of the typeid operator.
  • #12: In opposite to static_cast, dynamic_cast is type-checked at run time! It&amp;apos;s type safe to use dynamic_cast for downcasting! dynamic_cast is the only cast that can&amp;apos;t be expressed with the older cast syntaxes. The explanation why we didn&amp;apos;t use const&amp; as parameter types here (kind of breaking the rule we&amp;apos;ve defined lately) is, that we&amp;apos;d have to deal with std::bad_cast exceptions in this case (instead of 0-pointers). - And we didn&amp;apos;t discuss exceptions yet. There is the saying &amp;quot;polymorphism is always better than branching&amp;quot;. In this example the usage of dynamic type analysis (with RTTI or dynamic_cast) and if-else cascades would be the &amp;quot;branching&amp;quot;. - This approach requires to add new dynamic types to be potentially handled in Garage::TryToStartCar() and this is a disapproving approach; always prefer polymorphism! - Design patterns, which are often based on polymorphism, help to implement even complex problems w/o dynamic type analysis.
  • #13: We can also apply protected inheritance in C++.
  • #15: In fact the protected members of our types should be as good documented (e.g. via comments) as the public members to make white-box reuse possible!
  • #20: Abstraction from lat. abstrahere: &amp;quot;to remove something&amp;quot;. What does &amp;quot;incremental and iterative&amp;quot; mean? Incremental: the engineering will be performed in steps. Iterative: some parts of the engineering process will be repeated to improve these aspects.
  • #23: We can also have pure virtual dtors.
  • #25: SRP: Types should have only one reason to change. This requirement also applies to methods. OCP: Open types for extension, but close them for modification, i.e. extend them w/o modification of the core code, rather code will be added (e.g. via polymorphism). If we need to modify a type&amp;apos;s interface, the calling code needs to be modified (ripple effect), which is expensive. Separate things that change, from others that don&amp;apos;t (i.e. find the vector of change to drive design patterns). LSP: Exploit subtyping to put the substitution principle into effect. ISP: Use small type interfaces to which callers depend on, so that changes in other type interfaces don&amp;apos;t bother them. DIP: Let types only depend on abstract types (the contracts), never on concrete types (the implementation) to reduce coupling. Layers of lower abstraction (more concrete type) depend on layers of higher abstraction (more abstract types), never vice versa. The SOLID principle enumerates a set of guidelines, not laws.