inconsistent behavior with user-defined new and delete
We all know that a new-expression,
foo* a = new foo() ;
allocates memory for a single foo then calls foo::foo(). And we know
that
void* p = ::operator new(sizeof(foo)) ;
allocates a sizeof(foo)-sized buffer but does NOT call foo::foo().
So only new-expressions both allocate and initialize. Fair enough.
Now suppose we wish to use a custom memory pool,
my_pool pool ;
foo* a = new(pool) foo() ;
This calls
void* operator new( std::size_t size, my_pool & )
throw(std::bad_alloc) ;
and then calls foo::foo(). Naturally we also define
void operator delete( void* data, my_pool & ) throw() ;
But how are we going to call our custom delete?
my_pool pool ;
foo* a = new(pool) foo() ;
// compile error
delete(pool) a ;
// calls the default operator delete(), not our delete
delete a ;
// does not call foo::~foo()
::operator delete(a, pool) ;
// is this the idiom, then?
a->~foo() ;
::operator delete(a, pool) ;
There appears to be no such delete-expression to complement the
new-expression. Why the inconsistency? Surely this is an oversight
in the design of C++? In particular I expected "delete(pool) a" to
work.
To recap, we have a mechanism for default-allocate-then-construct
(no-argument new-expressions) and for destruct-then-default-deallocate
(no-argument delete-expressions). Furthermore, we have a mechanism
for custom-allocate-then-construct (custom new-expressions), but NOT
for destruct-then-custom-deallocate (no custom delete-expressions).
One workaround is to define operator new() and operator delete()
inside foo, but suppose I can't do that. Another workaround is to use
template< typename pool_type, typename T >
void custom_delete( pool_type & pool, T* p )
{
p->~T() ;
pool.deallocate(p, sizeof(T)) ;
}
but this just begs the question of why that should be necessary, i.e.,
why no delete-expressions, i.e. delete(pool) p.
Incidentally this begs a more general question: since a custom
operator new() defined outside of a class will not work with auto_ptr,
one wonders what is the point of such operator new()s. Surely someone
will eventually use auto_ptr with it by accident, wreaking havoc when
the wrong delete is called from auto_ptr::~auto_ptr(). This seems to
suggest that "delete p" should automagically call
void operator delete( void* data, my_pool & ) throw() ;
however that is probably impossible to implement efficiently (relative
to current C++ implementations). Another option is for the auto_ptr
constructor to take an optional (de)allocator but that relies on
programmers remembering to do so, which doesn't solve the problem but
just moves it around.
#include <iostream>
#include <ostream>
class my_pool
{
} ;
void* operator new( std::size_t size, my_pool & )
throw(std::bad_alloc)
{
std::cerr << "my_pool operator new" << std::endl ;
return ::operator new(size) ;
}
void operator delete( void* data, my_pool & ) throw()
{
std::cerr << "my_pool operator delete" << std::endl ;
return ::operator delete(data) ;
}
struct foo
{
foo()
{
std::cerr << "foo::foo()" << std::endl ;
}
~foo()
{
std::cerr << "foo::~foo()" << std::endl ;
}
} ;
int main()
{
my_pool pool ;
foo* a = new(pool) foo() ;
// compile error
//delete(pool) a ;
// calls the default operator delete(), not ours
//delete a ;
// does not call foo::~foo()
//::operator delete(a, pool) ;
// is this the idiom, then?
a->~foo() ;
::operator delete(a, pool) ;
return 0 ;
}