Fading Coder

One Final Commit for the Last Sprint

Home > Notes > Content

Implementing a Generic Array Class in C++ with Correct Copy Semantics and Common Operations

Notes 1

Functinoal Requirements

  • Supports storage of both primitive built-in data types and user-defined custom types
  • All underlying element storage is allocated on the heap
  • Constructor accepts a integer parameter to define the initial array capacity
  • Implements custom copy constructor and copy assignment operator to eliminate shallow copy-related heap double-free erors
  • Provides append (tail insert) and tail delete operations to modify array contents
  • Supports element access via subscript operator, including assignment to indexed positions
  • Exposes methods to query current element count and total array capacity

GenericArray Header Implementation (GenericArray.hpp)

#pragma once
#include <iostream>
using namespace std;

template <typename ElemType>
class GenericArray {
public:
    // Parameterized constructor: takes initial capacity
    GenericArray(int init_capacity) {
        cout << "GenericArray parameterized constructor invoked" << endl;
        max_capacity = init_capacity;
        current_length = 0;
        data_ptr = new ElemType[max_capacity];
    }

    // Copy constructor for deep copy
    GenericArray(const GenericArray& other) {
        cout << "GenericArray copy constructor invoked" << endl;
        max_capacity = other.max_capacity;
        current_length = other.current_length;
        data_ptr = new ElemType[max_capacity];
        for (int i = 0; i < current_length; i++) {
            // If ElemType is a custom type with pointer members, its = operator must be overloaded for deep copy
            data_ptr[i] = other.data_ptr[i];
        }
    }

    // Copy assignment operator, supports chained assignment
    GenericArray& operator=(const GenericArray& other) {
        cout << "GenericArray copy assignment operator invoked" << endl;
        // Free existing heap memory first
        if (data_ptr != nullptr) {
            delete[] data_ptr;
            data_ptr = nullptr;
            max_capacity = 0;
            current_length = 0;
        }
        // Deep copy content from source array
        max_capacity = other.max_capacity;
        current_length = other.current_length;
        data_ptr = new ElemType[max_capacity];
        for (int i = 0; i < current_length; i++) {
            data_ptr[i] = other.data_ptr[i];
        }
        return *this;
    }

    // Append element to the end of the array
    void append(const ElemType& value) {
        if (current_length >= max_capacity) return;
        data_ptr[current_length] = value;
        current_length++;
    }

    // Remove the last element from the array (logical deletion)
    void remove_last() {
        if (current_length == 0) return;
        current_length--;
    }

    // Subscript operator overload, returns reference to support left-value assignment
    ElemType& operator[](int index) {
        return data_ptr[index];
    }

    // Get total capacity of the array
    int get_max_capacity() {
        return max_capacity;
    }

    // Get current number of stored elements
    int get_current_length() {
        return current_length;
    }

    // Destructor to free heap memory
    ~GenericArray() {
        cout << "GenericArray destructor invoked" << endl;
        if (data_ptr != nullptr) {
            delete[] data_ptr;
            data_ptr = nullptr;
        }
    }

private:
    ElemType* data_ptr;
    int max_capacity;
    int current_length;
};

Test Execution Code (main.cpp)

#include <iostream>
#include "GenericArray.hpp"
#include <string>
using namespace std;

// Print function for integer arrays, pass-by-value triggers copy constructor
void print_integer_array(GenericArray<int> arr) {
    for (int i = 0; i < arr.get_current_length(); i++) {
        cout << arr[i] << endl;
    }
}

// Test case for built-in integer type
void test_builtin_types() {
    GenericArray<int> arr1(5);
    for (int i = 0; i < 5; i++) {
        arr1.append(i);
    }
    cout << "arr1 content output:" << endl;
    print_integer_array(arr1);
    cout << "arr1 capacity: " << arr1.get_max_capacity() << endl;
    cout << "arr1 current element count: " << arr1.get_current_length() << endl;

    GenericArray<int> arr2(arr1);
    cout << "arr2 content output:" << endl;
    print_integer_array(arr2);

    arr2.remove_last();
    cout << "After tail deletion on arr2:" << endl;
    cout << "arr2 capacity: " << arr2.get_max_capacity() << endl;
    cout << "arr2 current element count: " << arr2.get_current_length() << endl;

    GenericArray<int> arr3(100);
    arr3 = arr1;
}

int main() {
    test_builtin_types();
    cout << "Execution completed" << endl;
    system("pause");
    return 0;
}

All GenericArray instances arr1, arr2, arr3 are declared as local stack variables, so they are destroyed in reverse order of creation: arr3 is destructed first, followed by arr2, then arr1.

Related Articles

Designing Alertmanager Templates for Prometheus Notifications

How to craft Alertmanager templates to format alert messages, improving clarity and presentation. Alertmanager uses Go’s text/template engine with additional helper functions. Alerting rules referenc...

Deploying a Maven Web Application to Tomcat 9 Using the Tomcat Manager

Tomcat 9 does not provide a dedicated Maven plugin. The Tomcat Manager interface, however, is backward-compatible, so the Tomcat 7 Maven Plugin can be used to deploy to Tomcat 9. This guide shows two...

Skipping Errors in MySQL Asynchronous Replication

When a replica halts because the SQL thread encounters an error, you can resume replication by skipping the problematic event(s). Two common approaches are available. Methods to Skip Errors 1) Skip a...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.