Within the scope of a member function, I want to temporarly set a member variable to a certain value.
Then, when this function returns, I want to reset this member variable to a given known value.
To bo safe against exceptions and multiple returns, and I've done it with a simple RAII like class. It's defined within the scope of the member function.
void MyClass::MyMemberFunction() {
struct SetBackToFalse {
SetBackToFalse(bool* p): m_p(p) {}
~SetBackToFalse() {*m_p=false;}
private:
bool* m_p;
};
m_theVariableToChange = true;
SetBackToFalse resetFalse( &m_theVariableToChange ); // Will reset the variable to false.
// Function body that may throw.
}
It seems so obviously commonplace, that I was wondering if there was any such template class doing this in the C++ standard library?
Nothing physical happens. A typical implementation will allocate enough space in the program stack to store all variables at the deepest level of block nesting in the current function. This space is typically allocated in the stack in one shot at the function startup and released back at the function exit.
In C, variables are always statically (or lexically) scoped i.e., binding of a variable can be determined by program text and is independent of the run-time function call stack. For example, output for the below program is 0, i.e., the value returned by f() is not dependent on who is calling it.
When you declare a program element such as a class, function, or variable, its name can only be "seen" and used in certain parts of your program. The context in which a name is visible is called its scope. For example, if you declare a variable x within a function, x is only visible within that function body.
In C++ a scope is a static region of program text, and so something "out of scope", taken literally, means physically outside of a region of text. For instance, { int x; } int y; : the declaration of y is out of the scope in which x is visible.
Not yet (there have been proposals for this). But implementing a generic one is simple enough;
struct scope_exit {
std::function<void()> f_;
explicit scope_exit(std::function<void()> f) noexcept : f_(std::move(f)) {}
~scope_exit() { if (f_) f_(); }
};
// ...
m_theVariableToChange = true;
scope_exit resetFalse([&m_theVariableToChange]() { m_theVariableToChange = false; });
For simplicity above, I've redacted the copy and move constructors etc...
Marking them as = delete
will make the above a minimal solution. Further; moving could be allowed if desired, but copying should be prohibited.
A more complete scope_exit
would look like (online demo here);
template <typename F>
struct scope_exit {
F f_;
bool run_;
explicit scope_exit(F f) noexcept : f_(std::move(f)), run_(true) {}
scope_exit(scope_exit&& rhs) noexcept : f_((rhs.run_ = false, std::move(rhs.f_))), run_(true) {}
~scope_exit()
{
if (run_)
f_(); // RAII semantics apply, expected not to throw
}
// "in place" construction expected, no default ctor provided either
// also unclear what should be done with the old functor, should it
// be called since it is no longer needed, or not since *this is not
// going out of scope just yet...
scope_exit& operator=(scope_exit&& rhs) = delete;
// to be explicit...
scope_exit(scope_exit const&) = delete;
scope_exit& operator=(scope_exit const&) = delete;
};
template <typename F>
scope_exit<F> make_scope_exit(F&& f) noexcept
{
return scope_exit<F>{ std::forward<F>(f) };
}
Notes on the implementation;
std::function<void()>
can be used to erase the type of the functor. std::function<void()>
offers exception guarantees on the move constructors based on the exception specific of the held function. A sample of this implementation is found here
noexcept
, more substantial detail is found in the C++ proposal
throw
, this is also consistent with the C++11 specification on the default exception specification for a destructor. See cppreference, SO Q&A, GotW#47 and HIC++
Other implementations can be found;
You could 'abuse' shared_ptr
for this:
m_theVariableToChange = true;
std::shared_ptr<void> resetFalse(nullptr, [&](void*){ m_theVariableToChange = false; });
If there are concerns about using void
as template parameter T
, I found the following in the C++ standard:
20.8.2.2§2:
... The template parameter T of shared_ptr may be an incomplete type.
This indicates that T
is only used as a pointer, therefore using void
should be fine.
There is no standard version of this.
The CppGoreGuidelines Support Library (GSL) has a generalized version of this called finally but that library is not production quality yet. Its definitely recommended practice.
final_action
object to express cleanup if no suitable resource handle is availablefinally
is less verbose and harder to get wrong than try
/catch
.
void f(int n)
{
void* p = malloc(1, n);
auto _ = finally([p] { free(p); });
// ...
}
finally
is not as messy as try
/catch
, but it is still ad-hoc.
Prefer proper resource management objects.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With