RuLoViC RuLoViC - 7 days ago 5
C++ Question

destructor implementation in templated class

If I have a class like this:

template <class T>
class Array {
private:
T *m_pData;
unsigned int m_nSize;
public:
Array(unsigned int nSize) : m_nSize(nSize),m_pData(nullptr) {
if(m_nSize > 0)
m_pData = new T[m_nSize];
}

~Array() {
if(m_pData != NULL) {
delete [] m_pData;
}
};


If now I create an object of my class like this:

Array<int> miArray(5);


Implementation of the destructor should be fine, but if I create an object like this:

Array<int*> miArray(5);


In the destructor I should delete every object stored in the array to avoid memory leaks.

How can I achieve that?

Thanks

Answer

You could use a specialization of your destructor, or tag-dispatch to a deletion function (which would be more flexible). However, this leads to a pretty awkward and brittle design. Supposing you have implemented an equivalent to push_back, consider these user code snippets:

{
    Array<int*> arr;
    arr.push_back(new int(42)); // new is on the side of the user
} // ... but delete is not. Weird.

This also leads to a whole class of errors: you sohuld only give newed Ts to an Array<T*>, but there is no security whatsoever against passing in something else:

{
    Array<int*> arr;
    int i = 42;
    arr.push_back(&i); // No diagnostic
    arr.push_back(new int[17]); // No diagnostic either
} // Undefined behaviour from calling `delete` on stuff that hasn't been `new`ed

All of this is the reason for the existence of the No raw owning pointers rule: raw pointers should not manage resource lifetime, at all. If I use an Array<int*>, it should store my pointers and let me use them, but never ever delete them, because that's trying to manage lifetime.

Instead, if I want an Array that manages lifetime, I'll use the corresponding smart pointer, with Array<std::unique_ptr<int>>. This ties in nicely with Array, requiring it to only call the destructor of the contained objects (which it already does). These destructors (~unique_ptr, that is) will transitively deallocate the resources as is their job, and all is well.

Additional notes:

  • Take care to initialize your Array's buffer if you need -- m_pData = new T[m_nSize]; will allocate default-initialized objects. If these objects have no default constructor, their value will be indeterminate. A simple fix is to use new T[m_nSize]{}, which will perform value-initialization -- that is, initialize arithmetic types to zero, pointers to nullptr, and compound types recursively.

  • Implement copy and/or move semantics for your class, see Rule of three/five/zero. As it is now, copying an instance of Array will lead to two instances thinking they own the same buffer, and undefined behaviour when both try to delete it.

  • delete and delete check for null, so the if(m_pData != NULL) in the destructor is redundant.

Good luck :)