0
votes

Why this code not call CloseHandles in class destructor?
In my code test I call '((MyClass*)pThis)->CloseHandles();' explicitly, but variable m_bFinished have wrong value. Why ?

#include <windows.h>
#include <exception>

class MyClass
{
public:

    explicit MyClass( void **pThis)
    {
        *pThis = this;
        m_bFinished = false;

        //code open handle here

        //an error occurs
        throw new std::exception("Exception thrown!");
    }

    ~MyClass()
    {
        if ( ! m_bFinished ) CloseHandles();
    }

    void CloseHandles()
    {
        if ( m_bFinished ) return;

        //close handles here.

        m_bFinished = true;
    }

private:
    bool m_bFinished;
};

int main(int argc, char* argv[])
{
    MyClass * pMyClass;
    void * pThis = NULL;

    try
    {
        pMyClass = new MyClass(&pThis);
    }
    catch(std::exception * e)
    {
        //delete pThis;

        if ( pThis )
        {
            ((MyClass*)pThis)->CloseHandles();
        }
    }

    return 0;
}
1

1 Answers

3
votes

Because the destructor of a class doesnt' run when its constructor throws - the object hasn't been fully initialized yet.

Also, you're not actually throwing std::exception, but a pointer to it:

// dynamically allocates std::exception and throws a pointer to it
throw new std::exception("Exception thrown!");

EDIT: I noticed that you're catching a pointer, too, so that's not the problem. But, there's no constructor of std::exception that takes a string literal, so I wonder how your code even compiles.

In any case, if a constructor can possibly throw after a raw resource has been allocated, you have a potential leak.

You need to wrap the resource in class that manages it - a smart pointer perhaps or a similar RAII wrapper. And use member initializer lists!

Another option is constructor delegation (new in C++11). An object is considered fully constructed when any of its constructors finishes execution. This means that if an exception is thrown from a constructor that delegated to another constructor (where you'd do the acquisition of handles), the destructor will be called.

To illustrate with some code:

struct Handle {
    Handle() : handle(new int()) {}
    ~Handle() { delete handle; }
    int* handle;
};

class MyClass {
    Handle h;
    MyFlass() : h() // handle initialized here
    {
       /**** code that may throw ****/
       // this will properly close handles because
       // the destructors of already initialized
       // members (like h) will be called
    }
    ~MyClass() { /* not called if constructor throws */ }
};

And an example of constructor delegation:

#include <iostream>

class MyClass {
private:
    int* handle;
    MyClass(int)  // dummy parameter just for overloading
        : handle(new int()) { /* better not throw from here */ }
public:
    MyClass() : MyClass(0) // handle initialized here
    {
       /**** code that may throw ****/
       throw 42;
    }
    ~MyClass() { delete handle; std::cout << "dtor!"; }
};

int main()
{
    try { MyClass m; } catch (int) {};
}

Output is dtor!.