2

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.

Jacques Nel
  • 581
  • 2
  • 11

1 Answers1

1

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.

Curve25519
  • 654
  • 5
  • 17
  • Makes sense. Thanks. – Jacques Nel Sep 22 '16 at 21:34
  • 2
    This is *an* example method for allocations & deallocations, not all allocators have 4 bytes of overhead per allocation. – GManNickG Sep 22 '16 at 21:39
  • My concern is that this could be implementation dependent. Imagine I allocate an object on the heap. Then cast its pointer to a void pointer. Wouldn't that be equivalent to the problem in this question? http://stackoverflow.com/questions/12761875/does-deleting-void-pointer-guarantee-to-delete-right-size – Jacques Nel Sep 22 '16 at 21:41
  • "Calling a delete on a pointer returned from a call to new is safe." It's unclear what you mean here. You can use the `delete` operator on pointers that come from a `new` operator expression, and you can call `::operator delete(ptr)` on pointers that come from `::operator new(size_t)`, but **you cannot mix and match.** – Ben Voigt Sep 22 '16 at 21:41
  • @BenVoigt So my understanding is that ::operator new and ::operator delete are accessing the global namespace operators. Would a class member new / delete operator pair generally be continuous with their global counterparts, in their inner heap implementation mechanics, if they are not overloaded? – Jacques Nel Sep 22 '16 at 21:48
  • @JacquesNel The data will be deallocated, you just don't call the relevant dtors here. Note also that the question you're presenting is regarding the "delete" keyword, not the delete global function – Curve25519 Sep 22 '16 at 21:52
  • 1
    @JacquesNel: If a class doesn't define `operator new` and `operator delete`, then using the `new` and `delete` operators on pointers to that class type will result in class to the global `::operator new()` and `::operator delete` functions. – Ben Voigt Sep 22 '16 at 21:52
  • @TalShalti The Allocator's only purpose is to allocate and deallocate memory for use by the container. I plan to have the client container call the dtors. I will look at where the responsibility of calling the dtor is placed in the STL library's implementation of 'allocator' and the containers that use it, but my guess is it probably happens in the containers. – Jacques Nel Sep 22 '16 at 22:00
  • @JacquesNel It actually happens in the allocators. Might be the only thing that happens there, beside template magics and calls to the relevant C functions `malloc` and `dealloc`. – Curve25519 Sep 22 '16 at 22:06