Reputation: 621
Context:
I am trying to create a custom allocator which mimics std::allocator
(not derived from) in some ways, but allows instanced allocators. My generic containers have constructors, which allow the user to specify a pointer, to a custom Allocator
object. When no allocator is specified, I want it to default to a singleton NewDeleteAllocator
which derives from a abstract Allocator
class. This simply wraps the global new
and delete
operators. This idea is taken from Towards a Better Allocator Model by Pablo Halpern.
Client code that uses custom allocator:
// 'foo_container.hpp'
// enclosed in package namespace
template <class T>
class FooContainer
{
private:
// -- Private member properties --
Allocator * allocator;
public:
// -- Constructors --
FooContainer( Allocator * allocator = 0 )
{
this->allocator = !allocator ? (Allocator *)defaultAllocator : allocator;
}
FooContainer( const FooContainer &rhs, Allocator * allocator = 0 )
{
// don't implicitly copy allocator
this->allocator = !allocator ? (Allocator *)defaultAllocator : allocator;
// copying logic goes here
}
}
Custom allocator implementation:
// 'allocator.hpp'
// enclosed in package namespace
class Allocator
{
public:
virtual ~Allocator(){ };
virtual void * allocate( size_t bytes ) = 0;
virtual void deallocate( void * ptr ) = 0;
};
class NewDeleteAllocator : public Allocator
{
public:
virtual ~NewDeleteAllocator()
{
}
virtual void * allocate( size_t bytes )
{
return ::operator new( bytes );
}
virtual void deallocate( void * ptr )
{
::operator delete( ptr ); // memory leak?
}
private:
};
//! @todo Only for testing purposes
const Allocator * defaultAllocator = new NewDeleteAllocator();
Main Question:
I know that allocating via new
might also store information about the allocation along with the pointer. I realize calling delete
with the scope resolution operator ::
is not quite the same as just calling delete
, but how does ::delete( ptr )
know the size of the data that ptr
is pointing to? Is this a safe operation? From my understanding, deleting via a void pointer could result in undefined behaviour, according to the C++ standard. If this is bad, how else could I implement this?
Further details:
I did some very rough preliminary testing with the following code:
// inside member function of 'FooContainer'
for( size_t i = 0; i < 1000000; i++ )
{
for( size_t j; j = 1; j < 20; j++ )
{
void * ptr = allocator->allocate( j );
allocator->deallocate( ptr );
}
}
I observed the program total memory usage with Xcode's profiling tools. Memory usage stays constant at a low value. I'm aware that this is not the proper way to check for memory leaks. I don't know if the compiler could optimize this out. I am just experimenting with the idea. I would really appreciate some input to the main question, before I make any commitments to the architecture of my library. The whole approach might be flawed in the first place.
Thanks for the input. I don't want to make any bad assumptions.
Upvotes: 2
Views: 1037
Reputation: 694
Calling ::delete
on a pointer returned from a call to ::new
is safe.
Calling ::delete[]
on a pointer returned from a call to ::new[]
is safe.
Calling delete x
on a pointer returned from a call to auto x = new {...}
is safe if you don't away x's type.
Calling delete[] x
on a pointer returned from a call to auto x = new {...}[z]
is safe if you don't away x's type.
mixing is UB
"but how does ::delete( ptr ) know the size of the data that ptr is pointing to"
The dynamic allocated memory in C++ is usually implemented through a heap. The heap initially allocates a very large amount of space, and than he handles it, letting the program handle random chunks from it. The heap stores the size of every chunk of memory it allocates. For example, if you need 8 bytes of memory, the heap reserve for you at least 12 bytes, holding the size in the first 4 bytes and the data in the 8 latter. Than, it returns a pointer to the 8 bytes. So, in the process of deleting, the program knows how much "to delete" through accessing the pointer - 4.
Upvotes: 1