The code
#include <csetjmp>
template <typename Callable>
void create_checkpoint(std::jmp_buf buf, Callable&& callable)
{
if (setjmp(buf) != 0)
{
callable();
}
}
#include <iostream>
struct announcer {
int id;
announcer(int id):
id{id}
{
std::cout << "created announcer with id " << id << '\n';
}
~announcer() {
std::cout << "destructing announcer with id " << id << '\n';
}
};
void oopsie(std::jmp_buf buf, bool shouldJump)
{
if (shouldJump)
{
// std::cout << "performing jump...\n";
std::longjmp(buf, 1);
}
}
void test1()
{
std::jmp_buf buf;
announcer a1{1};
create_checkpoint(buf, []() {throw std::exception();});
oopsie(buf, true);
}
void test2()
{
std::jmp_buf buf;
announcer a1{1};
create_checkpoint(buf, []() {throw std::exception();});
oopsie(buf, false);
announcer a2{2};
create_checkpoint(buf, []() {throw std::exception();});
oopsie(buf, true);
}
int main()
{
try
{
test1();
}
catch (...)
{}
try
{
test2();
}
catch (...)
{}
}
Context
I have to call some C library that reports errors via longjmp
. To provide strong exception guarantee, I want to create a function that is very similar to std::lock_guard
, e.g. I just write create_checkpoint(buf, handler)
and keep calling the C library functions until I allocate more resources (destructors for objects created below setjmp
line are not called, if I understand correctly).
Question
Why undefined behavior is invoked in this case and how can I fix it?
How did I find out this is undefined behavior?
Printing message to std::cout
before std::longjmp
vs not printing produces very different results even though that line has little to do with control flow.
What do I understand by now?
I understand that std::longjmp
essentially restores the registers and jumps at the instruction pointer saved by setjmp
macro. Also, functions are not optimized away, and at least during compilation, there is an instruction to call longjmp
.
Converting the create_checkpoint
to macro seems to solve the issue. Yet I wonder is there a better way to do this?