请问C ++支持“最后”块? (而这是什么“RAII”我不断听到的?)(Does C++ sup

2019-08-31 14:38发布

请问C ++支持“ 最后 ”块?

什么是RAII成语

什么是C ++的RAII成语之间的差别C#的“使用”的语句

Answer 1:

不,C ++不支持“最后”块。 原因是,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用于在资源管理类中的所有成员,你可以逃脱使用非常简单,甚至默认,析构函数为业主类,因为它不需要手动管理其成员资源寿命。 (感谢Mike B中指出这一点。)

对于那些familliar用C#或VB.NET,你可能会认识到,RAII类似.NET中使用IDisposable接口和“使用”的语句确定性的破坏 。 事实上,这两种方法都非常相似。 主要的区别在于,RAII将确定性释放任何类型的资源 - 包括内存。 当实现了IDisposable在.NET(甚至在.NET语言C ++ / CLI),资源将被释放确定性除了存储器。 在.NET中,内存不确定性释放; 内存在垃圾收集周期才会释放。

 

†有人认为,“破坏是让渡资源”是为RAII成语更准确的名称。



Answer 2:

在C ++中的最后是不是必需的,因为RAII的。

RAII移动的异常安全的从对象的用户对象的设计(和执行者)的责任。 我认为这是正确的地方为你那么只需要得到异常安全正确的一次(在设计/实施)。 通过使用最后你需要得到异常安全纠正每次使用对象时。

此外海事组织代码看起来更简洁(见下文)。

例:

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

类似于C ++的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对象正确很容易。 该数据库对象将正确地使用析构函数无论我们如何努力和滥用它自行关闭。

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

当使用最后的正确使用对象的被委托对象的用户。 即它是对象用户的责任,正确地明确地关闭数据库连接。 现在,你可能会说,这可以在finaliser来完成,但资源可能有限或其他方面的限制,因此你一般都希望控制对象的释放,而不是依赖于垃圾回收器的不确定性的行为。

另外这是一个简单的例子。
当你有一个需要被释放多个资源的代码可能很复杂。

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



Answer 3:

RAII通常是越多越好,但你可以很容易地在最后语义在C ++中。 使用的代码少量。

此外,C ++ 核心原则给予最后。

下面是该链接GSL微软执行和链接到马丁Moene实施

Bjarne的Stroustrup的多次说,一切是在GSL这意味着在标准去也说不定。 因此,它应该是最后使用面向未来的方式。

如果你想,虽然,继续读书可以方便地实现自己。

在C ++ 11 RAII和lambda表达式允许做一般性最后:

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!

我个人使用这个几次,以确保关闭POSIX文件描述符的C ++程序。

有了这样的管理资源,因此避免了任何泄漏的真正类通常是更好的,但在那里制造类听起来像一个矫枉过正这次终于是在情况下很有用。

此外,我喜欢它比其他语言更好最后 ,因为如果使用自然你写附近开码关闭代码(在我的例子中,新删除 )和毁灭如下建设LIFO为了像往常一样在C ++中。 唯一的缺点是,你得到你真的不使用自动变量和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"; }


Answer 4:

除了使清理易与基于堆栈的对象,RAII也是有用的,因为当对象是另一个类的构件相同的“自动”清理发生。 当所属的类被破坏,由RAII类管理的资源得到清理,因为这个类的析构函数被调用的结果。

这意味着,当你达到RAII涅和类使用RAII所有成员(如智能指针),你可以用一个非常简单的(甚至默认),析构函数为货主类脱身,因为它不需要手动管理其成员资源的寿命。



Answer 5:

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

事实上,根据垃圾收集语言需要“终于”更多。 垃圾收集器不破坏及时你的对象,所以它不能依靠的正确清理非内存相关的问题。

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

然而...

RAII移动的异常安全的从对象的用户设计的责任

可悲的是,这是它自己的垮台。 旧的C编程习惯难改。 当你用C语言编写的或非常空调风格库,RAII将不被使用。 短的重写整个API的前端,这只是你有什么一起工作。 然后,缺乏“终于”真的咬。



Answer 6:

对不起,挖这样一个古老的线程,但在下面的推理主要错误:

RAII移动的异常安全的从对象的用户对象的设计(和执行者)的责任。 我认为这是正确的地方为你那么只需要得到异常安全正确的一次(在设计/实施)。 通过使用最后你需要得到异常安全纠正每次使用对象时。

通常情况下,你必须处理动态分配的对象,对象等。在试块的动态数字,有些代码可能会在列表中创建多个对象(有多少是在运行时确定),并存储指向他们。 现在,这是不是一个奇特的场景,而是非常普遍。 在这种情况下,你会想要写的东西一样

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();
  }
}

另外:那为什么连管理lanuages提供,尽管资源最终块由垃圾收集反正自动释放?

提示:还有更多你可以用“终于”做的不仅仅是内存释放。



Answer 7:

另一个“最后”使用块仿真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
    }
);

如果你愿意,你可以换这个成语为“尝试 - 终于”宏:

// 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 :(

现在,“最后”块是在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

我个人不喜欢“终于”成语的“宏观”的版本,并会喜欢使用即使语法在这种情况下,更笨重纯“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(); });

    ...


Answer 8:

FWIW,微软的Visual C ++不支持尝试,终于,它在历史上一直在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调试设置将打破,虽然这一机制。



Answer 9:

正如在其他的答案中指出,C ++可以支持finally样的功能。 这个功能是可能最接近于标准的语言的一部分实现是伴随着一个C ++核心准则 ,一套使用C ++由Bjarne Stoustrup和香草萨特编辑的最佳实践。 在实施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实现和使用是非常相似的一个在Paolo.Bolzoni的答案 。 一个区别是,通过创建对象gsl::finally()缺乏disable()调用。 如果你需要的功能(比如,一旦它的组装及无异常都会发生返回资源),你可能更喜欢保罗的实现。 否则,使用GSL是接近使用标准化的功能,你会得到。



Answer 10:

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

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;

需要注意的是最后块本身可能抛出一个异常之前的原始异常被再次抛出,从而丢弃原来的异常。 这是完全相同的行为作为Java终于块。 此外,您不能使用return在try&catch块内。



Answer 11:

我想出了一个finally宏,可用于几乎像 ¹的finally在Java中关键字; 它利用std::exception_ptr和朋友,lambda函数和std::promise ,因此它需要C++11或以上; 它也使得使用的复合语句表达 GCC扩展,这也由铛支持。

警告 :一个早期版本的这个答案使用不同的执行理念与更多的限制。

首先,让我们定义一个辅助类。

#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. 它不可能从与外环破break的语句从内trycatch()的块,因为他们生活lambda函数内;
  2. 必须有至少一个catch()的后块try :它是一个C ++要求;
  3. 如果函数具有非void的返回值,但还有的范围内没有返回trycatch()'s块,编译将失败,因为finally宏将会扩展代码,将要返回一个void 。 这可以,呃, 空隙由具有编finally_noreturn各种各样的宏。

所有的一切,我不知道如果我曾经用这个东西我自己,但很好玩的玩它。 :)



Answer 12:

我有一个使用情况下,我认为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);
        }
    }

我觉得这是更合乎逻辑的,把你的最后声明在循环的开始,因为它发生在循环退出后...但是,这是一厢情愿的想法,因为我们不能用C做++。 需要注意的是队列downstream连接到另一个线程,所以你不能把在定点push(nullptr)中的析构函数downstream ,因为它不能在此时被摧毁...它需要活着,直到其他线程接收nullptr

因此,这里是如何使用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);
        }
    }


Answer 13:

正如许多人所指出的,解决的办法是使用C ++ 11层的功能,以避免finally块。 的特点之一是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);
}

有些更介绍如何使用的unique_ptr与C ++标准库的容器是这里



Answer 14:

我想提供另一种选择。

如果你想finally块来总是叫,只是把它最后一个catch块之后(这大概应该是catch( ... )赶不知道除外)

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块作为最后的事情时,抛出了任何异常,您可以使用布尔局部变量 - 运行前,将其设置为false,并把真正的分配在try块的末尾,再经过该变量catch块检查值:

bool generalAppState = false;
try{
   // something that might throw exception

   //the very end of try block:
   generalAppState = true;
} catch( ... ){
   // what to do with uknown exception
}

//final code to be called only when exception was thrown,
//don't forget that it might throw some exception too
if( !generalAppState ){
   doSomeCleanUpOfDirtyEnd();
}

//final code to be called only when no exception is thrown
//don't forget that it might throw some exception too
else{
   cleanEnd();
}


Answer 15:

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


Answer 16:

我也觉得RIIA不是异常处理以及具有终于完全有用替代。 顺便说一句,我也觉得是RIIA一个不好的名字各地。 我把这些类型的类“看门人”,并使用他们制造了很多。 他们既没有初始化,也不获取资源95%的时间,他们是一个范围的基础上,运用一些变化,或采取已建立的东西,并确定它摧毁。 这是官方的图案名称迷恋上网我得到滥用于甚至暗示我的名字可能会更好。

我只是不认为这是合理的要求,有些东西在特设名单,每一个复杂的设置都必须有一个类写入包含它,以避免并发症需要赶上多的洁面这一切时,备份如果出现错误,在这个过程中的异常类型。 这将导致大量的特设课程,只是没有必要,否则的。

是的,它是罚款,旨在管理特定的资源,或旨在处理一组类似资源的一般那些类。 但是,即使所有涉及到的东西有这样的包装,清理的协调可能不只是在析构函数的相反顺序调用简单。

我认为它非常有意义的C ++有一个最后。 我的意思是,哎呀,这么多的位和鲍勃一直紧盯到它在过去的十年中,它似乎很奇怪,人们会突然在像最后这可能是非常有用的,可能没什么的那样复杂的是发生了一些其他的事情变得保守增加(虽然这只是我的猜测而已。)



文章来源: Does C++ support 'finally' blocks? (And what's this 'RAII' I keep hearing about?)