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
Exercise¶
Implement a temperature sensor class which resembles an embedded thing that uses memory-mapped registers
Requirements: tests/raw-mem-sensor.cpp
“Hardware” definition see toolcase/base/plat.h
Implementation to be done in toolcase/base/raw-mem-sensor.h
See toolcase/base/raw-mem-sensor.h and toolcase/base/raw-mem-sensor.cpp
Day 2¶
Morning Wakeup¶
Complete yesterday’s exercise. Put implementation into separate
.cpp
file, and discuss. See toolcase/base/raw-mem-sensor.h and toolcase/base/raw-mem-sensor.cppexplicit
: which problem does that solve again? See livehacking/explicit.cpp
More About Classes¶
-
Show
int i{};
Show how default ctor uses that
Show how default ctor is not generated when explicit ctor is in place
See
int
“constructor”: livehacking/int-ctor.cpp>Usage in default constructor instantiation: livehacking/point-default-init.cpp
Custom Constructor, Constructors: Member Initialization
class point
: Show how code does not compile when members areconst
Preparing for 2nd exercise (class FileSensor
)¶
Sketch a test for a sensor that reads from a file
Much like Linux presents temperature sensors (see for example Linux and OneWire (using DS18B20 Temperature Sensor as Slave))
Screenplay: Unittest: GTest Fixtures (from Unit Testing With googletest)
<fstream>
short livehack
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¶
Like Exercise: Sensor Interface, but with our sensors.
Project plan
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¶
Show how config (sensor, sink) can be brought in more statically, at link time ⟶ manual prototype
Generate code? ⟶ Screenplay: Generated Code (add_custom_command())
Livehacked outcome
Code generator invocation: programs/CMakeLists.txt
Code generator itself: programs/thermometer-firmware-confgen.py
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¶
Day 5¶
Morning Wakeup¶
toolcase/can/CanCoutPeriph.h
: dtor not neededShow
toolcase/can/SocketCANPeriph.h
⟶ no copy!
Show
programs/can-thermometer-firmware.cpp
Smart Pointers: std::unique_ptr<>
¶
Resource Management: Move¶
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
¶
Show function call operator (
operator()(...)
), and Lambdas: livehacking/lambda.cppNew-Style Union: std::variant, and
std::visit
: livehacking/variant.cpp
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
DataSinkPointerAlternative.h: pointer-like, using a
std::variant
to hold a predefined set of alternativescan-thermometer-firmware.cpp: application of it.
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
DataSinkObjectAlternative.h: pointer-like, using a
std::variant
to hold a predefined set of alternativescan-thermometer-firmware.cpp: application of it.