Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RAII state management

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.

like image 268
Jakob Schou Jensen Avatar asked Nov 30 '22 23:11

Jakob Schou Jensen


2 Answers

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 returns, multiple breaks, 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.

like image 61
BoBTFish Avatar answered Dec 15 '22 22:12

BoBTFish


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);
}  
like image 34
Ami Tavory Avatar answered Dec 15 '22 22:12

Ami Tavory