I really don't like answering my own question here, especially because I realized that I asked the wrong question. I was too much focused on OO terms and habits, but the reality is that what I wanted was a prodedure, not a class or anything that can actually store a state. Sorry for that.
Here's my (not quite working) solution (improved solution below):
I remembered that there are macros that manage "atomic blocks" by clearing an interrupt enable flag when the block is entered, and restoring it after the block was run. I had a look at those and they are basically just a for
loop.
Together with an anonymous struct definition in the for loop initialization (https://stackoverflow.com/a/11255852) this grew to a solution that works for me:
#include <iostream>
struct Settings
{
Settings(int v_) : v(v_) {}
int v;
};
//https://stackoverflow.com/a/11255852
#define useHardwareWith(settings) \
acquireHardware(settings); \
for( \
struct \
{ \
bool done() \
{ \
return done_; \
} \
bool run() \
{ \
releaseHardware(); \
done_ = true; \
} \
bool done_; \
} magic = {false}; !magic.done() ; magic.run())
void acquireHardware(const Settings& settings)
{
std::cout << "acquire(" << settings.v << ")\n";
}
void releaseHardware()
{
std::cout << "release()\n";
}
int main()
{
Settings settings(1);
useHardwareWith(settings)
{
std::cout << "Doing important hardware stuff\n";
}
}
Output:
acquire(1)
Doing important hardware stuff
release()
Online demo: http://ideone.com/eT91hD
The for
macro cannot be stored, so it's not disallowing non-local storage, but simply doing something in a specific order.
This is better
Above solution has a major problem: release()
is not called when break
or return
is called in the loop, because release()
is in the "increment" part, and not in a destructor. Unfortunately, a destructor for the anonymous cannot be defined. So I added a named inner struct to do that:
#include <iostream>
struct Settings
{
Settings(int v_) : v(v_) {}
int v;
};
//https://stackoverflow.com/a/11255852
#define useHardwareWith(settings) \
acquireHardware(settings); \
for( \
struct \
{ \
struct inner \
{ \
inner() : done_(false) \
{ \
} \
~inner() \
{ \
releaseHardware(); \
} \
bool done_; \
}; \
bool done() const \
{ \
return inner_.done_; \
} \
bool run() \
{ \
inner_.done_ = true; \
} \
inner inner_; \
} magic; !magic.done() ; magic.run())
void acquireHardware(const Settings& settings)
{
std::cout << "acquire(" << settings.v << ")\n";
}
void releaseHardware()
{
std::cout << "release()\n";
}
void doHardwareStuff(const Settings& settings)
{
useHardwareWith(settings)
{
if (settings.v == 0)
{
std::cout << "side-exit\n";
return;
}
std::cout << "Doing important hardware stuff\n";
}
}
int main()
{
Settings settings(0);
doHardwareStuff(settings);
doHardwareStuff(Settings(1));
}
Online demo: http://ideone.com/z3Qm8a
main
in the outermost scope, it's no different from having a static variable ...acquire()
andrelease()
pairs. The hardware driver used is quite simple and provides those methods as a means of synchronizing higher-level access, especially when those higher-level drivers use an interrupt service routine toacquire()
.