polymorphism

Nested classes

struct Outer {
    struct Nested {
    };
};
int s;
class Outer {
    int secret;
    inline static int s;
    static void f() {};
	
    class Inner {
        inline static int s;
        void foo() {
            f();
            size_t secret_s = sizeof(secret);
            secret = 3; // error: no instance
            Outer{}.secret = 3; // ok
            
            s = 4; // Outer::Inner::s
            Outer::s = 5;
            ::s = 6;
        }
    };
};

Nested classes

class List {
public:
    class iterator { ... };
    ...
};
class ManagerManager {
private:
    class implementation { ... };
    ...
};
struct Person {
    enum class Emotion {
        HAPPY, SCARED, 
        ANGRY, DISGUSTED,
        SAD, SURPRISED
    } emotion;
};
class Graph {
public:
    ...
private:
    class Impl;
    std::unique_ptr<Impl> pImpl;
};

polymorphism

polymorphism

AD Hoc

function overloading

operator overloading

float fma(float x, float y, float z) {
    return x * y + z;
}
double fma(double x, double y, double z) {
    return x * y + z;
}
...
std::ostream& operator<<(
     std::ostream& os, rect r
) {
     os << "{ width = "  << r.width
        << ", height = " << r.height
        << " }";
     return os;
}
std::string type_name(int) {
    return "int";
}
std::string type_name(char) {
    return "char";
}
...

polymorphism

Ad hoc polymorphism

Parametric polymorphism

Subtyping

static polymorphism

dynamic polymorhpism*

*constexpr virtual since C++20

function overloading

operator overloading

templates

virtual functions

float fma(float x, float y, float z) {
    return x * y + z;
}
double fma(double x, double y, double z) {
    return x * y + z;
}
...
std::ostream& operator<<(
     std::ostream& os, rect r
) {
     os << "{ width = "  << r.width
        << ", height = " << r.height
        << " }";
     return os;
}
#include <concepts>

template <std::floating_point F>
int fma(F x, F y, F z) {
    return x * y + z;
}
template <typename T>
class Array {
private:
    size_t size;
    std::unique_ptr<T> data;
public:
    ...
};
class Base {
    virtual void do();
};
class Derived : public Base {
    virtual void do() override();
};
void doer(Base& b) {
    b.do();
}
doer(Derived{});
  • bounded polymorphism

polymorphism

dynamic polymorhpism*

dynamic

interface
Base class
Derived class 1
Derived class 2
Derived class 3
interface
interface
interface

polymorphism

dynamic polymorhpism*

dynamic

interface
Base class
Derived class 1
Derived class 2
Derived class 3
interface
interface
interface

polymorphism

dynamic polymorhpism*

dynamic

interface
Base class
Derived class 1
Derived class 2
Derived class 3
interface
interface
interface

polymorphism

dynamic polymorhpism*

dynamic

void interface(const Base& b) {
   b.doSomething();
}
class Base {
public:
    void doSomething() const {
        std::cout << "Base\n";
    };
};
class Derived1 : public Base {
public:
    void doSomething() const {
        std::cout << "D1\n";
    };
};
class Derived2 : public Base {
public:
    void doSomething() const {
        std::cout << "D2\n";
    };
};
class Derived3 : public Base {
public:
    void doSomething() const {
        std::cout << "D3\n";
    };
};

polymorphism

dynamic polymorhpism*

dynamic

void interface(const Base& b) {
   b.doSomething();
}
class Base {
public:
    void doSomething() const {
        std::cout << "Base\n";
    };
};
class Derived1 : public Base {
public:
    void doSomething() const {
        std::cout << "D1\n";
    };
};
class Derived2 : public Base {
public:
    void doSomething() const {
        std::cout << "D2\n";
    };
};
class Derived3 : public Base {
public:
    void doSomething() const {
        std::cout << "D3\n";
    };
};

interface(Base{}); // Base
interface(Derived1{}); // Base
interface(Derived2{}); // Base
interface(Derived3{}); // Base

polymorphism

dynamic polymorhpism*

dynamic

void interface(const Base& b) {
  switch(b.type) {
  case Base::typeIdx:
    b.doSomething();
    break;
  case Derived1::typeIdx:
    static_cast<const Derived1&>(b)
      .doSomething();
    break;
  case Derived2::typeIdx:
    static_cast<const Derived2&>(b)
      .doSomething();
    break;
  case Derived3::typeIdx:
    static_cast<const Derived3&>(b)
      .doSomething();
    break;
    ...
  }
}
class Base {
public:
    constexpr static size_t typeIdx = 193843867;
    const size_t type = typeIdx;
protected:
    Base(size_t derivedTypeIdx) :
        type{derivedTypeIdx}
    {
    }
public:
    Base() = default;

    void doSomething() const {
        std::cout << "Base\n";
    };
};
class Derived1 : public Base {
public:
    constexpr static size_t typeIdx = 949769248;
public:
    Derived1() :
        Base{typeIdx}
    {
    }

    void doSomething() const {
        std::cout << "Derived1\n";
    };
};
...
interface(Base{}) // Base
interface(Derived1{}); // Derived1
interface(Derived2{}); // Derived2
interface(Derived3{}); // Derived3

polymorphism

dynamic polymorhpism*

dynamic

void interface(const Base& b) {
  switch(b.type) {
  case Base::typeIdx:
    b.doSomething();
    break;
  case Derived1::typeIdx:
    static_cast<const Derived1&>(b)
      .doSomething();
    break;
  case Derived2::typeIdx:
    static_cast<const Derived2&>(b)
      .doSomething();
    break;
  case Derived3::typeIdx:
    static_cast<const Derived3&>(b)
      .doSomething();
    break;
    ...
  }
}
class Base {
public:
    constexpr static size_t typeIdx = 193843867;
    const size_t type = typeIdx;
protected:
    Base(size_t derivedTypeIdx) :
        type{derivedTypeIdx}
    {
    }
public:
    Base() = default;

    void doSomething() const {
        std::cout << "Base\n";
    };
};
class Derived1 : public Base {
public:
    constexpr static size_t typeIdx = 949769248;
public:
    Derived1() :
        Base{typeIdx}
    {
    }

    void doSomething() const {
        std::cout << "Derived1\n";
    };
};
...
interface(Base{}) // Base
interface(Derived1{}); // Derived1
interface(Derived2{}); // Derived2
interface(Derived3{}); // Derived3

polymorphism

dynamic polymorhpism*

dynamic

interface
Base class
Created by potrace 1.15, written by Peter Selinger 2001-2017

polymorphism

dynamic polymorhpism*

dynamic

interface
Base class
Derived class 1
Derived class 2
Derived class 3
Created by potrace 1.15, written by Peter Selinger 2001-2017
interface
interface
interface

polymorphism

dynamic polymorhpism*

dynamic

interface
Base class
Derived class 1
Derived class 2
Derived class 3
interface
interface
interface

polymorphism

dynamic polymorhpism*

dynamic

void interface(const Base& b) {
   b.doSomething();
}
class Base {
public:
    void doSomething() const {
        std::cout << "Base\n";
    };
};
class Derived1 : public Base {
public:
    void doSomething() const {
        std::cout << "D1\n";
    };
};
class Derived2 : public Base {
public:
    void doSomething() const {
        std::cout << "D2\n";
    };
};
class Derived3 : public Base {
public:
    void doSomething() const {
        std::cout << "D3\n";
    };
};

interface(Base{}); // Base
interface(Derived1{}); // Base
interface(Derived2{}); // Base
interface(Derived3{}); // Base

polymorphism

dynamic polymorhpism*

dynamic

void interface(const Base& b) {
   b.doSomething();
}
class Base {
public:
    void(* const doSomething)();
    Base() :
        Base{[](){
            std::cout << "Base\n";
        }}
    {
    }
protected:
    Base(void(*pfn)()) :
        doSomething{pfn}
    {
    }
};
class Derived1 : public Base {
public:
    Derived1() : 
        Base{[](){
            std::cout << "Derived1\n";
        }}
    {
    };
};
...
interface(Base{}) // Base
interface(Derived1{}); // Derived1
interface(Derived2{}); // Derived2
interface(Derived3{}); // Derived3

polymorphism

dynamic polymorhpism*

dynamic

class Base {
public:
    void(* const greet)();
    int(* const lucky)();
    double(* const transform)(double);
    Base() :
        Base{[](){
            std::cout << "Base\n";
        },
        [](){
            return 42;
        },
        [](double x){
            return x * 2.0;
        }}
    {
    }
protected:
    Base(
        void(*pfnGreet)(),
        int(*pfnLucky)(),
        double(*pfnTransform)(double)
    ) : greet{pfnGreet},
        lucky{pfnLucky},
        transform{pfnTransform}
    {
    }
};
class Derived1 : public Base {
public:
    Derived1() : 
        Base{[](){
            std::cout << "Derived1\n";
        },
        [](){
            return 13;
        },
        [](double x){
            return x + 158.3;
        }}
    {
    };
};

polymorphism

dynamic polymorhpism*

dynamic

class Derived1 : public Base {
public:
    Derived1() : 
        Base{[](){
            std::cout << "Derived1\n";
        }} // error, 2 arguments missing
    {
    };
};
class Base {
public:
    void(* const greet)();
    int(* const lucky)();
    double(* const transform)(double);
    Base() :
        Base{[](){
            std::cout << "Base\n";
        },
        [](){
            return 42;
        },
        [](double x){
            return x * 2.0;
        }}
    {
    }
protected:
    Base(
        void(*pfnGreet)(),
        int(*pfnLucky)(),
        double(*pfnTransform)(double)
    ) : greet{pfnGreet},
        lucky{pfnLucky},
        transform{pfnTransform}
    {
    }
};

polymorphism

dynamic polymorhpism*

dynamic

class Base {
public:
    void(* const greet)();
    int(* const lucky)();
    double(* const transform)(double);
    Base() :
        Base{[](){
            std::cout << "Base\n";
        }}
    {
    }
protected:
    Base(
        void(*pfnGreet)(),
        int(*pfnLucky)() = [](){
            return 42;
        },
        double(*pfnTransform)(double) = [](double x){
            return x * 2.0;
        }
    ) : greet{pfnGreet},
        lucky{pfnLucky},
        transform{pfnTransform}
    {
    }
};
class Derived1 : public Base {
public:
    Derived1() : 
        Base{[](){
            std::cout << "Derived1\n";
        }} // ok
    {
    };
};

polymorphism

dynamic

Base

greet
lucky
transform

Base

greet
lucky
transform
[](){ std::cout << "Base\n"; }
[](double x){ return x * 2.0; }
[](){ return 42; }

Base

greet
lucky
transform

Base

greet
lucky
transform

polymorphism

dynamic polymorhpism*

dynamic

[](){ std::cout << "Base\n"; }
[](double x){ return x * 2.0; }
[](){ return 42; }

Base

vtable

Base

vtable

Base

vtable

Base

vtable

polymorphism

dynamic polymorhpism*

dynamic

class Base {
protected:
    struct Vtable {
        void(* const greet)();
        int(* const lucky)();
        double(* const transform)(double);
    };
    const Vtable& vptr;
private:
    constexpr static Vtable vtable{
        [](){
            std::cout << "Base\n";
        },
        [](){
            return 42;
        },
        [](double x){
            return x * 2.0;
        }
    };
public:
    Base(const Vtable& vt = vtable) :
        vptr{vt}
    {
    }
    void greet() const { vptr.greet(); }
    int lucky() const { return vptr.lucky(); }
    double transform(double x) const { return vptr.transform(x); }
};
class Derived1 : public Base {
private:
    constexpr static Vtable vtable{
        [](){
            std::cout << "Derived1\n";
        },
        [](){
            return 13;
        },
        [](double x){
            return x + 138.5;
        }
    };
public:
    Derived1() :
        Base{vtable}
    {
    }
};

functions

virtual
class Base {
public:
    virtual void greet() const {
        std::cout << "Base\n";
    }
    virtual int lucky() const {
        return 42;
    }
    virtual double transform(double x) const {
        return x * 2.0;
    }
    virtual ~Base() = default;
};
class Derived1 : public Base {
public:
    virtual void greet() const override {
        std::cout << "Derived1\n";
    }
    virtual int lucky() const override {
        return 13;
    }
    virtual double transform(double x) const override {
        return x * 158.3;
    }
};
void interface(const Base& b) {
   b.doSomething();
}
interface(Base{}) // Base
interface(Derived1{}); // Derived1
interface(Derived2{}); // Derived2
interface(Derived3{}); // Derived3

functions

virtual
class Base {
public:
    virtual void method() {
        ...
    }
    virtual ~Base() = default;
}
class Derived : public Base {
public:
    virtual void method() override {
        ...
    }
};
void user(Base& b) {
    b.method(); // <- dynamic dispatch
}
  • name
  • parameter types
  • cv-qualifiers
  • ref-qualifiers

Must match:

Return types can (either):

  • match
  • be covariant - be pointers/references to a base and derived class

final overrider

class Base {
    virtual void method() {
        ...
    }
    virtual ~Base() = default;
}
class Derived1 : public Base {
    virtual void method() override {
        ...
    }
};
class Derived2 : public Derived1 {
    virtual void method() override {
        ...
    }
};
class Derived3 : public Derived2 {
};
void user(Base& b) {
    b.method(); // calls the final overrider
}
Base::method
Derived1::method
Derived2::method
Derived2::method

destructor

virtual
class Base {
    virtual void method() {
        ...
    }
public:
    virtual ~Base() = default;
}
class Derived : public Base {
    virtual void method() override {
        ...
    }
};
class Base {
    virtual void method() {
        ...
    }
protected:
    ~Base() = default;
}
class Derived : public Base {
    virtual void method() override {
        ...
    }
};
{
    Base* b = new Derived();
    delete b; // UB
}
{
    Base* b = new Derived();
    delete b; // dynamic dispatch
}
{
    Base* b = new Derived();
    delete b; // ill-formed
}
override
class PostIncrementableString {
private:
	std::string data;
public:
    virtual PostIncrementableString operator++(int) {
    	...
    }
};
class BetterPostIncrementableString : public PostIncrementableString {
public:
    // oops! forgot int parameter, original method is hidden
    virtual BetterPostIncrementableString& operator++() {
    	...
    }
};
override
class PostIncrementableString {
private:
	std::string data;
public:
    virtual PostIncrementableString operator++(int) {
    	...
    }
};
class BetterPostIncrementableString : public PostIncrementableString {
public:
    // now the compiler can find the mistake
    virtual BetterPostIncrementableString& operator++() override {
    	...
    }
};
override
class PostIncrementableString {
private:
	std::string data;
public:
    virtual PostIncrementableString operator++(int) {
    	...
    }
};
class BetterPostIncrementableString : public PostIncrementableString {
public:
    // now the compiler can find the mistake
    virtual BetterPostIncrementableString& operator++(int) override {
    	...
    }
};
class Dog {
public:
    virtual std::string bark() {
    	return "bark";
    }
    virtual ~Dog() = default;
};
class Terrier : public Dog {
public:
    virtual std::string bark() override {
    	return "hahauau";
    }
};
class AustralianTerrier final : public Terrier {
public:
    virtual std::string bark() override {
    	return "ɥɐɥɐnɐn";
    }
};
class Boxer final : public Dog {
public:
    virtual std::string bark() override {
    	return "Borke";
    }
};
final
final
final
class Base {
public:
    virtual void f();
    virtual void g();
    virtual ~Base() = default;
};
class D1 : public Base {
public:
    virtual void f() override;
    virtual void g() override final;
    virtual ~Base() = default;
};
class D2 : public D1 {
public:
    virtual void f() override;
    virtual void g() override; // ERROR
    virtual ~Base() = default;
};

Slicing

class Base {
public:
    virtual int f() { return 42; };
    virtual ~Base() = default;
};
class D : public Base {
public:
    int a, b;
    virtual void f() override { return a + b; }
};
void user(Base b) {
    std::cout << b.f();
}
user(Derived{}); // 42
Derived d{2, 3};
Base& b = d;
b = Derived{1, 4};
// d.b still == 3

Slicing

class Base {
public:
    virtual int f() { return 42; };
    virtual ~Base() = default;
};
class D : public Base {
public:
    int a, b;
    virtual void f() override { return a + b; }
};
void user(const Base& b) {
    std::cout << b.f();
}
user(Derived{}); // 0
Derived d{2, 3};
Derived& b = d;
b = Derived{1, 4};
// d.b == 4

abstract classes

Class

private implementation

public interface

abstract Class

no implementation

public interface

abstract classes

struct Shape {
    virtual void draw() const {
        ??? draw what ???
    }
    virtual ~Shape() = default;
}
struct Triangle : Shape {
    virtual void draw() const override {
        ...
    }
}
struct Rectangle : Shape {
    virtual void draw() const override {
        ...
    }
}
struct Circle : Shape {
    virtual void draw() const override {
        ...
    }
}

???

abstract classes

struct Shape {
    // pure virtual function
    virtual void draw() = 0;
    virtual ~Shape() = default;
}
struct Triangle : Shape {
    virtual void draw() const override {
        ...
    }
}
struct Rectangle : Shape {
    virtual void draw() const override {
        ...
    }
}
struct Circle : Shape {
    virtual void draw() const override {
        ...
    }
}

<undefined>

abstract classes

class Base {
    virtual void f() = 0; // pure
    virtual void g() = 0; // pure
    virtual void h() {};
public:
    virtual ~Base() = default;
}
class Derived1 : public Base {
    virtual void f() override {};
    // g still pure
}
class Derived2 : public Derived1 {
    virtual void g() override {};
}

abstract

abstract

non-abstract

abstract classes

class Abstract {
public:
   virtual void something() = 0;
   virtual ~Abstract();
}
Abstract a{};
void f(Abstract a);
Abstract f();
static_cast<Abstract>(derived)

abstract classes

class Abstract {
public:
   virtual void something() = 0;
   virtual ~Abstract();
}
const Abstract& a = Derived{};
void f(Abstract& a);
Abstract& f();
static_cast<Abstract&>(derived)

interfaces

class IRenderable {
public:
    virtual void render() const = 0;
    virtual ~IRenderable() = default;
}
class Shape {
public:
    double x, y;
    virtual void draw() const = 0;
    virtual double perimiter() const = 0;
    virtual double area() const = 0;

    virtual ~Shape() = default;
}
class IDatabase {
public:
    virtual std::string& find(const std::string& key) = 0;
    virtual void insert(const std::string& key, const std::string& value) = 0;
    std::string& operator[](const std::string& key) {
        return find(key);
    }
    virtual ~IDatabase() = default;
}

interfaces

class Shape {
public:
    double x, y;
    virtual void draw() const = 0;
    virtual double perimiter() const = 0;
    virtual double area() const = 0;

    virtual ~Shape() = default;
}
class CompoundShape : public Shape {
private:
    std::vector<std::unique_ptr<Shape>> pieces;
public:
    virtual void draw() const {
        for (const auto& shape : pieces) {
            shape->draw();
        }
    }
    virtual double perimiter() const {
        double sum = 0;
        for (const auto& shape : pieces) {
            sum += shape->perimiter();
        }
        return sum;
    }
    virtual double area() const {
        double sum = 0;
        for (const auto& shape : pieces) {
            sum += shape->area();
        }
        return sum;
    }
};

interfaces

class Entity {
public:
    Vector<2> position;
    virtual void update(Duration) = 0;
    virtual void onCollision(Collider) = 0;
    ...
};
class Player : public Entity {
public:
    virtual void update(Duration deltaTime) {
        position = Input.getMoveDelta() * deltaTime;
        if (Input.pressed(Input::Fire)) {
            shoot();
        }
        ...
    }
    virtual void onCollision(Collider col) {
        if (col.type == Collider::Type::Bullet) {
            ...
        }
        ...
    }
};
class Enemy : public Entity {
public:
    virtual void update(Duration deltaTime) {
        position += playerDir() * deltaTime;
        if (inSight(player)) {
            shoot();
        }
        ...
    }
    virtual void onCollision(Collider col) {
        if (col.type == Collider::Type::Bullet) {
            ...
        }
        ...
    }
};
vector<std::unique_ptr<Entity>> entities;
for (auto entt : entities) {
    entt->update();
}

rtti

To be continued...

polymorphism

RAII

inheritance

virtual

  • constructor

  • member initializer list

  • destructor

  • default constructor

  • std::initializer_list

  • in-class initializer

  • copy ctor

  • cpy assign op

  • move ctor

  • mv assign op

  • default members

  • deleted members

  • rule of 0

  • rule of 3/5

  • smart-ptrs

  • converting constructor

  • explicit specifier

  • is-a relation

  • has-a relation

  • public inheritance

  • protected members

  • using declaration

  • final specifier

  • private & protected inheritance

  • multiple inheritance

  • diamond problem

  • empty base optimization

  • base method hiding problem (shadowing)

  • virtual methods

  • override specifier

  • dynamic type

    • RTTI

    • dynamic_cast

    • typeid

    • vtables

  • by value passing problem (slicing)

  • pure virtual function

  • abstract class

misc

  • friends

  • nested members

  • conversion operators

  • operator overloading

  • member pointers

  • unions

    • std optional

    • std variant

    • std any

type ereasure

pimpl

enable shared from this with private inheritance

linkage and static (non-member) functions

Liskov substitution principle

function member ref qualification

constexpr virtual

Polymorphism

By Jan Bielak

Polymorphism

A presentation about polymorphism: ad hoc polymorphism - function overloading and operator overloading, parametric polymorphism - templates - and dynamic polymorphism - virtual functions as well as nested classes. Also on the show: vtables, abstract classes, override, final, interfaces and slicing.

  • 697