c++是否支持'块吗?(And what's this '我一直听说?)

c++是否支持'< >强最后< / >强'块?

< >强RAII成语< / >强是什么?

c++的RAII习惯用法和 c#的using语句之间的区别是什么?

283241 次浏览

不,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时,您可以为所有者类使用非常简单的(甚至是默认的)析构函数,因为它不需要手动管理其成员资源生存期。(感谢迈克B指出这一点。)

适合那些熟悉c#或VB的人。NET,你可能会意识到RAII类似于< >。NET确定性销毁使用IDisposable和'using'语句。事实上,这两种方法非常相似。主要区别在于RAII将确定性地释放任何类型的资源——包括内存。当在。net中实现IDisposable(甚至是。net语言c++ /CLI)时,除了内存之外的资源将被确定性地释放。在.NET中,内存不会被确定性地释放;内存仅在垃圾收集周期期间释放。

,

†有些人认为“破坏是资源放弃”是RAII习语更准确的名称。

总之,微软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调试设置会破坏这种机制。

除了使基于堆栈的对象的清理变得容易之外,RAII还很有用,因为当对象是另一个类的成员时,同样的“自动”清理也会发生。当所属类被销毁时,由RAII类管理的资源将被清理,因为该类的dtor将被调用。

这意味着当您达到RAII的天堂,并且类中的所有成员都使用RAII(如智能指针)时,您可以为所有者类使用一个非常简单的(甚至是默认的)dtor,因为它不需要手动管理其成员资源生命周期。

在c++中,由于RAII, finally是必须的

RAII将异常安全的责任从对象的用户转移到对象的设计者(和实现者)。我认为这是正确的地方,因为你只需要让异常安全正确一次(在设计/实现中)。通过使用finally,您需要在每次使用对象时都获得正确的异常安全性。

在我看来,代码看起来更整洁了(见下文)。

例子:

一个数据库对象。为了确保使用了DB连接,必须打开并关闭它。通过使用RAII,这可以在构造函数/析构函数中完成。

像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对象将通过使用析构函数正确地关闭自身,无论我们如何尝试和滥用它。

Java终于来了

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.
}
}
}

当最终使用对象时,对象的正确使用委托给对象的用户。正确地显式地关闭DB连接是对象用户的责任。现在,您可能会认为这可以在终结器中完成,但资源可能有有限的可用性或其他约束,因此您通常希望控制对象的释放,而不是依赖于垃圾收集器的非确定性行为。

这也是一个简单的例子 当你有多个资源需要释放时,代码会变得复杂

更详细的分析可以在这里找到:http://accu.org/index.php/journals/236

try
{
...
goto finally;
}
catch(...)
{
...
goto finally;
}
finally:
{
...
}

很抱歉挖了这么老的一个线程,但下面的推理有一个重大错误:

RAII将异常安全的责任从对象的用户转移到对象的设计者(和实现者)。我认为这是正确的地方,因为你只需要让异常安全正确一次(在设计/实现中)。通过使用finally,您需要在每次使用对象时都获得正确的异常安全性。

通常情况下,你必须处理动态分配的对象,动态数量的对象等。在try块中,一些代码可能会创建许多对象(有多少是在运行时确定的),并将指向它们的指针存储在一个列表中。现在,这不是一个奇异的场景,但很常见。在这种情况下,你会想写这样的东西

void DoStuff(vector<string> input)
{
list<Foo*> 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(vector<string> input)
{
list<Foo*> 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();
}
}

另外:为什么即使是托管语言也会提供一个final块,尽管垃圾收集器会自动释放资源?

提示:使用“finally”可以做的不仅仅是内存释放。

为什么即使是托管语言也会提供final块,尽管垃圾收集器会自动释放资源?

实际上,基于垃圾收集器的语言需要更多的“finally”。垃圾收集器不会及时销毁您的对象,因此不能依赖它正确地清理与内存无关的问题。

就动态分配数据而言,许多人认为应该使用智能指针。

然而……

RAII将异常安全的责任从对象的用户转移到设计人员

可悲的是,这是它自己的失败。旧的C编程习惯很难改掉。当您使用用C或非常C风格编写的库时,将不会使用RAII。除了重写整个API前端,这就是你必须要处理的。然后缺少“finally”真的很伤人。

不一定,但你可以在一定程度上模仿他们,例如:

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

正如许多人所说,解决方案是使用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的更多介绍是在这里

RAII通常更好,但你可以在c++中轻松拥有最后语义。使用少量的代码。

此外,c++ 核心准则最后给出。 . 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文件描述符。

有一个真正的类来管理资源,从而避免任何类型的泄漏通常是更好的,但这个最后在类听起来有点多余的情况下是有用的。

此外,我更喜欢它比其他语言最后,因为如果自然使用,你将结束代码写在开始代码附近(在我的例子中是删除),并且在c++中按照后进先出的顺序进行构造。唯一的缺点是你得到了一个并不真正使用的auto变量,并且lambda语法使它有点混乱(在我的例子中,在第四行中,只有单词最后和右边的{}块是有意义的,其余的基本上都是混乱)。

另一个例子:

 [...]
auto precision = std::cout.precision();
auto set_precision_back = finally( [precision, &std::cout]() { std::cout << std::setprecision(precision); } );
std::cout << std::setprecision(3);

如果只有在失败的情况下才调用最后,则禁用成员非常有用。例如,你必须在三个不同的容器中复制一个对象,你可以设置最后来撤销每次复制,并在所有复制成功后禁用。这样做,如果破坏不能扔,你就保证了强有力的保证。

禁用例子:

//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++得到它的名字之前。

编辑

如果你没有中断/继续/返回等,你可以添加一个catch到任何未知的异常,并把always代码放在它后面。这也是当你不需要异常被重新抛出的时候。

try{
// something that might throw exception
} catch( ... ){
// what to do with uknown exception
}


//final code to be called always,
//don't forget that it might throw some exception too
doSomeCleanUp();

那么问题是什么呢?

通常,finally在其他编程语言中通常运行无论如何(通常意味着不管任何返回,中断,继续,…)除了用于某种系统exit() -这在每种编程语言中有很大不同-例如,PHP和Java只是在那一时刻退出,但Python无论如何都会执行最终,然后退出。

但是我上面描述的代码并不是这样工作的
=比;下面的代码输出只有 something wrong!:

#include <stdio.h>
#include <iostream>
#include <string>


std::string test() {
try{
// something that might throw exception
throw "exceptiooon!";


return "fine";
} catch( ... ){
return "something wrong!";
}
    

return "finally";
}


int main(void) {
    

std::cout << test();
    

    

return 0;
}

我有一个用例,我认为finally 应该是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++中实现它。注意队列downstream连接到另一个线程,所以你不能在downstream的析构函数中放入哨兵push(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);
}
}

我想出了一个finally宏,可以在Java中使用就像¹finally关键字;它使用std::exception_ptr及其友项,lambda函数和std::promise,因此它要求C++11或以上;它还使用了复合语句表达式 GCC扩展,clang也支持这个扩展。

警告:这个答案的早期版本使用了具有更多限制的概念的不同实现。

首先,让我们定义一个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那样工作。我能想到的是:

  1. 不可能从trycatch()的块中使用break语句打破外部循环,因为它们位于lambda函数中;
  2. try之后必须至少有一个catch()块:这是c++的要求;
  3. 如果函数的返回值不是void,但在trycatch()'s块中没有返回值,编译将失败,因为finally宏将扩展为想要返回void的代码。这可以通过使用finally_noreturn宏来实现__abc5ed。

总而言之,我不知道我自己是否会使用这些东西,但玩它很有趣。:)

另一个“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

如果你的代码中需要最后块,那么作用域的警卫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++可以支持类似__abc0的功能。这个功能的实现可能最接近标准语言的一部分是c++核心指南,由Bjarne Stoustrup和Herb Sutter编辑的一组使用c++的最佳实践。finally的实现指引及支援图书馆 (GSL)的一部分。在整个指南中,建议在处理旧式接口时使用finally,它也有自己的指南,名为如果没有合适的资源句柄可用,则使用final_action对象来表示清理

因此,c++不仅支持finally,实际上还建议在许多常见用例中使用它。

GSL实现的示例使用如下所示:

#include <gsl/gsl_util.h>


void 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的实现和用法与保罗。Bolzoni的回答中的非常相似。一个区别是由gsl::finally()创建的对象缺少disable()调用。如果您需要该功能(例如,在资源组装完成后返回资源,并且不会发生任何异常),那么您可能更喜欢Paolo的实现。否则,使用GSL就相当于使用标准化的特性。

我也认为RIIA并不是一个完全有用的异常处理和final的替代品。顺便说一句,我也认为RIIA是一个坏名字。我称这类课程为“看门人”,并经常使用它们。95%的时间他们既没有初始化也没有获取资源,他们只是在一定范围内应用一些变化,或者采用一些已经建立的东西,并确保它被销毁。这是官方模式的名字痴迷互联网,我被辱骂甚至建议我的名字可能更好。

我只是认为不合理的做法是,要求某些特殊列表的每一个复杂设置都必须编写一个类来包含它,以避免在清理过程中出现问题时需要捕获多个异常类型时的复杂性。这将导致大量的临时类,否则这些类是不必要的。

是的,对于专为管理特定资源而设计的类,或者专为处理一组类似资源而设计的泛型类,这是没问题的。但是,即使所有涉及的东西都有这样的包装器,清理的协调也可能不仅仅是简单的反序析构函数调用。

我认为c++有一个final。我的意思是,天哪,在过去的几十年里,它被粘上了这么多零碎的东西,似乎奇怪的是,人们突然对一些东西变得保守起来,比如最终,它可能非常有用,可能不像其他一些已经添加的东西那么复杂(尽管这只是我的猜测)。