Assignment Operator C++ Dynamic Array Examples

Automatics, Copy Constructor, Assignment Operator
 

Automatic Functions:

In C++, there are some special functions used with classes, some of which we have seen, like the constructor and destructor. One thing that makes each of these functions special is that if you do not explicitly define one in a class, a default version will automatically be built for you by the compiler. These functions include:
  • Constructor
  • Destructor
  • Copy Constructor
  • Assignment operator =

The automatic versions of the constructor and destructor don't do anything, but they will be there if you do not build them. (The constructor you get is the "default constructor" -- no parameters -- and this is automatically built if you don't define any constructor besides the cop constructor). The automatic versions of the Copy Constructor and the Assignment operator overload are similar to each other, and their default versions are always built in a standard way.

Copy Constructor:

A copy constructor IS a constructor, so it is a function with the same name as the class and no return type (just like any constructor). However, it is invoked implicitly when something is done that causes a COPY of an existing object to be created.  This happens when:
  1. An object is defined to have the value of another object of the same type
  2. An object is passed by value into a function
  3. An object is returned (by value) from a function

Remember that anything that is passed by value in and out of functions causes copies to be made!

Here's an example of item #1 in the above list:

Fraction f1, f2(3,4); // declaration of two fractions Fraction f3 = f2; // declaration of f3 being initialized as a copy of f2

Note: This last line of code calls the copy constructor, since the initialization is on the same line as the declaration of f3. Contrast this with the following:

f1 = f2; // this uses the assignment operator, since f1 and f2 already exist

Declaring and Defining:

Since the purpose of a copy constructor is to not only initialize the data in an object, but to initialize it as a copy of another existing object, the original object must be passed in as a parameter.  So, a copy constructor always has one parameter, which is of the same type as the class itself. It is always passed by reference, as well (it has to be -- since to pass by value, we must invoke a copy constructor, and this is what we are defining!)

Format:   

The const is not required, but it is usally a good idea, because we only want to make a copy -- we don't want to change the original.  Here are some examples of copy constructor declarations for classes we have seen:

Fraction(const Fraction & f); Timer(const Timer & t); Directory(const Directory & d); Store(const Store & s);

Shallow Copy vs. Deep Copy:

The default version of the copy constructor (created by the compiler) makes what is known as a shallow copy. This simply means that the object is copied exactly as is -- each member data value is copied exactly over to the corresponding member data location in the new object.  This is sufficient for many cases, but not for ALL cases.

Example:

Fraction f1(3,4);

This fraction object has a numerator of 3 and a denominator of 4. If this object is passed into a function by value, a copy will be made, and the new object's numerator will be 3, denominator 4. In this case, the shallow copy is sufficient.

Consider, however, the Directory class of the phone book example. The member data variables were currentsize and maxsize (both of type int), and a pointer, entryList (of type Entry * ), which pointed to dynamically allocated data outside the actual object. This is the situation in which a shallow copy is not sufficient. For instance, if the original object is storing the address 1024 in entryList, the copy will also get the 1024, and therefore the copy will be pointing to the original dynamic data!  This will especially pose problems if, when the copy goes out of scope, it cleans up the dynamic data along with it.

When there is a pointer (inside an object) that points to dynamic data, the shallow copy is not sufficient, because it does not copy the dynamic data, only the pointer.  A deep copy is needed.  Here is what we might write for a copy constructor definition in the Directory class (from the phonebook database example):

Directory::Directory(const Directory & d) // copies object 'd' into the new object being created (this one) { // copy the static variables normally maxsize = d.maxsize; currentsize = d.currentsize; // create a new dynamic array for the // new object's pointer entryList = new Entry[d.maxsize]; // copy the dynamic data for (int i = 0; i < currentsize; i++) entryList[i] = d.entryList[i]; }

Assignment Operator

The assignment operator is similar to the copy constructor.  It is called when one object is assigned to another.  Example call: Fraction f1, f2; f1 = f2; // this call invokes the assignment operator

Like the copy constructor, the assignment operator has to make a copy of an object. The default version makes a shallow copy. If a deep copy is desired for assignments on a user-defined type (e.g. a class), then the assignment operator should be overloaded for the class.

The task done by the assignment operator is very similar to that of a copy constructor, but there are a couple of differences.

1) The copy constructor is initializing a brand new object as a copy of an existing one.  The new object's data is being initialized for the first time.  An assignment operator sets an existing object's state to that of another existing object.  In situations with dynamic allocation, this may mean that old dynamic space must be cleaned up first before the copy is made.

2)  Also, an assignment operator also returns the value that was assigned (the copy constructor has no return).  Consider the case of integers.  In the statement:

a = b = c = 4;

The first operation is (c = 4), and this operation returns the assigned value (4), so that the result can be used as an operand in the next assignment (b = 4). This value should be returned by reference when overloading for objects. To return the object, we need to be able to refer to an object from inside the object itself.

The pointer :

From inside any member function, an object has access to its own address through a pointer called , which is a keyword in C++.  In an assignment operator, you must return the object itself (by reference), so you can return the target of the pointer (which would be )

Like the copy constructor, the original object needs to be passed in, so there will be one parameter (of the same type as the object itself).  The parameter is the same as in the copy constructor.

Declaration examples for a few classes:

Directory& operator= (const Directory &); Fraction& operator=(const Fraction &); Timer& operator=(const Timer &); Circle& operator=(const Circle &);

Here's an example of the assignment operator definition for the Directory class (in the phonebook example).

Definition in the implementation file:

Directory& Directory::operator=(const Directory & d); // copies object 'd' into the new object being created (this one) { if (this != &d) // only copy if the object passed in is not already this one { // since this is not a brand new object, we // should delete any information currently attached delete [] entryList; // similar to the copy constructor definition maxsize = d.maxsize; currentsize = d.currentsize; entryList = new Entry[d.maxsize]; for (int i = 0; i < currentsize; i++) entryList[i] = d.entryList[i]; } return *this; // return the object itself (by reference) }

Here is a link to the Phonebook example with the copy constructor and assignment operator added in.

Here is a link to what the Fraction class would look like with these functions added, although it should be noted that they are not needed in the Fraction class, because the automatic versions are sufficient.  The Fraction member data does not involve pointers or dynamic allocation -- it only consists of static data, so the shallow copy is enough.

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
  • 0 Replies to “Assignment Operator C++ Dynamic Array Examples”

    Lascia un Commento

    L'indirizzo email non verrà pubblicato. I campi obbligatori sono contrassegnati *