Sorry if the title feels convoluted. Consider the below code, it's not so long (85 lines) and trivial to understand.
#include <stdexcept>
#include <stdio.h>
constexpr void assert_that(bool statement, const char* message) {
if (!statement) throw std::runtime_error{ message };
}
struct SpeedUpdate {
double velocity_mps;
};
struct CarDetected {
double distance_m;
double velocity_mps;
};
struct BrakeCommand {
double time_to_collision_s;
};
template <typename T>
struct AutoBrake {
AutoBrake(const T& publish)
: collision_threshold_s{ 5 },
speed_mps{},
publish{ publish } {}
void observe(const SpeedUpdate& su) {
speed_mps = su.velocity_mps;
}
void observe(const CarDetected& cd) {
const auto relative_velocity_mps = speed_mps - cd.velocity_mps;
const auto time_to_collision_s = cd.distance_m / relative_velocity_mps;
if (time_to_collision_s > 0 &&
time_to_collision_s <= collision_threshold_s) {
publish(BrakeCommand{ time_to_collision_s });
}
}
void set_collision_threshold_s(double x) {
if (x < 1) throw std::out_of_range{ "Collision less than 1." };
collision_threshold_s = x;
}
double get_collision_threshold_s() const {
return collision_threshold_s;
}
double get_speed_mps() const {
return speed_mps;
}
private:
double collision_threshold_s;
double speed_mps;
const T& publish;
};
void alert_when_collision_imminent() {
int brake_commands_published{};
// case 1: no segfault
/*
auto f = [&brake_commands_published](const BrakeCommand&) { brake_commands_published++; };
AutoBrake auto_brake{ f };
*/
// case 2: segfault
/*
AutoBrake auto_brake{
[&brake_commands_published](const BrakeCommand&) {
brake_commands_published++;
}
};
*/
auto_brake.set_collision_threshold_s(10L);
auto_brake.observe(SpeedUpdate{ 100L });
auto_brake.observe(CarDetected{ 100L, 0L });
assert_that(brake_commands_published == 1, "brake commands published not one");
}
int main() {
alert_when_collision_imminent();
}
On GCC 13.2.1 in Arch Linux, the program fails when case 2 is uncommented: it causes a segfault when the callback function accesses brake_commands_published
.
This doesn't occur in case 1 where, instead of constructing the function object as a rvalue and passing it directly to AutoBrake
's constructor, we construct a function object, create an lvalue and then pass that lvalue to the constructor.
I simply don't understand why.
Obs: The code has been extracted from Josh Lospinoso's C++ Crash Course.
AutoBrake
's constructor saves a reference to its parameter in a class member.
In the first case, the reference is to the object f
, which remains in scope, it continues to exist, for the duration of the relevant code.
In the second case, the reference is to a temporary object, that goes out of scope and gets destroyed after the object is constructed. AutoBrake
's constructor stores a reference to a temporary object. The temporary object gets destroyed after AutoBrake
gets constructed, and it now holds a dangling reference.
Subsequent usage of the referenced, but destroyed, object becomes undefined behavior.