Back Midas Rome Roody Rootana
  Midas DAQ System  Not logged in ELOG logo
Entry  16 Jul 2026, Konstantin Olchanski, Info, c++ exceptions, follow up 
    Reply  16 Jul 2026, Konstantin Olchanski, Info, c++ exceptions, follow up 
       Reply  16 Jul 2026, Yiwen Yang, Info, c++ exceptions, follow up 
          Reply  16 Jul 2026, Yiwen Yang, Info, c++ exceptions, follow up main.cpp
Message ID: 3247     Entry time: 16 Jul 2026     In reply to: 3246     Reply to this: 3248
Author: Yiwen Yang 
Topic: Info 
Subject: c++ exceptions, follow up 
Thanks for sharing the videos, Konstantin.

> Unpredictable code can be dangerous:
> 
> void boil_kettle()
>
{
>    i2c_set_bit(10, 1); // bit 10 is stove heater control, turn it on
>    foo(); // wait for kettle to
start boiling
>    i2c_set_bit(10, 0); // turn heater off
> }
> 
> If foo() starts throwing exceptions, I
will likely have a fire in my kitchen!
> 

Indeed, that would certainly be problematic. However, there is a
way to get around this while not having to wrap everything in try ... catch ... blocks with more C++
machinery using the catch-all handler and RAII.

To quickly expand upon your example:


=== main.cpp ===


#include <iostream>
#include <unistd.h>

void i2c_set_bit(int bit, int val) {
	std::cout << "Bit " << bit <<
" set to " << val << "\n";
}

class i2c_temp_bit {
	int bit; /* which bit */
	int newval; /* value to set bit
to */
	int oldval; /* value to reset bit to */
public:
	i2c_temp_bit(int b, int nv, int ov)
		: bit{b},
newval{nv}, oldval{ov}
	{
		std::cout << "Bit " << bit << " set to " << newval << "\n";
	}
	~i2c_temp_bit() 

{
		std::cout << "Bit " << bit << " set to " << oldval << "\n";
	}
	i2c_temp_bit(const i2c_temp_bit&) =
delete;
	i2c_temp_bit(i2c_temp_bit&&) = delete;
	i2c_temp_bit& operator=(const i2c_temp_bit&) = delete;
	
i2c_temp_bit& operator=(i2c_temp_bit&&) = delete;
};

int check_kettle_temp() {
	static int callnum = 0;
	if
(++callnum > 5) {
		return 100;
	}
#ifdef BADNUM
	else if (callnum == 4) { /* Don't like this number */
		
throw callnum;
	}
#endif
	else {
		return 25;
	}
}

void foo() {
	std::cout << "Waiting for kettle to boil"
<< std::endl;
	do { /* Check every 100 ms */
		usleep(100000);
	} while (check_kettle_temp() < 100);
	
std::cout << "Water is boiled!" << std::endl;
}

void boil_kettle()
{
#ifdef RAII
	i2c_temp_bit temp(10, 1,
0);
	foo();
#else
	i2c_set_bit(10, 1); // bit 10 is stove heater control, turn it on
	foo(); // wait for
kettle to start boiling
	i2c_set_bit(10, 0); // turn heater off
#endif
}

int main()
{
	try {
		
boil_kettle();
	} catch (...) {
		std::cerr << "Abnormal termination\n";
	}
}

=== main.cpp ===


When we run
this:

$ g++ -o main main.cpp && ./main
Bit 10 set to 1
Waiting for kettle to boil
Water is boiled!
Bit 10
set to 0

This is as expected, of course.
But indeed as you say, when foo() can throw an exception, we get a
problem:

$ g++ -DBADNUM -o main main.cpp && ./main
Bit 10 set to 1
Waiting for kettle to boil
Abnormal
termination

The exception is caught by the catch-all handler, but because boil_kettle() never finishes
execution, bit 10 never gets reset to 0 and that's a big problem.

With RAII however, we get this instead:

$
g++ -DBADNUM -DRAII -o main main.cpp && ./main
Bit 10 set to 1
Waiting for kettle to boil
Bit 10 set to 0

Abnormal termination

Because the i2c_temp_bit object is destroyed upon unwinding the boil_kettle() function,
it calls the destructor which resets the bit.

For closure, it does behave identically to the non-RAII
implementation when there are no exceptions:

$ g++ -DRAII -o main main.cpp && ./main
Bit 10 set to 1
Waiting
for kettle to boil
Water is boiled!
Bit 10 set to 0


Personally, I don't like this as it adds way too much
complexity to the code in order to avoid C++ foot-guns (see how the copy and move constructors etc. need to
be deleted, or else requires a bit of thinking to implement properly). 
But, it is an alternative that exists
to safely handle these situations.
ELOG V3.1.6-083448f7