【问题标题】:RAII state managementRAII 状态管理
【发布时间】:2016-03-10 08:37:27
【问题描述】:

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

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

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

编辑:感谢所有答案。

有几个建议可以创建一个自定义类来处理状态变化(BoBTFish、mindriot、Mattias Johansson)。这个解决方案看起来很好而且很清楚。但是我认为这是一个缺点,它将行数从 4 增加到 20+。如果使用很多,这会使代码膨胀。此外,似乎有一个单独的班级会丢失一些地方性。

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

sp2danny 提出了一个可重用的通用状态更改类。这避免了代码膨胀,只要它可以替换几个自定义类。我会接受这个答案 - 但我想正确的方法确实取决于上下文。

【问题讨论】:

    标签: c++ c++11 c++14 raii


    【解决方案1】:

    RAII:R资源A采集初始化。

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

    重点是,你在对象的构造函数中做了一些工作,并在析构函数中有效地反转它。这意味着无论你如何退出作用域都会进行清理:多个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 用于删除动态分配的对象,在某些情况下可以用于其他事情,尽管它有点难看。 std::vector 可以看作是动态数组的 RAII 类,还提供了一些其他的管理功能,但是这个不太容易被滥用于其他目的。

    【讨论】:

      【解决方案2】:

      也许实现scoped guard pattern 的最简洁的方式(尽管可能不是最易读的)是使用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);
      }  
      

      【讨论】:

      • 我认为交换不适用于我的情况 - 但我想我可以在逆向器 lambda 中编写 setActivationOrder(oldActivation)。
      【解决方案3】:

      你可以这样做:

      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
      }
      

      【讨论】:

        【解决方案4】:

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

        我会这样写课程:

        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
        

        为了确保没有其他用户通过将其分配到空闲存储/堆而不是本地范围来滥用此代码,您可以删除 operator 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

        【讨论】:

        • “没有免费的商店分配”可以作为单行来完成:template&lt;typename... Args&gt; void* operator new(std::size_t,Args...) = delete;
        • @sp2danny 好评论,我用 delete 编辑了答案以获得更清晰的代码。
        【解决方案5】:

        你可以做一个通用模板:

        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();
        }
        

        如果你经常使用这种模式也许值得

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2020-11-26
          • 2020-07-13
          • 2021-08-18
          • 2021-08-20
          • 2021-04-11
          • 2017-11-26
          • 2021-03-04
          相关资源
          最近更新 更多