RAII状态管理

RAII state management

我需要换个状态。然后做事。然后将状态重置为原来的状态 - 例如:

auto oldActivationOrder = mdiArea->activationOrder();
mdiArea->setActivationOrder( QMdiArea::StackingOrder );
mdiArea->cascadeSubWindows();
mdiArea->setActivationOrder( oldActivationOrder );

如何以 RAII 方式执行此操作? (c++ 11 and/or 14)

编辑:感谢您的所有回答。

有几个创建自定义 class 来处理状态更改的建议(BoBTFish、mindriot、Mattias Johansson)。这个解决方案看起来很好很清楚。但是,我认为将行数从 4 增加到 20+ 是一个缺点。如果经常使用,这会使代码膨胀。此外,似乎某些地方因单独的 class.

而丢失

Ami Tavory 建议使用 std::unique_ptr。这没有代码膨胀问题并保持局部性。但是,正如 Ami 还指出的那样,它可能不是最易读的解决方案。

sp2danny 建议可以重复使用的通用状态更改 class。这避免了代码膨胀,前提是它可以替换多个自定义 classes。我将接受这个答案 - 但我想正确的方法确实取决于上下文。

你可以这样做:

class SetActivationOrder
{
public:
  SetActivationOrder(QMdiArea *mdiArea, QMdiArea::WindowOrder order)
    : m_mdiArea(mdiArea),
      m_oldActivationOrder(mdiArea->activationOrder())
  {
    m_mdiArea->setActivationOrder(order);
  }

  ~SetActivationOrder()
  {
    m_mdiArea->setActivationOrder(m_oldActivationOrder)
  }

private:
  QMdiArea *m_mdiArea;
  QMdiArea::WindowOrder m_oldActivationOrder;
};

然后像这样使用它:

{
  // This sets the order:
  SetActivationOrder sao(mdiArea, QMdiArea::StackingOrder);
  mdiArea->cascadeSubWindows();
  // Destructor is called at end of scope and sets the old order
}

RAII: Resource Acquisition Is I初始化。

这也意味着资源释放就是破坏,尽管我从未见过人们谈论 RRID,尽管这是它更有用的一面。 (也许应该是终止,或最终确定?)

关键是,您在对象的构造函数中做了一些工作,并在析构函数中有效地反转了它。这意味着无论您如何退出范围,都会执行清理:多个returns,多个breaks,抛出异常,...(甚至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

标准库不为此提供任何通用工具。它确实提供了一些用于更具体的用途,例如 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 可以看作是动态数组的 RAII class,还提供了一些其他管理设施,但是这个不太容易被滥用于其他目的。

也许是实现 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);
}  

你可以做一个通用模板:

template< typename Obj, typename Getter, typename Setter , typename StateType >
class ScopedStateChangeType
{
public:
    ScopedStateChangeType( Obj& o, Getter g, Setter s, const StateType& state )
        : o(o), s(s)
    {
        oldstate = (o.*g)();
        (o.*s)(state);
    }
    Obj* operator -> () { return &o; }
    ~ScopedStateChangeType()
    {
        (o.*s)(oldstate);
    }

private:
    Obj& o;
    Setter s;
    StateType oldstate;
};

template< typename Obj, typename Getter, typename Setter , typename StateType >
auto MakeScopedStateChanger( Obj& o, Getter g, Setter s, StateType state )
     -> ScopedStateChangeType<Obj,Getter,Setter,StateType>
{
    return { o, g, s, state };
}

像这样使用它:

QMdiArea mdiArea;

{
    auto ref = MakeScopedStateChanger(
        mdiArea, &QMdiArea::activationOrder, &QMdiArea::setActivationOrder,
        QMdiArea::StackingOrder );
    ref->cascadeSubWindows();
}

如果你经常使用这个模式,也许是值得的

使用 RAII(资源分配即初始化),您将在本地范围内(即在堆栈上)创建存储实例 class。您将要存储的状态传递给存储对象的构造函数,并确保存储对象的析构函数再次恢复状态。因为 C++ 保证当对象超出范围时,本地范围内的对象的析构函数会自动为您调用,而且如果抛出异常,您不必担心再次恢复状态。

我会这样写class:

class ActivationOrderState
{
public:
    ActivationOrderState(QMdiArea& area)
        : m_area(area)
    {
        // Get the old value
        m_oldOrder = area.activationOrder();
    }

    ~ActivationOrderState()
    {
        // Restore the old value
        m_area.setActivationOrder( m_oldOrder );
    }

private:
    QMdiArea& m_area;
    QMdiArea::WindowOrder m_oldOrder;
};

这个对象然后像这样使用

{
    ActivationOrderState state(*mdiArea); // saves the state
    mdiArea->setActivationOrder( QMdiArea::StackingOrder ); // set the new state

    // do other things here...

} // end of scope, destructor is called and state is restored again

为了确保没有其他用户通过在免费 store/heap 而不是本地范围内分配代码来滥用此代码,您可以删除运算符 new:

class ActivationOrderState
{
public:
    ActivationOrderState(QMdiArea& area)
        : m_area(area)
    {
        // Get the old value
        m_oldOrder = area.activationOrder();
    }

    ~ActivationOrderState()
    {
        // Restore the old value
        m_area.setActivationOrder( m_oldOrder );
    }

    // Remove the possibility to create this object on the free store.
    template<typename... Args> void* operator new(std::size_t,Args...) = delete;

private:
    QMdiArea& m_area;
    QMdiArea::WindowOrder m_oldOrder;
};

另请参阅 Using RAII to raise thread priority temporarily