c++支持“finally”块吗?

RAII习语是什么?

c++的RAII习语和c#的using语句有什么区别?


当前回答

在c++中,由于RAII, final是不需要的。

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

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

例子:

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

C+像火车

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

其他回答

在c++中,由于RAII, final是不需要的。

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

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

例子:

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

C+像火车

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

我想出了一个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.

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

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

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

编辑

如果你不中断/继续/返回等等,你可以添加一个捕获到任何未知的异常,并把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无论如何都执行finally,然后退出。

但是我上面描述的代码并不是这样工作的 =>下面的代码只输出一些错误!:

#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;
}

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

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。