从事 Java 工作很多年了,所以一直没有关注 C++。 语言定义中的 C++ 异常处理是否已添加 finally 子句?
有没有一种流行的模仿 Java 的 try/finally 的习惯用法?
我还担心 C++ 没有针对所有可能抛出的异常的最终超类型 - 就像 Java 的 Throwable 类。
我可以写:
try {
// do something
} catch(...) {
// alas, can't examine the exception
// can only do cleanup code and perhaps rethrow, ala:
throw;
}
附录编辑:
我最终接受了这个答案
得票最多,即使用
析构函数进行清理。 当然,
从我自己的评论来看,很明显我
不完全同意这一点。
然而,C++ 就是这样,所以在
我的申请努力
介意,我或多或少会努力
坚持共同社区
实践。 我将使用模板类
包装尚未拥有的资源
类析构函数(即 C 库
资源),从而赋予他们
析构函数语义。
新附录编辑:
嗯,而不是最后然后是一个闭包
也许有特色? 闭包结合
ScopeGuard 方法(请参阅其中之一
下面的答案)将是一种方法
任意完成清理
操作和清理权限
代码的外部范围上下文。 清理可以按照 Ruby 编程中常见的惯用方式来完成,其中它们在打开资源时提供清理块。 不是一个
正在考虑的闭合功能
C++?
Been doing Java for number of years so haven't been tracking C++. Has finally clause been added to C++ exception handling in the language definition?
Is there a favored idiom that mimics Java's try/finally?
Am also bothered that C++ doesn't have an ultimate super type for all possible exceptions that could be thrown - like Java's Throwable class.
I can write:
try {
// do something
} catch(...) {
// alas, can't examine the exception
// can only do cleanup code and perhaps rethrow, ala:
throw;
}
ADDENDUM EDIT:
I ended up accepting the answer that
had the most up votes, i.e., use
destructors to do cleanup. Of course,
from my own comments, it is clear I
don't entirely agree with that.
However, C++ is what it is and so in
the application endeavor I have in
mind, I'm going to more or less strive
to adhere to common community
practice. I'll use template classes to
wrap resources that don't already have
a class destructor (i.e., C library
resources), thus bestowing on them
destructor semantics.
NEW ADDENDUM EDIT:
Hmm, instead of finally then a closure
feature perhaps? A closure combined with
ScopeGuard approach (see one of the
answers below) would be a way to
accomplish cleanup with arbitrary
actions and access to the cleanup
code's outer scope context. Cleanup could be done in the idiom fashion that is seen in Ruby programming where they supply cleanup blocks when a resource is being opened. Isn't a
closure feature being considered for
C++?
发布评论
评论(15)
通过有效地使用析构函数。 当在 try 块中引发异常时,在其中创建的任何对象都将立即被销毁(并因此调用其析构函数)。
这与 Java 不同,在 Java 中,您不知道何时调用对象的终结器。
更新:直接来自马口:为什么不' C++ 提供“finally”结构吗?
By making effective use of destructors. When an exception is thrown in a try block, any object created within it will be destroyed immediately (and hence its destructor called).
This is different from Java where you have no idea when an object's finalizer will be called.
UPDATE: Straight from the horse's mouth: Why doesn't C++ provide a "finally" construct?
我的 0.02 美元。 我多年来一直使用 C# 和 Java 等托管语言进行编程,但为了提高速度,我被迫切换到 C++。 起初我无法相信我如何必须在头文件中写出两次方法签名,然后在 cpp 文件中写出两次,而且我不喜欢没有finally块,并且没有垃圾收集意味着跟踪到处的内存泄漏 -天哪,我一点也不喜欢它!
然而,正如我所说,我被迫使用 C++。 所以我被迫认真学习它,现在我终于理解了像 RAII 这样的所有编程习惯,并且我了解了该语言的所有微妙之处等等。 我花了一段时间,但现在我发现它与 C# 或 Java 相比有多么不同。
如今我认为 C++ 是最好的语言! 是的,我可以理解有时会多一些我所说的“干扰”(看似不必要的东西来写),但在实际认真使用该语言之后,我完全改变了我的想法。
我以前一直有内存泄漏的情况。 我曾经将所有代码写入 .h 文件,因为我讨厌代码分离,我无法理解他们为什么要这样做! 我过去总是以愚蠢的循环包含依赖关系而告终,而且还有更多。 我真的很迷恋 C# 或 Java,对我来说 C++ 是一个巨大的退步。 这些天我明白了。 我几乎从来没有内存泄漏,我喜欢接口和实现的分离,并且我不再有循环依赖的问题。
我也不会错过finally 块。 老实说,我的观点是,您所说的在 catch 块中编写重复的清理操作的这些 C++ 程序员对我来说听起来就像他们只是糟糕的 C++ 程序员。 我的意思是,看起来该线程中的任何其他 C++ 程序员都没有遇到您提到的任何问题。 RAII 确实让最终变得多余,而且如果有的话,那就是工作量减少了。 您编写了一个析构函数,然后您最终就不必再编写另一个析构函数了! 至少对于那种类型来说是这样。
恕我直言,我认为现在的情况是您现在已经习惯了 Java,就像我以前一样。
My $.02. I've been programming in managed languages like C# and Java for years, but was forced to make the switch to C++ for the purposes of speed. At first I couldn't believe how I had to write out the method signature twice in the header file and then the cpp file, and I didn't like how there was no finally block, and no garbage collection meant tracking memory leaks everywhere - gosh I didn't like it at all!
However, as I said I was forced to use C++. So I was forced to seriously learn it, and now I've finally understood all the programming idioms like RAII and I get all the subtleties of the language and such. It took me a while but now I see just how different of a language it is compared to C# or Java.
These days I think C++ is the best language there is! Yes, I can understand that there is a little more what I call 'chaff' sometimes (seemingly unnecessary stuff to write), but after actually using the language seriously, I've changed my mind about it completely.
I used to have memory leaks all the time. I used to write all my code into the .h file because I hated the separation of code, I couldn't understand why they would do that! And I used to always end up with stupid cyclic include dependencies, and heaps more. I was really hung up on C# or Java, to me C++ was a huge step down. These days I get it. I almost never have memory leaks, I enjoy the separation of interface and implementation, and I don't have problems with cycle dependencies anymore.
And I don't miss the finally block either. To be honest, my opinion is that these C++ programmers that you talk about writing repeated cleanup actions in catch blocks just sound to me like they're just bad C++ programmers. I mean, it doesn't look like any of the other C++ programmers in this thread are having any of the problems you mention. RAII really does make finally redundant, and if anything, it's less work. You write one destructor and then you never have to write another finally ever! Well at least for that type.
With respect, what I think is going on is you're just used to Java now, just like I had been.
C++ 的答案是 RAII:对象的析构函数将在超出范围时执行。 无论是通过返回、异常还是其他方式。 如果您在其他地方处理异常,则可以确保从被调用函数到处理程序的所有对象都将通过调用其析构函数来正确销毁。 他们会为你打扫卫生。
阅读http://en.wikipedia.org/wiki/Resource_acquisition_is_initialization
C++'s answer is RAII: The object's destructor will be executed when they go out of scope. Whether by a return, by an exception or whatever. If you handle the exception somewhere else, you can be sure all objects from the called function down to your handler will be properly destructed by having their destructor called. They will clean up for you.
Read http://en.wikipedia.org/wiki/Resource_acquisition_is_initialization
Nofinally 还没有被添加到 C++ 中,也不可能被添加。
C++ 使用构造函数/析构函数的方式使得不需要finally。
如果您使用 catch(...) 进行清理,那么您没有正确使用 C++。 清理代码应该全部位于析构函数中。
尽管不要求使用它,但 C++ 确实有 std::exception。
强制开发人员从特定类派生以使用异常违背了 C++ 的“保持简单”理念。 这也是为什么我们不要求所有类都从 Object 派生的原因。
阅读: C++ 支持“finally”块吗? (我一直听说的“RAII”是什么?)
使用finally 进行清理比使用析构函数更容易出错。
这是因为您强制对象的用户而不是类的设计者/实现者进行清理。
No finally has not been added to C++, nor is it likely to ever be added.
The way C++ uses constructor/destructor makes the need for finally unnecessary.
If you are using catch(...) to cleanup then you are not using C++ properly. The cleanup code should all be in the destructor.
Though it is not a requirement to use it C++ does have a std::exception.
Forcing developers to derive from a specific class to use exception goes against the keep it simple philosophy of C++. Its also why we don't require all classes to derive from Object.
Read: Does C++ support 'finally' blocks? (And what's this 'RAII' I keep hearing about?)
The use of finally is more error prone than destructors to do clean up.
This is because you are forcing the user of the object to do clean up rather than the designer/implementer of the class.
好的,我必须添加对您在单独的答案帖子中提出的观点的答案:
(如果您将其编辑到原始问题中,会方便得多,这样它就不会出现在答案下方的底部。
catch 有一个完全独立的目的,作为 Java 程序员,您应该意识到这一点。 finally 子句用于“无条件”清理操作。 无论如何退出区块,都必须这样做。 Catch 用于有条件的清理。 如果抛出此类异常,我们需要执行一些额外的操作。
真的吗? 如果我们希望这种类型总是发生(例如,我们总是希望在完成数据库连接后关闭它),那么为什么我们不定义它一次< /em>? 在类型本身? 让数据库连接自行关闭,而不是在每次使用它时都进行尝试/最终操作?
这就是析构函数的要点。 它们保证每种类型在每次使用时都能够处理自己的清理工作,而调用者不必考虑它。
不会。C++ 程序员从来没有受过这个困扰。 C 程序员有。 C 程序员意识到 C++ 有类,然后称自己为 C++ 程序员。
我每天都用 C++ 和 C# 编程,我觉得我被 C# 可笑的坚持所困扰,即我每次使用数据库连接或其他东西时都必须提供一个 finally 子句(或
using
块)必须清理掉。C++ 让我可以一劳永逸地指定“每当我们完成此类型时,它都应该执行这些操作”。 我不会冒忘记释放内存的风险。 我不会冒忘记关闭文件句柄、套接字或数据库连接的风险。 因为我的内存、句柄、套接字和数据库连接都是自行完成的。
每次使用类型时都必须编写重复的清理代码,这怎么可能更好呢? 如果您需要包装类型,因为它本身没有析构函数,您有两个简单的选择:
不,你从来没有学过 C++。 您已经完成了 CFront,或者带有类的 C。 不是C++。 有很大的不同。 别再说这些答案蹩脚了,你可能会学到一些你认为你懂的语言的知识。 ;)
Ok, I have to add in an answer to the points you made in a separate answer post:
(It would be a lot more convenient if you'd edited this into the original question, so it doesn't end up at the bottom below the answers to it.
catch has a completely separate purpose, and as a Java programmer you should be aware of that. The finally clause is for "unconditional" cleanup actions. No matter how the block is exited, this must be done. Catch is for conditional cleanup. If this type of exception is thrown, we need to perform a few extra actions.
Really? If we want it to always happen for this type (say, we always want to close a database connection when we're done with it), then why don't we define it once? In the type itself? Make the database connection close itself, rather than having to put a try/finally around every single use of it?
That's the point in destructors. They guarantee that each type is able to take care of its own cleanup, every time it's used, without the caller having to think of it.
No. C++ programmers have never been plagued by that. C programmers have. And C programmers who realized that c++ had classes, and then called themselves C++ programmers have.
I program in C++ and C# daily, and I feel I'm plagued by C#'s ridiculous insistence that I must supply a finally clause (or a
using
block) EVERY SINGLE TIME I use a database connection or something else that must be cleaned up.C++ lets me specify once and for all that "whenever we're done with this type, it should perform these actions". I don't risk forgetting to release memory. I don't risk forgetting to close file handles, sockets or database connections. Because my memory, my handles, sockets and db connections do it themselves.
How can it ever be preferable to have to write duplicate cleanup code every time you use a type? If you need to wrap the type because it doesn't have a destructor itself, you have two easy options:
No, you've never done C++. You've done CFront, or C with classes. Not C++. There's a huge difference. Quit calling the answers lame, and you might learn something about the language you thought you knew. ;)
清理功能本身就非常蹩脚。 他们的凝聚力较低,因为他们期望执行一系列仅与活动发生时间相关的活动。 它们具有高耦合性,因为当实际执行某些操作的函数发生更改时,它们需要修改其内部结构。 因此,它们很容易出错。
try...finally 构造是清理函数的框架。 这是一种语言鼓励的编写糟糕代码的方式。 此外,由于它鼓励一遍又一遍地编写相同的清理代码,因此破坏了 DRY 原则。
对于这些目的,C++ 方式要好得多。 资源的清理代码在析构函数中只编写一次。 它与该资源的其余代码位于同一位置,因此具有良好的内聚性。 清理代码不必放入不相关的模块中,因此这减少了耦合。 如果设计得当,它只需一次编写。
而且,C++的方式更加统一。 C++ 添加了智能指针,以相同的方式处理各种资源,而 Java 可以很好地处理内存,但提供的结构不足以释放其他资源。
C++ 有很多问题,但这不是其中之一。 Java 在某些方面比 C++ 更好,但这不是其中之一。
Java 如果有一种实现 RAII 的方法而不是 try...finally 会更好。
Cleanup functions, themselves, are thoroughly lame. They have low cohesion, in that they are expected to perform a series of activities only related in when they happen. They have high coupling, in that they need to have their internals modified when the functions that actually do something are changed. Because of this, they're error-prone.
The try...finally construct is a framework for cleanup functions. It is a language-encouraged way to write lousy code. Moreover, since it encourages writing the same cleanup code over and over, it undermines the DRY principle.
The C++ way is far preferable for these purposes. The cleanup code for a resource is written precisely once, in the destructor. It's in the same place as the rest of the code for that resource, and therefore has good cohesiveness. The cleanup code doesn't have to be put into unrelated modules, and therefore this cuts down on coupling. It is written precisely once, when well designed.
Moreover, the C++ way is much more uniform. C++, with the smart pointer additions, handles all sorts of resources in the same way, while Java handles memory well and provides inadequate constructs to release other resources.
There are plenty of problems with C++, but this isn't one of them. There are ways in which Java is better than C++, but this isn't one of them.
Java would be much better off with a way to implement RAII instead of try...finally.
为了避免必须为每个可释放资源定义包装类,您可能对 ScopeGuard (http:// www.ddj.com/cpp/184403758),它允许人们即时创建“清洁器”。
例如:
To avoid having to define a wrapper class for every releasable resource, you may be interested in ScopeGuard (http://www.ddj.com/cpp/184403758) which allows one to create "cleaners" on the fly.
For example:
一个例子说明正确使用finally是多么困难。
打开和关闭两个文件。
您想要保证文件正确关闭的地方。
等待 GC 不是一个选择,因为文件可能会被重复使用。
在 C++ 中
,在没有析构函数但有finally 子句的语言中。
这是一个简单的示例,代码已经变得很复杂。 这里我们只尝试整理 2 个简单的资源。 但随着需要管理的资源数量的增加和/或其复杂性的增加,在存在异常的情况下正确使用finally块变得越来越困难。
最后的使用将正确使用的责任转移到对象的用户身上。 通过使用 C++ 提供的构造函数/析构函数机制,您可以将正确使用的责任转移给类的设计者/实现者。 这本质上是更安全的,因为设计者只需要在类级别正确地执行一次(而不是让不同的用户尝试以不同的方式正确地执行它)。
An Example of how difficult it is to use finally correctly.
Open and closing two files.
Where you want to guarantee that the file is closed correctly.
Waiting for the GC is not an option as the files may be re-used.
In C++
In a language with no destructors but has a finally clause.
This is a simple example and already the code is getting convoluted. Here we are only trying to marshal 2 simple resources. But as the number of resources that need to be managed increases and/or their complexity increases the use of a finally block becomes harder and harder to use correctly in the presence of exceptions.
The use of finally moves responsibility for correct usage onto the user of an object. By using constructor/destructor mechanism provided by C++ you move the responsibility of correct usage to the designer/implementer of the class. This is inheritanly safer as the designer only needs to do it correctly once at the class level (rather than have different users try and do it correctly in different ways).
使用 C++11 及其 lambda 表达式,我最近开始使用下面的代码来模仿
finally
:FinallyGuard
是一个用可调用的类似函数的参数(最好是lambda表达式)构造的对象。 它只会记住该函数,直到调用其析构函数为止,这是当对象超出范围时的情况,无论是由于正常的控制流还是由于异常处理期间的堆栈展开。 在这两种情况下,析构函数都会调用该函数,从而执行相关代码。有点奇怪的是,您必须在
try
块的代码之前编写finally
的代码,但除此之外,它实际上感觉很像 Java 中的真正的try
/finally
。 我想人们不应该在具有自己适当的析构函数的对象更合适的情况下滥用这种方法,但在某些情况下我认为上述方法更合适。 我在这个问题中讨论了一个这样的场景。据我了解,
std::function
将使用一些指针间接寻址和至少一个虚拟函数调用来执行其 类型擦除,因此会有性能开销。 不要在性能至关重要的紧密循环中使用此技术。 在这些情况下,其析构函数只做一件事的专用对象会更合适。Using C++11 with its lambda expressions, I've recently started using the following code to mimic
finally
:The
FinallyGuard
is an object which is constructed with a callable function-like argument, preferrably a lambda expression. It will simply remember that function until its destructor is called, which is the case when the object goes out of scope, either due to normal control flow or due to stack unwinding during exception handling. In both cases, the destructor will call the function, thus executing the code in question.It is a bit strange that you have to write the code for the
finally
before the code for thetry
block, but apart from that it actually feels a lot like a genuinetry
/finally
from Java. I guess one should not abuse this for situations where an object with its own proper destructor would be more appropriate, but there are cases where I consider this approach above more suitable. I discussed one such scenario in this question.As far as I understand things,
std::function<void()>
will use some pointer indirection and at least one virtual function call to perform its type erasure, so there will be a performance overhead. Don't use this technique in a tight loop where performance is critical. In those cases, a specialized object whose destructor does one thing only would be more appropriate.C++ 析构函数使
finally
变得多余。 通过将清理代码从finally移至相应的析构函数,可以获得相同的效果。C++ destructors make
finally
redundant. You can get the same effect by moving the cleanup code from finally to corresponding destructors.我认为您没有抓住
catch (...)
的作用。您在示例中说“唉,无法检查异常”。 好吧,您没有有关异常类型的信息。 您甚至不知道它是否是多态类型,因此即使您有某种对它的无类型引用,您甚至无法安全地尝试
dynamic_cast
。如果您了解可以使用的某些异常或异常层次结构,那么这就是具有显式命名类型的 catch 块的位置。
catch (...)
在 C++ 中并不常用。 它可以用在必须保证不抛出或仅抛出某些约定异常的地方。 如果您使用 catch (...) 进行清理,那么您的代码在任何情况下都很可能不是稳健的异常安全的。正如其他答案中提到的,如果您使用本地对象来管理资源(RAII),那么您经常需要很少的 catch 块,这可能会令人惊讶和启发 - 如果您不需要在本地执行任何异常操作 - 即使是try 块可能是多余的,因为您让异常流出到可以响应它们的客户端代码,同时仍然保证没有资源问题。
为了回答您最初的问题,如果您需要在块末尾运行一些代码,无论是否有异常,那么配方就是。
请注意我们如何完全取消
try
、catch
和throw
。如果函数中的数据最初是在 try 块之外声明的,并且需要在“finally”块中访问,那么您可能需要将其添加到辅助类的构造函数中,并将其存储到析构函数中。 然而,此时我会认真重新考虑是否可以通过更改本地资源处理对象的设计来解决问题,因为这意味着设计中存在问题。
I think that you are missing the point of what
catch (...)
can do.You say in your example "alas, can't examine the exception". Well, you have no information about the type of the exception. You don't even know if it's a polymorphic type so even if you had some sort of an untyped reference to it, you couldn't even safely attempt a
dynamic_cast
.If you know about certain exceptions or exception hierarchies that you can do something with then this is the place for catch blocks with explicity named types.
catch (...)
is not often useful in C++. It can be used in places which have to guarantee that they don't throw, or only throw certain contracted exceptions. If you are usingcatch (...)
for cleanup then there is a very good chance that your code is not robustly exception safe in any case.As mentioned in other answers, if you are using local objects to manage resources (RAII) then it can be surprising and enlightening how few catch blocks you need, often - if you don't need to do anything locally with an exception - even the try block can be redundant as you let the exceptions flow out to the client code that can respond to them while still guaranteeing no resource issues.
To answer your original question, if you need some piece of code to run at the end of a block, exception or no exception, then a recipe would be.
Note how we can completely do away with
try
,catch
andthrow
.If you had data in the function that was originally declared outside the try block that you needed access to in the "finally" block, then you may need to add that to the constructor of the helper class and store it until the destructor. However, at this point I would seriously reconsider whether the problem could be resolved by altering the design of the local resource handling objects as it would imply something awry in the design.
并非完全偏离主题。
Java 中的 Boiler Plate DB 资源清理
讽刺模式:不是吗Java 习语精彩吗?
Not completetely offtopic.
Boiler Plating DB Resource Cleanup in Java
sarcasm mode: Isn't the Java idiom wonderful?
在这 15 年里,我用 C++ 进行了大量的类设计和模板包装设计,并且在析构函数清理方面全部采用 C++ 方式完成。 然而,每个项目也总是涉及 C 库的使用,这些库提供了打开它、使用它、关闭它的使用模型的资源。 try/finally 意味着这样的资源可以在需要的地方被消耗——以一种完全健壮的方式——并完成它。 对这种情况进行编程的最简单的方法。 可以处理清理逻辑期间发生的所有其他状态,而不必在某些包装器析构函数中进行限制。
我的大部分 C++ 编码都是在 Windows 上完成的,因此在这种情况下总是可以使用 Microsoft 的 __try/__finally 。 (它们的结构化异常处理具有一些与异常交互的强大能力。)唉,看起来 C 语言从未批准过任何可移植的异常处理结构。
但这并不是理想的解决方案,因为在 try 块中混合 C 和 C++ 代码并不简单,因为在 try 块中可能会抛出任何一种类型的异常。 添加到 C++ 中的finally 块对于这些情况会有帮助,并且可以实现可移植性。
I've done plenty of class design and template wrapper design in C++ over those 15 years and done it all the C++ way in terms of destructors cleaning up. Every project, though, also invariably involved the use of C libraries that provided resources with the open it, use it, close it usage model. A try/finally would mean such a resource can just be consumed where it needs to be - in a completely robust manner - and be done with it. The least tedium approach to programming that situation. Could deal with all the other state going on during the logic of that cleanup without having to be scoped away in some wrapper destructor.
I did most of my C++ coding on Windows so could always resort to using Microsoft's __try/__finally for such situations. (Their structured exception handling has some powerful abilities for interacting with exceptions.) Alas, doesn't look like C language has ever ratified any portable exception handling constructs.
That wasn't ideal solution, though, because it was not straightforward to blend C and C++ code in a try block where either style of exception might get thrown. A finally block added to C++ would have been helpful for those situations and would enable portability.
关于您的附录编辑,是的,C++0x 正在考虑闭包。 它们可以与 RAII 作用域防护一起使用,以提供易于使用的解决方案,请检查 Pizer 的博客。 它们还可以用于模仿 try-finally,请参阅 这个答案; 但是这真的是吗好主意吗?。
Regarding your addendum-edit, yes closures are being considered for C++0x. They can be used with RAII scoped guards to provide an easy to use solution, check Pizer's weblog. They can also be used to mimic try-finally, see this answer ; but is this really a good idea ? .
我想我应该添加我自己的解决方案 - 一种智能指针包装器,用于当您必须处理非 RAII 类型时。
像这样使用:
这里是 Finaliser 的实现:
... 这里是 Releaser:
我有几种不同类型的释放器,包括一种用于 free() 和一种用于 CloseHandle()。
Thought I'd add my own solution to this - a kind of smart pointer wrapper for when you have to deal with non-RAII types.
Used like this:
So here's the implementation of Finaliser:
... and here's Releaser:
I have a few different kinds of releaser like this, including one for free() and one for CloseHandle().