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?
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:
With the second example
new X(5.0);
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With