* What is an Abstract Class? - It is a class with at least one abstract function. * And what is an abstract function? - It is a function that has no implementation. * How are they declared in C++? - With an assignment to zero. * Can you give an example? struct Token { virtual std::string toString() const = 0; }; * Why does the bodyless method need to be virtual? - Otherwise it would never be called :) * Can you instantiate an abstract class? // error: allocating an object of abstract class type 'Token' #include #include "Token.h" int main() { Token *t = new Token(); } * How can you implement a method from an abstract class? struct StringTK: Token { StringTK(const std::string st): _str_tk(st) {} std::string toString() const { return "STR_" + _str_tk; } private: const std::string _str_tk; }; * Do we have to write StringTK::toString as "virtual"? - Only if it will be used as the base class of inheritance, and the method is meant to be called through a pointer to the base class. * Can StrintTK be instantiated? // Sure! #include #include #include "Token.h" int main(int argc, char** argv) { Token *t = new StringTK(argv[1]); std::cout << t->toString() << std::endl; } * Does the program above contain a memory leak? * How to remove it? #include #include #include "Token.h" int main(int argc, char** argv) { Token *t = new StringTK(argv[1]); std::cout << t->toString() << std::endl; delete t; } * But now we get this warning. How to get rid of it? struct Token { virtual std::string toString() const = 0; virtual ~Token() {}; }; * Why do we need a virtual destructor? - Any user of your class will probably hold a pointer to the interface, not a pointer to the concrete implementation. When they come to delete it, if the destructor is non-virtual, they will call the interface's destructor (or the compiler-provided default, if you didn't specify one), not the derived class's destructor. Instant memory leak. * Why can't we have a virtual destructor without body? E.g.: struct Token { virtual std::string toString() const = 0; virtual ~Token() = 0; }; * Actually, if we do not instantiate any concrete class, we will be fine: int main(int argc, char** argv) { Token *t; } === Reviewing Dynamic Dispatch === * Let's create a few more types of tokens. What about a token for integers? struct IntegerTK: Token { IntegerTK(const std::string st): _int_tk(std::stoi(st)) {} virtual ~IntegerTK() {}; virtual std::string toString() const { return "INT_" + std::to_string(_int_tk); } private: const int _int_tk; }; * And, what about a token for fractions? struct FractionTK: IntegerTK { FractionTK(const std::string st): IntegerTK(getNum(st)), _den_tk(std::stoi(getDen(st))) { } std::string toString() const { return "FRC_(" + IntegerTK::toString() + ")/" + std::to_string(_den_tk); } private: const int _den_tk; std::string getNum(std::string int_str) { unsigned index = int_str.find('/'); return int_str.substr(0, index); } std::string getDen(std::string int_str) { unsigned index = int_str.find('/'); return int_str.substr(index+1, int_str.size()); } }; * Do you understand what the private methods getNum and getDen are doing? * So, what is this program below going to print? #include #include #include "Token.h" int main(int argc, char** argv) { Token *t0 = new StringTK(argv[1]); Token *t1 = new IntegerTK(argv[1]); Token *t2 = new FractionTK(argv[1]); std::cout << t0->toString() << std::endl; std::cout << t1->toString() << std::endl; std::cout << t2->toString() << std::endl; delete t0; delete t1; delete t2; } * And this program below, what will it print? #include #include #include "Token.h" int main(int argc, char** argv) { IntegerTK *t2 = new FractionTK(argv[1]); std::cout << t2->toString() << std::endl; delete t2; } * Let's modify the class IntegerTK, so that it can return the integer that it represents: struct IntegerTK: Token { IntegerTK(const std::string st): _int_tk(std::stoi(st)) {} virtual ~IntegerTK() {}; virtual std::string toString() const { return "INT_" + std::to_string(_int_tk); } int getInt() const { return _int_tk; } private: const int _int_tk; }; * Does this program below work? #include #include #include "Token.h" int main(int argc, char** argv) { Token *t0 = new IntegerTK(argv[1]); std::cout << t0->getInt() << std::endl; delete t0; } * What is the problem with the program above? - The static type of t0 does not contain the method getInt() * How can you fix it? #include #include #include "Token.h" int main(int argc, char** argv) { IntegerTK *t0 = new IntegerTK(argv[1]); std::cout << t0->getInt() << std::endl; delete t0; } * What is the static type of an object? - That's the type with which the object is declared? * What's the static type of object "t0" in: IntegerTK *t0 = new IntegerTK(argv[1])? - IntegerTK * What about in: Token *t0 = new IntegerTK(argv[1])? - Token * What is the dynamic type? - The dynamic type is the type with each the object is instantiated. * What's the dynamic type of object "t0" in: IntegerTK *t0 = new IntegerTK(argv[1])? - IntegerTK * What about in: Token *t0 = new IntegerTK(argv[1])? - IntegerTK * Again: why do we need a virtual destructor in every base class? * Can you give an example that would be bad programming without the virtual destructor? #include struct A { }; struct B: A { B() { b = new int(); } ~B() { delete b; } int *b; }; int main() { A* a = new B(); delete a; } * The program above has a memory leak. Can you spot it? * How to fix this problem with the memory leak? #include struct A { virtual ~A() {} }; struct B: A { B() { b = new int(); } ~B() { delete b; } int *b; }; int main() { A* a = new B(); delete a; } * What is a type cast? - int x = 0; - float y = x; - double d = (double)x; * Can you produce wrong code with type casts? * Does this program below compile? #include class A { float i,j; }; class B { int x,y; public: B (int a, int b): x(a), y(b) {} int result() { return x+y;} }; int main () { A a; B *b; b = (B*) &a; std::cout << b->result() << std::endl; return 0; } * Is the program above correct? * Is there a safer way to perform a type cast? * We can use a dynamic cast. For instance: #include class A { float i,j; public: virtual ~A() {}; }; class B { int x,y; public: B (int a, int b): x(a), y(b) {} int result() { return x+y;} }; int main () { A a; B *b; b = dynamic_cast(&a); std::cout << b->result() << std::endl; return 0; } * Why did we have to add a virtual destructor to class A? - Because only polymorphic classes, i.e., classes with virtual methods, can be used as the target of a dynamic cast. * What happens once this program runs? * How can we use this behavior to test if a cast has worked out well? #include class A { float i,j; public: virtual ~A() {}; }; class B { int x,y; public: B (int a, int b): x(a), y(b) {} int result() { return x+y;} }; int main () { A a; B *b = dynamic_cast(&a); if (b) { std::cout << b->result() << std::endl; } else { std::cout << "Casting was not possible\n"; } return 0; } * What is dynamic type casting? - Is a cast that is verified at runtime. * What about the program below, does it work? #include class A { float i,j; public: virtual ~A() {}; }; class B: public A { int x,y; public: B (int a, int b): x(a), y(b) {} int result() { return x+y;} }; int main () { A *a = new B(1, 2); B *b = dynamic_cast(a); if (b) { std::cout << b->result() << std::endl; } else { std::cout << "Casting was not possible\n"; } return 0; } * And this new program, does it compile? #include class A { float i,j; public: virtual ~A() {}; }; class B: public A { int x,y; public: B (int a, int b): x(a), y(b) {} int result() { return x+y;} }; int main () { A *a = new B(1, 2); B *b = a; if (b) { std::cout << b->result() << std::endl; } else { std::cout << "Casting was not possible\n"; } return 0; } * We can still force a coercion with a static cast. This might lead to wrong programs, e.g.: #include class A { float i,j; public: virtual ~A() {}; }; class B: public A { int x,y; public: B (int a, int b): x(a), y(b) {} int result() { return x+y;} }; int main () { A *a = new A(); B *b = static_cast(a); if (b) { std::cout << b->result() << std::endl; } else { std::cout << "Casting was not possible\n"; } return 0; } * Notice that static casts still check, at compilation time, if the classes are relate by inheritance. * Does this program compile? #include class A { float i,j; public: virtual ~A() {}; }; class B { int x,y; public: B (int a, int b): x(a), y(b) {} int result() { return x+y;} }; int main () { A *a = new A(); B *b = static_cast(a); if (b) { std::cout << b->result() << std::endl; } else { std::cout << "Casting was not possible\n"; } return 0; } * Is there a way to force conversion, even if the classes are not related? #include class A { float i,j; public: virtual ~A() {}; }; class B { int x,y; public: B (int a, int b): x(a), y(b) {} int result() { return x+y;} }; int main () { A *a = new A(); B *b = reinterpret_cast(a); if (b) { std::cout << b->result() << std::endl; } else { std::cout << "Casting was not possible\n"; } return 0; } * Why does this program fail to compile? #include void print (char * str) { std::cout << str << std::endl; } int main () { const char * c = "sample text"; print(c); return 0; } * How can we make it compile? #include void print (char * str) { std::cout << str << std::endl; } int main () { const char * c = "sample text"; print ( const_cast (c) ); return 0; } * Is there a way to check the type of an object at runtime? #include #include int main () { int * a,b; a = 0; b = 0; if (typeid(a) != typeid(b)) { std::cout << "a and b are of different types:\n"; std::cout << "a is: " << typeid(a).name() << '\n'; std::cout << "b is: " << typeid(b).name() << '\n'; } return 0; } * Can you use typeid to avoid bad casting? #include #include class A { float i,j; public: virtual ~A() {}; }; class B: public A { int x,y; public: B (int a, int b): x(a), y(b) {} int result() { return x+y;} }; int main () { A *a = new B(1, 2); if (typeid(*a) == typeid(B)) { B *b = dynamic_cast(a); if (b) { std::cout << b->result() << std::endl; } else { std::cout << "Casting was not possible\n"; } } return 0; } * What is the difference between typeid(a), typeid(*a), typeid(A) and typeid(*A)? * What do you think is this program below going to print? #include #include class A { float i,j; public: virtual ~A() {}; }; class B: public A { int x,y; public: B (int a, int b): x(a), y(b) {} int result() { return x+y;} }; int main () { A a0; std::cout << "a0 is " << typeid(a0).name() << std::endl; B b0(1, 2); std::cout << "b0 is " << typeid(b0).name() << std::endl; A *a1 = new A(); std::cout << "a1 is " << typeid(a1).name() << std::endl; std::cout << "*a1 is " << typeid(*a1).name() << std::endl; A *a2 = new B(1, 2); std::cout << "a2 is " << typeid(a2).name() << std::endl; std::cout << "*a2 is " << typeid(*a2).name() << std::endl; B *b1 = new B(3, 4); std::cout << "b1 is " << typeid(b1).name() << std::endl; std::cout << "*b1 is " << typeid(*b1).name() << std::endl; return 0; } * What about this new program? #include #include class A { float i,j; public: virtual ~A() {}; }; class B: public A { int x,y; public: B (int a, int b): x(a), y(b) {} int result() { return x+y;} }; int main () { std::cout << "A is " << typeid(A).name() << std::endl; std::cout << "B is " << typeid(B).name() << std::endl; std::cout << "A* is " << typeid(A*).name() << std::endl; std::cout << "B* is " << typeid(B*).name() << std::endl; return 0; }