Phase 01 · Week 4 · 90 minutes

Day 22: RAII, ownership, references, and smart pointers

Modern C++, Linux, and real-time habits · Deterministic, observable software at the hardware boundary.

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 run node week-04-night-gateway.mjs and 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_sensor when 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; const can 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)
  1. For a 100 Hz control loop, T = 1/100 s.

  2. Convert: 0.01 s = 10 ms per cycle.

  3. If work sometimes takes 13 ms, it misses the 10 ms deadline by 3 ms; measure the distribution, not only the average.

Programmer analogy

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

Nleaked=NacquiredNreleased=11=0.N_{\text{leaked}}=N_{\text{acquired}}-N_{\text{released}}=1-1=0.

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.

  1. Mark the port wrapper as the owner because it acquired the operating-system handle.

  2. Mark the driver reference as a borrower; it may use the port but must not outlive or close it.

  3. Place the wrapper before the borrowing driver so destruction occurs in reverse order: driver first, then port.

  4. Follow the bad-checksum branch and note that returning early still leaves both scopes normally.

  5. Run the destruction order: the driver stops using the port, then the wrapper closes the handle exactly once.

  6. Ask what changes if a worker thread borrows the driver: shutdown must stop and join that worker before either borrowed object dies.

Result

The error path releases the handle once, and every borrower ends before the resource owner.

What this proves

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.

Look for:

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.

Look for:

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.

  1. Write Port so its constructor prints open and its destructor prints close.

  2. Delete its copy constructor and copy assignment so one port object cannot be accidentally copied.

  3. Write a read_once(Port&) function that borrows the existing port and returns early on a fake invalid sample.

  4. Create the owner as a local value, call read_once, and verify that close prints once after the early return.

  5. Move a std::unique_ptr<Port> between two clearly named owners and confirm the moved-from handle is empty.

  6. Add a short ownership note stating who owns the port, who borrows it, and the required worker-stop order.

Observe

Construction and destruction messages make lifetime visible; moving transfers ownership, while borrowing leaves the original owner responsible.

Done when

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

Wrong

Using shared_ptr everywhere because several functions touch an object.

Better

Touching is borrowing, not owning. Use references for required borrowers and reserve shared ownership for genuinely independent lifetime owners.

Wrong

Calling RAII automatic memory management.

Better

RAII manages any resource with a deterministic release action, including files, locks, sockets, and device handles—not only heap memory.

Wrong

Closing the device first and then asking the worker thread to stop.

Better

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?
Model interview answer

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?
Model interview answer

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?
Model interview answer

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.