Search code examples
c++exceptiontry-catchthrow

Need I Destroy a Thrown Object Manually?


I just wonder if in C++, need I destroy a thrown object manually?

In the following code, how to destroy the thrown 0?

try
{
   ...
   throw 0;
}
catch(int i)
{
   // How to destroy the thrown 0?
}

In the following code, how to destroy the thrown CString object?

try
{
   ...
   throw CString(_T("Hello"));
}
catch(CString& str)
{
   // How to destroy the thrown str?
}

In the following code, I can destroy the thrown object since it is allocated from heap as a CString*

try
{
   ...
   throw new CString(_T("Hello"));
}
catch(CString* lpStr)
{
   delete lpStr;
}

Solution

  • You don't need to and are not allowed to manually destroy the exception object or the catch parameter.

    throw will copy-initialize a new unnamed object, the exception object, from its operand and this object is then caught by the catch block. Caught here means that the parameter of the catch block will be initialized from the exception object.

    When the catch block exits its parameter is automatically destroyed and if it exits without re-throwing (and if there are no std::exception_ptr referencing it left), the exception object will also be destroyed, the same way that objects with automatic storage duration are destroyed when the block in which they are declared exits or that temporary objects are destroyed when the full-expression they were created in ends.

    In your first code example the exception object of type int is initialized with the value 0 and you capture it by-value, meaning that you are creating a new object of type int as the variable in the catch parameter, initialized from the exception object. When the catch block exits the int from the catch parameter is destroyed and, assuming you didn't re-throw, the exception object is also destroyed.

    In your second example the exception object of type CString is initialized from the temporary object created by CString(_T("Hello")). The temporary CString(_T("Hello")) is destroyed after the exception object is initialized, as that is the end of the full-expression in which it was created.

    Due to copy elision the temporary will however never be really materialized and the CString exception object is directly initialized from _T("Hello"). (This is mandatory since C++17 and was allowed before.)

    You catch the exception object by reference, meaning that no new copy will be created and str will refer to the exception object. When leaving the catch block without re-throwing, the exception object will again be destroyed automatically.

    In the third example you are throwing a CString* and you capture it by value, so the same considerations as in example 1 apply. When leaving the catch block without re-throwing, the pointer lpStr and the exception object, which is also a CString*, will be destroyed.

    Destroying a pointer does however not mean that the object the pointer is pointing to is destroyed. (This is why raw pointers should not be used as owning pointers.)

    Therefore, since the pointer points to an object created with new, you indeed need to destroy that object by a call to delete lpStr; if you don't want the newed CString to leak. This delete however does not delete the exception object or the catch parameter. delete destroys the object that the pointer given to it points to. The catch parameter is the pointer lpStr itself, an automatic storage duration variable that cannot be deleted. The exception object is another pointer (with the same value) and is unnamed without any possible way to reference it in your example.

    You should prefer catching by-reference as in your second example and you should avoid using new for anything really. There are smart pointers std::unique_ptr and std::shared_ptr for free store allocations that with correct ownership semantics.