c++支持“finally”块吗?
RAII习语是什么?
c++的RAII习语和c#的using语句有什么区别?
c++支持“finally”块吗?
RAII习语是什么?
c++的RAII习语和c#的using语句有什么区别?
当前回答
为什么即使是托管语言也会提供final块,尽管垃圾收集器会自动释放资源?
实际上,基于垃圾收集器的语言需要更多的“finally”。垃圾收集器不会及时销毁您的对象,因此不能依赖它正确地清理与内存无关的问题。
就动态分配数据而言,许多人认为应该使用智能指针。
然而……
RAII将异常安全的责任从对象的用户转移到设计人员
可悲的是,这是它自己的失败。旧的C编程习惯很难改掉。当您使用用C或非常C风格编写的库时,将不会使用RAII。除了重写整个API前端,这就是你必须要处理的。那么,“终于”这个词的缺失真的很伤人。
其他回答
我有一个用例,我认为它最终应该是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);
}
}
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++得到它的名字之前就可以编写这种代码。
不一定,但你可以在一定程度上模仿他们,例如:
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。
总之,微软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调试设置会破坏这种机制。
为什么即使是托管语言也会提供final块,尽管垃圾收集器会自动释放资源?
实际上,基于垃圾收集器的语言需要更多的“finally”。垃圾收集器不会及时销毁您的对象,因此不能依赖它正确地清理与内存无关的问题。
就动态分配数据而言,许多人认为应该使用智能指针。
然而……
RAII将异常安全的责任从对象的用户转移到设计人员
可悲的是,这是它自己的失败。旧的C编程习惯很难改掉。当您使用用C或非常C风格编写的库时,将不会使用RAII。除了重写整个API前端,这就是你必须要处理的。那么,“终于”这个词的缺失真的很伤人。