Upload
others
View
10
Download
0
Embed Size (px)
Citation preview
Lecture 8 / 9
Software Engineering Group
Philipp D. Schubert
C++ Programming
1. Object oriented programming
2. Object oriented programming
3. Object oriented programming
4. Object oriented programming
5. Object oriented programming
6. Object oriented programming
7. Object oriented programming
Contents
Polymorphism Dynamic binding (at runtime)
struct shape {
virtual void draw() { cout << "shape" << endl; }
};
struct rectangle : shape {
void draw() override { cout << "rectangle" << endl; }
};
void print_shape(const shape& s)
{
s.draw(); // cannot bind statically
}
int main()
{
shape s;
rectangle r;
print_shape(s);
print_shape(r);
return 0;
}
Static binding (at compile time)
struct shape {
void draw() { cout << "shape" << endl; }
};
void print_shape(const shape& s)
{
s.draw(); // binds statically
}
int main()
{
shape s;
print_shape(s);
return 0;
}
Virtual method table/ vtable
int main()
{
B b;
C c;
call_print(b);
call_print(c);
return 0;
}
User (call_print()) only needs to know location of
pointer to vtbl
Index used for each virtual function
Efficiency compared to normal function call
Time: within 25%, but in reality much smaller
Space: one pointer to vtabl + one vtabl
#include <iostream>
using namespace std;
struct A {
virtual void print() { cout << "A" << endl; }
virtual void other() { cout << "do other stuff" << endl; }
};
struct B : A {};
struct C : A {
void print() override { cout << "C" << endl; }
};
// How can call_print() choose the right print() function?
// size and layout of B and C is UNKOWN to call_print()!!!
void call_print(A& a)
{
a.print();
}
A has virtual functions
Use virtual function table / vtable / vtbl
B
C
A::print()
C::print()
A::other()
A::other()
Virtual method table
class Base
{
public:
FunctionPointer *__vptr;
virtual void function1() {};
virtual void function2() {};
};
class D1: public Base
{
public:
virtual void function1() {};
};
class D2: public Base
{
public:
virtual void function2() {};
};
class Base
{
public:
virtual void function1() {};
virtual void function2() {};
};
class D1: public Base
{
public:
virtual void function1() override {};
};
class D2: public Base
{
public:
virtual void function2() override {};
};
[Figure from http://www.learncpp.com/cpp-tutorial/125-the-virtual-table/]
Special case: Interfaces
Abstract classes/ structs
Miss one or more function implementations
Cannot be instantiated
An interface is an abstract class with no function definitions!
Why is there a need for interfaces?
Example
A bird might quack
But it is not useful to inherit from base class “quack”
Make it an interface!
A duck might inherit from bird
And additionally implements the quack interface
An interface specifies what has to be implemented
Special case: Interfaces
int main()
{
duck d;
d.fly();
d.do_quack();
// the following line does not work
// quack q; cannot instantiate abstract class!
return 0;
}
#include <iostream>
using namespace std;
class bird {
public:
virtual void fly() { cout << "fly" << endl; }
};
class quack {
public:
virtual ~quack() = default;
virtual void do_quack() = 0; //provide no implementation
};
class duck : public bird, public quack {
public:
void quack() override { cout << "quack, quack!" << endl;}
};
Multiple inheritance
Allowing more than one parent
Quite useful
Some language do not support that
Considered as dangerous (Java does not allow this)
“Deadly diamond of death” (diamond problem)
#include <iostream>
using namespace std;
struct A { virtual void print() { cout << "A" << endl; } };
struct B : A { void print() override { cout << "B" << endl; } };
struct C : A { void print() override { cout << "C" << endl; } };
struct D : B, C {};
int main()
{
D d;
d.print();
return 0;
}[Figure from https://upload.wikimedia.org/wikipedia/commons/8/8e/Diamond_inheritance.svg]
What print() should be called?
C++ warns you when this happens
Problem can be mitigated (override print() in D and manually direct the call)
Multiple inheritance
“Deadly diamond of death” (diamond problem)
#include <iostream>
using namespace std;
struct A { virtual void print() { cout << "A" << endl; } };
struct B : A { void print() override { cout << "B" << endl; } };
struct C : A { void print() override { cout << "C" << endl; } };
struct D : B, C {
void print() override { B::print(); }
};
int main()
{
D d;
d.print();
return 0;
}
[Figure from https://upload.wikimedia.org/wikipedia/commons/8/8e/Diamond_inheritance.svg]
What print() should be called?
C++ warns you when this happens
Problem can be mitigated (override print() in D and manually direct the call)
Composition vs Inheritance
Sometimes composition is more useful than inheritance
Depends on your problem
GO does not support inheritance
[Figure from http://pumpkinprogrammer.com/2014/07/14/c-tutorial-inheritance-and-composition/]
Templates vs Inheritance
Inheritance
Is vertical and goes from abstract to more concrete
Example: Shape, triangle, right angle triangle
Runtime abstraction
Dynamic polymorphism
Templates
Is horizontally, defines parallel instances of code
Example: Sorting: integers, doubles, …, can all be sorted
Code generation tool
Static polymorphism
Both are orthogonal: can be used together
Example: I need both to implement a generic static analysis framework
[Figure from http://stackoverflow.com/questions/7264402/when-to-use-template-vs-inheritance/]
Type Casting
Casting is the mechanism of type conversion
Happens implicitly for most primitive types
Be careful with casting
Example: Casting primitive types
int main()
{
int i = (int) 12.1234; // just cut off after decimal point (you might like to use floor and ceil from <cmath>)
short c = (short) 123123123123; // bad: overflow
double d = (double) 14; // okay
double p = 12.5*i; // right computation is casted to the most precise representation involved (double)
cout << p << endl;
cout << i << endl;
cout << c << endl;
cout << d << endl;
return 0;
}
Dynamic cast – reference version
int main()
{
D d; // the most derived object
A& a = d; // upcast, dynamic_cast may be used,
but unnecessary
B& new_c = dynamic_cast<B&>(a); // downcast
C& new_b = dynamic_cast<C&>(a); // sidecast
return 0;
}
#include <iostream>
struct A {
virtual void f() {}; // must be polymorphic
otherwise we cannot dynamic_cast
};
struct B : A {};
struct C : A {};
struct D : B, C {};
Dynamic cast – pointer version
int main()
{
Base* b1 = new Base;
if(Derived* d = dynamic_cast<Derived*>(b1))
{
std::cout << "downcast from b1 to d
successful\n";
}
Base* b2 = new Derived;
if(Derived* d = dynamic_cast<Derived*>(b2))
{
std::cout << "downcast from b2 to d
successful\n";
}
delete b1;
delete b2;
return 0;
}
#include <iostream>
struct Base {
virtual void f() {}; // must be polymorphic
otherwise we cannot dynamic_cast
};
struct Derived: Base {};
Check ISA relation with dynamic cast int main()
{
D d;
try {
A& a = dynamic_cast<A&>(d);
cout << "cast succeeded" << endl;
} catch (const bad_cast& e) {
cout << "cast failed" << endl;
}
D* d_ptr = new D();
A* a_ptr = dynamic_cast<A*>(d_ptr);
if (a_ptr != nullptr) {
cout << "cast succeeded" << endl;
} else {
cout << "cast failed" << endl;
}
delete d_ptr;
return 0;
}
#include <iostream>
#include <typeinfo>
using namespace std;
struct A {
virtual void f() {}; // must be polymorphic
otherwise we cannot dynamic_cast
};
struct B : A {};
struct C : A {};
struct D : B, C {};
Use exceptions to check for references
Check for nullptr for pointers
Const cast#include <iostream>
using namespace std;
void modify(const int& i)
{
const_cast<int&>(i) = 100;
}
int main()
{
int i = 3; // note i is not declared const
const int& cref_i = i;
// cref_i = 5; this line would fail!
const_cast<int&>(cref_i) = 4; // OK: modifies i
cout << "i = " << i << '\n';
int other = 1;
modify(other);
cout << other << endl;
return 0;
}
Cast constness away
Remove constness when underlying type is
not const!
This is bad style
You are working against the type system
Type system shall prevent you from errors
Only use a const cast in rare/ extreme situtations
[Image from http://www.drodd.com/images14/forrest-gump17.jpg]
Reinterpret cast
Converts between type by reinterpreting underlying bit pattern
Returns a different interpretation of bits
#include <iostream>
using namespace std;
int main()
{
int bits = 0b110001010; // is 394 in decimal
double& d = reinterpret_cast<double&>(bits); // is 6.99566e-249
cout << bits << endl;
cout << d << endl;
return 0;
}
Very rarely needed
E.g. if you have to implement some device drivers
I never needed a reinterpet cast
Other casts
Some more casts exist
Static cast
Performing an explicit cast
Casting in hierarchies with non-virtual methods/ or primitive types
int i = 42;
double d = static_cast<double>(i);
Explicit cast
(new type) expression
int i = 2;
double d = (double) i * i;
Implicit conversion
int j = 3;
double e = j;
bool mybool = 1;
GOTO #include <iostream>
using namespace std;
int main() {
int counter = 0;
label: // loop using goto
cout << counter << '\n';
if (counter < 10) {
counter++;
goto label; // jump
}
for (int x = 0; x < 3; x++) {
for (int y = 0; y < 3; y++) {
cout << "x + y still smaller than 3\n";
if (x + y >= 3)
goto endloop;
}
}
endloop:
cout << '\n';
return 0;
}
An evil mechanism
GOTO just exists
GOTO performs an unconditional jump
When to use GOTO?
If you’d like to introduce bugs
If you’d like to make maintenance impossible
If you’d like to make a program unreadable
Better than encryption
Every program containing GOTO can be
expressed in a program not-containing GOTO
I only saw one example where it was useful
Some router software error handling
[Image from http://zdnet1.cbsistatic.com/hub/i/r/2014/10/04/adf68e5f-4bdc-11e4-b6a0-d4ae52e95e57/resize/770xauto/602ba37ff5be136930d7e9ab78e65bc2/ssl-validation-setup-failure.png]
GOTO #include <iostream>
using namespace std;
int main() {
int counter = 0;
label: // loop using goto
cout << counter << '\n';
if (counter < 10) {
counter++;
goto label; // jump
}
for (int x = 0; x < 3; x++) {
for (int y = 0; y < 3; y++) {
cout << "x + y still smaller than 3\n";
if (x + y >= 3)
goto endloop;
}
}
endloop:
cout << '\n';
return 0;
}
An evil mechanism
GOTO just exists
GOTO performs an unconditional jump
When to use GOTO?
If you’d like to introduce bugs
If you’d like to make maintenance impossible
If you’d like to make a program unreadable
Better than encryption
Every program containing GOTO can be
expressed in a program not-containing GOTO
I only saw one example where it was useful
Some router software error handling
[Image from http://zdnet1.cbsistatic.com/hub/i/r/2014/10/04/adf68e5f-4bdc-11e4-b6a0-d4ae52e95e57/resize/770xauto/602ba37ff5be136930d7e9ab78e65bc2/ssl-validation-setup-failure.png]
Nice talks about OO
“Intro to the C++ Object Model”, Richard Powell (CppCon 2015)
Technical presentation of OO in C++
https://www.youtube.com/watch?v=iLiDezv_Frk
A more general talk “SOLID Principles of Object Oriented & Agile Desing”, Robert Martin
(Yale school of management 2014)
High level overview
How to design large software systems
https://www.youtube.com/watch?v=TMuno5RZNeE
Questions?
Thank you for your attention