First, not to the OP but to other readers: as I understand it the OP is not talking about constructing an object in preallocated storage.
And I'm not talking about that.
To OP: you don't, just let your placement form of operator delete
forward to the ordinary operator delete
. After all that's the deallocation function that will be called for any successfully constructed dynamically allocated object. So you need to support it no matter what.
If you want to associate debug information with the allocated memory for use in the deallocation function then one practical option is to allocate a bit more than requested and place the info at the start or end of that block, return pointer to the unused portion. Then operator delete
needs to do the opposite. Caution: alignment.
I guess an impractical option is to use a static std::map
(or other associative array, like a hash table). It runs in thread safety issues and such, but it avoids alignment issue.
Addendum, a complete example:
// Note: while this example works nicely, it doesn't support threading.
#include <iostream>
#include <new> // std::bad_alloc
#include <stddef.h> // ptrdiff_t, size_t, max_align_t
#include <stdlib.h> // malloc, EXIT_*,
typedef unsigned char Byte;
struct Source_reference
{
char const* filename;
int line_number;
};
#define SOURCE_REF Source_reference{ __FILE__, __LINE__ }
auto operator<<( std::ostream& stream, Source_reference const& ref )
-> std::ostream&
{
if( ref.filename == nullptr )
{
return stream << "[unknown source location]";
}
return stream << "\"" << ref.filename << "\"@" << ref.line_number;
}
struct Block_info
{
Source_reference source_ref;
Block_info* p_prev;
Block_info* p_next;
void link_in_after( Block_info& predecessor )
{
p_prev = &predecessor;
p_next = predecessor.p_next;
predecessor.p_next = this;
p_next->p_prev = this;
}
void unlink()
{
p_next->p_prev = p_prev;
p_prev->p_next = p_next;
}
};
namespace g {
size_t const max_align = sizeof( max_align_t );
size_t const prefix_size =
((sizeof( Block_info ) + max_align - 1)/max_align)*max_align;
Block_info block_list_header =
{{nullptr,0}, &block_list_header, &block_list_header};
} // namespace g
auto tracking_alloc( size_t const n_bytes_requested )
-> void*
{
size_t const n_bytes = n_bytes_requested + g::prefix_size;
Byte* const result = static_cast<Byte*>( malloc( n_bytes ) );
if( !result ) { throw std::bad_alloc(); }
Block_info* const p_info = ::new( result ) Block_info();
p_info->link_in_after( g::block_list_header );
return result + g::prefix_size;
}
void tracking_dealloc( void* p )
{
Block_info* p_info = reinterpret_cast<Block_info*>(
static_cast<Byte*>( p ) - g::prefix_size
);
p_info->unlink();
free( p_info );
}
auto operator new( size_t const n_bytes )
-> void*
{ return tracking_alloc( n_bytes ); }
auto operator new[]( size_t const n_bytes )
-> void*
{ return operator new( n_bytes ); }
void operator delete( void* p )
{ tracking_dealloc( p ); }
void operator delete[]( void* p )
{ operator delete( p ); }
auto operator new( size_t const n_bytes, Source_reference const& ref )
-> void*
{
Byte* const p = static_cast<Byte*>( operator new( n_bytes ) );
Block_info* const p_info = reinterpret_cast<Block_info*>( p - g::prefix_size );
p_info->source_ref = ref;
return p;
}
void operator delete( void* p, Source_reference const& )
{
using namespace std;
cout << "!placement delete called." << endl;
operator delete( p );
}
void list_blocks()
{
using namespace std;
cout << "Known allocated blocks:" << endl;
for(
Block_info* p_info = g::block_list_header.p_next;
p_info != &g::block_list_header;
p_info = p_info->p_next
)
{
void* const p_data = reinterpret_cast<Byte*>( p_info ) + g::prefix_size;
cout
<< "- Basic allocation " << p_data
<< " from " << p_info->source_ref << "."
<< endl;
}
cout << "- End list." << endl;
}
#include <vector>
auto main()
-> int
{
using namespace std;
int* p = new( SOURCE_REF ) int( 42 );
cout << "An int allocated with ref at " << p << "." << endl;
list_blocks();
int* p2 = new int( 43 );
cout << "\nAn int allocated sans ref at " << p << "." << endl;
list_blocks();
{
vector<double> v( 3 );
cout << "\nA vector constructed" << endl;
list_blocks();
try
{
struct Ungood{ Ungood() { throw 666; } };
cout << "\nAllocating with ref an object that fails construction." << endl;
new( SOURCE_REF ) Ungood;
}
catch( ... )
{}
list_blocks();
delete p;
cout << "\nThe int-with-ref deleted." << endl;
list_blocks();
}
cout << "\nThe vector destroyed" << endl;
list_blocks();
delete p2;
cout << "\nThe int-sans-ref deleted." << endl;
list_blocks();
}
Output with MinGW g++ 4.8.2:
An int allocated with ref at 0x213c0.
Known allocated blocks:
- Basic allocation 0x213c0 from "foo.cpp"@134.
- End list.
An int allocated sans ref at 0x213c0.
Known allocated blocks:
- Basic allocation 0x21410 from [unknown source location].
- Basic allocation 0x213c0 from "foo.cpp"@134.
- End list.
A vector constructed
Known allocated blocks:
- Basic allocation 0x21460 from [unknown source location].
- Basic allocation 0x21410 from [unknown source location].
- Basic allocation 0x213c0 from "foo.cpp"@134.
- End list.
Allocating with ref an object that fails construction.
!placement delete called.
Known allocated blocks:
- Basic allocation 0x21460 from [unknown source location].
- Basic allocation 0x21410 from [unknown source location].
- Basic allocation 0x213c0 from "foo.cpp"@134.
- End list.
The int-with-ref deleted.
Known allocated blocks:
- Basic allocation 0x21460 from [unknown source location].
- Basic allocation 0x21410 from [unknown source location].
- End list.
The vector destroyed
Known allocated blocks:
- Basic allocation 0x21410 from [unknown source location].
- End list.
The int-sans-ref deleted.
Known allocated blocks:
- End list.