Exception safe constructor/destructor

This is a discussion on Exception safe constructor/destructor within the C++ Programming forums, part of the General Programming Boards category; Am I right in that code like this is inherently vulnerable to memory leaks (if the constructor fails)? Code: class ...

  1. #1
    Registered User
    Join Date
    Mar 2009
    Posts
    399

    Exception safe constructor/destructor

    Am I right in that code like this is inherently vulnerable to memory leaks (if the constructor fails)?

    Code:
    class Foo
    {
    public:
    	Foo()
            : a(new int), b(new int)
    	{}
    	~Foo()
    	{
    		delete a;
    		delete b;
    	}
    private:
    	int *a;
    	int *b;
    };
    If a is assigned memory and then new fails in allocating memory for b, the destructor is never called if I understand this correctly, and thus we have a memory leak (a).

    I also understand why destructors should never throw exceptions, but how can you guarantee it if it needs to free up some memory or handle synchronization with an external resource (database etc) that in turn might throw exceptions? The calls to delete in this example might very well fail for some reason making the destructor here not exception safe.

  2. #2
    The larch
    Join Date
    May 2006
    Posts
    3,573
    Yes, if allocating b fails, a will be leaked. That's why you typically don't keep naked pointers.

    The calls to delete shouldn't fail (they can only throw an exception if the object being deleted violates the principle that it shouldn't throw from the destructor). Other reason why it might fail is when you have corrupted the heap somehow, but then your program will rather just explode.

    If I'm not mistaken you can catch exceptions thrown by code inside the destructor (so they don't propagate out of the destructor - one might try a function-level try block?) but there isn't much you can do about them.

    I suppose, if you have a case where releasing sources have a high chance of failing and you want to do something about it, you might need a special method for that. Once the destructor is invoked, there is no way back. (I think it is too late to ask the user about unsaved documents inside the destructor of Application.)
    Last edited by anon; 09-18-2009 at 08:29 AM.
    I might be wrong.

    Thank you, anon. You sure know how to recognize different types of trees from quite a long way away.
    Quoted more than 1000 times (I hope).

  3. #3
    Frequently Quite Prolix dwks's Avatar
    Join Date
    Apr 2005
    Location
    Canada
    Posts
    8,047
    If a is assigned memory and then new fails in allocating memory for b, the destructor is never called if I understand this correctly, and thus we have a memory leak (a).
    It depends on whether you're using the new which throws an exception when it runs out of memory, or whether you're using the new which returns NULL. operator new - C++ Reference

    To elaborate a bit on anon's "That's why you typically don't keep naked pointers": if you make a and b "smart" pointers (not pointers really, but a class which acts like a pointer), you can prevent this memory leak. Let's say you had a really simple smart pointer class like this:
    Code:
    template <typename Type>
    class SmartPointer {
    private:
        Type *data;
    public:
        SmartPointer(Type *data) : data(data) {}
        ~SmartPointer() { delete data; }
    
        Type &operator * () { return *data; }
        Type *operator -> () { return data; }
    };
    Then your Foo class could look like this:
    Code:
    class Foo
    {
    public:
    	Foo()
            : a(new int), b(new int)
    	{}
    	~Foo()
    	{
    		// SmartPointers automatically free themselves.
    		//delete a;
    		//delete b;
    	}
    private:
    	SmartPointer<int> a;
    	SmartPointer<int> b;
    };
    You could have the SmartPointer's constructor throw an exception when given a NULL pointer (or derive a NonNullSmartPointer class . . .). That way, if a gets allocated and constructed but b is NULL (or b's new throws an exception), a will be destructed, and the memory allocated for it automatically freed by ~SmartPointer().

    I should note that there are many other things you'd probably want from a SmartPointer class -- for example, most smart pointers let you copy one SmartPointer to another, and the underlying pointer will only be freed once (this requires, for example, a reference-counting implementation). If you're interested in seeing more about this you could look at Boost's shared_ptr. shared_ptr
    dwk

    Seek and ye shall find. quaere et invenies.

    "Simplicity does not precede complexity, but follows it." -- Alan Perlis
    "Testing can only prove the presence of bugs, not their absence." -- Edsger Dijkstra
    "The only real mistake is the one from which we learn nothing." -- John Powell


    Other boards: DaniWeb, TPS
    Unofficial Wiki FAQ: cpwiki.sf.net

    My website: http://dwks.theprogrammingsite.com/
    Projects: codeform, xuni, atlantis, nort, etc.

  4. #4
    Registered User
    Join Date
    Jun 2005
    Posts
    6,425
    Sorry, but the previous responses are incorrect.

    Section 15.2, para 2 states (some fonts for highlighting not shown)
    An object that is partially constructed or partially destroyed will have destructors executed for all of its fully constructed subobjects, that is, for subobjects for which the constructor has completed execution and the destructor has not yet begun execution. Should a constructor for an element of an automatic array throw an exception, only the constructed elements of that array will be destroyed. If the object or array was allocated in a new-expression and the new-expression does not contain a new- placement, the deallocation function (3.7.3.2, 12.5) is called to free the storage occupied by the object; the deallocation function is chosen as specified in 5.3.4. If the object or array was allocated in a new-expression and the new-expression contains a new-placement, the storage occupied by the object is deallocated only if an appropriate placement operator delete is found, as specified in 5.3.4.
    A consequence of this paragraph is that for the constructor;
    Code:
    Foo()
            : a(new int), b(new int)
    	{}
    if the allocation of b throws an exception, then a will be released. However, if the constructor was written as;
    Code:
    Foo()       
    {
        a = new int;
        b = new int;
    }
    then a will not be released if the allocation of b throws. In this case, it would be necessary to use smart pointers or some other mechanism to ensure required cleanup. The same requirement exists for any other function that does the allocations separately.
    Last edited by grumpy; 09-18-2009 at 03:36 PM.
    Right 98% of the time, and don't care about the other 3%.

  5. #5
    The larch
    Join Date
    May 2006
    Posts
    3,573
    Sorry but your interpretation seems to be incorrect. The only clause that seems to apply here is:

    An object that is partially constructed or partially destroyed will have destructors executed for all of its fully constructed subobjects, that is, for subobjects for which the constructor has completed execution and the destructor has not yet begun execution.
    Plain pointers don't have a destructor that does anything, so their pointee will be leaked.

    Compare with either of the typedefs:

    Code:
    #include <iostream>
    #include <stdexcept>
    #include <memory>
    
    class Bad
    {
    public:
        Bad(): id(++counter) { if (counter == 2) throw std::runtime_error("666\n"); }
    
        ~Bad() { std::cout << "Destroyed " << id << "\n"; }
        int id;
        static int counter;
    };
    
    int Bad::counter = 0;
    
    typedef Bad* BadPtr;
    //typedef std::auto_ptr<Bad> BadPtr;
    
    class X
    {
        BadPtr a;
        BadPtr b;
    public:
        X(): a(new Bad), b(new Bad) {}
    };
    
    int main()
    {
        try {
            X x;
        }
        catch (std::exception& e){
            std::cout << e.what() << '\n';
        }
    }
    I might be wrong.

    Thank you, anon. You sure know how to recognize different types of trees from quite a long way away.
    Quoted more than 1000 times (I hope).

  6. #6
    Registered User
    Join Date
    Jun 2005
    Posts
    6,425
    Quote Originally Posted by anon View Post
    Sorry but your interpretation seems to be incorrect.
    You didn't read far enough.

    The relevant part in the extract I quoted is "If the object or array was allocated in a new-expression and the new-expression does not contain a new- placement, the deallocation function (3.7.3.2, 12.5) is called to free the storage occupied by the object."
    Right 98% of the time, and don't care about the other 3%.

  7. #7
    Frequently Quite Prolix dwks's Avatar
    Join Date
    Apr 2005
    Location
    Canada
    Posts
    8,047
    Well then why does Valgrind report a memory leak for anon's code?
    Code:
    $ valgrind --leak-check=full --show-reachable=yes ./sp
    ==19907== Memcheck, a memory error detector.
    ==19907== Copyright (C) 2002-2006, and GNU GPL'd, by Julian Seward et al.
    ==19907== Using LibVEX rev 1658, a library for dynamic binary translation.
    ==19907== Copyright (C) 2004-2006, and GNU GPL'd, by OpenWorks LLP.
    ==19907== Using valgrind-3.2.1, a dynamic binary instrumentation framework.
    ==19907== Copyright (C) 2000-2006, and GNU GPL'd, by Julian Seward et al.
    ==19907== For more details, rerun with: -v
    ==19907==
    666
    
    ==19907==
    ==19907== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 15 from 1)
    ==19907== malloc/free: in use at exit: 4 bytes in 1 blocks.
    ==19907== malloc/free: 4 allocs, 3 frees, 113 bytes allocated.
    ==19907== For counts of detected errors, rerun with: -v
    ==19907== searching for pointers to 1 not-freed blocks.
    ==19907== checked 88,204 bytes.
    ==19907==
    ==19907== 4 bytes in 1 blocks are definitely lost in loss record 1 of 1
    ==19907==    at 0x4005B65: operator new(unsigned) (vg_replace_malloc.c:163)
    ==19907==    by 0x8048C48: X::X() (in sp)
    ==19907==    by 0x8048A92: main (in sp)
    ==19907==
    ==19907== LEAK SUMMARY:
    ==19907==    definitely lost: 4 bytes in 1 blocks.
    ==19907==      possibly lost: 0 bytes in 0 blocks.
    ==19907==    still reachable: 0 bytes in 0 blocks.
    ==19907==         suppressed: 0 bytes in 0 blocks.
    $
    Last edited by dwks; 09-18-2009 at 04:19 PM.
    dwk

    Seek and ye shall find. quaere et invenies.

    "Simplicity does not precede complexity, but follows it." -- Alan Perlis
    "Testing can only prove the presence of bugs, not their absence." -- Edsger Dijkstra
    "The only real mistake is the one from which we learn nothing." -- John Powell


    Other boards: DaniWeb, TPS
    Unofficial Wiki FAQ: cpwiki.sf.net

    My website: http://dwks.theprogrammingsite.com/
    Projects: codeform, xuni, atlantis, nort, etc.

  8. #8
    The larch
    Join Date
    May 2006
    Posts
    3,573
    If the object or array was allocated in a new-expression and the new-expression does not contain a new- placement, the deallocation function (3.7.3.2, 12.5) is called to free the storage occupied by the object.
    This means that if you do

    Code:
    Bad* b = new Bad;
    then this alone won't leak the raw memory allocated for this Bad object, even if the constructor of Bad throws.

    When you do
    Code:
    X(): a(new Bad), b(new Bad) {}
    then the first object is allocated successfully, and if the second throws, it won't leak the memory allocated for that object. When it concerns a, the first sentence applies: a will essentially go out of scope and the first Bad object is leaked.

    The new-expression in pseudo-code operates like this:
    Code:
    T* new:
        void* p = allocate(sizeof(T))
        if (!p) throw std::bad_alloc(...); //? this or allocate is supposed to throw
        try:
            T* t = construct_T(p);
            return t
        catch (...)
            deallocate(p)
            throw
    Last edited by anon; 09-18-2009 at 04:22 PM.
    I might be wrong.

    Thank you, anon. You sure know how to recognize different types of trees from quite a long way away.
    Quoted more than 1000 times (I hope).

  9. #9
    and the Hat of Guessing tabstop's Avatar
    Join Date
    Nov 2007
    Posts
    14,185
    Quote Originally Posted by grumpy View Post
    You didn't read far enough.

    The relevant part in the extract I quoted is "If the object or array was allocated in a new-expression and the new-expression does not contain a new- placement, the deallocation function (3.7.3.2, 12.5) is called to free the storage occupied by the object."
    But that only means that if you call new Foo, and Foo blows up, then you don't lose those bytes. I don't see where it has anything to do with an initializer list.

  10. #10
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    21,886
    Quote Originally Posted by grumpy
    The relevant part in the extract I quoted is "If the object or array was allocated in a new-expression and the new-expression does not contain a new- placement, the deallocation function (3.7.3.2, 12.5) is called to free the storage occupied by the object."
    I agree with anon's interpretation: the object here refers to the object itself, not the object that its member pointer points to. Read GotW #66: Constructor Failures. If your interpretation was correct, then Sutter's advice to "always perform unmanaged resource acquisition in the constructor body, never in initializer lists" is (at least partially) rubbish, since the opposite should be considered best practice (at least in the case where the resource acquisition is done via a new-expression).
    C + C++ Compiler: MinGW port of GCC
    Version Control System: Bazaar

    Look up a C++ Reference and learn How To Ask Questions The Smart Way

  11. #11
    Algorithm Dissector iMalc's Avatar
    Join Date
    Dec 2005
    Location
    New Zealand
    Posts
    6,308
    Aw crap, I used the URL syntax for the wrong forum. Lemme try again...

    This is the relveant GotW article that should answer your questions.
    My homepage
    Advice: Take only as directed - If symptoms persist, please see your debugger

    Linus Torvalds: "But it clearly is the only right way. The fact that everybody else does it some other way only means that they are wrong"

  12. #12
    Guest Sebastiani's Avatar
    Join Date
    Aug 2001
    Location
    Waterloo, Texas
    Posts
    5,708
    Quote Originally Posted by iMalc View Post
    <a href="http://www.gotw.ca/gotw/056.htm">This</a> is the relevant GotW article that should answer your questions.
    FYI, that should be a [url] tag...

    EDIT: Nevermind

    EDIT#2: Nice link, by the way, iMalc. I even learned something new.
    Last edited by Sebastiani; 09-19-2009 at 01:43 PM.

  13. #13
    Algorithm engineer
    Join Date
    Jun 2006
    Posts
    286
    Haven't read through the whole thread, but I have a question:

    If you have a class MyClass, which constructors throw exceptions, how are you going to create an object of that class? Say the constructors throw the exception bad_alloc, if you're supposed to immediatelly handle the error, the code will look like this:

    Code:
    try {
        MyClass MyObj();
    }
    catch (bad_alloc error) {
        ... //Handle error
    }
    
    ... //Code for using MyObj
    But does it work? I mean, after leaving the try catch-blocks, won't MyObj be out of scope already?

    Another possibility would be to only use MyObj within the try-block, but is that the only solution, and is it always possible?
    Come on, you can do it! b( ~_')

  14. #14
    Guest Sebastiani's Avatar
    Join Date
    Aug 2001
    Location
    Waterloo, Texas
    Posts
    5,708
    If an exception is thrown within the constructor then, effectively, the object does not exist. In other words, it was never 'in scope' to begin with.

  15. #15
    Algorithm engineer
    Join Date
    Jun 2006
    Posts
    286
    So...

    How do you then create an object of a class which constructors throw exceptions? According to what you say, the object doesn't exist, so you can't use it. In other words, code trying to use it will produce compile time errors?
    Come on, you can do it! b( ~_')

Page 1 of 2 12 LastLast
Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Exception handling in a large project
    By EVOEx in forum C++ Programming
    Replies: 7
    Last Post: 01-25-2009, 06:33 AM
  2. exception handling
    By coletek in forum C++ Programming
    Replies: 2
    Last Post: 01-12-2009, 04:28 PM
  3. using swap to make assignment operator exception safe
    By George2 in forum C++ Programming
    Replies: 9
    Last Post: 01-10-2008, 05:32 AM
  4. Bjarne's exception safe sample
    By George2 in forum C++ Programming
    Replies: 13
    Last Post: 12-28-2007, 04:38 PM
  5. Signal and exception handling
    By nts in forum C++ Programming
    Replies: 23
    Last Post: 11-15-2007, 01:36 PM

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21