I need to change a state. Then do stuff. Then reset the state back to what it was - e.g:
auto oldActivationOrder = mdiArea->activationOrder();
mdiArea->setActivationOrder( QMdiArea::StackingOrder );
mdiArea->cascadeSubWindows();
mdiArea->setActivationOrder( oldActivationOrder );
How do I do this in a RAII way? (c++ 11 and/or 14)
Edit: Thanks for all the answers.
There are several suggestions to create a custom class for handling the state change (BoBTFish, mindriot, Mattias Johansson). This solution seems good and clear. However I think it is a drawback that it increases the line count from 4 to 20+. If used a lot this would bloat the code. Also it seems that some locality is lost by having a separate class.
Ami Tavory suggests using std::unique_ptr. This does not have the code bloat issue and maintains locality. However, as Ami also indicates, it may not be the most readable solution.
sp2danny suggests a generalized state-change class that can be reused. This avoids code bloat provided that it can replace several custom classes. I'm going to accept this answer - but I guess the right approach really depends on the context.
RAII: Resource Acquisition Is Initialisation.
Which also implies that Resource Release Is Destruction, although I've never seen people talk about RRID, even though that's the more useful side of it. (Perhaps that should be Termination, or Finalisation?)
The point is, you do some work in the constructor of an object, and effectively reverse it in the destructor. This means that the cleanup is carried out no matter how you exit the scope: multiple return
s, multiple break
s, throw an exception, ... (even goto
!)
class ScopedActivationOrderChange {
QMdiArea& area_; // the object to operate on
QMdiArea::WindowOrder oldOrder_; // save the old state
public:
ScopedActivationOrderChange(QMdiArea& area, ActivationOrder newOrder)
: area_(area)
, oldOrder_(area_.activationOrder()) // save old state
{
area_.setActivationOrder(newOrder); // set new state
}
~ScopedActivationOrderChange()
{
area_.setActivationOrder(oldOrder_); // reset to old state
}
};
// ...
{ // <-- new scope, just to establish lifetime of the change
ScopedActivationOrderChange orderChange{*mdiArea, QMdiArea::StackingOrder};
mdiArea->cascadeSubWindows();
} // <-- end of scope, change is reversed
The Standard Library doesn't provide any general facility for this. It does provide some for more specific uses, such as std::unique_ptr
for deleting dynamically allocated objects, which can in some cases be used for other things, though it's a bit ugly. std::vector
can be seen as a RAII class for dynamic arrays, providing some other management facilities also, but this one is less easily abused for other purposes.
Perhaps the most succinct way (albeit possibly not the most readable) of implementing the scoped guard pattern is to use a std::unique_ptr
with a custom deleter:
#include <memory>
#include <utility>
int main()
{
void *p, *q;
auto reverser = [&p, &q](char *){std::swap(p, q);};
/* This guard doesn't really release memory -
it just calls the lambda at exit. */
auto guard = std::unique_ptr<char, decltype(reverser)>{nullptr, reverser};
std::swap(p, q);
}
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