C++是否支持' finally '块?
什么是RAII成语?
C++的RAII习语与C#的'using'语句有什么区别?
不,C++不支持'finally'块.原因是C++反而支持RAII:"资源获取是初始化" - 一个非常有用的概念的蹩脚名称†.
这个想法是对象的析构函数负责释放资源.当对象具有自动存储持续时间时,将在创建对象的块退出时调用该对象的析构函数 - 即使在存在异常时退出该块也是如此.这是Bjarne Stroustrup对该主题的解释.
RAII的一个常见用途是锁定互斥锁:
// A class with implements RAII class lock { mutex &m_; public: lock(mutex &m) : m_(m) { m.acquire(); } ~lock() { m_.release(); } }; // A class which uses 'mutex' and 'lock' objects class foo { mutex mutex_; // mutex for locking 'foo' object public: void bar() { lock scopeLock(mutex_); // lock object. foobar(); // an operation which may throw an exception // scopeLock will be destructed even if an exception // occurs, which will release the mutex and allow // other functions to lock the object and run. } };
RAII还简化了将对象用作其他类的成员.当拥有类'被破坏时,由RAII类管理的资源被释放,因为RAII管理的类的析构函数被调用.这意味着当您对管理资源的类中的所有成员使用RAII时,您可以使用非常简单的,甚至是默认的所有者类的析构函数,因为它不需要手动管理其成员资源生命周期.(感谢Mike B指出这一点.)
对于那些使用C#或VB.NET的人来说,你可能会认识到RAII类似于使用IDisposable和'using'语句的.NET确定性破坏.的确,这两种方法非常相似.主要区别在于RAII将确定性地释放任何类型的资源 - 包括内存.在.NET中实现IDisposable(甚至是.NET语言C++/CLI)时,除了内存之外,资源将被确定性地释放.在.NET中,内存不是确定性释放的; 内存仅在垃圾回收周期中释放.
†有些人认为"毁灭是资源放弃"是RAII习语的更准确的名称.
在C++中总算是不是必需的,因为RAII的.
RAII将异常安全的责任从对象的用户转移到对象的设计者(和实现者).我认为这是正确的地方因为你只需要一次异常安全(在设计/实现中).通过最后使用,您需要在每次使用对象时获得异常安全性.
此外,IMO代码看起来更整洁(见下文).
例:
数据库对象.要确保使用DB连接,必须打开和关闭它.通过使用RAII,可以在构造函数/析构函数中完成.
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对象都将通过使用析构函数正确关闭自身.
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.
}
}
}
最后使用时,将对象的正确使用委托给对象的用户.即,对象用户有责任正确地关闭数据库连接.现在您可以争辩说这可以在终结器中完成,但资源可能具有有限的可用性或其他约束,因此您通常希望控制对象的释放而不依赖于垃圾收集器的非确定性行为.
这也是一个简单的例子.
当您有多个需要释放的资源时,代码会变得复杂.
可以在此处找到更详细的分析:http://accu.org/index.php/journals/236
在C++ 11中,如果需要,RAII允许最终:
namespace detail { //adapt to your "private" namespace templatestruct FinalAction { FinalAction(F f) : clean_{f} {} ~FinalAction() { if(enabled_) clean_(); } void disable() { enabled_ = false; }; private: F clean_; bool enabled_{true}; }; } template detail::FinalAction finally(F f) { return detail::FinalAction (f); }
使用示例:
#includeint main() { int* a = new int; auto delete_a = finally([a] { delete a; std::cout << "leaving the block, deleting a!\n"; }); std::cout << "doing something ...\n"; }
输出将是:
doing something... leaving the block, deleting a!
我个人用了几次来确保在C++程序中关闭POSIX文件描述符.
拥有一个管理资源的真正的类,因此避免任何类型的泄漏通常会更好,但这最终在使类听起来像过度杀伤的情况下非常有用.
此外,我最终比其他语言更喜欢它,因为如果自然使用你在开始代码附近编写结束代码(在我的例子中是new和delete),并且破坏遵循LIFO顺序构造,就像在C++中一样.唯一的缺点是你得到一个你并没有真正使用的自动变量,lambda语法会让它有点吵(在我的例子中,第四行只有最后一个单词,而右边的{} -block是有意义的,休息基本上是噪音).
另一个例子:
[...] auto precision = std::cout.precision(); auto set_precision_back = finally( [precision, &std::cout]() { std::cout << std::setprecision(precision); } ); std::cout << std::setprecision(3);
在禁用该成员是否有用最终只有在失败的情况下被调用.例如,您必须在三个不同的容器中复制一个对象,您可以设置finally以撤消每个副本,并在所有副本成功后禁用.这样做,如果破坏不能扔,你保证强有力的保证.
禁用示例:
//strong guarantee void copy_to_all(BIGobj const& a) { first_.push_back(a); auto undo_first_push = finally([first_&] { first_.pop_back(); }); second_.push_back(a); auto undo_second_push = finally([second_&] { second_.pop_back(); }); third_.push_back(a); //no necessary, put just to make easier to add containers in the future auto undo_third_push = finally([third_&] { third_.pop_back(); }); undo_first_push.disable(); undo_second_push.disable(); undo_third_push.disable(); }
除了使用基于堆栈的对象轻松清理之外,RAII也很有用,因为当对象是另一个类的成员时,会发生相同的"自动"清理.当拥有类被破坏时,由RAII类管理的资源被清除,因为该类的dtor被调用.
这意味着当你到达RAII天堂并且一个类中的所有成员都使用RAII(如智能指针)时,你可以为所有者类放弃一个非常简单(甚至可能是默认的)dtor,因为它不需要手动管理它成员资源生命周期.
为什么即使托管语言提供了最终阻止,尽管垃圾收集器自动解除分配资源?
实际上,基于垃圾收集器的语言需要"最终"更多.垃圾收集器不会及时销毁您的对象,因此无法依赖它来正确清除与内存无关的问题.
就动态分配的数据而言,许多人会争辩说你应该使用智能指针.
然而...
RAII将异常安全的责任从对象的用户转移到设计者
可悲的是,这是它自己的垮台.旧的C编程习惯很难.当您使用以C或非常C风格编写的库时,将不会使用RAII.没有重写整个API前端,这正是你必须要处理的. 然后缺乏"终于"真的咬人.
使用C ++ 11 lambda函数的另一个“最终”块仿真
templateinline void with_finally(const TCode &code, const TFinallyCode &finally_code) { try { code(); } catch (...) { try { finally_code(); } catch (...) // Maybe stupid check that finally_code mustn't throw. { std::terminate(); } throw; } finally_code(); }
希望编译器可以优化上面的代码。
现在我们可以编写如下代码:
with_finally( [&]() { try { // Doing some stuff that may throw an exception } catch (const exception1 &) { // Handling first class of exceptions } catch (const exception2 &) { // Handling another class of exceptions } // Some classes of exceptions can be still unhandled }, [&]() // finally { // This code will be executed in all three cases: // 1) exception was not thrown at all // 2) exception was handled by one of the "catch" blocks above // 3) exception was not handled by any of the "catch" block above } );
如果愿意,可以将此成语包装到“ try-finally”宏中:
// Please never throw exception below. It is needed to avoid a compilation error // in the case when we use "begin_try ... finally" without any "catch" block. class never_thrown_exception {}; #define begin_try with_finally([&](){ try #define finally catch(never_thrown_exception){throw;} },[&]() #define end_try ) // sorry for "pascalish" style :(
现在,“最终”块在C ++ 11中可用:
begin_try { // A code that may throw } catch (const some_exception &) { // Handling some exceptions } finally { // A code that is always executed } end_try; // Sorry again for this ugly thing
我个人不喜欢“ finally”惯用语的“ macro”版本,并且宁愿使用纯的“ with_finally”功能,即使在这种情况下语法更庞大。
您可以在此处测试上面的代码:http : //coliru.stacked-crooked.com/a/1d88f64cb27b3813
聚苯乙烯
如果您的代码中需要一个finally块,那么作用域保护或ON_FINALLY / ON_EXCEPTION宏可能会更适合您的需求。
这是用法ON_FINALLY / ON_EXCEPTION的简短示例:
void function(std::vector&vector) { int *arr1 = (int*)malloc(800*sizeof(int)); if (!arr1) { throw "cannot malloc arr1"; } ON_FINALLY({ free(arr1); }); int *arr2 = (int*)malloc(900*sizeof(int)); if (!arr2) { throw "cannot malloc arr2"; } ON_FINALLY({ free(arr2); }); vector.push_back("good"); ON_EXCEPTION({ vector.pop_back(); }); ...
很抱歉挖掘这样一个旧线程,但在以下推理中存在重大错误:
RAII将异常安全的责任从对象的用户转移到对象的设计者(和实现者).我认为这是正确的地方因为你只需要一次异常安全(在设计/实现中).通过最后使用,您需要在每次使用对象时获得异常安全性.
通常情况下,您必须处理动态分配的对象,动态数量的对象等.在try-block中,某些代码可能会创建许多对象(在运行时确定多少个对象)并在列表中存储指向它们的指针.现在,这不是一个奇特的场景,但很常见.在这种情况下,你想写一些像
void DoStuff(vectorinput) { list 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(vectorinput) { list 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(); } }
另外:为什么即使管理语言提供了最终阻止,尽管垃圾收集器自动解除分配资源?
提示:除了内存释放之外,你还能做更多的事情.
FWIW,Microsoft 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调试设置会打破这种机制.
正如其他答案所指出的,C++可以支持finally
类似功能.这个功能的实现可能最接近于标准语言的一部分,是C++核心指南的附件,这是一套使用Bjarne Stoustrup和Herb Sutter编辑的C++的最佳实践.的实施finally
是部分指引支持库(GSL).在整个指南中,finally
建议在处理旧式接口时使用它,并且它还有自己的指南,如果没有合适的资源句柄,则使用标题为使用final_action对象来表示清理.
因此,不仅C++支持finally
,实际上建议在很多常见用例中使用它.
GSL实现的示例用法如下:
#includevoid example() { int handle = get_some_resource(); auto handle_clean = gsl::finally([&handle] { clean_that_resource(handle); }); // Do a lot of stuff, return early and throw exceptions. // clean_that_resource will always get called. }
GSL的实现和使用与Paolo.Bolzoni的答案非常相似.一个区别是由于gsl::finally()
缺少disable()
调用而创建的对象.如果您需要该功能(比如,一旦组装完就返回资源并且不会发生任何异常),您可能更喜欢Paolo的实现.否则,使用GSL就像使用标准化功能一样接近.