Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is the memory automatically reclaimed when a delegating constructor throws?

Tags:

c++

c++11

Following on from this: Is the destructor called when a delegating constructor throws?

class X
{
public:
    X()       {};
    X(int)    : X() { throw std::exception(); }
    X(double)       { throw std::exception(); }
    ~X();
};

What about dynamic memory? Normally an exception in the constructor means the object was not fully constructed and thus the memory is cleanedup and not lost.

But the argument in the previous question is that the object is fully constructed (or fully initialized) after the delegate completes. How does this affect reclaiming the memory? I would hope that memory is still cleanedup!

int main()
{
    new X(5);        // new called 
                     // delete called because delegate completed.
                     // I assume:  
                     //      Memory re-claimed (because constructor did not complete)
                     //      I assume the C++11 standard adjusted to compensate.
                     //      As a constructor did complete.
}

Compared too:

int main()
{
    new X(5.0);      // new called 
                     //    Delete **NOT** called
                     // Memory re-claimed (because constructor did not complete)
}

If the memory is cleaned up, then the definition of when the memory is cleanup needs to be altered from C++03 spec. How is the behavior changed?

like image 832
Martin York Avatar asked Jul 15 '13 16:07

Martin York


1 Answers

If the constructor called by new throws an exception then the memory allocated by new is automatically deallocated. Delegating constructors change nothing in this regard.

If any part of the object initialization described above76 terminates by throwing an exception and a suitable deallocation function can be found, the deallocation function is called to free the memory in which the object was being constructed

                                                                                                       — C++11 [expr.new] 5.3.4/18

The 'any part of the object initialization' described includes both the constructor calls and evaluation of the expressions passed to the constructor.

Also, this behavior is specified identically in the C++98 standard [C++98 5.4.3/17]. The only difference delegating constructors make is if your mental model was previously based on the object being completely constructed or not. Given delegating constructors that's no longer equivalent to the actual specification of when deallocation occurs.


In your first example:

new X(5);

The order of events is:

  • allocation function called
  • X(int) called
    • X() called (and exits successfully)
    • X(int) throws an exception
    • ~X() called
  • X(int) exits via exception
  • deallocation function called because object initialization failed
  • exception continues to propagate normally

With the second example

new X(5.0);
  • allocation function called
  • X(double) called
  • X(double) fails with an exception
  • deallocation function called because object initialization failed
  • exception continues to propagate normally

You can observe this behavior by replacing the allocation and deallocation functions:

#include <iostream>
#include <cstdlib>
#include <stdexcept>
#include <new>
    
void *operator new(std::size_t s) {
    if (void *ptr = std::malloc(s)) {
        std::cout << "allocation\n";
        return ptr;
    }
    throw std::bad_alloc{};
}

void operator delete(void *ptr) noexcept {
    if (ptr) {
        std::cout << "deallocation\n";
        std::free(ptr);
    }
}

struct S {
    S() {};
    S(int) : S{} { throw std::exception(); }
    S(double) { throw std::exception(); }
    ~S() { std::cout << "destructor\n"; }
};

int main() {
    std::cout << "test 1\n";
    try {
        new S(1);
    } catch(...) {
        std::cout << "exception caught\n";
    }

    std::cout << "test 2\n";
    try {
        new S(1.);
    } catch(...) {
        std::cout << "exception caught\n";
    }
}

The correct output of this program is:

test 1
allocation
destructor
deallocation
exception caught
test 2
allocation
deallocation
exception caught
like image 192
bames53 Avatar answered Oct 12 '22 01:10

bames53