Skip to content

Dynamic Array

Here is the code that I worked on with the help of Copilot.

Dynamic Array Header

This header file contains the struct and template functions that allow us to work with the dynamic array. I saved this in a file named dynamic-array.hpp.

#ifndef DYNAMIC_ARRAY_HEADER
#define DYNAMIC_ARRAY_HEADER
#include <cstdlib>
#include <new>
/**
* @brief A dynamic array struct that contains the size, capacity,
* and data pointer used to implement this dynamic structure.
*
* @tparam T the type of data to store in the dynamic array
* @field data a pointer to the data in the dynamic array on the heap
* @field size the number of elements used in the dynamic array
* @field capacity the number of elements the dynamic array can hold
*/
template<typename T>
struct dynamic_array
{
T *data;
unsigned int size;
unsigned int capacity;
};
/**
* @brief Create a new dynamic array with the indicated initial capacity.
*
* @tparam T the type of data the array will store
* @param capacity its initial capacity, with a default value of 50
* @return dynamic_array<T>* a pointer to the new dynamic array
*/
template<typename T>
dynamic_array<T> *new_dynamic_array(unsigned int capacity = 50)
{
dynamic_array<T> *result = (dynamic_array<T>*)malloc(sizeof(dynamic_array<T>));
new(result) dynamic_array<T>();
// Make sure result was allocated
if (result == nullptr)
{
return result;
}
result->data = (T*)malloc(sizeof(T) * capacity);
for (int i = 0; i < capacity; i++)
{
// Call constructor to initialise each of the 10 elements
new(&result->data[i]) T();
}
result->size = 0;
// Make sure that data was allocated, if not set capacity to 0
if(result->data == nullptr)
{
result->capacity = 0;
}
else
{
result->capacity = capacity;
}
return result;
}
/**
* @brief Free the memory allocated to the dynamic array. Once freed
* the data in the array will no longer be accessible.
*
* @tparam T the data type of the dynamic array
* @param array a pointer to the dynamic array to free
*/
template<typename T>
void delete_dynamic_array(dynamic_array<T> *array)
{
// Ensure that the array is allocated
if (!array) return;
// Clear to ensure we remove any data from memory before freeing it
array->size = 0;
// Call destructors on all elements
for (int i = 0; i < array->capacity; i++)
{
array->data[i].~T();
}
array->capacity = 0;
// Free the data and the array itself
free(array->data);
// Ensure we don't have a dangling pointer
array->data = nullptr;
// Free the dynamic array itself
free(array);
}
/**
* @brief Resize the capacity of the dynamic array.
*
* If the new capacity is smaller than the current size, the size will be updated to match the new capacity.
*
* @tparam T the type of data in the dynamic array
* @param array the dynamic array to grow
* @param new_capacity the new capacity of the dynamic array
* @returns true if this succeeded, or false if it could not reallocate memory
*/
template<typename T>
bool resize(dynamic_array<T> *array, unsigned int new_capacity)
{
// Ensure that the array is allocated
if (!array) return false;
// Call destructors if we are reducing size
for(int i = array->capacity - 1; i >= (int)new_capacity; i--)
{
array->data[i].~T();
}
// Resize the data in the array
T* new_data = (T*)realloc(array->data, sizeof(T) * new_capacity);
// Check if the allocation failed
if (new_data == nullptr)
{
// We failed to allocate memory, so we can't resize the array
return false;
}
// Call constructors if we increased size
for(int i = array->capacity; i < new_capacity; i++)
{
new(&array->data[i]) T();
}
// Update the array's data and capacity
array->data = new_data;
array->capacity = new_capacity;
// Update the size if the new capacity is smaller than the current size
if (new_capacity < array->size)
{
array->size = new_capacity;
}
return true;
}
/**
* @brief Add an element to the end of the dynamic array
*
* @tparam T the type of data in the dynamic array
* @param value the value to add to the end of the dynamic array
* @param array the dynamic array to add the value to
*/
template<typename T>
bool add(dynamic_array<T> *array, T value)
{
// Ensure that the array is allocated
if (!array) return false;
// Check if we need to resize the array, and if we failed to resize the array
// We double the capacity and add 1 to address issues where capacity is 0 initially
if (array->size >= array->capacity && !resize(array, array->capacity * 2 + 1))
{
// We didn't have space, and we failed to resize the array!
return false;
}
// Add the value to the end of the array
array->data[array->size] = value;
array->size++;
return true;
}
/**
* @brief read and return the value of the indicated element from the dynamic array.
*
* If the index is out of bounds, the function will return the indicated default value.
*
* @tparam T the type of data in the dynamic array
* @param array the dynamic array to remove the element from
* @param index the index of the element to remove
* @param default_value the value to return if the index is out of bounds
*/
template<typename T>
T get(const dynamic_array<T> *array, unsigned int index, T default_value)
{
// Check if the index is out of bounds
if (!array || index >= array->size)
{
// The index is out of bounds, so return the default value
return default_value;
}
return array->data[index];
}
/**
* @brief set the value of the indicated element from the dynamic array.
*
* If the index is out of bounds, the function will do nothing and return false.
*
* @tparam T the type of data in the dynamic array
* @param array the dynamic array to set the element in
* @param index the index of the element to change
* @param value the value to set the element to
* @returns true when the value is set, or false if this failed
*/
template<typename T>
bool set(dynamic_array<T> *array, unsigned int index, T value)
{
// Check if the index is out of bounds
if (!array || index >= array->size)
{
// The index is out of bounds, so do nothing
return false;
}
array->data[index] = value;
return true;
}
#endif

A test program

My test program was as follows:

#include <cstdio>
#include "dynamic-array.hpp"
int main()
{
// Create a dynamic array of int
// and initialise it to a new dynamic array of 10 elements
dynamic_array<int> *array = new_dynamic_array<int>(10);
// Print the size and capacity of the array
printf("size: %d, capacity: %d\n", array->size, array->capacity);
// Add 15 values to the array
for(int i = 0; i < 15; i++)
{
add(array, i);
}
// Reprint the size and capacity of the array after adding
printf("size: %d, capacity: %d\n", array->size, array->capacity);
// Print and update the values in the array, using the get and set functions
for(int i = 0; i < array->size; i++)
{
printf("array[%d] = %d\n", i, get(array, i, -1));
set(array, i, i * 2);
}
// Attempt to access an element out of bounds using get
printf("array[99] = %d\n", get(array, 99, -1));
// Attempt to access an element out of bounds using set
if (set(array, 99, 99))
{
printf("array[99] = %d\n", get(array, 99, -1));
}
else
{
printf("Failed to set array[99]\n");
}
printf("Before resize - size: %d, capacity: %d\n", array->size, array->capacity);
// Change the size of the array
resize(array, 5);
printf("After resize - size: %d, capacity: %d\n", array->size, array->capacity);
for(int i = 0; i < 20; i++)
{
printf("array[%d] = %d\n", i, get(array, i, -1));
}
// Free the array and ensure we do not have a dangling pointer
delete_dynamic_array(array);
array = nullptr;
return 0;
}