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,尽管这是它更有用的一面。 (也许应该是终止,或最终确定?)
关键是,您在对象的构造函数中做了一些工作,并在析构函数中有效地反转了它。这意味着无论您如何退出范围,都会执行清理:多个return
s,多个break
s,抛出异常,...(甚至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;
};
我需要换个状态。然后做事。然后将状态重置为原来的状态 - 例如:
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,尽管这是它更有用的一面。 (也许应该是终止,或最终确定?)
关键是,您在对象的构造函数中做了一些工作,并在析构函数中有效地反转了它。这意味着无论您如何退出范围,都会执行清理:多个return
s,多个break
s,抛出异常,...(甚至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;
};