Is it ever not safe to throw an exception in a constructor?

前端 未结 5 1766
长情又很酷
长情又很酷 2020-12-05 03:46

I know that it\'s not safe to throw exceptions from destructors, but is it ever unsafe to throw exceptions from constructors?

e.g. what happens for objects that ar

相关标签:
5条回答
  • 2020-12-05 04:00

    Throwing exceptions from a constructor is a good thing. When something fails in a constructor, you have two options:

    • Maintain a "zombie" state, where the class exists but does nothing, or
    • Throw an exception.

    And maintaining zombie classes can be quite a hassle, when the real answer should have been, "this failed, now what?".

    According to the Standard at 3.6.2.4:

    If construction or destruction of a non-local static object ends in throwing an uncaught exception, the result is to call terminate (18.6.3.3).

    Where terminate refers to std::terminate.


    Concerning your example, no. This is because you aren't using RAII concepts. When an exception is thrown, the stack will be unwound, which means all objects get their destructor's called as the code gets to the closest corresponding catch clause.

    A pointer doesn't have a destructor. Let's make a simple test case:

    #include <string>
    
    int main(void)
    {
        try
        {
            std::string str = "Blah.";
            int *pi = new int;
    
            throw;
    
            delete pi; // cannot be reached
        }
        catch(...)
        {
        }
    }
    

    Here, str will allocate memory, and copy "Blah." into it, and pi will be initialized to point to an integer in memory.

    When an exception is thrown, stack-unwinding begins. It will first "call" the pointer's destructor (do nothing), then str's destructor, which will free the memory that was allocated to it.

    If you use RAII concepts, you'd use a smart pointer:

    #include <memory>
    #include <string>
    
    int main(void)
    {
        try
        {
            std::string s = "Blah.";
            std::auto_ptr<int> pi(new int);
    
            throw;
    
            // no need to manually delete.
        }
        catch(...)
        {
        }
    }
    

    Here, pi's destructor will call delete and no memory will be leaked. This is why you should always wrap your pointers, and is the same reason we use std::vector rather than manually allocating, resizing, and freeing pointers. (Cleanliness and Safety)

    Edit

    I forgot to mention. You asked this:

    I think I want to put an autoptr around P and call release on the autoptr after dostuff to prevent a memory leak, would that be correct?

    I didn't state it explicitly, and only implied it above, but the answer is no. All you have to do is place it inside of auto_ptr and when the time comes, it will be deleted automatically. Releasing it manually defeats the purpose of placing it in a container in the first place.

    I would also suggest you look at more advanced smart pointers, such as those in boost. An extraordinarily popular one is shared_ptr, which is reference counted, making it suitable for storage in containers and being copied around. (Unlike auto_ptr. Do not use auto_ptr in containers!)

    0 讨论(0)
  • 2020-12-05 04:08

    As Spence mentioned, throwing from a constructor (or allowing an exception to escape a constructor) risks leaking resources if the constructor is not written carefully to handle that case.

    This is one important reason why using RAII objects (like smart pointers) should be favored - they'll automatically handle the cleanup in the face of exceptions.

    If you have resources that require deleting or otherwise manually releasing, you need to make certain that they're cleaned up before the exception leaves. This is not always as easy as it might sound (and certainly not as easy as letting an RAII object handle it automatically).

    And don't forget, if you need to manually handle clean up for something that happens in the constructor's initialization list, you'll need to use the funky 'function-try-block' syntax:

    C::C(int ii, double id)
    try
         : i(f(ii)), d(id)
    {
         //constructor function body
    }
    catch (...)
    {
         //handles exceptions thrown from the ctor-initializer
         //and from the constructor function body
    }
    

    Also, remember that exception safety is the main (only??) reason that the 'swap' idiom gained widespread favor - it's an easy way to ensure that copy constructors don't leak or corrupt objects in the face of exceptions.

    So, the bottom line is that using exceptions to handle errors in constructors is fine, but it's not necessarily automatic.

    0 讨论(0)
  • 2020-12-05 04:11

    When you throw an exception from a constructor a few things will happen.

    1) All fully constructed members will have thir destructors called.
    2) The Memory allocated for the object will be released.

    To help make things automatic you should not have RAW pointer in your class, one of the standard smart pointers will usually do the trick and the compiler optimization will reduce most of the overhead to practically nothing [or only the work you should have been doing manually anyway].

    Passing pointers to Functions

    The other thing that I would NOT do; is pass a value to a function as a pointer.
    The probelm here is that you are not indicating who owns the object. Without the implicit ownership information it is unclear (like all C functions) who is responcable for cleaning up the pointer.

    dostuff(P *p)
    {
        // do something with P
    }
    

    You mention that p is stored in a que and used at some later point. This implies that you are passing ownership of the object to the function. So make this relationship explicit by using std::auto_ptr. By doing so the caller of dostuff() knows that he can not use the pointer after calling dostuff() becuase the act of calling the function will have actually transfered the pointer into the function (ie the callers local auto_ptr will contain a NULL pointer after the call to dostuff() ).

    void doStuff(std::auto_ptr<P> p)
    {
        // do something with p
        //
        // Even if you do nothing with the RAW pointer you have taken the
        // pointer from the caller. If you do not use p then it will be auto
        // deleted.
    }
    
    
    int main()
    {
        // This works fine.
        dostuff(std::auto_ptr<P>(new P));
    
    
        // This works just as well.
        std::auto_ptr<P>    value(new P);
        dostuff(value);
    
        // Here you are guranteed that value has a NULL pointer.
        // Because dostuff() took ownership (and physically took)
        // of the pointer so it could manage the lifespan.
    }
    

    Storing pointers in a cointainer

    You mention that dostuff() is used to store a list of p objects for deferred processing.
    So this means you are sticking the objects into a container. Now the normal containers do not support std::auto_ptr. But boost does have support for containers of pointers (where the container takes ownership). Also these contairs understand auto_ptr and will automatically transfer ownership from the auto_ptr to the container.

    boost::ptr_list<P>   messages;
    void doStuff(std::auto_ptr<P> p)
    {
        messages.push_front(p);
    }
    

    Note when you access members of these containers it always returns a reference (not a pointer) to the contained object. This is indicating that the lifespan of the object is tied to the lifespan of the container and the reference is valid as long as the container is valid (unless you explicitly remove the object).

    0 讨论(0)
  • 2020-12-05 04:16

    The previous answers have been excellent. I just want to add one thing, based on Martin York's and Michael Burr's answers.

    Using the example constructor from Michael Burr, I've added an assignment in the constructor body:

    C::C(int ii, double id)
    try
         : i(f(ii)), d(id)
    {
         //constructor function body
    
         d = sqrt(d);
    
    }
    catch (...)
    {
         //handles exceptions thrown from the ctor-initializer
         //and from the constructor function body
    }
    

    The question now is, when is d considered 'fully constructed', so that its destructor will be invoked if an exception is thrown within the constructor (as in Martin's post)? The answer is: after the initializer

     : i(f(ii)), d(id)
    

    The point is your object's fields will always have their destructors invoked if an exception is thrown from the constructor body. (This is true whether or not you've actually specified initializers for them.) Conversely, if an exception is thrown from another field's initializer, then destructors will be invoked for those fields whose initializers have already run (and only for those fields.)

    This implies that the best practice is not to allow any field to reach the constructor body with an undestructable value (e.g., an undefined pointer.) That being the case, it's best to actually give your fields their real values through the initializers, rather than (say) first setting the pointers to NULL, and then giving them their 'real' values in the constructor body.

    0 讨论(0)
  • 2020-12-05 04:20

    If you take a resource in a constructor, such as a socket etc, then this would be leaked if you threw an exception would it not?

    But I guess this is the argument for doing no work in a constructor, lazy initializing your connections as you need them.

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