I implemented a simplified version of a C++ std::vector
/ArrayList
learning from this link. From it, I tried to write an ArrayList
class rather than a Vector. In my ArrayList
implementation, I didn't add iterators, operator[]
, emplace and a lot of other stuff because my main focus is to practice the following aspects:
- My
ArrayList
class guarantees strong exception safety, using the copy and swap idiom - The container still works if
T
passed in is not default-constructible - Correctness of the implementation of the Rule of Five
- General correctness and efficiency (i.e: No memory leaks, dangling pointers, etc.)
Because of those 4 areas I'd like to focus on practicing, I only had extra add and remove method in the class.
Please focus on those 4 areas I mentioned for reviewing my code, and any other important style choice errors or any other problems that I missed.
using namespace std;
template <typename T>
class ArrayList
{
public:
ArrayList();
ArrayList(int size);
ArrayList(const ArrayList<T>& other);
ArrayList(ArrayList&& other);
~ArrayList();
ArrayList<T>& operator= (const ArrayList<T>& other);
ArrayList<T>& operator= (ArrayList&& other);
void add(const T& item);
void add(T&& item);
void remove(int index);
friend void swap(ArrayList& A, ArrayList& B)
{
using std::swap;
swap(A.actualSize, B.actualSize);
swap(A.allocatedSize, B.allocatedSize);
swap(A.arr, B.arr);
}
private:
T* arr;
size_t allocatedSize;
size_t actualSize;
void resize();
void addInternal(const T& item);
void addInternalMove(T&& item);
};
template <typename T>
ArrayList<T>::ArrayList()
{
arr = static_cast<T*>(::operator new(sizeof(T)*100));
actualSize = 0;
allocatedSize = 100;
}
template <typename T>
ArrayList<T>::ArrayList(int size)
{
if(size < 0)
throw;
arr = static_cast<T*>(::operator new(sizeof(T)*size));
actualSize = 0;
allocatedSize = size;
}
template <typename T>
ArrayList<T>::ArrayList(const ArrayList<T>& other)
{
arr = static_cast<T*>(::operator new(sizeof(T)*size));
allocatedSize = other.allocatedSize;
actualSize = actualSize;
for(size_t i = 0; i<other.actualSize; i++)
add(other.arr[i]);
}
template <typename T>
ArrayList<T>::ArrayList(ArrayList&& other)
{
swap(*this, other);
}
template <typename T>
ArrayList<T>::~ArrayList()
{
for(size_t i = 0; i<actualSize; i++)
{
arr[i].~T();
}::operator delete(arr);
}
template <typename T>
ArrayList& ArrayList<T>::operator =(const ArrayList<T>& other)
{
ArrayList tmp(other);
swap(*this, tmp);
return *this;
}
template <typename T>
ArrayList& ArrayList<T>::operator =(ArrayList<T>&& other)
{
swap(*this, other);
}
template <typename T>
void ArrayList<T>::resize()
{
allocatedSize *= 2;
ArrayList tmp(allocatedSize);
for(size_t i=0; i<actualSize; i++)
addInternal(arr[i]);
swap(*this, tmp);
}
template <typename T>
void ArrayList<T>::add(const T& item)
{
if(actualSize >= allocatedSize)
resize();
addInternal(item);
}
template <typename T>
void ArrayList<T>::add(T&& item)
{
if(actualSize >= allocatedSize)
resize();
addInternalMove(move(item));
}
template <typename T>
void ArrayList<T>::addInternal(const T& item)
{
new (arr+actualSize)T(item);
actualSize++;
}
template <typename T>
void ArrayList<T>::addInternalMove(T&& item)
{
new (arr+actualSize)T(move(item));
actualSize++;
}
template <typename T>
void ArrayList<T>::remove(int index)
{
if(index<0 || index>actualSize - 1)
throw;
for(size_t i=0; i<actualSize; i++)
{
if(index==i)
{
arr[i].~T();
arr[i+1] = arr[i];
}
else if(index > i)
arr[i+1] = arr[i];
}
}
throw;
). – Matthieu M. yesterdaythrow;
is legal syntax that does compile. Though testing would have a shown the error as it is not semantically correct usage. – Loki Astari yesterdaythrow e;
would be sufficient if only this did not copy (or even move)e
. – Matthieu M. yesterday