3 minute read

Some old notes I took while listening to that talk. Not sure if really of any use though…

Talk: Back to Basics: Object-Oriented Programming - Jon Kalb - CppCon 2019

Resources:

DISCLAIMER: Modern philosophy is to use value-semantics instead of reference semantics (which OOP makes use of).

Great talk on the basics of “old-school” object-oriented programming (“OOP).

OOP is still in wide spread use, but modern resources (e.g. talks, books) do not focus on it anymore. This talk on the other hand explains how to make use of modern language feature for old-school OOP.

  • modern: compile-time

Theory of OOP

  • def: polymorphism based on runtime function dispatch using virtual functions
  • base class defines API, similar to derived objects are independent libraries
  • base class: define API
  • derived class: provides different implementations
  • pointer to base:
    • difference: static vs dynamic type!!!
    • virtual method: dispatches to correct type

Liskov substitution

Subtype Requirement: code written against Base API will also work correctly for objects of the derived type

counter example:

struct SurpriseLogger final: Logger {
    virtual void LogMessage(char const* message) override {
        std::exit(EXIT_FAILURE); // fails to provide semantics of LogMessage API
    }
};

OOP: not only hierarchy BUT As we derive we respect requirements of base class

talk: virtual dispatch: inbal levi cppcon 2019

Design guidelines

  • Use OOP to model “is-a” relationship, not for code-reuse
  • Make non-leaf classes abstract.
  • Use the non-virtual interface (NVI) idiom

Building guidelines

Example: Logging

simple example

struct Logger { // base class: defines interface
    virtual void LogMessage(char const* message) = 0;
    virtual ~Logger() = default;
};

#include <iostream>
struct ConsoleLogger final: Logger { // derived class: provides implementation
    virtual void LogMessage(char const* message) override {
        std::cout << message << '\n';
    }
};

#include <memory>
int main() {
    auto logger{std::make_unique<ConsoleLogger>()};
    Logger* logger_ptr(logger.get());
    logger_ptr->LogMessage("Hello, World!");
    // static (compile-time) type of logger_ptr: Logger*
    // dynamic (run-time) type of logger_ptr: ConsoleLogger*
}

LogHelloWorld


void LogHelloWorld(Logger& logger) {
    // neither knows nor depends on dynamic type of logger
    logger.LogMessage("Hello, World!");
}

#include <memory>
int main() {
    auto Logger{std::make_unique<ConsoleLogger>()};
    LogHelloWorld(*logger);
    LogHelloWorld(*static_cast<Logger*>(logger));
}

FileLogger

#include <fstream>
struct FileLogger final : Logger {
    FileLogger(char const* filename) : output_{filename} {}
    virtual void LogMessage(char const* message) override {
        output_ << message << '\n';
    }
private: 
    std::ofstream output_;
}

#include <memory>
int main() {
    auto cl{ConsoleLogger{}};
    auto fl{FileLogger{"logfile.text"}};
    LogHelloWorld(cl);
    LogHelloWorld(fl);
}