c++支持“finally”块吗?
RAII习语是什么?
c++的RAII习语和c#的using语句有什么区别?
c++支持“finally”块吗?
RAII习语是什么?
c++的RAII习语和c#的using语句有什么区别?
当前回答
为什么即使是托管语言也会提供final块,尽管垃圾收集器会自动释放资源?
实际上,基于垃圾收集器的语言需要更多的“finally”。垃圾收集器不会及时销毁您的对象,因此不能依赖它正确地清理与内存无关的问题。
就动态分配数据而言,许多人认为应该使用智能指针。
然而……
RAII将异常安全的责任从对象的用户转移到设计人员
可悲的是,这是它自己的失败。旧的C编程习惯很难改掉。当您使用用C或非常C风格编写的库时,将不会使用RAII。除了重写整个API前端,这就是你必须要处理的。那么,“终于”这个词的缺失真的很伤人。
其他回答
很抱歉挖了这么老的一个线程,但下面的推理有一个重大错误:
RAII将异常安全的责任从对象的用户转移到对象的设计者(和实现者)。我认为这是正确的地方,因为你只需要让异常安全正确一次(在设计/实现中)。通过使用finally,您需要在每次使用对象时都获得正确的异常安全性。
通常情况下,你必须处理动态分配的对象,动态数量的对象等。在try块中,一些代码可能会创建许多对象(有多少是在运行时确定的),并将指向它们的指针存储在一个列表中。现在,这不是一个奇异的场景,但很常见。在这种情况下,你会想写这样的东西
void DoStuff(vector<string> input)
{
list<Foo*> myList;
try
{
for (int i = 0; i < input.size(); ++i)
{
Foo* tmp = new Foo(input[i]);
if (!tmp)
throw;
myList.push_back(tmp);
}
DoSomeStuff(myList);
}
finally
{
while (!myList.empty())
{
delete myList.back();
myList.pop_back();
}
}
}
当然,当超出作用域时,列表本身将被销毁,但这不会清除您创建的临时对象。
相反,你必须走一条丑陋的路:
void DoStuff(vector<string> input)
{
list<Foo*> myList;
try
{
for (int i = 0; i < input.size(); ++i)
{
Foo* tmp = new Foo(input[i]);
if (!tmp)
throw;
myList.push_back(tmp);
}
DoSomeStuff(myList);
}
catch(...)
{
}
while (!myList.empty())
{
delete myList.back();
myList.pop_back();
}
}
另外:为什么即使是托管语言也会提供一个final块,尽管垃圾收集器会自动释放资源?
提示:使用“finally”可以做的不仅仅是内存释放。
I also think that RIIA is not a fully useful replacement for exception handling and having a finally. BTW, I also think RIIA is a bad name all around. I call these types of classes 'janitors' and use them a LOT. 95% of the time they are neither initializing nor acquiring resources, they are applying some change on a scoped basis, or taking something already set up and making sure it's destroyed. This being the official pattern name obsessed internet I get abused for even suggesting my name might be better.
我只是认为不合理的做法是,要求某些特殊列表的每一个复杂设置都必须编写一个类来包含它,以避免在清理过程中出现问题时需要捕获多个异常类型时的复杂性。这将导致大量的临时类,否则这些类是不必要的。
是的,对于专为管理特定资源而设计的类,或者专为处理一组类似资源而设计的泛型类,这是没问题的。但是,即使所有涉及的东西都有这样的包装器,清理的协调也可能不仅仅是简单的反序析构函数调用。
我认为c++有一个final。我的意思是,天哪,在过去的几十年里,它被粘上了这么多零碎的东西,似乎奇怪的是,人们突然对一些东西变得保守起来,比如最终,它可能非常有用,可能不像其他一些已经添加的东西那么复杂(尽管这只是我的猜测)。
在c++中,由于RAII, final是不需要的。
RAII将异常安全的责任从对象的用户转移到对象的设计者(和实现者)。我认为这是正确的地方,因为你只需要让异常安全正确一次(在设计/实现中)。通过使用finally,您需要在每次使用对象时都获得正确的异常安全性。
在我看来,代码看起来更整洁了(见下文)。
例子:
一个数据库对象。为了确保使用了DB连接,必须打开并关闭它。通过使用RAII,这可以在构造函数/析构函数中完成。
C+像火车
void someFunc()
{
DB db("DBDesciptionString");
// Use the db object.
} // db goes out of scope and destructor closes the connection.
// This happens even in the presence of exceptions.
RAII的使用使得正确使用DB对象变得非常容易。DB对象将通过使用析构函数正确地关闭自身,无论我们如何尝试和滥用它。
Java终于来了
void someFunc()
{
DB db = new DB("DBDesciptionString");
try
{
// Use the db object.
}
finally
{
// Can not rely on finaliser.
// So we must explicitly close the connection.
try
{
db.close();
}
catch(Throwable e)
{
/* Ignore */
// Make sure not to throw exception if one is already propagating.
}
}
}
当最终使用对象时,对象的正确使用委托给对象的用户。例如,正确地显式地关闭DB连接是对象用户的责任。现在,您可能会认为这可以在终结器中完成,但资源可能有有限的可用性或其他约束,因此您通常希望控制对象的释放,而不是依赖于垃圾收集器的非确定性行为。
这也是一个简单的例子。 当需要释放多个资源时,代码可能会变得复杂。
更详细的分析可以在这里找到:http://accu.org/index.php/journals/236
正如许多人所说,解决方案是使用c++ 11的特性来避免最终阻塞。其中一个特性是unique_ptr。
以下是Mephane使用RAII模式编写的答案。
#include <vector>
#include <memory>
#include <list>
using namespace std;
class Foo
{
...
};
void DoStuff(vector<string> input)
{
list<unique_ptr<Foo> > myList;
for (int i = 0; i < input.size(); ++i)
{
myList.push_back(unique_ptr<Foo>(new Foo(input[i])));
}
DoSomeStuff(myList);
}
这里有更多关于在c++标准库容器中使用unique_ptr的介绍
总之,微软Visual c++确实支持try,它在MFC应用中一直被用作捕获严重异常的方法,否则会导致崩溃。例如;
int CMyApp::Run()
{
__try
{
int i = CWinApp::Run();
m_Exitok = MAGIC_EXIT_NO;
return i;
}
__finally
{
if (m_Exitok != MAGIC_EXIT_NO)
FaultHandler();
}
}
在过去,我用它来做一些事情,比如在退出之前保存打开文件的备份。不过,某些JIT调试设置会破坏这种机制。