An example of elementwise summation of two arrays. Class DoubleArray. Overloading the + operator. Dynamic memory allocation in class. Copy constructor. Bitwise copy disadvantages

An example of elementwise summation of two arrays. Class DoubleArray. Overloading the + operator. Dynamic memory allocation the class. Copy constructor. Bitwise copy disadvantages

Using this example, you can implement your own classes, in which arrays are declared, for which memory is dynamically allocated. Memory allocated dynamically, is released in the destructor of the class.


Contents



1. The DoubleArray class and main() function

The DoubleArray class is declared, which implements:

  • internal private pointer variable p to a double. This variable is an array of numbers. For pointer p, memory will be allocated dynamically;
  • internal private variable size – the size of the array;
  • constructor without parameters DoubleArray();
  • constructor with one parameter DoubleArray(int size). In this constructor, memory for the array p is allocated dynamically. The size of the array is determined by the input parameter size;
  • copy constructor DoubleArray(const DoubleArray & A). This constructor is necessary to avoid bitwise copying. Bitwise copying has significant drawbacks in the case of dynamic memory allocation in a class. In the copy constructor for a newly created object, memory is allocated in its own way;
  • operator function operator+(DoubleArray &). This function overloads the + operator in such a way that objects of the DoubleArray class can be summed together in the form obj1+obj2. In the operator function, arrays are summed elementwise. If one array is shorter than the other, then the elements of the smaller array are considered to be equal to 0;
  • operator function operator=(DoubleArray &). This function overloads the assignment operator =. The function is necessary to avoid bit-wise copying when assigning objects like obj3 = obj1 + obj2 or obj3 = obj2 = obj1. If you do not define this function in a class, then there will be a called implicit assignment operator that performs bitwise copying that is invalid for our class;
  • ~DoubleArray() class destructor. In the class destructor, the allocated memory is released;
  • function Show(), which displays the contents of the p array.

The implementation of the DoubleArray class and the demonstration of its work in the main() function are as follows

#include <iostream>
#include <time.h>
using namespace std;

// dynamic array
class DoubleArray
{
  double * p;
  int size;

public:
  // constructors
  DoubleArray()
  {
    size = 0;
    p = nullptr;
  }

  DoubleArray(int size)
  {
    this->size = size;
    p = new double[size];
    for (int i = 0; i < size; i++)
      p[i] = 0;
  }

  // destructor
  ~DoubleArray()
  {
    if (size > 0)
      delete p;
  }

  // create an array at random
  void FormArrayRandom()
  {
    srand(time(NULL));
    for (int i = 0; i < size; i++)
      p[i] = rand() % 10;
  }

  // form an array by the formula
  void FormArray()
  {
    for (int i = 0; i < size; i++)
      p[i] = i;
  }

  // access methods
  double GetAi(int index)
  {
    if (p != nullptr)
    {
      if ((0 <= index) && (index < size))
        return p[index];
    }
    else
      return 0.0;
  }

  void SetAi(int index, double value)
  {
    if (p != nullptr)
    {
      if ((index >= 0) && (index < size))
        p[index] = value;
    }
  }

  // display the array
  void Show()
  {
    cout << "The Array:\n";
    for (int i = 0; i < size; i++)
      cout << "p[" << i << "] = " << p[i] << endl;
    cout << "------------------------------" << endl;
  }

  // add a copy constructor to correctly execute the destructor and initialize the object
  DoubleArray(const DoubleArray& A)
  {
    size = A.size;

    // allocate memory for the array
    p = new double[size];

    // fill with the values
    for (int i = 0; i < size; i++)
      p[i] = A.p[i];
  }

  // Add an operator function that overloads the assignment operator =
  // It is necessary for the correct operation of the assignment operator (avoid bitwise copying)
  DoubleArray operator=(const DoubleArray& A)
  {
    size = A.size;

    // allocate memory for pointers
    p = new double[size];

    // fill with the values of each element of the array
    for (int i = 0; i < size; i++)
      p[i] = A.p[i];

    // return the current object,
    // copy constructor DoulbeArray(const DoubleArray &) will be called
    return *this;
  }

  // An operator function that overloads the + operator.
  // Itemwise summation of arrays, if one array is smaller than another,
  // then the elements of the smaller array are considered equal to 0.
  DoubleArray operator+(DoubleArray& A)
  {
    int min = size; // the size of smaller array
    int max = size; // the size of a large array
    if (min > A.size) min = A.size;
    if (max < A.size) max = A.size;

    DoubleArray objA;
    objA.size = max;
    objA.p = new double[objA.size]; // allocate memory
    // ---

    // element summation loop
    for (int i = 0; i < max; i++)
    {
      if (i < min)
        objA.p[i] = p[i] + A.p[i];
      else
      {
        if (size < max)
          objA.p[i] = A.p[i];
        else
          objA.p[i] = p[i];
      }
    }
  return objA;
}

void main()
{
  // Demonstration: DoubleArray class - implements a dynamic array of elements
  DoubleArray A(5);
  A.FormArray();
  A.Show();

  DoubleArray A2 = A; // copy constructor call

  DoubleArray A3;
  A.SetAi(2, 55);
  A3 = A; // call operator function operator=(const DoubleArray & A)

  A3.Show();

  // The assignment operator in the form of a chain
DoubleArray A4;
  A4 = A2 = A3; // it works correctly
  cout << "A4=A2=A3:" << endl;
  A4.Show();

  cout << "A2:" << endl;
  A2.Show();

  cout << "A3:" << endl;
  A3.Show();

  // overloading operator +
  DoubleArray A5;
  A5 = A2 + A2 + A3 + A4;
  A5.Show();

  DoubleArray A6(3);

  A6.SetAi(0, 1.5);
  A6.SetAi(1, 2.2);
  A6.SetAi(2, 1.8);
  cout << "A6:" << endl;
  A6.Show();

  cout << "A6=A5+A6:" << endl;
  A6 = A5 + A6;
  A6.Show();
}

 

2. Why use the DoubleArray class’s copy constructor? Explanation

Let us explain the work of the copy constructor in the DoubleArray class (see p. 1).
If the class does not implement its own copy constructor, the implicit copy constructor will be called. This copy constructor is generated automatically by the compiler in case there is no developed own copy constructor in the class. The implicit copy constructor performs the so-called “bitwise copying”. For classes that do not contain dynamic memory allocation (do not contain T * p pointers), bitwise copying works correctly without errors.

However, if a class contains code that allocates memory dynamically for some pointer, then bitwise copying will completely copy the value of that pointer. That is, when performing

DoubleArray obj1 = obj2;

then pointers values in obj1 and obj2 objects will be identical if the class does not implement its own copy constructor. This means that both pointers will point to one memory location as shown in Figure 1. This is a mistake.

In our case, the DoubleArray class dynamically allocates memory for the pointer p. And this means that for the class to work correctly, you need to include your own developed copy constructor.

C++. Copy constructor. The obj1.p and obj2.p pointers point to the same memory location

Figure 1. The lack of bit-wise copying if the class uses dynamic memory allocation. The obj1.p and obj2.p pointers point to the same memory location

After implementing your own copy constructor, this constructor will override the implicit copy constructor generated by the compiler. In your own copy constructor, you need to implement memory allocation for the pointer p. The main task of including this constructor in a class is bypassing bitwise copying.

For example, for the DoubleArray class, the copy constructor might look like this:

// add a copy constructor so that the destructor and object initialization are executed correctly when it is created
DoubleArray(const DoubleArray& A)
{
  size = A.size; 

  // allocate memory for pointer p in your own way
  p = new double[size];

  // fill with the values
  for (int i=0; i<size; i++)
    p[i] = A.p[i];
}

After adding the copy constructor to the body of the DoubleArray class and executing code in the main() function

// function main()
void main()
{
  // instance obj1 of class DoubleArray
  DoubleArray obj1;

  // Instance obj1 of class DoubleArray
  // An explicit copy constructor is invoked,
  // as a result, obj1.p and obj2.p point to different areas of memory.
  DoubleArray obj2=obj1; // Invoke obj2.DoubleArray(const DoubleArray& obj1)

  // ...
};

both obj1, obj2 objects will point to different memory areas as shown in the figure.

Class has a copy constructor. The obj1.p and obj2.p pointers point to different areas of memory

Figure 2. Class has a copy constructor. The obj1.p and obj2.p pointers point to different areas of memory.

This will lead to the correct operation of the class objects in those cases where the copy constructor is called.

 

3. Overloading operator function operator=(), which overrides assignment operator = (copy operator). Why do you need to overload this function? Explanation

To correctly assign objects, in the DoubleArray class, in addition to the copy constructor, you must also overload the assignment operator =.

As in the case of the copy constructor, two possible situations affect the need to use the operator function operator=() in a class:

  • the class does not contain data (pointers) for which the memory is allocated dynamically. In this case, it does not make sense to develop your own operator function, which overloads the copy operator. Because the compiler provides an implicit copy operator = that performs bitwise copying. And for classes in which dynamic memory allocation is not implemented, bitwise copying is enough;
  • class contains pointers for which memory is allocated dynamically. In this case, bitwise copying does not work correctly. To ensure correct operation of the class, it is necessary to include the operator function operator=(), which overloads the assignment operator (copy operator). In this function, you need to implement the memory allocation code for dynamic data of class.

For the DoubleArray class when assigning objects

DoubleArray obj1;
DoubleArray obj2;
obj2 = obj1; // assignment of objects

an assignment statement (copy statement) will be invoked. If the class does not contain dynamic memory allocation, then such code will work correctly. If in a class the memory for data is dynamically allocated (the class contains pointers), then in order for this code to work correctly, it is necessary to include the operator function operator=() in it and to implement memory allocation in this function.

Our DoubleArray class implements dynamic memory allocation for the p pointer. Therefore, for the DoubleArray class, it is necessary to implement an operator function that overloads the assignment operator =. For example, the function may be as follows:

// Add an operator function that overloads the assignment operator =.
// Necessary for the correct operation of the assignment operator (avoid bitwise copying)
DoubleArray operator=(DoubleArray& A)
{
  size = A.size;

  // allocate memory for pointers
  p = new double[size];

  // fill in the values of each element of the array
  for (int i=0; i<size; i++)
    p[i] = A.p[i];

  // return the current object,
  // copy constructor will be called
  return *this;
}

 

4. Conclusions

The DoubleArray class contains data for which memory is dynamically allocated. This means that the implementation of a class must include two special functions:

  • copy constructor DoubleArray(const DoubleArray &);
  • the function of copy operator operator=(const DoubleArray &), which overloads the assignment operator =.

If this is not done, then there will be problems associated with bitwise copying. For more information on the bitwise copy problem, see the topic:

  • The concept of bitwise copy. Problems that may arise with bitwise copying.

In addition, if class data are dynamically allocated and occupy a large amount of memory, then copying this data will take additional time. To speed up the copying process, it is recommended that you add the move constructor DoubleArray(DoubleArray &&) and the move operator=(DoubleArray &&) to the DoubleArray class. This will greatly speed up the time to copy large data arrays. However, in our case, a simple dynamic double array is implemented, which is not very expensive (copying takes a minimum of time). Therefore, in this example, the move constructor and the move operator are not included in the class body. The operation of these functions is described in detail in another topic.

 


Related topics