This is the mail archive of the
libstdc++@gcc.gnu.org
mailing list for the libstdc++ project.
operator new/delete forwarding
- From: Howard Hinnant <hhinnant at apple dot com>
- To: libstdc++ <libstdc++ at gcc dot gnu dot org>
- Date: Thu, 20 Jul 2006 13:20:37 -0400
- Subject: operator new/delete forwarding
We currently have:
operator new forwards to malloc. operator
delete forwards to free.
operator new(nothrow) forwards to malloc. operator
delete(nothrow) forwards to free.
operator new [] forwards to operator new. operator
delete [] forwards to operator delete.
operator new(nothrow)[] forwards to operator new(nothrow). operator
delete(nothrow)[] forwards to operator delete.
Note the asymmetry (nothing forwards to delete(nothrow)). This can
cause confusion for those clients which overload only a subset of
these 8 operators.
The careful programmer will override all 8 of the operators and thus
not be affected by our implementation. However our implementation
currently allows the programmer to override only the 4 non-array
forms of new/delete (both ordinary and nothrow), and get the array
forms for free (as long as the user's operator delete can handle a
destruction triggered by an exception emanating from a new(nothrow)[]
expression - from a constructor, not from the operator new[](size_t,
nothrow)).
I view this forwarding as a valuable convenience to the client,
however I believe it is incomplete, and slightly wrong. I
respectfully propose the following slight alteration:
operator new forwards to malloc. operator
delete forwards to free.
operator new(nothrow) forwards to operator new. operator delete
(nothrow) forwards to operator delete.
operator new [] forwards to operator new. operator delete
[] forwards to operator delete.
operator new(nothrow)[] forwards to operator new[]. operator delete
(nothrow)[] forwards to operator delete[].
This will allow the client to only override:
void* operator new(std::size_t size) throw(std::bad_alloc);
void operator delete(void* ptr) throw();
And get both the array forms and nothrow forms for free (which is the
most common use case). If the client wishes to handle array
allocation/deallocation differently than non-array, then he can
override only these 4:
void* operator new(std::size_t size) throw(std::bad_alloc);
void operator delete(void* ptr) throw();
void* operator new[](std::size_t size) throw(std::bad_alloc);
void operator delete[](void* ptr) throw();
And still get correctly behaving nothrow forms for both the array and
non-array overrides (our default nothrow forms will forward correctly
to the client's non-array and array variants).
Specifically, change:
new_opnt.cc from:
_GLIBCXX_WEAK_DEFINITION void *
operator new (std::size_t sz, const std::nothrow_t&) throw()
{
void *p;
/* malloc (0) is unpredictable; avoid it. */
if (sz == 0)
sz = 1;
p = (void *) malloc (sz);
while (p == 0)
{
new_handler handler = __new_handler;
if (! handler)
return 0;
try
{
handler ();
}
catch (bad_alloc &)
{
return 0;
}
p = (void *) malloc (sz);
}
return p;
}
to:
_GLIBCXX_WEAK_DEFINITION void *
operator new (std::size_t sz, const std::nothrow_t&) throw()
{
try
{
return ::operator new(sz);
}
catch (...)
{
}
return 0;
}
and change new_opvnt.cc from:
_GLIBCXX_WEAK_DEFINITION void*
operator new[] (std::size_t sz, const std::nothrow_t& nothrow) throw()
{
return ::operator new(sz, nothrow);
}
to:
_GLIBCXX_WEAK_DEFINITION void*
operator new[] (std::size_t sz, const std::nothrow_t&) throw()
{
try
{
return ::operator new[](sz);
}
catch (...)
{
}
return 0;
}
and change del_opnt.cc from:
_GLIBCXX_WEAK_DEFINITION void
operator delete (void *ptr, const std::nothrow_t&) throw ()
{
if (ptr)
free (ptr);
}
to:
_GLIBCXX_WEAK_DEFINITION void
operator delete (void *ptr, const std::nothrow_t&) throw ()
{
::operator delete (ptr);
}
and change del_opvnt.cc from:
_GLIBCXX_WEAK_DEFINITION void
operator delete[] (void *ptr, const std::nothrow_t&) throw ()
{
::operator delete (ptr);
}
to:
_GLIBCXX_WEAK_DEFINITION void
operator delete[] (void *ptr, const std::nothrow_t&) throw ()
{
::operator delete[] (ptr);
}
Remaining unchanged are:
new_opv.cc
del_opv.cc
Here is a brief example demonstration:
#include <cstdio>
#include <new>
const std::size_t N = 1025*1024;
char pool[N];
char* p = pool;
unsigned s = 0;
void* operator new(std::size_t size) throw(std::bad_alloc)
{
if (size > N - (p-pool))
throw std::bad_alloc();
std::printf("Allocating\n");
void* r = p + s;
p += s;
return r;
}
void operator delete(void*) throw()
{
std::printf("Deallocating\n");
}
int main()
{
std::printf("new\n");
delete new char;
std::printf("new(nothrow)\n");
delete new(std::nothrow) char;
std::printf("new[]\n");
delete [] new char[3];
std::printf("new(nothrow)[]\n");
delete [] new(std::nothrow) char[3];
}
Today's output is:
new
Allocating
Deallocating
new(nothrow)
Deallocating
new[]
Allocating
Deallocating
new(nothrow)[]
Deallocating
The desired output is:
new
Allocating
Deallocating
new(nothrow)
Allocating
Deallocating
new[]
Allocating
Deallocating
new(nothrow)[]
Allocating
Deallocating
As a last detail, I can see valid arguments for three different
implementations of the array-nothrow forms:
operator new(nothrow)[] forwards to operator new[]. operator delete
(nothrow)[] forwards to operator delete[].
or
operator new(nothrow)[] forwards to operator new(nothrow). operator
delete(nothrow)[] forwards to operator delete(nothrow).
or
operator new(nothrow)[] forwards to operator new. operator delete
(nothrow)[] forwards to operator delete.
I somewhat arbitrarily chose the first implementation in my
description above. But I think any of these three choices is
reasonable (note that our current implementation is a cross between
the second and third choice). The most important change is to have
the non-array nothrow variants forward to the non-array, throwing
implementations.
-Howard