Chapter 04
Production C++, Linux, and deterministic timing
Turn familiar software-engineering habits into reliable robot-boundary software: make resource ownership explicit, build small testable components, package them reproducibly, connect them to Linux devices without unsafe shortcuts, measure timing under load, and preserve enough evidence to replay a failure.
Before you start
- Read a short function, loop, class, and test in any programming language; no prior C++ syntax is assumed.
- Use a terminal to create a folder and run a command, with all hardware exercises kept unpowered or simulated.
- Understand from Week 3 that a control loop has a period, measured input, bounded output, and deadline.
By the end
- Explain C++ scope, lifetime, RAII, references, raw pointers, unique ownership, and shared ownership without treating smart pointers as decoration.
- Design small value types and interfaces that keep units, validity rules, hardware access, and control logic at clear dependency boundaries.
- Build a target-based CMake project with a library, executable, tests, warnings, and a checked-in preset.
- Diagnose a Linux device or network bring-up problem through processes, device nodes, identities, permissions, stable names, interfaces, addresses, and ports.
- Measure loop period, execution time, latency, deadline misses, and jitter; explain threads, locks, priority inversion, and the limits of real-time priority.
- Use sanitizers, profiling, structured event records, deterministic replay, and CI as complementary evidence rather than interchangeable badges.
- Ship a bounded sensor-to-controller pipeline whose build, normal run, overload, cancellation, device failure, and replay can be reproduced by another engineer.
The field story
The Night-Shift Sensor Gateway
The cold-storage team now needs a reliable gateway between a simulated encoder stream and the bounded door controller. Its previous prototype compiled, but an early return leaked a device handle, a background task outlived the object it borrowed, one stale packet became a fresh command, and the only crash could not be reproduced. You are asked to turn those symptoms into explicit ownership, narrow value contracts, a target-based build, least-privilege device access, measured timing, and a replayable regression path.
The Night-Shift Sensor Gateway stays intentionally small: source sample, validator, filter, controller, command limiter, and fake sink. That fixed path lets every software habit connect to one observable outcome. Resource lifetime must survive failures, live and replay inputs must use the same calculations, timing claims must include tails and misses, and cancellation must reach a terminal state before dependencies disappear. The finish line is not ‘build succeeded’; it is a clean repository that another engineer can configure, test, fault, replay, and shut down from documented commands.
- Why this chapter now
Robot software is about to cross Linux devices, threads, ROS callbacks, and controller deadlines; ordinary happy-path code is not enough.
- Ignore for now
Ignore powered I/O, vendor drivers, formal hard-real-time guarantees, and distributed ROS behavior. Keep the source and sink simulated.
- This unlocks
A production-shaped software boundary for ROS nodes, ros2_control plugins, sensor health, deterministic replay, and CI.
- Proof you will leave with
Save ownership traces, interface tests, clean CMake build output, permission/network diagnosis, timing distribution, sanitizer fault, replay fixture, CI result, starter output, and bounded shutdown evidence.
Environment contractUbuntu 24.04 is the course Linux target for device, udev, process, and timing exercises. The dependency-free starter runs with repository-supported Node.js 22.13.0 or newer and does not make a real-time claim.
- Compatibility boundary
macOS or another desktop may run the starter and pure calculations, but udev, Linux scheduling, device permissions, and target timing require a qualified Linux environment. Compiler and CMake versions must be recorded per run.
- Smoke check
On the teaching path, record
uname -a,node --version, compiler version, and CMake version; then runnode week-04-night-gateway.mjsand compare exact output.- Contract reviewed
2026-07-25
- Runtime evidence
The dependency-free starter is executed by repository tests on the supported Node.js baseline. Chapter-specific ROS 2, Gazebo, model, dataset, checkpoint, and hardware environments are learner-created unless the repository supplies an explicit asset; run the smoke check and preserve its versions and output before claiming runtime compatibility.
- Drift risk
medium
Today in the field story
One problem, then the next
The Night-Shift Sensor Gateway first failed during an early return, so begin by assigning one owner to every file, device handle, lock, and worker lifetime. Borrowers may inspect the resource but may not silently prolong it. Trace normal, error, and cancellation paths, then prove the worker stops and joins before the resource owner is destroyed.
- Why now
Every later boundary depends on deterministic cleanup and valid borrower lifetime.
- Ignore today
Ignore shared ownership unless two independent lifetimes are proven to need it.
- Unlocks next
A gateway that can fail or cancel without leaking resources or using dead objects.
Understand
Build the physical picture first
Treat every scarce thing as a borrowed workshop tool: one named owner is responsible for returning it, and the return happens automatically when that owner's working area ends.
A C++ object has a lifetime: a moment when it begins to exist and a moment when it stops existing. A local object normally dies when execution leaves its surrounding braces, called its scope. Its destructor runs at that boundary. RAII uses this ordinary language rule to manage a resource: acquire the resource while constructing an owning object, then release it in the destructor. The resource could be memory, a file, a serial port, a mutex, or a camera handle. Cleanup still runs when a function returns early or an exception crosses the scope, so correctness does not depend on remembering every manual close path.
Ownership answers who must keep an object alive and who must release its resource. A plain local value has a simple owner: its scope. A std::unique_ptr<T> is an owning handle that cannot be copied; moving it transfers the one ownership responsibility. A std::shared_ptr<T> counts cooperating owners and destroys the object only when the last owner leaves, which adds cost and can leak through ownership cycles. Choose shared ownership only when the design truly has several independent lifetime owners, not because it feels safer than deciding.
A reference such as Sensor& is a temporary alias for an existing object and normally means the called function does not take ownership. A const Sensor& also promises not to modify the sensor through that reference. A raw pointer such as Sensor* can express a non-owning optional relationship when nullptr has a meaning, but a pointer alone does not tell the reader who owns the object. Passing unique_ptr or shared_ptr into every helper falsely suggests ownership transfer or sharing; a helper that merely uses a sensor should usually receive a reference or non-owning pointer.
Robot drivers make lifetime mistakes physical. If a serial-port owner is copied accidentally, two objects may try to close the same handle. If a background callback keeps a dangling pointer after the driver dies, it may read freed memory while commands are active. If shutdown waits forever for a worker, the actuator may never receive its final quiet command. Design the stop sequence with the resource lifetime: request the defined safe output and stop, wake blocked work, let bounded shutdown logic deliver or confirm that safe transition, join the worker, verify the required quiet state, and only then let the owning handle close.
Words you need
Name each idea precisely
- Scope
The region of code in which a name is usable; leaving a local object's scope normally ends that object's lifetime.
Physical example:A serial-session object created inside one test case is destroyed when that case finishes.
- RAII
A C++ pattern that ties acquiring and releasing a resource to the construction and destruction of an owning object.
Physical example:A port wrapper opens
/dev/robot_sensorwhen constructed and closes it in its destructor.- Ownership
The responsibility for keeping an object alive and eventually releasing the resource it controls.
Physical example:The driver object owns the file descriptor; a parser only borrows bytes read from it.
- Reference
A non-owning alias to an existing object;
constcan forbid modification through that alias.Physical example:A filter receives
const Sample&to inspect a sample without copying it or controlling its lifetime.- Smart pointer
An owning standard-library handle whose type states unique or shared lifetime responsibility.
Physical example:A factory returns
std::unique_ptr<Camera>so the caller becomes the camera owner's single successor.
Math, one line at a time
Work through today’s relationship
Prerequisite rescue · optionalRates, deadlines, jitter, and memory budgets
Hardware-facing software must finish work predictably before its deadline.
- f
- loop frequencyUnit: hertz (Hz)
- T = 1/f
- time available for one loopUnit: seconds (s)
- jitter
- variation around the expected timingUnit: milliseconds (ms)
For a 100 Hz control loop, T = 1/100 s.
Convert: 0.01 s = 10 ms per cycle.
If work sometimes takes 13 ms, it misses the 10 ms deadline by 3 ms; measure the distribution, not only the average.
This is a strict rendering or audio-processing budget, except a missed robot deadline can destabilize an actuator.
How much time does a 50 Hz loop have per cycle?
1/50 s = 0.02 s = 20 ms.
For the one-handle example, the leak count is
This arithmetic is evidence for that test path, while RAII defines which lifetime performs the release.
Trace ownership through a sensor read
A function creates a port owner, constructs a sensor driver that borrows the port, reads one sample, and returns early when the checksum is bad.
Mark the port wrapper as the owner because it acquired the operating-system handle.
Mark the driver reference as a borrower; it may use the port but must not outlive or close it.
Place the wrapper before the borrowing driver so destruction occurs in reverse order: driver first, then port.
Follow the bad-checksum branch and note that returning early still leaves both scopes normally.
Run the destruction order: the driver stops using the port, then the wrapper closes the handle exactly once.
Ask what changes if a worker thread borrows the driver: shutdown must stop and join that worker before either borrowed object dies.
The error path releases the handle once, and every borrower ends before the resource owner.
Correct lifetime is a graph of owners and borrowers, not a choice between raw and smart pointer spellings.
Physical examples
Where this appears in real life
Spring-loaded soldering-iron stand
Taking the iron from its holder begins a responsibility; returning it ends that responsibility even when the original task is abandoned halfway.
RAII is stronger than a reminder note because cleanup is attached to the holder's lifetime, including early exits.
One motor port, two software owners
Two independently copied driver handles both believe they may close or reconfigure the same motor-controller connection.
Double close, commands after close, and disagreement over shutdown order reveal that the resource never had one clear owner.
Hands-on exercise
Make the idea observable
Use a local C++ compiler and a harmless fake Port class that only prints open, read, and close; do not connect powered hardware.
Write
Portso its constructor printsopenand its destructor printsclose.Delete its copy constructor and copy assignment so one port object cannot be accidentally copied.
Write a
read_once(Port&)function that borrows the existing port and returns early on a fake invalid sample.Create the owner as a local value, call
read_once, and verify thatcloseprints once after the early return.Move a
std::unique_ptr<Port>between two clearly named owners and confirm the moved-from handle is empty.Add a short ownership note stating who owns the port, who borrows it, and the required worker-stop order.
Construction and destruction messages make lifetime visible; moving transfers ownership, while borrowing leaves the original owner responsible.
Normal and early-return runs both close exactly once, copying the owner fails to compile, and your note names every borrower that must stop before destruction.
Build today
Create a Linux C++ sensor→filter→controller pipeline with device permissions, bounded timing, tests, CI, structured logs, and deterministic replay.
Evidence to save
DONE when the learning log explains “RAII, ownership, references, and smart pointers” in five precise points and a checked example produces the predicted output.
Common mistakes
Catch the wrong mental model
Using shared_ptr everywhere because several functions touch an object.
Touching is borrowing, not owning. Use references for required borrowers and reserve shared ownership for genuinely independent lifetime owners.
Calling RAII automatic memory management.
RAII manages any resource with a deterministic release action, including files, locks, sockets, and device handles—not only heap memory.
Closing the device first and then asking the worker thread to stop.
Request stop, wake and join work that borrows the device, establish the required safe state, and only then destroy the device owner.
Job connection
How this becomes employable evidence
Review a C++ camera or serial driver so handles, buffers, callbacks, and worker threads have explicit owners and a bounded safe shutdown order.
Relevant target roles
- Robotics Software Engineer — ROS 2 / AMR
- Robotics Application / ROS 2 Integration Engineer
- Robotics Deployment, Integration & Validation Engineer
Chapter 04 interview drill
Interview questions: RAII, ownership, references, and smart pointers
Practise a 60–90 second answer: define the idea, connect it to a physical robot, state assumptions, frames, and units when relevant, then finish with the failure signal or evidence you would inspect.
Primary interview scenario
A callback sometimes runs after its device driver is destroyed. Draw the owner-and-borrower lifetimes, explain why a smart pointer alone may not repair the design, and propose a testable shutdown sequence.
Answer shape: clarify the situation → trace the physical and software path → test the most likely boundaries → name the evidence that would confirm the result.
Technical follow-up questions
Q1Why does `read_sensor(const Sensor&)` usually not need a smart-pointer parameter?
It only borrows an existing sensor for the call and does not transfer or share lifetime ownership; the const reference states that narrower contract.
Q2What happens to a correctly designed RAII port wrapper when a function returns early?
Its destructor runs as the local scope is left, so the owned handle is released without a separate close on that branch.
Q3What must happen before an object dies if a worker thread holds a reference to it?
The worker must be requested to stop, unblocked if necessary, and joined so it can no longer use the borrowed object.
Chapter references
- C++ Core GuidelinesRAII, resource ownership, value semantics, parameter passing, interfaces, error handling, and the rule that smart-pointer parameters should express lifetime semantics.
- CMake — official tutorialTarget-based executables and libraries, usage requirements, presets, testing, installation, and finding dependencies.
- systemd — udev manualLinux device events, device-node permissions, rule matching, and stable symlinks under /dev.
- Linux kernel — PREEMPT_RT theory of operationNormal versus real-time scheduling, preemption, threaded interrupts, rtmutexes, and priority inheritance.
- Clang — sanitizers documentationOfficial AddressSanitizer, UndefinedBehaviorSanitizer, and ThreadSanitizer guidance, including detected defects, instrumentation, limits, and security considerations.