Rod*_*ddy 31 c++ exception raii
我熟悉RAII的优点,但我最近在这样的代码中遇到了问题:
class Foo
{
public:
Foo()
{
DoSomething();
...
}
~Foo()
{
UndoSomething();
}
}
Run Code Online (Sandbox Code Playgroud)
一切都很好,除了构造函数...部分中的代码抛出异常,结果UndoSomething()从未被调用.
有明显的方法来解决这个特定的问题,比如...在一个try/catch块然后调用UndoSomething(),但是a:那是重复的代码,而b:try/catch块是一种代码气味,我试图通过使用RAII技术来避免.而且,如果涉及多个Do/Undo对,代码可能会变得更糟,更容易出错,而且我们必须在中途进行清理.
我想知道有一个更好的方法来做到这一点 - 也许一个单独的对象需要一个函数指针,并在它反过来被破坏时调用该函数?
class Bar
{
FuncPtr f;
Bar() : f(NULL)
{
}
~Bar()
{
if (f != NULL)
f();
}
}
Run Code Online (Sandbox Code Playgroud)
我知道不会编译,但它应该显示原则.Foo然后变成......
class Foo
{
Bar b;
Foo()
{
DoSomething();
b.f = UndoSomething;
...
}
}
Run Code Online (Sandbox Code Playgroud)
请注意,foo现在不需要析构函数.这听起来比它的价值更麻烦吗,或者这已经是一个常见的模式,有助于我处理繁重的事情吗?
Mik*_*our 30
问题是你的班级试图做太多.RAII的原理是它获取资源(在构造函数中或稍后),析构函数释放它; 该类仅用于管理该资源.
在您的情况下,除了DoSomething()并且UndoSomething()应该由类的用户负责,而不是类本身.
正如Steve Jessop在评论中所说:如果你有多个资源可以获得,那么每个资源都应该由自己的RAII对象管理; 将这些聚合为另一个依次构造每个类的数据成员可能是有意义的.然后,如果任何获取失败,则所有先前获得的资源将由各个类成员的析构函数自动释放.
(另外,请记住规则三 ;您的类需要防止复制,或以某种合理的方式实现它,以防止多次调用UndoSomething()).
R. *_*des 17
只是让DoSomething/ UndoSomething成适当RAII手柄:
struct SomethingHandle
{
SomethingHandle()
{
DoSomething();
// nothing else. Now the constructor is exception safe
}
SomethingHandle(SomethingHandle const&) = delete; // rule of three
~SomethingHandle()
{
UndoSomething();
}
}
class Foo
{
SomethingHandle something;
public:
Foo() : something() { // all for free
// rest of the code
}
}
Run Code Online (Sandbox Code Playgroud)
小智 6
我也会用RAII来解决这个问题:
class Doer
{
Doer()
{ DoSomething(); }
~Doer()
{ UndoSomething(); }
};
class Foo
{
Doer doer;
public:
Foo()
{
...
}
};
Run Code Online (Sandbox Code Playgroud)
doer是在ctor体启动之前创建的,当析构函数通过异常失败或者对象被正常销毁时会被破坏.
你的班上有太多了.将DoSomething/UndoSomething移动到另一个类('Something'),并将该类的对象作为类Foo的一部分,因此:
class Foo
{
public:
Foo()
{
...
}
~Foo()
{
}
private:
class Something {
Something() { DoSomething(); }
~Something() { UndoSomething(); }
};
Something s;
}
Run Code Online (Sandbox Code Playgroud)
现在,在调用Foo的构造函数时调用DoSomething,如果Foo的构造函数抛出,则UndoSomething会被正确调用.
try/catch一般不是代码味道,应该用来处理错误.在你的情况下,它会是代码味道,因为它不处理错误,只是清理.这就是破坏者的用途.
(1)如果构造函数失败时应该调用析构函数中的所有内容,只需将其移动到私有清理函数(由析构函数调用)和构造函数(如果失败).这似乎就是你已经完成的事情.做得好.
(2)一个更好的想法是:如果有多个do/undo对可以单独破坏,它们应该被包装在他们自己的小RAII类中,这就是它的微型任务,并在它自己之后进行清理.我不喜欢你当前给它一个可选的清理指针函数的想法,这只是令人困惑.清理应始终与初始化配对,这是RAII的核心概念.
| 归档时间: |
|
| 查看次数: |
3244 次 |
| 最近记录: |