Deleting an object if the constructor failed.

This is a discussion on Deleting an object if the constructor failed. within the C++ Programming forums, part of the General Programming Boards category; Hi guys, I want to ask you about how to delete an object if its constructor failed to initialize one ...

  1. #1
    In the Land of Diddly-Doo g4j31a5's Avatar
    Join Date
    Jul 2006
    Posts
    476

    Deleting an object if the constructor failed.

    Hi guys, I want to ask you about how to delete an object if its constructor failed to initialize one of its members. I tried it like below but failed:

    Code:
    B* A:CreateB(std::string paramStr)
    {
        B* pB= NULL;
        try
        {
             pB = new pB (paramStr);
        }
        catch (int result)
        {
             if (result == OK) // OK == 1
             {
                    m_VectorB.push_back(pB);
             }
              else if (result == FAILED)
             {
                    delete pB;
                    pB = NULL;
             }
        }
        return pB;
    }
    
    B::B(std::string paramStr)
    {
        int result = initMembers (paramStr); //returns OK / 1 if successful or FAILED / 0 if failed
        throw result ;
    }
    Now, FYI, I have never ever ever used exception in my entire life as a programmer. All my error handlings are either by return value or assertion. I know that it is not a good habit but I still don't have a good understanding about it. So, how can I solve this problem? Thanks in advance.
    Last edited by g4j31a5; 09-15-2009 at 01:54 AM.
    ERROR: Brain not found. Please insert a new brain!

    “Do nothing which is of no use.” - Miyamoto Musashi.

  2. #2
    Guest Sebastiani's Avatar
    Join Date
    Aug 2001
    Location
    Waterloo, Texas
    Posts
    5,600
    First let's clarify what 'delete' means: to invoke the destructor and then release the memory (internally, usually done using free()) associated with the object. Furthermore, only the 'owner' of the object calls 'delete' on an object. If you're assigning the result of 'new' to a raw pointer (very bad idea) then that would be you. Otherwise, it's whatever container you're using to manage it with.

    So then I suppose the question is "does a destructor get invoked on a partially constructed object?" (eg: when an exception is thrown, but not caught, during the constructor). The answer is 'no' (it will, however, be called on whatever class members were fully constructed up to that point).

    As far as throwing integers as return values, IMO, the is a very bad design. There are a lot of differing opinions on the matter, but the general consensus is that exceptions should only be thrown from an object in the most extreme situations (eg: 'fatal' errors). On the other hand, exceptions thrown and caught from within the same object is often a useful technique for overcoming complicated control-flow situations, but that's a different matter entirely.

  3. #3
    The larch
    Join Date
    May 2006
    Posts
    3,573
    new does two things: a) allocate memory, b) call constructor. delete does the opposite: a) call destructor, b) deallocate memory.

    Now if the constructor throws an error, new deallocates the memory and rethrows the exception. Also, when the constructor throws, the object never comes to existence (members initialized so far are destroyed) and therefore it would be an error to invoke the destructor on the zombie.

    So your attempt to signal success by throwing an exception from a constructor couldn't possibly work.

    Correct way:

    Code:
    B* A::CreateB(std::string paramStr)
    {
       //no special error handling needed here
       //some caller handles allocation/construction failures
        B* pB = new pB (paramStr);
    
        //if the previous line didn't throw (= if we get this far in the first place), everything's OK
        m_VectorB.push_back(pB);
             
        return pB;
    }
    
    B::B(std::string paramStr)
    {
        int result = initMembers (paramStr); //returns OK / 1 if successful or FAILED / 0 if failed
        if (!result) throw some_exception("something is wrong"); //only throw in case of failure
    }
    Last edited by anon; 09-15-2009 at 02:48 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).

  4. #4
    Cat without Hat CornedBee's Avatar
    Join Date
    Apr 2003
    Posts
    8,892
    Of course, even better would be to have each member initializer in turn use exceptions to signal errors, because the entire point of exceptions is that they propagate upwards automatically.
    All the buzzt!
    CornedBee

    "There is not now, nor has there ever been, nor will there ever be, any programming language in which it is the least bit difficult to write bad code."
    - Flon's Law

  5. #5
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    20,973
    I suggest that you read GotW #66: Constructor Failures.
    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

  6. #6
    In the Land of Diddly-Doo g4j31a5's Avatar
    Join Date
    Jul 2006
    Posts
    476
    Thanks for the reply guys

    @Sebastiani: Well, I know that the 'new' method mallocs and then calls the constructor and 'delete' calls the destructor and then free the memory. What I don't know is how exception works in general. And I thought that the object will be fully created even if I throw an exception in the constructor. I guess I'm wrong.

    About passing int as an exception, yeah, I know that it's not a good idea. But it is sufficient as of now.

    BTW, as for assigning 'new' to a raw pointer, I've seen lots of open source libraries do that also. So i thought, why can't I?

    @anon: OIC, I thought we can throw anything at anytime. My mistake then. BTW, your code didn;t work. I tried this one though and it worked:
    Code:
    B* A:CreateB(std::string paramStr)
    {
        B* pB= NULL;
        try
        {
             pB = new pB (paramStr);
        }
        catch (int result)
        {
              pB = NULL;
        }
    
        m_VectorB.push_back(pB);
        return pB;
    }
    
    B::B(std::string paramStr)
    {
        int result = initMembers (paramStr); //returns OK / 1 if successful or FAILED / 0 if failed
        if (result == FAILED)
                       throw result ;
    }
    @CornedBee: Well I would if there's no time constraint. Heck, I will rewrite the entire code with exceptions if there's time. But for now, it's okay to do this quick hack. :P

    @laserlight: Thanks. Another source of information is always good.
    ERROR: Brain not found. Please insert a new brain!

    “Do nothing which is of no use.” - Miyamoto Musashi.

  7. #7
    Guest Sebastiani's Avatar
    Join Date
    Aug 2001
    Location
    Waterloo, Texas
    Posts
    5,600
    BTW, as for assigning 'new' to a raw pointer, I've seen lots of open source libraries do that also. So i thought, why can't I?
    Yes, unfortunately it's fairly common, actually. There are numerous good articles on the subject (ie: RAII), but briefly, consider this bit of code:

    Code:
    struct fatal
    {    };
    
    struct object
    {
        object( bool chaos )
        {
            if( chaos )
                throw fatal( );
            cout << "object( )" << endl;    
        }
        
        virtual ~object( void )
        {
            cout << "~object( )" << endl;    
        }    
    };
    
    void test( void )
    {
        object*
            ptr = new object( false );
        object
            obj( true );
    }
    
    int main( void )
    try
    {
        test( );
    }
    catch( ... )
    {    }
    Output:

    object( )
    Here, 'ptr' is not 'protected' by a destructor, so when/if an exception is thrown, it never gets cleaned up. You might be tempted to do something like this:

    Code:
    void test( void )
    {
        object*
            ptr;
        try
        {
            ptr = new object( true );
            object
                obj( true );
        }
        catch( ... )
        {
            delete ptr; // whoops!
        }
    }
    But that isn't correct either. In this case, the exception was thrown while allocating 'ptr', so an invalid delete is applied!

    You might think you could simply call delete if the exception isn't an std::bad_alloc, but unfortunately, that won't work since it could have originated from anywhere between the try block.

    The solution, of course, is to follow the RAII strategy:

    Code:
    void test( void )
    {
        std::auto_ptr< object >
            ptr( new object( false ) );
        object
            obj( true );
    }
    Output:

    object( )
    ~object( )
    Perfect!

  8. #8
    Cat without Hat CornedBee's Avatar
    Join Date
    Apr 2003
    Posts
    8,892
    Doing exceptions without strictly following RAII principles is just setting yourself up for lots and lots of headaches, and bugs in code paths that are very rarely tested.
    All the buzzt!
    CornedBee

    "There is not now, nor has there ever been, nor will there ever be, any programming language in which it is the least bit difficult to write bad code."
    - Flon's Law

  9. #9
    Malum in se abachler's Avatar
    Join Date
    Apr 2007
    Posts
    3,189
    Quote Originally Posted by g4j31a5 View Post
    BTW, as for assigning 'new' to a raw pointer, I've seen lots of open source libraries do that also. So i thought, why can't I?
    I commonly assign new to LPVOID when I am abstracting a class to hide internal details when distributing a closed source middelware library. This way I can have a header with only the public methods and data showing, enforcing privatization and at the same time protecting trade secrets.

    e.g.
    Code:
    class Public_Class {
       LPVOID pObject;
    
       // public functions and data go here
    then in the actual constructor
    Code:
    Public_Class::Public_Class(){
    
       this->pObject = (LPVOID) new ActualClass();
    
       return;
       }
    Since my public class functions will have access tot he ActualClass header they can manipulate the object by recasting pObject to (ActualClass*), but the end user only gets the compiled library and the Public_Class header, so they do not have access to the ActualClass member functions or variables and can only access it through the methods I allow.
    Last edited by abachler; 09-16-2009 at 01:44 AM.
    Until you can build a working general purpose reprogrammable computer out of basic components from radio shack, you are not fit to call yourself a programmer in my presence. This is cwhizard, signing off.

  10. #10
    Cat without Hat CornedBee's Avatar
    Join Date
    Apr 2003
    Posts
    8,892
    But why not do this?

    Code:
    class Public_Class {
      class Impl;
      boost::scoped_ptr<Impl> m_impl; // or equivalent to scoped_ptr
    
    public:
      Public_Class();
      ~Public_Class();
    };
    
    // implementation:
    class Public_Class::Impl
    {
      // ...
    };
    
    Public_Class::Public_Class()
      : m_impl(new Impl)
    {}
    Public_Class::~Public_Class()
    {} // Empty. It's only out-of-line to force an instantiation location for scoped_ptr's destructor.
    No casting, no chance of exception unsafety. You have to implement copying if you want it, of course, but you have to do that anyway.
    All the buzzt!
    CornedBee

    "There is not now, nor has there ever been, nor will there ever be, any programming language in which it is the least bit difficult to write bad code."
    - Flon's Law

  11. #11
    Ex scientia vera
    Join Date
    Sep 2007
    Posts
    478
    Not my intention to hijack the topic - but the reason you shouldn't be using new directly with raw pointers with custom objects is more or less because it never goes out of scope, due to the simple fact that it's not allocated on the stack? And using std::auto_ptr solves this for us because when it goes out of scope, it destroys the objects it is encapsulating?

    Very informative post - still learning how to write proper code in C++.
    "What's up, Doc?"
    "'Up' is a relative concept. It has no intrinsic value."

  12. #12
    Cat without Hat CornedBee's Avatar
    Join Date
    Apr 2003
    Posts
    8,892
    but the reason you shouldn't be using new directly with raw pointers with custom objects is more or less because it never goes out of scope, due to the simple fact that it's not allocated on the stack?
    More generally, you shouldn't do it because you have to explicitly control (and document) the lifetime and ownership of the object, whereas smart pointers automate, enforce and document a specific ownership and lifetime model. For example, scoped_ptr enforces single, local ownership with scoped lifetime. If you see a scoped_ptr, you know that this is the case. If you see a raw pointer, you always have to wonder.
    All the buzzt!
    CornedBee

    "There is not now, nor has there ever been, nor will there ever be, any programming language in which it is the least bit difficult to write bad code."
    - Flon's Law

  13. #13
    Ex scientia vera
    Join Date
    Sep 2007
    Posts
    478
    Quote Originally Posted by CornedBee View Post
    More generally, you shouldn't do it because you have to explicitly control (and document) the lifetime and ownership of the object, whereas smart pointers automate, enforce and document a specific ownership and lifetime model. For example, scoped_ptr enforces single, local ownership with scoped lifetime. If you see a scoped_ptr, you know that this is the case. If you see a raw pointer, you always have to wonder.
    I see. Thanks =)

    I was actually wondering how I should go about solving a problem, and by surfing this forum, not looking for the solution at all, I stumbled upon it, heh.
    "What's up, Doc?"
    "'Up' is a relative concept. It has no intrinsic value."

  14. #14
    In the Land of Diddly-Doo g4j31a5's Avatar
    Join Date
    Jul 2006
    Posts
    476
    @Sebastiani & CornedBee: RAII? what's that? sorry that's still new to my ear. I swear, the profs have never mentioned that word when I was in college. -_-a

    @abachler: LPVOID is the same as void*, right? Although it's doable, it's too much of a hassle IMHO. I rarely use void* so that the code can be read better. :P

    You know, I've also never used auto_ptr. It's not portable IIRC. Maybe I'll try it in the future. As for passing pointers out of scope, I usually does that and control them strictly by myself. So I have to make sure if the object is still alive or not before doing anything to it on different objects. I usually code what came up first in my head. I confess, I suck at designing my codes. :P
    ERROR: Brain not found. Please insert a new brain!

    “Do nothing which is of no use.” - Miyamoto Musashi.

  15. #15
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    20,973
    Quote Originally Posted by g4j31a5
    You know, I've also never used auto_ptr. It's not portable IIRC.
    It is part of the C++ standard library.
    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

Page 1 of 3 123 LastLast
Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Deleting object after list removal (C++ visual studio)
    By RancidWannaRiot in forum Windows Programming
    Replies: 2
    Last Post: 10-20-2005, 06:06 PM
  2. Question on l-values.
    By Hulag in forum C++ Programming
    Replies: 6
    Last Post: 10-13-2005, 04:33 PM
  3. A question about constructors...
    By Wolve in forum C++ Programming
    Replies: 9
    Last Post: 05-04-2005, 04:24 PM
  4. deleting dynamic object problem
    By eth0 in forum C++ Programming
    Replies: 17
    Last Post: 05-19-2004, 01:17 PM
  5. Array of Pointers + Deleting An Object = Problems
    By Nereus in forum C++ Programming
    Replies: 3
    Last Post: 03-04-2004, 11:16 AM

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