Igneous01
Igneous01

Reputation: 739

Delete template allocated array, that may have allocated indices

I am re-working an old circular buffer class i wrote to be more robust. I am allocating a buffer on the heap that is of type T (so the class is templated). However I am having issues with freeing resources that could be possible with T being a pointer to dynamically allocated space.

Here is a ctor with default value parameter in short

template <typename T, unsigned int SIZE>
CircularBuffer(const T default_val) {
    _buffer = new T[SIZE];
    // assign each block default value, etc
}

// dtor
~CircularBuffer()
{
    delete [] _buffer;
}

however, say for example someone decides to do this:

CircularBuffer<int*, 4> cb(new int); // buffer of 4, holding int*, with default value of new int

// later ~CircularBuffer call is made
// user allocated memory is not freed

How would I be able to (or let the user) free this memory? I have tried manually from the user perspective:

delete cb.at(0);  // .at returns T& (so it would effectively return the pointer)
// above is access violation

I tried to figure out how to do this in the destructor, but I wasnt able to do any sort of delete _buffer[i] since the compiler thinks that template T is not a pointer (even though it could be).

Can I safely handle this situation, or is there something the user can do about this so that the responsibility is not mine (since the class is not internally allocating this, the user is)?

Edit*** I just realized that the allocation with new when passing a T* as template parameter does not return the buffer size expected.

// call default ctor
CircularBuffer<double*, 2> cb(); // _buffer = new T[SIZE];
// sizeof(_buffer) == 4;
// sizeof(double*) == 4;
// sizeof(double*[2]) == 8;  // _buffer should be this size, as it holds 2 4byte pointers, what happened?

Im not sure if I should make a new question for this, or leave it here with the original question, but I think this explains some access violations I was getting before (after trying to dereference _buffer[1] of the above instance. Unfortunately Im not sure what is causing this.

Upvotes: 2

Views: 2930

Answers (2)

emesx
emesx

Reputation: 12755

One idea is to partially specialize the template by writing

template<typename T, unsigned int SIZE>
class CircularBuffer<T*> { ... }

so that you operate on pointers you can delete. You can then use some SFINAE tricks like enable_if so compilation of templates specialized for pointers will fail. Anyway, first delete the memory the pointers from the array point to, then delete the array itself. This sounds like a lot of possible trouble.

Other idea is to allow your users to control some part of the memory; you can copy the ideas from other containers, e.g. by defining custom allocators and deallocators

template<typename T>
class Buffer {
public:
    typedef function<T(unsigned)> Allocator;
    typedef function<void(T)> Deallocator;

    Buffer(unsigned size,  Allocator allocator, Deallocator deallocator )
        : _size(size), _buffer(new T[1024]), 
          _allocator(allocator), _deallocator(deallocator)
    {
        for(unsigned i=0; i<_size; ++i)
            _buffer[i] = _allocator(i);
    };

    ~Buffer(){
        for(unsigned i=0; i<_size; ++i)
            _deallocator(_buffer[i]);

        delete[] _buffer;
    };

private:
    unsigned _size;
    Allocator _allocator;
    Deallocator _deallocator;
    T* _buffer;
};

...

Buffer<double*> b2(
    128, 
    [](unsigned idx) { return new double(idx + 0.123); }, 
    [](double* d) { cout << "deleting " << *d << endl; delete d; }
);

This is a very simple and quick draft, but you should get the idea.


By the way, using a template parameter for buffer's size (unsigned int SIZE) is probably not the best idea, since the compiler will generate independent code for the templates differing only in size, which will grow your executable size... try making SIZE a construction argument, just like some containers do.

Upvotes: 5

thomas
thomas

Reputation: 525

template<typename T>
struct is_pointer{
    static const bool value = false;
};

template<typename T>
struct is_pointer<T*>{
    static const bool value = true;
};

template <bool, class T = void> 
struct enable_if 
{};

template <class T> 
struct enable_if<true, T> 
{ 
  typedef T type; 
};

template <bool, class T = void> 
struct disable_if 
{};

template <class T> 
struct disable_if<false, T> 
{ 
  typedef T type; 
};

template <typename T, unsigned int SIZE>
class CircularBuffer
{
public: 
    CircularBuffer(){
        _buffer = new T[SIZE];
    }

    ~CircularBuffer()
    {
        free_helper<T,SIZE>();
        delete [] _buffer;
    }

private:

    template<class U, unsigned int SIZE>
    void free_helper( typename enable_if< is_pointer<U>::value >::type *dummy=0 ){
        //pointer container
        for(int i=0;i<SIZE;++i){
           //delete it?  
           U t = _buffer[i];  
        }
    }

    template<class U, unsigned int SIZE>
    void free_helper( typename disable_if< is_pointer<U>::value >::type *dummy=0 ){
        //none pointer container
    }

    T* _buffer;
};


void main(){

    CircularBuffer<int,10> cb;
    CircularBuffer<int*,10> cb2;

}

Upvotes: 5

Related Questions