#include <iostream>
void* operator new (std::size_t count, int i1, int i2, int i3){
void *p = malloc(count);
if (!p) throw std::bad_alloc{};
std::cout << "operator new" << '\n';
return p;
}
void operator delete (void* p, int j1, int j2, int j3)
{
free(p);
std::cout << "operator delete" << '\n';
std::cout << j1 << ' ' << j2 << ' ' << j3 << '\n';
}
class T{};
class A {
public:
A() { std::cout << "A()" << '\n'; throw T{}; };
~A() { std::cout << "~A()" << '\n'; }
};
int main()
{
try
{
A *p = new(1, 2, 3) A;
delete p;
}
catch (std::bad_alloc&) { exit(1); }
catch ( T& ) { }
}
operator new
A()
operator delete
1 2 3
See live example.
However, if I comment out the catch(T&){ } above, the code aborts without invoking the operator delete(void*, int, int, int).What I'm trying to understand is the logic used by the compiler to determine when the placement delete is invoked, or not. With that in mind, I posted below my idea of a pseudo-code for the new-expression new(1, 2, 3) A used above inside main().
int main()
{
try
{
A* p = operator new(sizeof(A), 1, 2, 3);
try
{
p->A();
}
catch ( T& )
{
operator delete(p, 1, 2, 3);
}
delete p; // this will invoke the usual operator delete(void*)
}
catch (std::bad_alloc&) { exit(1); }
catch ( T& ) { }
}
[except.handle]p9: "If no matching handler is found, the function std::terminate() is called; whether or not the stack isunwound before this call to std::terminate() is implementation-defined (15.5.1)."[expr.new]p20: "If any part of the object initialization described above terminates by throwing an exception, storage has been obtained for the object, and a suitable deallocation function can be found, the deallocation function is called to free the memory in which the object was being constructed, after which the exception continues to propagate in the context of the new-expression."Note that when the initialization causes an exception to be thrown, one of two things happens:1) The stack is unwound. In this case, the initialization terminates by throwing an exception as the stack is unwound out of the constructor call, and the deallocation function will then be called.2) The stack is not unwound and std::terminate is called. In this case, the initialization does not ever terminate (because std::terminate does not return), so the deallocation function is not called.So, for an implementation that does not do stack unwinding when there is no matching handler for a thrown exception, your 'operator delete' will not be invoked (and nor will any destructors for local variables).
Where is the handler for the T that is thrown in the A constructor? Even if the compiler did generate code that looks like your pseudo-code, there's still a throw in there that is not caught. As such, the program terminates, and since the program is terminating, there's no need to actually unwind the stack.
int main()
{
try
{
A* p = operator new(sizeof(A), 1, 2, 3);
try
{
p->A();
}
catch ( T& ) // the throw is caught here
On Tuesday, December 1, 2015 at 6:45:13 PM UTC-2, Greg Marr wrote:Where is the handler for the T that is thrown in the A constructor? Even if the compiler did generate code that looks like your pseudo-code, there's still a throw in there that is not caught. As such, the program terminates, and since the program is terminating, there's no need to actually unwind the stack.Even if the compiler did generate code that looks like your pseudo-code, there's still a throw in there that is not caught.
int main()
{
try
{
A* p = operator new(sizeof(A), 1, 2, 3);
try
{
p->A();
}
catch ( T& ) // the throw is caught here
{
operator delete(p, 1, 2, 3);
}
delete p; // this will invoke the usual operator delete(void*)
}
catch (std::bad_alloc&) { exit(1); }
catch ( T& ) { }
}
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussio...@isocpp.org.
To post to this group, send email to std-dis...@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
No, this was not in your original program, and is not implied by the semantics of the new-expression. Here's a more accurate lowering of your original code:int main() {try {A *p;{void *q = operator new(sizeof(A), 1, 2, 3);struct cleanup {void *p; int a; int b; int c;~cleanup() { operator delete(p, 1, 2, 3); }} __cleanup = {q, 1, 2, 3};p = new (q) A; // call constructor of A}delete p; // this will invoke the usual operator delete(void*)}catch (std::bad_alloc &) { exit(1); }catch (T &) {}}Note that if A's constructor throws an exception for which there is no handler, and the implementation chooses to call std::terminate before unwinding the stack, the destructor of __cleanup is not run, so your operator delete(void*, int, int, int) is not run.
In other words, what this pseudo-code is doing that mine is lacking or mistaken?
On Tuesday, December 1, 2015 at 6:45:13 PM UTC-2, Greg Marr wrote:Where is the handler for the T that is thrown in the A constructor? Even if the compiler did generate code that looks like your pseudo-code, there's still a throw in there that is not caught. As such, the program terminates, and since the program is terminating, there's no need to actually unwind the stack.Even if the compiler did generate code that looks like your pseudo-code, there's still a throw in there that is not caught.
int main()
{
try
{
A* p = operator new(sizeof(A), 1, 2, 3);
try
{
p->A();
}
catch ( T& ) // the throw is caught here
{
operator delete(p, 1, 2, 3);
}
It's what it's *not* doing: it doesn't introduce a handler.
No, this was not in your original program, and is not implied by the semantics of the new-expression. Here's a more accurate lowering of your original code:int main() {try {A *p;{void *q = operator new(sizeof(A), 1, 2, 3);struct cleanup {void *p; int a; int b; int c;~cleanup() { operator delete(p, 1, 2, 3); }} __cleanup = {q, 1, 2, 3};p = new (q) A; // call constructor of A}delete p; // this will invoke the usual operator delete(void*)}catch (std::bad_alloc &) { exit(1); }catch (T &) {}}Note that if A's constructor throws an exception for which there is no handler, and the implementation chooses to call std::terminate before unwinding the stack, the destructor of __cleanup is not run, so your operator delete(void*, int, int, int) is not run.
{
operator delete(p, 1, 2, 3);
}
delete p; // this will invoke the usual operator delete(void*)
}
catch (std::bad_alloc&) { exit(1); }
catch ( T& ) { }
}
There seems to be a problem with your pseudo-code. See this example. The code aborts when A() doesn't throw. free() is called twice for the same address., i.e., both the placement and the usual operator delete are called in this case.
void* operator new(std::size_t, int, double);
void operator delete(void*, int, double);
struct A { A(); };
void f() { new(1, 2.0) A; }
void g() {
void* q = ::operator new(sizeof(A), 1, 2.0);
struct __cleanup {
void* p; int i; double d; bool active;
~__cleanup(){ if(active) ::operator delete(p, i, d); }
void dismiss() { active = false; }
} guard = {q, 1, 2.0, true};
::new(q) A;
guard.dismiss();
}
From: Patrice Roy Sent: Wednesday, December 2, 2015 10:22 PM Reply To: std-dis...@isocpp.org Subject: Re: [std-discussion] Invoking placement delete when object construction throws |
It's non-tested pseudocode, what do you expect? You actually need a dismiss-able scope guard or something equivalent (e.g., Alexandrescu's SCOPE_FAIL) that can distinguish between stack unwinding and normal destruction.The following functions generate identical assembly with g++ -O2 (http://goo.gl/kX4GCc):
void* operator new(std::size_t, int, double);
void operator delete(void*, int, double);
struct A { A(); };
void f() { new(1, 2.0) A; }
void g() {
void* q = ::operator new(sizeof(A), 1, 2.0);
struct __cleanup {
void* p; int i; double d; bool active;
~__cleanup(){ if(active) ::operator delete(p, i, d); }
void dismiss() { active = false; }
} guard = {q, 1, 2.0, true};
::new(q) A;
guard.dismiss();
}
Read ~__cleanup() again.
~__cleanup(){ if(active) ::operator delete(p);
No, the whole point of that example is to illustrate when/how placement delete is called.