The disadvantages of bitwise copying. Example. The need to use the copy constructor and copy operator for classes containing dynamic memory allocation

The disadvantages of bitwise copying. Example. The need to use the copy constructor and copy operator for classes containing dynamic memory allocation

This topic has a common interpretation with the following topics:


Contents


1. The concept of bitwise copying. Copy constructor and assignment operator generated by default compiler

Bitwise copying is a way to get a copy of a class object by copying every bit (byte) of a particular class object (instance). Bitwise copying is used when it is necessary to obtain an identical destination object from the source object.

 

2. In what cases is bitwise copying of objects called?

Bitwise copying of objects is called in the following cases:

  • if the class does not have an explicitly specified copy constructor that ensures the creation of a new object from an existing one. In this case, the compiler automatically generates its own implicit copy constructor. This copy constructor implements the creation of a copy of the object by bitwise copying;
  • in the operator function operator=(), which copies one object to another. In this case, the compiler also automatically generates its own implicit operator function, which makes a complete copy of the object.

However, bitwise copying, which is implemented in the compiler, can lead to invisible programming errors if memory is allocated dynamically in the class.

 

3. What class functions use bitwise copying of objects?

Two class functions use bitwise copying of objects:

  • an implicit copy constructor that is automatically generated by the compiler in the absence of an explicit copy constructor. More details about the copy constructor are described here;
  • the implicit operator function operator=(), which is automatically generated by the compiler in the absence of an explicitly specified operator function. A detailed example of using the operator function operator=() is described here.

 

4. What classes are required to implement their own explicitly defined copy constructor and copy operator?

The disadvantages of bitwise copying are manifested for classes in which the memory for variables is allocated dynamically. To avoid these drawbacks, you must include your own explicitly defined copy constructor as well as the copy operator in the class.

If the class does not allocate memory dynamically (no pointers), then the explicitly specified copy constructor and copy operator operator=() are not necessary.



 

5. Problems that arise if the class does not have an explicit copy constructor and copy operator

If the class memory is allocated dynamically and there is no explicitly specified copy constructor, then bitwise copying leads to the following problems.

1. When copying

ClassName obj1;
ClassName obj2 = obj1;

in classes where there are pointers to the allocated memory, both pointers of objects obj1, obj2 will point to the same memory area. As a result, changes in one object will change the values of another, it is unacceptable.

2. The class in which memory is allocated for the pointer must necessarily free it in the destructor. Freeing up memory for a pointer in a class destructor is common practice. After the program ends, there is no allocated memory that was not freed by mistake. The destructor is called when the object is destroyed. If two objects obj1 and obj2 point to a common area of memory, then this area of memory will be freed up twice (for the object obj1 and the object obj2). And this is a mistake: memory allocated once can only be freed once and no more. As a result, the system will generate an exception.

 

6. An example that demonstrates the disadvantages of bitwise copying
6.1. Version of class Copy, which contains erroneous code

In the example, the Copy class is declared, which contains the erroneous code. A pointer to an int type is declared in the Copy class. The class contains the following elements:

  • variable-pointer to int type;
  • constructor with 1 parameter. In this constructor, memory for pointer p is allocated dynamically;
  • access method Get(), returning the value at the pointer *p;
  • access method Set(), which sets a new value using the pointer p. If the memory is not previously allocated, then it is allocated again.

The text of the Copy class is as follows:

#include <iostream>
using namespace std;

// The disadvantages of bitwise copying
class Copy
{
private:
  int * p; // pointer to int

public:
  Copy() { p = nullptr; }

  Copy(int value)
  {
    p = new int; // allocate memory
    *p = value; // fill with value
  }

  int Get()
  {
    if (p != nullptr)
      return *p;
    else
      return 0;
  }

  void Set(int value)
  {
    // if no memory is allocated, then allocate memory
    if (p == nullptr)
      p = new int;
    *p = value;
  }
};

void main()
{
  // The disadvantages of bitwise copying
  Copy obj1(15);
  Copy obj2;

  // A call to the operator function operator=(), which is generated by compiler
  // bitwise copying
  obj2 = obj1;
  cout << "After obj2=obj1" << endl;
  cout << "*(obj1.p) = " << obj1.Get() << endl;
  cout << "*(obj2.p) = " << obj2.Get() << endl;

  obj2.Set(5); // changes in obj2 lead to changes in obj1 - this is a flaw
  cout << endl << "After obj2.Set(5)" << endl;
  cout << "*(obj1.p) = " << obj1.Get() << endl;
  cout << "*(obj2.p) = " << obj2.Get() << endl;

  obj1.Set(-20);
  cout << endl << "After obj1.Set(-20)" << endl;
  cout << "*(obj1.p) = " << obj1.Get() << endl;
  cout << "*(obj2.p) = " << obj2.Get() << endl;

  // Implicit copy constructor call - bitwise copying
  Copy obj3 = obj1;
  cout << endl << "obj3 = obj1" << endl;

  // changes in obj3 lead to changes in obj2 and obj1
  obj3.Set(50);
  cout << endl << "After obj3.Set(50)" << endl;
  cout << "*(obj1.p) = " << obj1.Get() << endl;
  cout << "*(obj2.p) = " << obj2.Get() << endl;
  cout << "*(obj3.p) = " << obj3.Get() << endl;
}

If you run the program for execution, it will produce the following result

After obj2=obj1
*(obj1.p) = 15
*(obj2.p) = 15

After obj2.Set(5)
*(obj1.p) = 5
*(obj2.p) = 5

After obj1.Set(-20)
*(obj1.p) = -20
*(obj2.p) = -20

obj3 = obj1

After obj3.Set(50)
*(obj1.p) = 50
*(obj2.p) = 50
*(obj3.p) = 50

As you can see from the result, there are two errors in the Copy class:

  • in obtain of a copy of a class object, the pointers in the original and copies of the class point to the same memory area – this is error;
  • the class does not contain the ~Copy() destructor, which frees memory for the pointer p. If we try to add a destructor to the Copy class, which will free memory for the pointer p, then the program will throw an exception. This is due to the fact that the same memory area will be freed up twice.

 

6.2. Version of the Copy class that contains the correct code (fixed version)

To fix the situation in the Copy class, you need to include the following functions:

  • copy constructor. A copy of the parameter object will be formed in this constructor;
  • operator function operator=(), which will also form a copy of the object-parameter.

After that, you can add a destructor to the Copy class, which will correctly free memory, since all objects in the program will point to different sections of memory. The version of the Copy class that works correctly has the following form:

// Disadvantages of bitwise copying:
// fixed version of the Copy class
class Copy
{
private:
  int * p; // pointer to int

public:
  Copy() { p = nullptr; }

  Copy(int value)
  {
    p = new int; // allocate memory
    *p = value; // fill with value
  }

  // copy constructor added
  Copy(const Copy& obj)
  {
    p = new int;
    *p = *(obj.p);
  }

  int Get()
  {
    if (p != nullptr)
      return *p;
    else
      return 0;
  }

  void Set(int value)
  {
    // if no memory is allocated, then allocate memory
    if (p == nullptr)
      p = new int;
    *p = value;
  }

  // operator function operator=() is added
  Copy& operator=(const Copy& obj)
  {
    // 1. Allocate memory if it has not been previously allocated
    if (p == nullptr)
      p = new int;

    // 2. Copy data
    *p = *(obj.p);

    // 3. Return the current object
    return *this;
  }

  // Now you can add a destructor
  ~Copy()
  {
    // Release previously allocated memory
    if (p != nullptr)
      delete p;
  }
};

The main() function remains in the previous version. After running, the program will produce the following result:

After obj2=obj1
*(obj1.p) = 15
*(obj2.p) = 15

After obj2.Set(5)
*(obj1.p) = 15
*(obj2.p) = 5

After obj1.Set(-20)
*(obj1.p) = -20
*(obj2.p) = 5

obj3 = obj1

After obj3.Set(50)
*(obj1.p) = -20
*(obj2.p) = 5
*(obj3.p) = 50

As you can see from the result, after adding the copy constructor

// copy constructor is added
Copy(const Copy& obj)
{
  p = new int;
  *p = *(obj.p);
}

and adding an operator function

// operator function operator=() is added
Copy& operator=(const Copy& obj)
{
  // 1. Allocate memory if it has not been previously allocated
  if (p == nullptr)
    p = new int;

  // 2. Copy data
  *p = *(obj.p);

  // 3. Return the current object
  return *this;
}

the program works correctly. also works correctly freeing the memory for objects obj1, obj2, obj3 in the class destructor

// Now we can add a destructor
~Copy()
{
  // Release previously allocated memory
  if (p != nullptr)
    delete p;
}

as a result, a critical situation (exception) is not generated. Thus, the disadvantages of bitwise copying have been eliminated.

 


Related topics