Skip to content

Assignment Operator C++ Dynamic Arrays

Dynamic Allocation and Copy Constructors

Dr. Lawlor, CS 202, CS, UAF

(See Gaddis Chapter 9.8 for more info on new and delete, and Chapter 14.4 and 14.5 for info on copy constructors.)

So the basic dynamic allocation syntax is pretty simple: you call "new" to get a pointer, use the space, and finally call "delete" to give up that space.

The typical use is to allocate arrays at runtime:
  • "someptr=new int[n];" allocates space for an array of n integers, and returns you a pointer to the first integer.  Unlike just declaring an array, dynamic allocation with new allows "n" to be a runtime variable.
  • "delete[] someptr;" frees the array.  The "[]" looks like some sort of weird typo, but it indicates you're deleting the whole array.
You can also allocate a single object, although this isn't nearly as common:
  • "someptr=new int;" allocates space for one single integer, and returns you a pointer to the integer.
  • "delete someptr;" frees up the space again.  You really need to free any space you allocate, especially in a long-running program.
Note that for both array and individual allocations, you get a bare pointer back.  This is annoying, because it means you MUST remember whether you've got an array or an individual object, so you can access the space properly.

Here's an example of array allocation:
int foo(void) {
int n=5; cin>>n; // how many integers?
int *ptr=new int[n]; // make an array
for (int i=0;i<n;i++) // write data in
ptr[i]=10*i;
for (int i=0;i<n;i++) // read data back out
cout<<"ptr["<<i<<"]="<<ptr[i]<<"\n";
delete[] ptr; // free allocated space
return 0;
}

(Try this in NetRun now!)

Here's an example of allocating a single integer:
int foo(void) {
int *ptr=new int; // make a pointer
*ptr=7; // write data in
int retval=*ptr; // read data back out
delete ptr; // free allocated space
return retval;
}

(Try this in NetRun now!)

Unfortunately, there are a bunch of things you MUST do with dynamic allocations, and the compiler often can't detect any of these!
  • Setup: You MUST initialize your pointers before using them.  Luckily, the compiler can usually warn you about uninitialized pointers, and uninitialized pointers usually crash immediately.
  • Embezzlement: You MUST access your pointers within the array bounds.  If you asked for [10] elements, just reading from [13] might cause you to crash, or you might read garbage.  Writing is even worse--if you don't crash, you'll overwrite some other part of the program, which will then crash at some unknown later date.
  • Amnesia: You MUST remember to call delete.  If you don't call delete, memory marked as being in use will build up in your program (a "memory leak"), until the machine runs out of memory or your program exits.
  • Doppleganger: You MUST call the correct version of delete: "delete[]" for arrays, and plain "delete" for individual pointers.  Unfortunately, the compiler doesn't detect when you use the wrong delete; it just silently screws up memory so your program crashes sometime in the distant future.
  • Overkill: You MUST not call delete more than once on the same pointer.  You can protect against this by zeroing out your pointers after deleting them (like "delete[] someptr; someptr=0;").  This "double delete bug" is actually common enough that some machines' "delete" has explicit code to check for it.  But it's really hard to detect if you allocate some space and delete it, then somebody else allocates the same space and you then delete their space!
  • Zombies: You MUST not access a pointer after you've deleted it.  Unfortunately, these "living dead" pointers usually work, and some of your data is often still there, but of course that space could be reused by anybody else at any time, resulting in hideous weird crashes.
Because bare pointers are so error-prone, it's common to wrap them in a nicer class interface.

Building a "Wrapper Class" for Nicer Pointers

Here's a simple class that puts a slightly nicer interface onto an array of integers.  The constructor calls "new", there's an overloaded bracket operator to check accesses to the elements of the array, and the destructor calls "delete".
// A nice wrapper around a dynamically allocated array of integers.
class SmartArray {
private:
int *data;
int length;
public:
SmartArray(int len) {
data=new int[len]; // allocate space
length=len;
}
int &operator[](int index) {
if (index<0 || index>=length) { // bounds-check the array index
cout<<"Index "<<index<<" out of bounds!\n";
exit(1);
}
return data[index];
}
~SmartArray() {
delete[] data; // free space
data=0; /* zero out our pointer, to indicate we're gone */
length=-1;
}
};

int foo(void) {
int n=5; cin>>n;
SmartArray arr(n); // array has n integers
for (int i=0;i<n;i++) arr[i]=10*i;
for (int i=0;i<n;i++) cout<<"arr["<<i<<"] = "<<arr[i]<<"\n";
return 0; // destructor automatically deallocates array!
}

(Try this in NetRun now!)

The nice part about this is:
  • The array remembers how long it is, and checks to make sure each access is inside this range.
  • The destructor will be called automatically by the compiler, so you can't possibly forget it.
This is an example of a standard C++ trick called Resource Aquisition Is Initialization (RAII): the constructor allocates, and the destructor deallocates.

Copy Constructor and Assignment Operator

One problem with the above "SmartArray" class is that the C++ compiler automatically (and stupidly) allows people to make a simple shallow copy of a SmartArray object.  Unfortunately, the two copies share the same pointer, so the pointer will be deleted twice!  For example:
class SmartArray {
private:
int *data;
int length;
public:
SmartArray(int len) {
data=new int[len]; // allocate space
length=len;
cout<<"Running SmartArray constructor: data="<<data<<"\n";
}
~SmartArray() {
cout<<"Running SmartArray destructor: data="<<data<<"\n";
delete[] data; // free space
data=0; /* zero out our pointer, to indicate we're gone */
length=-1;
}
};

int foo(void) {
SmartArray arr(2); // array has 2 integers
if (2==2) { // make a copy of the array
cout<<"Making another SmartArray...\n";
SmartArray evilArr=arr; // compiler-generated assignment operator!
// evilArr's destructor will delete arr's pointer!
}
cout<<"Returning from foo...\n";
return 0;
// uh oh! arr's destructor calls delete *again*!
}

(Try this in NetRun now!)

There are two different ways the compiler might make a copy of "arr":
  • "SmartArray somebody(arr);" makes a new SmartArray as a copy of "arr"'s values.  This is called a "copy constructor".
  • "somebody = arr;" overwrites an existing SmartArray with an assignment of "arr"'s values.  This is called an "assignment operator".
Unfortunately, the compiler's automatically generated copy constructor and assignment operator WILL cause big problems in any class with pointers.  And people tend to copy and assign classes a lot, for example to pass them into a function or return them.  So you often need to write your own copy constructor and assignment operator.  This is known as the "Law of the Big Three": if you've got any one of a destructor, copy constructor, or assignment operator, then you probably need all three of them.

One weird trick is to declare a private copy constructor and assignment operator.  That way nobody can call them.  If nobody calls them, you don't even need a body for these functions:
class SmartArray {
private:
int *data;
int length;
// You can't copy or assign SmartArrays (yet)
SmartArray(const SmartArray &from); // copy constructor
SmartArray& operator=(const SmartArray &from); // assignment operator
public:
... rest of stuff ...
};

(Try this in NetRun now!)

This makes any attempt to copy or assign SmartArrays a compile error, which is way better than getting a horrible crash at runtime. 

If you're really ambitious, you can write the copy constructor and assignment operator to do the right thing, making a new copy of the class's data.  This is trickier than it sounds, especially if speed is important, or for the case where some joker assigns an instance to itself (self assignment, like "x=x;").   The question is, to implement "a=b;" for arrays, do you just copy the pointers, a "shallow copy" like the compiler does?  If so, how do you keep track of when to delete the array data?  (There are some cool implementations like "reference counting" and "garbage collection" out there.)  Or do you just copy all the data in the array?  This is called a "deep copy", which uses more time and space, but is a little easier to write.

Here, I've written a deep copy implementation, and added some new little utility methods called "alloc" and "free" to do the data allocation.
/*
A nice wrapper around a dynamically allocated array of integers.
*/
class SmartArray {
public:
SmartArray(int len) { alloc(len);} // ordinary constructor

SmartArray(const SmartArray &from) { // copy constructor
alloc(from.length);
for (int i=0;i<length;i++) data[i]=from[i];
}

SmartArray& operator=(const SmartArray &from) { // assignment operator
if (data==from.data) return *this; // self assignment!
free(); // throw away our old data
alloc(from.length);
for (int i=0;i<length;i++) data[i]=from[i];
return *this;
}

// Array indexing:
int &operator[](int index) { check(index); return data[index]; }

// Constant array indexing:
const int &operator[](int index) const { check(index); return data[index]; }

// Destructor
~SmartArray() { free(); }
private:
int *data;
int length;
void alloc(int len) {// allocate space for len bytes of data
data=new int[len];
length=len;
}
void free(void) {// deallocate data
delete[] data; // free space
data=0; /* zero out our pointer, to indicate we're really gone */
length=-1;
}
void check(int index) const {// bounds-check array index
if (index<0 || index>=length) { // bounds-check the array index
cout<<"Index "<<index<<" out of bounds!\n";
exit(1);
}
}
};

int foo(void) {
int n=5; cin>>n;
SmartArray arr(n); // array has n integers
for (int i=0;i<n;i++) arr[i]=10*i;
if (2==2) { // make a copy of the array
cout<<"Making another SmartArray...\n";
SmartArray evilArr=arr; // our own assignment operator (OK)
// evilArr's destructor will delete its own separate pointer
}
for (int i=0;i<n;i++) cout<<"arr["<<i<<"] = "<<arr[i]<<"\n";
cout<<"Returning from foo...\n";
return 0;
}

(Try this in NetRun now!)

Clearly, this is not something you want to write very often.  So you should re-use something like "SmartArray" rather than writing it from scratch each time!
abstract 

This article provides example of dynamic array implementation using C++ templates. It uses standard malloc/realloc memory allocation functions and simple "doubling size" resizing strategy. Our AeDynArray class interface resembles MFC standard CArray class, but uses only standard C libraries.

compatible 

  • Any modern C++ compiler
  • AeDynArray class interface is quite simple. There are two constructors(default constructor and copy constructor), assignment operator, overloaded index [] operator and following member functions:

    • Add(el item) — adds item to the end of array, resizes it twice its current capacity if there is no more space
    • GetSize() — returns current size of an array
    • SetSize(unsigned int size) — sets array size
    • Fill(int c) — fills all array's memory with a given integer
    • Clear() — removes all elemets from the array, resets memory alocation
    • Delete(unsigned int pos) — deletes specified element from the array

    AeDynArray class difinition is prefixed by the keyword template: el is a type parameter — it can be any type. Functions Add() and operator [] uses type el specified while instantiating a class.

      #include <cstdlib>   template<class el> class AeDynArray { public: AeDynArray(); // constructor AeDynArray(const AeDynArray &a); // copy constructor ~AeDynArray(); // distructor AeDynArray& operator = (const AeDynArray &a); // assignment operator   el& operator [](unsignedint index); // get array item void Add(const el &item); // Add item to the end of array   unsignedint GetSize(); // get size of array (elements)void SetSize(unsignedint newsize); // set size of array (elements)void Clear(); // clear arrayvoid Delete(unsignedint pos); // delete array item void* getptr(); // get void* pointer to array data   enum exception { MEMFAIL }; // exception enum   private: el *array; // pointer for array's memory unsignedint size; // size of array (elemets)unsignedint realsize; // actual size of allocated memory   conststaticint dyn_array_step = 128; // initial size of array memory (elements)conststaticint dyn_array_mult = 2; // multiplier (enlarge array memory // dyn_array_mult times )};   //////////////////////////////////////////////////////////////////////   template <class el> AeDynArray<el>::AeDynArray(){ realsize = dyn_array_step; // First, allocate step // for dyn_array_step items size = 0; array = (el *)malloc(realsize*sizeof(el));   if(array == NULL) throw MEMFAIL; }     template <class el> AeDynArray<el>::~AeDynArray(){if(array){ free(array); // Freeing memory array = NULL; }}     template <class el> AeDynArray<el>::AeDynArray(const AeDynArray &a){ array = (el *)malloc(sizeof(el)*a.realsize); if(array == NULL) throw MEMFAIL;   memcpy(array, a.array, sizeof(el)*a.realsize); // memcpy call -- coping memory contents realsize = a.realsize; size = a.size; }     template <class el> AeDynArray<el>& AeDynArray<el>::operator = (const AeDynArray &a){if(this == &a)// in case somebody tries assign array to itself return *this;   if(a.size == 0)// is other array is empty -- clear this array Clear();   SetSize(a.size); // set size   memcpy(array, a.array, sizeof(el)*a.size);   return *this; }   template <class el> unsignedint AeDynArray<el>::GetSize(){return size; // simply return size}     template <class el> void AeDynArray<el>::SetSize(unsignedint newsize){ size = newsize;   if(size != 0){// change array memory size // if new size is larger than current // or new size is less then half of the current if((size > realsize) || (size < realsize/2)){ realsize = size; array = (el *)realloc(array, sizeof(el)*size);   if(array == NULL) throw MEMFAIL; }}else Clear(); }   template <class el> void AeDynArray<el>::Delete(unsignedint pos){if(size == 1)// If array has only one element Clear(); // than we clear it, since it will be deleted else{// otherwise, shift array elements for(unsignedint i=pos; i<size-1; i++) array[i] = array[i+1];   // decrease array size size--; }}   template <class el> void AeDynArray<el>::Clear()// clear array memory { size = 0; array = (el *)realloc(array, sizeof(el)*dyn_array_step); // set initial memory size again realsize = dyn_array_step; }   template <class el> void* AeDynArray<el>::getptr(){return array; // return void* pointer }   template <class el> el& AeDynArray<el>::operator[](unsignedint index){return array[index]; // return array element }   template <class el> void AeDynArray<el>::Add(const el &item){ size++;   if(size > realsize){ realsize *= dyn_array_mult;   array = (el *)realloc(array, sizeof(el)*realsize);   if(array == NULL) throw MEMFAIL; }   array[size-1] = item; }    

    Since majority of C++ doesn't support separation of template classes, you should save all source code to a single .h file

    You may also download ready to use aedynarray.h and include it with your project.

    To illustrate class usage, here is a program which do various array opration on AeDynArray<int> (array of integers)

      #include "aedynarray.h"#include <iostream>   using namespace std;   // function for outputting array items void output_array(AeDynArray<int> &array){for(unsignedint i=0; i<array.GetSize(); i++)cout << array[i] << ", ";   cout << endl; }   int main(void){ AeDynArray<int> array;   // setting array size array.SetSize(15);   // filling array with pseudo-random values for(unsignedint i=0; i<15; i++) array[i] = rand() % 100;   // lets add some values using Add() array.Add(7); array.Add(94); array.Add(1);   // output all array items output_array(array);   // delete 1-st and last items array.Delete(0); array.Delete(array.GetSize() - 1);   // output all array items (again) output_array(array);   // let's sort all array items using extemly ineffective sortfor(unsignedint i=0; i<array.GetSize();)if(array[i] > array[i+1]){int x = array[i]; array[i] = array[i+1]; array[i+1] = x; i = 0; continue; }else i++;   output_array(array);   // create another array, based on first AeDynArray<int> array2(array); array2.Clear(); // clear it   // check multiple additionfor(int i=0; i<1000000; i++) array2.Add(rand());   // check assigment operator array = array2;   // output arraycout << "array 2 size " << array2.GetSize() << endl; cout << "array 1 size " << array.GetSize() << endl;   // check random elementint testel = rand() % 1000000; cout << "array 2 element " << testel << " = " << array2[testel] << endl; cout << "array 1 element " << testel << " = " << array[testel] << endl;   // that's all! return0; }  


    warning 

  • In majority of real-life applications you sould use STL vector class for dynamic arrays, since it provides more detailed and reliabile implementation
  • As you may see in operator [] source code, AeDynArray arrays doesn't have boundary checking.
  • AeDynArray class is not suitable for storing custom C++ objects(with constructors), becase of low-level memory access functions (malloc, memcpy, realloc). It can handle only basic types (char, int, double, float) and structs based on basic types.
  • tested 

  • Windows XP :: Microsoft Visual C++ 2003
  • Free BSD 5.2 :: gcc 3.3.3
  • Mac OS X 10.4.8 :: gcc 4.0.1