C++: An Embedded Wild Ride (2024-09-30 - 2024-10-04)

Day 1

“New” vs. “Old” C++: An Overview

C++ is a huge pile of language constructs built on top of each other, always backwards compatible with its origins. In 2011, C++ got a major overhaul which is still ongoing. It is not always easy for someone who is new to C++ to understand why things are how they are.

A Live-Hacked Tour Around The New C++ tries to draw the boundary between the old and new C++.

(Omitting material from threading onwards)

Basic C++: Classes

From Data Encapsulation

See livehacking/point.cpp

Exercise

Implement a temperature sensor class which resembles an embedded thing that uses memory-mapped registers

See toolcase/base/raw-mem-sensor.h and toolcase/base/raw-mem-sensor.cpp

Day 2

Morning Wakeup

More About Classes

Preparing for 2nd exercise (class FileSensor)

Exercise: class FileSensor

Implement based on test which we agreed upon earlier.

Day 3

Morning Wakeup

  • Recap: = default, brace initialization, member initialization, “vector anomaly”

  • Show CMake dependencies (.dot files). Libraries, And Dependencies

Inheritance, And Polymorphism

From Inheritance And Object Oriented Design

C++11: additional keywords

Livehacked outcome in livehacking/inheritance.cpp

Exercise

../../../../_images/project-plan.jpg ../../../../_images/uml.jpg

Day 4

Morning Wakeup

  • #include coding guidelies: e.g. toolcase/base/CoutSink.h has #include <base/IDataSink.h>

    • Discuss "" vs. <>

    • toolcase/base/CMakeLists.txt: target_include_directories(... INTERFACE ...)

  • Project structure: split toolcase/base

    • No-dependency, and basic interfaces in toolcase/base

    • Separate nodes (add_library()) for CAN, CSV, SensorReader

Code Generation With CMake

Livehacked outcome

Project Work (Tests err Requirements)

  • CSV error tests (file not found, appending to existing file vs. overwriting)

    std::expected

  • SensorReader tests? ⟶ SensorReader::one(). How about more loops, with abstracted time? Discuss!

Resource Management: Copy

Smart Pointers: std::shared_ptr<>

Day 5

Morning Wakeup

  • toolcase/can/CanCoutPeriph.h: dtor not needed

  • Show toolcase/can/SocketCANPeriph.h

  • no copy!

  • Show programs/can-thermometer-firmware.cpp

Smart Pointers: std::unique_ptr<>

Resource Management: Move

See livehacking/string.cpp

Eliminating virtual: Template CanDataSink<>

The Strategy Pattern has many facets.

Originally, it was conceived as using a polymorphic type. Like CanDataSink (CanDataSink.h), using a polymorphic CAN interface to abstract away CAN communication (ICan.h). While this is the most flexible application of the pattern, it is not always applicable in embedded systems where code size matters.

Instead of parameterizing CanDataSink with a polymorphic object - at runtime - of base type ICan, the same effect can be achieved by parameterizing CanDataSink at compile time, by turning CanDataSink into a template class, parameterized with a concrete class that does the physical output.

See embedded-nonvirtual-polymorphic-pointers/toolcase/can/CanDataSink.h.

Note that templates, instantiated with parameter types that are lacking required functionality, can cause a compiler to frustrate developers. Concepts are a newer language feature to prevent such situations.

Eliminating virtual: using std::variant

Idea

The approach seen in Eliminating virtual: Template CanDataSink<> does not permit runtime dispatch between two strategies. If runtime dispatch is needed, virtual sometimes runs into opposition from embedded developers for the following reasons.

  • It has a certain runtime overhead. The compiler is prevented from applying optimizations across indirect calls. inline virtual is much desired, but not a feature of the language.

  • Code bloat. Unsure though if that is really the case.

Approach 1: Pointers To Alternatives In A std::variant

Rather than implementing the Strategy Pattern using a classic pointer to polymorphic type, union (err std::variant) is used to hold a fixed number of unrelated alternative pointer types. Advantages:

  • No virtual. The alternatives only need to match the requirements (see Eliminating virtual: Template CanDataSink<>); Concepts could additionally be used to check requirements.

  • Dynamic dispatch is done using std::visit, using the std::variant’s type discriminator, with the effect that the compiler is allowed to optimize aggressively.

  • If the std::variant is instantiated with only one type (when the embedded firmware is burnt into hardware), chances are that dynamic dispatch is omitted altogether.

Implementation

Approach 2: Alternative Objects In A std::variant

As a case study (and to show Move Semantics in action), Approach 1: Pointers To Alternatives In A std::variant is modified to cram entire objects in a std::variant.

  • Advantage: no pointer usage. Objects are moved or copied, thus there are no references that might dangle.

  • Disadvantage: takes a while to understand.

Implementation