Is there a favored idiom for mimicing Java's try/finally in C++?

前端 未结 15 807
无人及你
无人及你 2021-02-05 19:16

Been doing Java for number of years so haven\'t been tracking C++. Has finally clause been added to C++ exception handling in the language definition?<

相关标签:
15条回答
  • 2021-02-05 19:51

    My $.02. I've been programming in managed languages like C# and Java for years, but was forced to make the switch to C++ for the purposes of speed. At first I couldn't believe how I had to write out the method signature twice in the header file and then the cpp file, and I didn't like how there was no finally block, and no garbage collection meant tracking memory leaks everywhere - gosh I didn't like it at all!

    However, as I said I was forced to use C++. So I was forced to seriously learn it, and now I've finally understood all the programming idioms like RAII and I get all the subtleties of the language and such. It took me a while but now I see just how different of a language it is compared to C# or Java.

    These days I think C++ is the best language there is! Yes, I can understand that there is a little more what I call 'chaff' sometimes (seemingly unnecessary stuff to write), but after actually using the language seriously, I've changed my mind about it completely.

    I used to have memory leaks all the time. I used to write all my code into the .h file because I hated the separation of code, I couldn't understand why they would do that! And I used to always end up with stupid cyclic include dependencies, and heaps more. I was really hung up on C# or Java, to me C++ was a huge step down. These days I get it. I almost never have memory leaks, I enjoy the separation of interface and implementation, and I don't have problems with cycle dependencies anymore.

    And I don't miss the finally block either. To be honest, my opinion is that these C++ programmers that you talk about writing repeated cleanup actions in catch blocks just sound to me like they're just bad C++ programmers. I mean, it doesn't look like any of the other C++ programmers in this thread are having any of the problems you mention. RAII really does make finally redundant, and if anything, it's less work. You write one destructor and then you never have to write another finally ever! Well at least for that type.

    With respect, what I think is going on is you're just used to Java now, just like I had been.

    0 讨论(0)
  • 2021-02-05 19:51

    Using C++11 with its lambda expressions, I've recently started using the following code to mimic finally:

    class FinallyGuard {
    private:
      std::function<void()> f_;
    public:
      FinallyGuard(std::function<void()> f) : f_(f) { }
      ~FinallyGuard() { f_(); }
    };
    
    void foo() {
      // Code before the try/finally goes here
      { // Open a new scope for the try/finally
        FinallyGuard signalEndGuard([&]{
          // Code for the finally block goes here
        });
        // Code for the try block goes here
      } // End scope, will call destructor of FinallyGuard
      // Code after the try/finally goes here
    }
    

    The FinallyGuard is an object which is constructed with a callable function-like argument, preferrably a lambda expression. It will simply remember that function until its destructor is called, which is the case when the object goes out of scope, either due to normal control flow or due to stack unwinding during exception handling. In both cases, the destructor will call the function, thus executing the code in question.

    It is a bit strange that you have to write the code for the finally before the code for the try block, but apart from that it actually feels a lot like a genuine try/finally from Java. I guess one should not abuse this for situations where an object with its own proper destructor would be more appropriate, but there are cases where I consider this approach above more suitable. I discussed one such scenario in this question.

    As far as I understand things, std::function<void()> will use some pointer indirection and at least one virtual function call to perform its type erasure, so there will be a performance overhead. Don't use this technique in a tight loop where performance is critical. In those cases, a specialized object whose destructor does one thing only would be more appropriate.

    0 讨论(0)
  • 2021-02-05 19:53

    No finally has not been added to C++, nor is it likely to ever be added.

    The way C++ uses constructor/destructor makes the need for finally unnecessary.
    If you are using catch(...) to cleanup then you are not using C++ properly. The cleanup code should all be in the destructor.

    Though it is not a requirement to use it C++ does have a std::exception.
    Forcing developers to derive from a specific class to use exception goes against the keep it simple philosophy of C++. Its also why we don't require all classes to derive from Object.

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

    The use of finally is more error prone than destructors to do clean up.
    This is because you are forcing the user of the object to do clean up rather than the designer/implementer of the class.

    0 讨论(0)
  • 2021-02-05 19:53

    Thought I'd add my own solution to this - a kind of smart pointer wrapper for when you have to deal with non-RAII types.

    Used like this:

    Finaliser< IMAPITable, Releaser > contentsTable;
    // now contentsTable can be used as if it were of type IMAPITable*,
    // but will be automatically released when it goes out of scope.
    

    So here's the implementation of Finaliser:

    /*  Finaliser
        Wrap an object and run some action on it when it runs out of scope.
        (A kind of 'finally.')
        * T: type of wrapped object.
        * R: type of a 'releaser' (class providing static void release( T* object )). */
    template< class T, class R >
    class Finaliser
    {
    private:
        T* object_;
    
    public:
        explicit Finaliser( T* object = NULL )
        {
            object_ = object;
        }
    
        ~Finaliser() throw()
        {
            release();
        }
    
        Finaliser< T, R >& operator=( T* object )
        {
            if (object_ != object && object_ != NULL)
            {
                release();
            }
            object_ = object;
    
            return *this;
        }
    
        T* operator->() const
        {
            return object_;
        }
    
        T** operator&()
        {
            return &object_;
        }
    
        operator T*()
        {
            return object_;
        }
    
    private:
        void release() throw()
        {
            R::release< T >( object_ );
        }
    };
    

    ... and here's Releaser:

    /*  Releaser
        Calls Release() on the object (for use with Finaliser). */
    class Releaser
    {
    public:
        template< class T > static void release( T* object )
        {
            if (object != NULL)
            {
                object->Release();
            }
        }
    };
    

    I have a few different kinds of releaser like this, including one for free() and one for CloseHandle().

    0 讨论(0)
  • 2021-02-05 19:54

    To avoid having to define a wrapper class for every releasable resource, you may be interested in ScopeGuard (http://www.ddj.com/cpp/184403758) which allows one to create "cleaners" on the fly.

    For example:

    FILE* fp = SomeExternalFunction();
    // Will automatically call fclose(fp) when going out of scope
    ScopeGuard file_guard = MakeGuard(fclose, fp);
    
    0 讨论(0)
  • 2021-02-05 19:55

    C++ destructors make finally redundant. You can get the same effect by moving the cleanup code from finally to corresponding destructors.

    0 讨论(0)
提交回复
热议问题