c++支持“finally”块吗?
RAII习语是什么?
c++的RAII习语和c#的using语句有什么区别?
c++支持“finally”块吗?
RAII习语是什么?
c++的RAII习语和c#的using语句有什么区别?
当前回答
我想出了一个finally宏,可以像¹Java中的finally关键字一样使用;它使用std::exception_ptr及其友项,lambda函数和std::promise,因此它要求c++ 11或以上;它还使用了clang也支持的复合语句表达式GCC扩展。
警告:这个答案的早期版本使用了这个概念的不同实现,有更多的限制。
首先,让我们定义一个helper类。
#include <future>
template <typename Fun>
class FinallyHelper {
template <typename T> struct TypeWrapper {};
using Return = typename std::result_of<Fun()>::type;
public:
FinallyHelper(Fun body) {
try {
execute(TypeWrapper<Return>(), body);
}
catch(...) {
m_promise.set_exception(std::current_exception());
}
}
Return get() {
return m_promise.get_future().get();
}
private:
template <typename T>
void execute(T, Fun body) {
m_promise.set_value(body());
}
void execute(TypeWrapper<void>, Fun body) {
body();
}
std::promise<Return> m_promise;
};
template <typename Fun>
FinallyHelper<Fun> make_finally_helper(Fun body) {
return FinallyHelper<Fun>(body);
}
然后是实际的宏观。
#define try_with_finally for(auto __finally_helper = make_finally_helper([&] { try
#define finally }); \
true; \
({return __finally_helper.get();})) \
/***/
它可以这样使用:
void test() {
try_with_finally {
raise_exception();
}
catch(const my_exception1&) {
/*...*/
}
catch(const my_exception2&) {
/*...*/
}
finally {
clean_it_all_up();
}
}
使用std::promise使其非常容易实现,但它可能也引入了相当多不必要的开销,这些开销可以通过只从std::promise中重新实现所需的功能来避免。
注意:有一些东西不像java版本的finally那样工作。我能想到的是:
it's not possible to break from an outer loop with the break statement from within the try and catch()'s blocks, since they live within a lambda function; there must be at least one catch() block after the try: it's a C++ requirement; if the function has a return value other than void but there's no return within the try and catch()'s blocks, compilation will fail because the finally macro will expand to code that will want to return a void. This could be, err, avoided by having a finally_noreturn macro of sorts.
总而言之,我不知道我自己是否会使用这些东西,但玩它很有趣。:)
其他回答
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。我的意思是,天哪,在过去的几十年里,它被粘上了这么多零碎的东西,似乎奇怪的是,人们突然对一些东西变得保守起来,比如最终,它可能非常有用,可能不像其他一些已经添加的东西那么复杂(尽管这只是我的猜测)。
我想出了一个finally宏,可以像¹Java中的finally关键字一样使用;它使用std::exception_ptr及其友项,lambda函数和std::promise,因此它要求c++ 11或以上;它还使用了clang也支持的复合语句表达式GCC扩展。
警告:这个答案的早期版本使用了这个概念的不同实现,有更多的限制。
首先,让我们定义一个helper类。
#include <future>
template <typename Fun>
class FinallyHelper {
template <typename T> struct TypeWrapper {};
using Return = typename std::result_of<Fun()>::type;
public:
FinallyHelper(Fun body) {
try {
execute(TypeWrapper<Return>(), body);
}
catch(...) {
m_promise.set_exception(std::current_exception());
}
}
Return get() {
return m_promise.get_future().get();
}
private:
template <typename T>
void execute(T, Fun body) {
m_promise.set_value(body());
}
void execute(TypeWrapper<void>, Fun body) {
body();
}
std::promise<Return> m_promise;
};
template <typename Fun>
FinallyHelper<Fun> make_finally_helper(Fun body) {
return FinallyHelper<Fun>(body);
}
然后是实际的宏观。
#define try_with_finally for(auto __finally_helper = make_finally_helper([&] { try
#define finally }); \
true; \
({return __finally_helper.get();})) \
/***/
它可以这样使用:
void test() {
try_with_finally {
raise_exception();
}
catch(const my_exception1&) {
/*...*/
}
catch(const my_exception2&) {
/*...*/
}
finally {
clean_it_all_up();
}
}
使用std::promise使其非常容易实现,但它可能也引入了相当多不必要的开销,这些开销可以通过只从std::promise中重新实现所需的功能来避免。
注意:有一些东西不像java版本的finally那样工作。我能想到的是:
it's not possible to break from an outer loop with the break statement from within the try and catch()'s blocks, since they live within a lambda function; there must be at least one catch() block after the try: it's a C++ requirement; if the function has a return value other than void but there's no return within the try and catch()'s blocks, compilation will fail because the finally macro will expand to code that will want to return a void. This could be, err, avoided by having a finally_noreturn macro of sorts.
总而言之,我不知道我自己是否会使用这些东西,但玩它很有趣。:)
我有一个用例,我认为它最终应该是c++ 11语言中完全可以接受的一部分,因为我认为从流的角度来看,它更容易阅读。我的用例是线程的消费者/生产者链,其中在运行结束时发送一个哨兵nullptr以关闭所有线程。
如果c++支持它,你会希望你的代码看起来像这样:
extern Queue downstream, upstream;
int Example()
{
try
{
while(!ExitRequested())
{
X* x = upstream.pop();
if (!x) break;
x->doSomething();
downstream.push(x);
}
}
finally {
downstream.push(nullptr);
}
}
我认为把finally声明放在循环的开始更符合逻辑,因为它发生在循环退出之后……但这只是一厢情愿的想法,因为我们无法在c++中实现它。注意,下游队列连接到另一个线程,所以你不能在下游的析构函数中放入哨兵推(nullptr),因为此时它不能被销毁……它需要保持活动状态,直到另一个线程接收到nullptr。
下面是如何使用带有lambda的RAII类来做同样的事情:
class Finally
{
public:
Finally(std::function<void(void)> callback) : callback_(callback)
{
}
~Finally()
{
callback_();
}
std::function<void(void)> callback_;
};
下面是你如何使用它:
extern Queue downstream, upstream;
int Example()
{
Finally atEnd([](){
downstream.push(nullptr);
});
while(!ExitRequested())
{
X* x = upstream.pop();
if (!x) break;
x->doSomething();
downstream.push(x);
}
}
不一定,但你可以在一定程度上模仿他们,例如:
int * array = new int[10000000];
try {
// Some code that can throw exceptions
// ...
throw std::exception();
// ...
} catch (...) {
// The finally-block (if an exception is thrown)
delete[] array;
// re-throw the exception.
throw;
}
// The finally-block (if no exception was thrown)
delete[] array;
注意,final -block本身可能在原始异常被重新抛出之前抛出一个异常,从而丢弃原始异常。这与Java final -block中的行为完全相同。同样,你不能在try&catch块中使用return。
除了使基于堆栈的对象的清理变得容易之外,RAII还很有用,因为当对象是另一个类的成员时,同样的“自动”清理也会发生。当所属类被销毁时,由RAII类管理的资源将被清理,因为该类的dtor将被调用。
这意味着当您达到RAII的天堂,并且类中的所有成员都使用RAII(如智能指针)时,您可以为所有者类使用一个非常简单的(甚至是默认的)dtor,因为它不需要手动管理其成员资源生命周期。