C++ Template-Based ArrayList Implementation As part of my learning C++, I have created an ArrayList class. It shares some of its interface with the Java ArrayList, but it by no means implements anywhere close to the full set of functions an ArrayList or vector supports.

Here is my ArrayList.h:

#ifndef ARRAYLIST_ARRAYLIST_H
#define ARRAYLIST_ARRAYLIST_H

#include &lt;initializer_list&gt;

template&lt;typename T&gt;
class ArrayList{
public:
    //lifecycle
    ArrayList() : m_array(new T[0]) {}
    ArrayList(std::initializer_list&lt;T&gt; it);
    ~ArrayList() { delete [] m_array; }

    //copy control
    ArrayList(const ArrayList&lt;T&gt; &amp;arraylist) { *this = arraylist; }
    ArrayList(const ArrayList&lt;T&gt; &amp;&amp;arraylist) { *this = arraylist; }
    ArrayList&amp; operator=(const ArrayList&lt;T&gt; &amp;arrayList);
    ArrayList&amp; operator=(ArrayList&lt;T&gt; &amp;&amp;arrayList);

    //element access
    T&amp; operator[](unsigned i) { return m_array[i]; }
    void add(const T &amp;t) { resizeIfFull(); m_array[m_frontendLength++] = t; }
    void add(const T &amp;&amp;t) { resizeIfFull(); m_array[m_frontendLength++] = std::move(t); }

    //utilities
    unsigned length() { return m_frontendLength; }

private:
    T *m_array = nullptr;
    unsigned m_backendLength, m_frontendLength;

    void resizeIfFull();
};

#include "ArrayList.tpp"

#endif //ARRAYLIST_ARRAYLIST_H


Here is my ArrayList.tpp:

#ifndef ARRAYLIST_TPP
#define ARRAYLIST_TPP

#include "ArrayList.h"

#include &lt;utility&gt;

template&lt;typename T&gt;
ArrayList&lt;T&gt;::ArrayList(std::initializer_list&lt;T&gt; ls){
    m_array = new T[ls.size()];

    m_backendLength = ls.size();
    m_frontendLength = ls.size();

    unsigned i = 0;
    for(auto it=ls.begin();it!=ls.end();++it){
        m_array[i++] = *it;
    }
}

template&lt;typename T&gt;
ArrayList&lt;T&gt;&amp; ArrayList&lt;T&gt;::operator=(const ArrayList&lt;T&gt; &amp;arrayList){
  m_backendLength = arrayList.m_backendLength;
  m_frontendLength = arrayList.m_frontendLength;

  delete [] m_array;

  m_array = new T[m_backendLength];

  for(unsigned i=0;i&lt;m_frontendLength;++i){
    m_array[i] = arrayList.m_array[i];
  }

  return *this;
}

template&lt;typename T&gt;
ArrayList&lt;T&gt;&amp; ArrayList&lt;T&gt;::operator=(ArrayList&lt;T&gt; &amp;&amp;arrayList){
    m_backendLength = arrayList.m_backendLength;
    m_frontendLength = arrayList.m_frontendLength;  
    m_array = arrayList.m_array;
    arrayList.m_array = nullptr;
}

template&lt;typename T&gt;
void ArrayList&lt;T&gt;::resizeIfFull(){
  if(m_backendLength == m_frontendLength){
    m_backendLength *= 2;
    T *newArray = new T[m_backendLength];

    for(unsigned i=0;i&lt;m_frontendLength;++i){
      newArray[i] = std::move(m_array[i]);
    }

    delete [] m_array;
    m_array = newArray;
  }
}

#endif


I am hoping to get some information on things I did badly, and things I did well. I am particularly interested in: compliance with C++ convention, writing good code with regard to organization (ie. OOP, SOLID, etc.), and efficiency. I know Java much better, and I would like to know if I am bringing Java stuff in here that doesn't belong in C++.
Memory Allocation

This is a house built on sand, so to speak. Quite a bit of the house itself looks perfectly fine, but the foundation is completely rotten, so everything else is liable to fall over without notice.

The specific problem is using new [] to allocate storage. The problem with this is that it actually creates objects in the memory you allocate. If you're storing objects that are expensive to construct, this can impose unnecessary overhead. If you're storing objects that can't be default constructed...well, you're not storing those, because it just won't work.

The right (at least IMO) way to do this is to use the same strategy as vector does (at least by default). You allocate raw memory using operator new, and then construct objects in that raw memory using placement new. This means the unused part of the memory really is unused--i.e., it's just raw memory that doesn't contain any actual objects.

Naming

frontEndLength and backEndLength don't seem particularly descriptive, at least to me. Apparently they really mean something like "amount allocated" and "amount in use". If so, I'd change the names to reflect that more directly.

Exception Safety

Your assignment operator is written a lot like most of us typically wrote them in the '90s or so, before (for one example) Exceptional C++ was published. It has a number of problems. For example, if it ends up doing a self assignment:

ArrayList a;

// code that puts something into `a` goes here

a = a;


The self-assignment will often be somewhat indirect, such as passing the same collection as both the source and the destination of some function. Regardless of how it happens, it leads to a serious problem. It obviously shouldn't really do anything--but with your code, it destroys a. Nearly the first thing your operator= does is delete [] m_array;, so if the source and the destination are the same, you've just destroyed your source, and after that, everything breaks badly.

Quite a few people noticed this problem, so at one time it was pretty typical to see code that did something like this:

T &amp;operator=(T const &amp;r) { 
    if (this == &amp;r) // if source and dest are the same, do nothing
        return;
    // rest of the assignment goes here.
}


This fixes that particular problem, but leaves a more pernicious one. You'd typically like your assignment to either succeed entirely or fail entirely--but as it stands now, it might half-succeed, so you've lost the old contents, but gotten only part of the new contents. Worse, you aren't sure where in the copying process it was when the exception was thrown, so you don't have any way of knowing how much of the content is really valid.

One simple way of dealing with all of these is called the copy and swap idiom. It consists of creating a temporary copy of the source. Then when that's completed, you swap the destination with the temporary.

template&lt;typename T&gt;
ArrayList&lt;T&gt;&amp; ArrayList&lt;T&gt;::operator=(const ArrayList&lt;T&gt; &amp;arrayList){

    ArrayList&lt;T&gt; temp(arrayList);
    std::swap(*this, temp);
    return *this;    
}


You can also shorten this a tiny bit by using the parameter as the temporary object:

template&lt;typename T&gt;
ArrayList&lt;T&gt;&amp; ArrayList&lt;T&gt;::operator=(ArrayList&lt;T&gt; arrayList){

    std::swap(*this, arrayList);
    return *this;    
}


Either way, if an exception is thrown during the copying process, the temporary is never created, and the swap never happens. The original stays exactly as it was. We normally expect (and typically enforce) that the swap can't throw any exceptions (pretty trivial in this case, because all it's doing is copying pointers and ints around).

Once that's done, the temporary gets destroyed, releasing the memory that was previously used by the destination object.

Of course, there are other ways to write an exception-safe assignment operator--this is just one that's simple, well-known and idiomatic.

Expansion factor

Right now, when you run out of space, you multiply the size by two. That's a fairly reasonable approach, but there's a theoretical argument to be made in favor of making the expansion factor less than or equal to the golden mean. I wrote a little more about it in an earlier reply.