Search code examples
c++templatesmemory-managementconstructor

calling to constuctor using(along with) realloc


i'm writing my own type of std::vector for homework assignments,using class templates. i wan't it to be efficient as possible so i prefer to use realloc function rather then new. this causes me a problem because when i allocate memory to new member it doesn't call to the constructor of the new member and these causes a lot of problems.

the relevant part of my class template:

template<class T>
class myVec {
    T *m_data;
    unsigned m_size;
public:
    //some methods
    //some methods
    myVec<T>& resize(unsigned size) {
        if (size == 0)
        {
            delete[] m_data;
            m_data = nullptr;
            m_size = 0;
            return *this;
        }
        m_data = (T*)realloc(m_data, size * sizeof(T));  //should call to constructor of T here!
        m_size = size;
        return *this;
    };
    void push(const T& t) {
        m_data = (T*)realloc(m_data, ++m_size * sizeof(T));
        memcpy(m_data, &t, sizeof(t));
        //m_data[m_size - 1] = t;
    };
    ~myVec() { delete[] m_data; };
};

Does it wrong to use realloc(from C like malloc and free) along with new and delete? if so, is there realloc like function for c++ that calls to constructor? if not,how can i call manually call to the constructor after the allocation?


Solution

  • You can't mix malloc()/realloc()/free() with new/delete. Use one or the other.

    If you insist on manually allocating the array using the C runtime functions, you will have to use placement-new to manually call C++ constructors AFTER you have allocated the array. And, as such, you will have to manually call C++ destructors.

    Also, it is not safe for your push() to use memcpy() for non-POD types, either. You need to implement proper copy/move semantics for pushed elements.

    And, don't forget the Rule of 5 so your class implements proper copy/move semantics for itself, too.

    Try something more like this instead:

    template<class T>
    class myVec
    {
        T *m_data = nullptr;
        unsigned m_size = 0;
    
    public:
        ...
    
        myVec() = default;
    
        myVec(const myVec<T> &src)
        {
            if (src.m_size)
            {
                m_data = (T*) malloc(src.m_size * sizeof(T));
                if (!m_data) throw std::runtime_error("malloc failed!");
    
                for(unsigned i = 0; i < m_size; ++i)
                    new (&m_data[i]) T(src.m_data[i]);
    
                m_size = src.m_size;
            }
        }
    
        myVec(myVec<T> &&src)
        {
            std::swap(m_data, src.m_data);
            std::swap(m_size, src.m_size);
        }
    
        ~myVec()
        {
            resize(0);
        }
    
        void resize(unsigned size)
        {
            if (m_size != size)
            {
                T *temp;
    
                if (size)
                {
                    temp = (T*) malloc(size * sizeof(T));
                    if (!temp) throw std::runtime_error("malloc failed!");
                }
                else
                    temp = nullptr;
    
                unsigned numToMove = std::min(m_size, size);
    
                for(unsigned i = 0; i < numToMove; ++i)
                    new (&temp[i]) T(std::move(m_data[i]));
    
                if (size < m_size)
                {
                    for(unsigned i = m_size; i-- > size; )
                        m_data[i].~T();
                }
                else
                {
                    for(unsigned i = m_size; i < size; ++i)
                        new(&temp[i]) T();
                }
    
                free(m_data);
                m_data = temp;
                m_size = size;
            }
        };
    
        void push(T t)
        {
            resize(m_size + 1);
            m_data[m_size - 1] = std::move(t);
        }
    
        myVec<T>& operator=(myVec<T> rhs)
        {
            std::swap(m_data, rhs.m_data);
            std::swap(m_size, rhs.m_size);
            return *this;
        }
    
        ...
    };
    

    That said, you should consider adding an m_capacity member to your class so you can reduce when you need to reallocate the array:

    template<class T>
    class myVec
    {
        T *m_data = nullptr;
        unsigned m_size = 0;
        unsigned m_capacity = 0;
    
    public:
        ...
    
        myVec() = default;
    
        myVec(const myVec<T> &src)
        {
            if (src.m_size)
            {
                reserve(src.m_size);
    
                for(unsigned i = 0; i < src.m_size; ++i)
                    new (&m_data[i]) T(src.m_data[i]);
    
                m_size = src.m_size;
            }
        }
    
        myVec(myVec<T> &&src)
        {
            std::swap(m_data, src.m_data);
            std::swap(m_size, src.m_size);
            std::swap(m_capacity, src.m_capacity);
        }
    
        ~myVec()
        {
            resize(0);
        }
    
        void reserve(unsigned capacity)
        {
            if (capacity > m_capacity)
            {
                T *temp = (T*) malloc(capacity * sizeof(T));
                if (!temp) throw std::runtime_error("malloc failed!");
    
                for(unsigned i = 0; i < m_size; ++i)
                    new (&temp[i]) T(std::move(m_data[i]));
    
                free(m_data);
                m_data = temp;
                m_capacity = capacity;
            }
        };
    
        void resize(unsigned size)
        {
            if (m_size != size)
            {
                if (size < m_size)
                {
                    for(unsigned i = m_size; i-- > size; )
                        m_data[i].~T();
                }
                else
                {
                    reserve(size);
                    for(unsigned i = m_size; i < size; ++i)
                        new(&m_data[i]) T();
                }
    
                m_size = size;
            }
        };
    
        void push(T t)
        {
            if (m_size == m_capacity) reserve(m_size > 0 ? m_size * 1.5 : 4);
            new(&m_data[m_size]) T(std::move(t));
            ++m_size;
        }
    
        myVec<T>& operator=(myVec<T> rhs)
        {
            std::swap(m_data, rhs.m_data);
            std::swap(m_size, rhs.m_size);
            std::swap(m_capacity, rhs.m_capacity);
            return *this;
        }
    
        ...
    };