Implementing a Generic Array Class in C++ with Correct Copy Semantics and Common Operations
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.