exception within constructor results in memory leak
Wolfgang Loch -- Wolfgang.Loch@RZ.TU-Ilmenau.DE Friday, August 09, 1996 Environment: VC 2.0, Windows 95 I ran into a subtle problem using a exception thrown by an object's constructor. What I wanted to do is: class CMyObject: public CObject { CMyObject(); virtual ~CMyObject(); ... }; CMyObject::CMyObject() { if( !DoCriticalThing() ) throw new CMyException; ... } void SomeFunction() { CMyObject* pObject = NULL; try { CMyObject* pObject = new CMyObject; } catch( CMyException* e ) { if( pObject != NULL ) delete pObject; e->Delete(); MessageBox( ... ); } ... } And what happens is: 1. NULL is assigned to pObject 2. memory is allocated for CMyObject member variables 3. CMyObject constructor is called 4. if the CriticalThing() fails an exception is thrown 5. CMyObject destructor is called 6. the Object *is not* assigned to pObject 7. exception is catched And now there is no way to deallocate the memory, because pObject is still NULL. A possible way would be do do the CriticalThing() in an extra Create() function. But I don't like this, because I want to have a valid Object to be constructed. Any Idea? Wolfgang -- /-------------------------------------------------\ | Wolgang Loch (Technical University of Ilmenau) | | e-mail: Wolfgang.Loch@rz.TU-Ilmenenau.DE | | www : http://www.rz.tu-ilmenau.de/~wolo | \-------------------------------------------------/
Mike Blaszczak -- mikeblas@nwlink.com Monday, August 12, 1996 [Mini-digest: 3 responses] At 08:21 AM 8/9/96 +0200, you wrote: >Environment: VC 2.0, Windows 95 You really need to upgrade. >And what happens is: > [...] >And now there is no way to deallocate the memory, because >pObject is still NULL. Right. Back when VC++ 2.0 came out, it was Microsoft's first crack at making a compiler that supports exceptions. There's nothing in the compiler to handle this situation, and that's why you see this behaviour. I'd bet a dollar, too, that the C++ draft language standard _AT THE TIME_ didn't even discuss what to do. >A possible way would be do do the CriticalThing() in an >extra Create() function. But I don't like this, because >I want to have a valid Object to be constructed. > >Any Idea? The only thing I've ever seen people do here is write an override for new() for each type that can throw an exception. This is necessary because the language dictates that only completely cosntructed objects are destroyed in this situation. So, if you have: class CInner; class CMiddle; // inherits from CInner class COuter; // inherits from CMiddle and it turns out the COuter constructor throws an exception, you end up calling the destructor on the excepted object's CMiddle and CInner parts, but not on its COuter part... because it wasn't _fully_ constructed. If you think about a new implementation that can handle this without inherent support from the compiler, you'll realize that the only way to get it done is with explicit code that calls malloc() and calls the in-place constructor explicitly. Maybe you could do that in-line in your code: CMyObject* pObject = (CMyObject*) malloc(sizeof(CMyObject)); try { pObject->CMyObject::CMyObject(); } catch( CMyException* e ) { pObject->CMyObjectParentClass::~CMyObjectParentClass(); free(pObject); e->Delete(); MessageBox( ... ); } pretty sick, huh? The current compilers -- certainly, VC++ 4.1 and VC++ 4.2 -- implement what the current UnStandard suggest. I don't know if VC++ 4.0 does, and I don't know if VC++ 2.1 or 2.2 do. If you upgraded to VC++ 4.2, though, you'd certainly get a compiler that generates code to free and destroy on exceptions in constructors, as you'd like. It's funny that more people don't know about problems like this in the language specification and the compiler. MFC is often (and its designers more often) flamed for not being "properly object-oriented". One of the things that people just love to cite is the fact that most MFC classes have Create() or other initialization functions which are separate from the constructors. Well, it turns out that this design is there for a very real reason: 16-bit compilers didn't support exceptions at all. For the 32-bit compilers, the language standard was late to dictate and/or Microsoft was late to describe and implemnet subtleties like this one. So, imagine how much easier your job would be if you had a separate Create() function: CMyObject* pObject = new CMyObject(); // nothing interesting! try { pObject->Create(); } catch( CMyException* e ) { delete pObject; e->Delete(); MessageBox( ... ); } and that's just fine: it works. You can get your knickers in a twist about what's "true OOP", or you can get some work done. MFC has address the latter goal. .B ekiM http://www.nwlink.com/~mikeblas/ These words are my own. I do not speak on behalf of Microsoft. -----From: Gabriel Parlea-VisalonSee "The try, catch and throw Statements" in the "C++ language reference". Once in a catch handler the "unwinding of the stack" is performed which will delete all automatic variables created in the try statement. To check for memory leaks search for help on CMemoryState. By the way even if the new is successful your pointer goes out of scope at the end of the try statement, but then thats probably why you declared one before try, so get rid of the second declaration. Gabriel -- Gabriel Parlea-Visalon Software Engineer Derivative Trading Systems gabriel@derivs.demon.co.uk -----From: Pradeep Tapadiya First things first. When a constructor throws an error: 1. The destructor is NEVER called; the object could have been partially constructed. 2. The memory allocated by new is released automatically. You do not (and should not) explictly release memory. 3. In your code, it is good that you were initializing pObject to NULL. Otherwise, if CMyObject throws an exception in the constructor, the return value is not specified and most likely will not be equal to NULL. For obvious reasons, one should never look at the return value from a new if the constructor throws an exception. Now, more important problem: There is a leak in the way exceptions are handled by our beloved Microsoft Software engineers. The problem is as follows: One can redefine operator new to take in additional parameters. One typical use is for logging file/line information of all the allocations made in the program. That is, one can redefine operator new such that it can be used as CMyObject* pObject = new (__FILE__, __LINE__) CMyObject; As a matter of fact, if you look at crtdbg.h, Microsoft does provide an overloaded operator new which takes in three paramters, two of which are __FILE__ and __LINE__. If you are using MFC, macro DEBUG_NEW replaces your "new" by this overloaded "new" operator. Now, the problem with Microsoft's implementation is, if CMyObject throws an exception, the memory is not freed at all. I have talked to Microsoft's technical support people about this. They acknowledge the problem. However, they have no intentions to fix it even in upcoming version (5.0) of the compiler. Note that the problem appears only when new is overloaded to take in extra parameters. It doesn't happen for CMyObject* pObject = new CMyObject; However, beware of the fact that class wizard generated code may replace all "new" keyword by "DEBUG_NEW" which gets replaced by new(NORMAL_BLOCK, __FILE__, __LINE__). So, if you are using Microsoft's compiler, I suggest either (i) do not let the construct throw an exception in the constructor. Instead, define a method init(). CMyObject* pObject = new CMyObject; if (NULL == pObject) { blah .... return; } try { pObject->Init(); } catch( CMyException* e ) { delete pObject; ... } (ii) or make sure that your "new" doesn't get replaced by "DEBUG_NEW." Also, I see in your code that you try to check if a pointer is NULL before deleting it. You need not check if a variable is NULL to delete it; in the current specifications, it is legal to pass a NULL pointer to delete. Best, Pradeep pradeep@nuview.com
Ash Williams -- ash@digitalworkshop.co.uk Thursday, August 15, 1996 If a constructor hasn't been properly called, then its destructor won't get called at all - true for stack or heap objects, so don't worry about deleting pObject if new failed. Word of warning: if CMyObject happens to successfully new up some of its own members, then these _must_ be deleted. ** class Resource {...}; // pretend class CMyObject::CMyObject() : m_pResource( new Resource ) { if( !DoCriticalThing() ) // ~CMyObject won't get called, so if it does anything // important such as freeing stuff, better do it here delete m_pResource; throw new CMyException; ... } void SomeFunction() { CMyObject* pObject = NULL; try { CMyObject* pObject = new CMyObject; } catch( CMyException* e ) { // no worries, nothing got constructed so don't destruct e->Delete(); MessageBox( ... ); } ... } ** A better solution would to have a management object to manage deleting: template< class T > // T is your class which you new up class Mangager { public: Manager( T* pT ) { m_pT = pT; } T* GetPtr() { return m_pT; } ~Manager() { delete m_pT; } ... private: T* m_pT; }; void Eg() { Manager< Resource > man = new Resource; throw( int ); } As you can see you don't ever have to remember to use delete and since Manager is stack based, its d'tor will get called even when an exception is thrown halfway down the stack frame it happens to be on. Hope these are good tactics for you. Ash
| Вернуться в корень Архива |