代码之家  ›  专栏  ›  技术社区  ›  Kevin

C++支持“最后”块吗?(我一直听到的‘RAII’是什么意思?)

  •  241
  • Kevin  · 技术社区  · 17 年前

    C++支持吗? finally “街区?

    RAII idiom ?

    C++的RAII成语与什么区别? C#'s 'using' statement

    16 回复  |  直到 14 年前
        1
  •  273
  •   alwaysmpe    9 年前

    不,C++不支持“最后”块。原因是C++支持了RAII:“资源获取是初始化” 一个非常有用的概念。

    其思想是对象的析构函数负责释放资源。当对象具有自动存储持续时间时,当创建它的块退出时(即使该块在出现异常时退出),也将调用该对象的析构函数。这是 Bjarne Stroustrup's explanation

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

    因为你指出了这一点。)

    .NET deterministic destruction using IDisposable and 'using' statements . 实际上,这两种方法非常相似。主要的区别在于RAII将决定性地释放任何类型的资源——包括内存。在.NET中实现IDISPosiabl(甚至.NET语言C++/CLI),除了内存之外,资源将被确定性释放。在.NET中,内存不是确定释放的;内存只在垃圾回收周期期间释放。

    有人认为,“破坏就是资源的让渡”是一个更准确的说法。

        2
  •  79
  •   johnchen902    13 年前

    在C++中,最后是 因为RAII需要。

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

    同样,IMO的代码看起来更整洁(见下文)。

    数据库对象。要确保使用数据库连接,必须打开和关闭它。通过使用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对象变得非常容易。无论我们如何尝试和滥用,DB对象都将使用析构函数正确地关闭自己。

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

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

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

    以下是更详细的分析: http://accu.org/index.php/journals/236

        3
  •  63
  •   Paolo.Bolzoni    7 年前

    C++中的语义使用少量代码。

    Core Guidelines give finally.

    这里有一个链接到 GSL Microsoft implementation 和一个链接到 Martin Moene implementation

    比亚恩·斯特劳斯特罗普多次表示,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++一样,在LIFO命令中构造的破坏也一样。唯一的缺点是你得到了一个你没有真正使用的自动变量,并且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"; }
    
        4
  •  32
  •   Michael Burr    17 年前

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

        5
  •  30
  •   Joe Pineda    13 年前

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

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

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

    那么

        6
  •  9
  •   anton_rh    7 年前

    用C++ 11λ函数实现另一种“最后”块仿真

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

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

    您可以在这里测试上面的代码: http://coliru.stacked-crooked.com/a/1d88f64cb27b3813

    如果你需要 最后 屏蔽你的代码,然后 scoped guards ON_FINALLY/ON_EXCEPTION

    下面是在最后/ON-u异常上使用的简短示例:

    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(); });
    
        ...
    
        7
  •  7
  •   Mephane    16 年前

    很抱歉挖出这么一条老线索,但以下推理有重大错误:

    通常,您必须处理动态分配的对象、对象的动态数量等。在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();
      }
    }
    

    另外:为什么即使是托管的lanuages也会提供finally块,尽管垃圾收集器会自动释放资源?

    提示:除了内存释放,“finally”还有更多的功能。

        8
  •  6
  •   SmacL    17 年前

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

        9
  •  6
  •   tobi_s    7 年前

    finally -就像功能一样。这个功能的实现可能最接近于标准语言的一部分,它是 C++ Core Guidelines 一组使用C++和Bjarne Stoustrup萨特编辑的最佳实践。一个 implementation of finally Guidelines Support Library 最后 Use a final_action object to express cleanup if no suitable resource handle is available .

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

    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's answer gsl::finally() 缺少 disable()

        10
  •  3
  •   bcmpinc    12 年前

    不完全是这样,但您可以在某种程度上模仿它们,例如:

    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;
    

    请注意,finally块本身可能在重新引发原始异常之前引发异常,从而丢弃原始异常。这与Java finally块中的行为完全相同。而且,你不能使用 return 在try&catch块内。

        11
  •  3
  •   Toby Speight    9 年前

    finally 可以使用的宏 这个 最后 Java中的关键字;它利用 std::exception_ptr 还有朋友,lambda函数和 std::promise ,所以它需要 C++11 或更高;它还利用 compound statement expression GCC扩展,这也是clang支持的。

    警告 :一个 earlier version

    首先,让我们定义一个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();
        }    
    }
    

    标准:承诺 使实现变得非常容易,但它可能也会带来一些不必要的开销,只需重新实现 标准:承诺 .


    警告: 有一些事情不太像java版本的 最后

    1. 不可能用 break 内部声明 try catch()
    2. 必须至少有一个 尝试 这是C++的要求;
    3. 如果函数的返回值不是void,但是 尝试 catch()'s 最后 宏将展开为要返回 void 空的 通过一个 finally_noreturn

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

        12
  •  2
  •   Mark Lakata    10 年前

    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 连接到另一条线,所以你不能把哨兵 push(nullptr) 下游 零位

        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);
            }
        }
    
        13
  •  1
  •   Mark Lakata    12 年前

    正如很多人所说的,解决方案是使用C++ 11的特性来避免最后的块。其中一个特点是 unique_ptr

    #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++标准库容器的使用UNIQUYPPTR的更多介绍 here

        14
  •  0
  •   jave.web    10 年前

    如果希望始终调用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 block,可以使用布尔局部变量-在运行之前,将其设置为false,并将true赋值放在try block的最末尾,然后在catch block检查变量值之后:

    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();
    }
    
        15
  •  0
  •   Dean Roddey    7 年前

    我还认为,RIIA并不是一个完全有用的异常处理替代品,也不是一个最终的替代品。顺便说一句,我也认为瑞亚是个坏名字。我把这类课程称为“看门人”,并经常使用。95%的情况下,他们既不初始化也不获取资源,而是在限定范围的基础上应用一些更改,或者获取已设置的内容并确保其被销毁。这是一个官方的模式名称痴迷的互联网,我被滥用,甚至认为我的名字可能更好。

    我只是不认为有理由要求每一个复杂的特殊列表设置都必须有一个类来包含它,以避免在处理过程中出现错误时需要捕获多个异常类型而在清理时出现复杂情况。这将导致很多不需要的特别类。

    是的,对于设计用于管理特定资源的类或设计用于处理一组类似资源的泛型类来说,这是很好的。但是,即使所有涉及的东西都有这样的包装器,清理的协调可能不仅仅是一个简单的析构函数的反向调用。

    我认为C++有一个完美的意义。我的意思是,天哪,在过去的几十年里,有那么多零碎的东西被粘在上面,以至于奇怪的人会突然变得保守起来,像finally这样的东西可能会非常有用,而且可能不会像其他一些已经添加的东西那么复杂(尽管这只是我的猜测)

        16
  •  -2
  •   Hans Olsson    14 年前
    try
    {
      ...
      goto finally;
    }
    catch(...)
    {
      ...
      goto finally;
    }
    finally:
    {
      ...
    }
    
    推荐文章