c++支持“finally”块吗?
RAII习语是什么?
c++的RAII习语和c#的using语句有什么区别?
c++支持“finally”块吗?
RAII习语是什么?
c++的RAII习语和c#的using语句有什么区别?
当前回答
我有一个用例,我认为它最终应该是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);
}
}
其他回答
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++ 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的介绍
另一个“finally”块模拟使用c++ 11 lambda函数
template <typename TCode, typename TFinallyCode>
inline 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 :(
现在"finally"块在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”习语,宁愿使用纯粹的“with_finally”函数,即使在这种情况下语法更笨重。
您可以在这里测试上面的代码:http://coliru.stacked-crooked.com/a/1d88f64cb27b3813
PS
如果你的代码中需要一个finally块,那么作用域守卫或ON_FINALLY/ON_EXCEPTION宏可能会更好地满足你的需求。
下面是ON_FINALLY/ON_EXCEPTION的用法示例:
void function(std::vector<const char*> &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(); });
...
不,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 also simplifies using objects as members of other classes. When the owning class' is destructed, the resource managed by the RAII class gets released because the destructor for the RAII-managed class gets called as a result. This means that when you use RAII for all members in a class that manage resources, you can get away with using a very simple, maybe even the default, destructor for the owner class since it doesn't need to manually manage its member resource lifetimes. (Thanks to Mike B for pointing this out.)
For those familliar with C# or VB.NET, you may recognize that RAII is similar to .NET deterministic destruction using IDisposable and 'using' statements. Indeed, the two methods are very similar. The main difference is that RAII will deterministically release any type of resource -- including memory. When implementing IDisposable in .NET (even the .NET language C++/CLI), resources will be deterministically released except for memory. In .NET, memory is not deterministically released; memory is only released during garbage collection cycles.
†有些人认为“破坏是资源放弃”是RAII习语更准确的名称。
RAII通常更好,但在c++中可以很容易地获得finally语义。使用少量的代码。
此外,c++核心指南最后给出了。
这里有一个到GSL微软实现的链接和一个到Martin Moene实现的链接
Bjarne Stroustrup多次表示,GSL中的所有内容最终都将被纳入标准。所以它最终应该是一种经得起考验的使用方式。
如果你想,你可以很容易地实现自己,继续阅读。
在c++ 11中RAII和lambdas允许做出一般的最后:
namespace detail { //adapt to your "private" namespace
template <typename F>
struct FinalAction {
FinalAction(F f) : clean_{f} {}
~FinalAction() { if(enabled_) clean_(); }
void disable() { enabled_ = false; };
private:
F clean_;
bool enabled_{true}; }; }
template <typename F>
detail::FinalAction<F> finally(F f) {
return detail::FinalAction<F>(f); }
使用示例:
#include <iostream>
int 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),并且在c++中按照后进先出(LIFO)的顺序进行构造。唯一的缺点是你得到了一个你并不真正使用的auto变量,lambda语法使它有点嘈杂(在我的例子中,在第四行中,只有单词finally和右边的{}块是有意义的,其余的基本上都是嘈杂的)。
另一个例子:
[...]
auto precision = std::cout.precision();
auto set_precision_back = finally( [precision, &std::cout]() { std::cout << std::setprecision(precision); } );
std::cout << std::setprecision(3);
如果只有在失败的情况下才必须调用finally,则disable成员非常有用。例如,你必须在三个不同的容器中复制一个对象,你可以设置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(); }
如果你不能使用c++ 11,你仍然可以使用,但是代码会变得有点冗长。只需定义一个只有构造函数和析构函数的结构,构造函数引用所需的任何内容,而析构函数执行所需的操作。这就是lambda的作用,手动完成。
#include <iostream>
int main() {
int* a = new int;
struct Delete_a_t {
Delete_a_t(int* p) : p_(p) {}
~Delete_a_t() { delete p_; std::cout << "leaving the block, deleting a!\n"; }
int* p_;
} delete_a(a);
std::cout << "doing something ...\n"; }
希望你能使用c++ 11,这段代码更多地是为了表明“c++最终不支持”是如何从c++的最初几周开始就毫无意义的,甚至在c++得到它的名字之前就可以编写这种代码。