'Copy Constructor vs Assignment Operator Overload in the dynamic array class in C++

We made a dynamic array class during lecture and the instructor copy-pasted the copy_constructor code on the assignment_operator_overload. But don't we need to delete the existing dynamic array first for the assignment operator ? (Please refer void "operator=(DynamicArray const &d){}" function at the 29th line)

#include <iostream>
using namespace std;

class DynamicArray {
    int *data;
    int nextIndex;
    int capacity;           // total size

    public :

    DynamicArray() {
        data = new int[5];
        nextIndex = 0;
        capacity = 5;
    }

    DynamicArray(DynamicArray const &d) {
        //this -> data = d.data;        // Shallow copy
    
        // Deep copy
        this -> data = new int[d.capacity];
        for(int i = 0; i < d.nextIndex; i++) {
            this -> data[i] = d.data[i];
        }
        this -> nextIndex = d.nextIndex;
        this -> capacity = d.capacity;
    }

    void operator=(DynamicArray const &d) {
        // I think here we should add // delete []this->data;
        this -> data = new int[d.capacity];
        for(int i = 0; i < d.nextIndex; i++) {
            this -> data[i] = d.data[i];
        }
        this -> nextIndex = d.nextIndex;
        this -> capacity = d.capacity;
    }


    void add(int element) {
        if(nextIndex == capacity) {
            int *newData = new int[2 * capacity];
            for(int i = 0; i < capacity; i++) {
                newData[i] = data[i];
            }
            delete [] data;
            data = newData;
            capacity *= 2;
        }
        data[nextIndex] = element;
        nextIndex++;
    }

    //Here add can only modify the previous, present and just_next index.
    void add(int i, int element) {
            if(i < nextIndex) {
                data[i] = element;
            }
            else if(i == nextIndex) {
                add(element);
            }
            else {
                return;
            }
    }

};


int main() {
    DynamicArray d1;

    d1.add(10);
    d1.add(20);
    d1.add(30);
    d1.add(40);
    d1.add(50);
    d1.add(60);

    d1.add(9, 100);
    d1.print();

    DynamicArray d2(d1);        // Copy constructor

    DynamicArray d3 = d1;      // Copy constructor

    d3 = d1;                   // Assignment operator
}


Solution 1:[1]

Right, you do. But before that you need to check for the self assignment before deleting. A step forward would be to use copy and swap idiom. Its more clean, less error prone and gives better exception safety guaranties.

Solution 2:[2]

Indeed you're right. In the implementation of the class little care was taken for preventing a memory leak as clearly indicated by the lack of an destructor. As for deleting the old array first: you may not want to do this, if you want a strong exception guarantee, since new int[] could throw an exception and a strong exception guarantee requires the old data to still be available, if the memory allocation throws.

void operator=(DynamicArray const &d) {
    
    int* newData = new int[d.capacity]; // may throw; don't delete old data yet
    delete[] data;// the rest of the function doesn't throw -> free the memory now
    data = newData;

    for(int i = 0; i < d.nextIndex; i++) {
        data[i] = d.data[i];
    }
    nextIndex = d.nextIndex;
    capacity = d.capacity;
}

// also need to free the memory at the end of the object lifetime
~DynamicArray()
{
    delete[] data;
}

You may btw want to use std::unique_ptr to take care of managing the memory for you; this gives you a strong exception guarantee without the need of explicitly delete and writing a destructor. Furthermore you could simply default move constructor&move assignment operators this way:

class DynamicArray {
    std::unique_ptr<int[]> data;
    size_t nextIndex;
    size_t capacity;           // total size

public:
    DynamicArray(size_t initialCapacity = 5) // we can allow the user to optionally specify the initial capacity with little effort
        : data(std::make_unique<int[]>(initialCapacity)),
        nextIndex(0),
        capacity(initialCapacity)
    {
    }

    DynamicArray(DynamicArray const &d)
        : data(std::make_unique<int[]>(d.capacity)),
        nextIndex(d.nextIndex),
        capacity(d.capacity)
    {
        std::copy_n(d.data.get(), nextIndex, data.get()); // we can use the algorithms lib to write the loop as a single function call
    }

    DynamicArray& operator=(DynamicArray const &d) { // standard signature returns a reference to self
        data = std::make_unique<int[]>(d.capacity);
        capacity = d.capacity;
        nextIndex = d.nextIndex;
        std::copy_n(d.data.get(), nextIndex, data.get());
        return *this;
    }

    // we get the following 2 "for free", since all member variables are move constructible/assignable
    DynamicArray(DynamicArray&&) = default;
    DynamicArray& operator=(DynamicArray&&) = default;

    void add(int element) {
        ...
    }

    //Here add can only modify the previous, present and just_next index.
    void add(size_t i, int element) {
        ...
    }
};

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Eduard Rostomyan
Solution 2