c++exceptionunique-ptr

Detecting the erroneous deletion of a raw pointer


I'm well aware that throwing exceptions from destructors is almost universally regarded as a Bad Idea.

But I've been dealing with a case where I have a class that is only ever intended to be held within a std::unique_ptr, so that each object's lifetime is managed by the unique pointer. However, there are occasions when I have to pass such an object's raw pointer to legacy code, or even to third-party libraries.

If the temporary owner of that raw pointer attempts to delete the object, I want to know about it straight away. Otherwise, when the std::unique_ptr goes out of scope, it will try to delete the object a second time and cause a crash - but by the time that happens, it will be very difficult to work out where the first deletion (via the raw pointer) took place. I'd far prefer for the crash to take place at the first deletion, so I can easily locate the problem and fix the code there.

Here's the best solution I've been able to think of so far:

#include <iostream>
#include <memory>

class managedLifetime
{
public:
    managedLifetime() { }
    
    virtual ~managedLifetime()
    {
        throw()
    }
};

class managedLifetimeCustomDeleter
{
public:
    // Delete the object, safely catching the exception 
    void operator()(managedLifetime * ml) const
    {
        try
        {
            delete ml;
        }
        catch
        {
        }
    }
};

function hazard(managedLifetime * object)
{
    // I want this to crash
    delete object;
}

int main()
{
    std::unique_ptr<managedLifetime, managedLifetimeCustomDeleter> obj( new managedLifetime(); )
    
    hazard(obj.get());

    std::cout << "I want the application to crash before I get to here" << std::endl;
}

The code will crash anyway after the text is outputted, but I want the crash to occur before then, so I have a chance of identifying it.

Is this a genuine case of where throwing from a destructor can be excused? Or is there a simpler and safer way of achieving the same objective?


Solution

  • The more I thought about it, the more I took fright at the thought of throwing an exception from a destructor: it's so comprehensively frowned upon that I shouldn't wonder if it gets removed from C++ altogether in some future standard.

    So partially following @bolov's suggestion, I used an assert:

    class undeletable
    {
    public:
        undeletable() : safeToDelete(false) {}
    
    protected:  // Prevents deletion from within the code
        virtual ~undeletable()
        {
            assert(safeToDelete);
        }
    
    private:
        void setSafeToDelete { safeToDelete = true; }
        bool safeToDelete;
    
        friend class deleteTheUndeletable;
    };
    
    
    class deleteTheUndeletable
    {
    public:
         void operator()(undeletable * obj) const
         {
             obj->setSafeToDelete();
             delete obj;
         }
     };
    

    Then, any attempt to delete an 'undeletable' by any means at all, even if it happens from the depths of a third-party library, except by explicitly calling:

    deleteTheUndeletable(myObject);
    

    ... will cause an assert failure at runtime when running with DEBUG enabled, so that the location of the unintended deletion can easily be identified and fixed.