Разница в выделении памяти для данных типа Obj в перегруженной функции оператора= и конструкторе копирования

Вопрос или проблема

Предположим, у нас есть следующие 2 класса:

class Class1
{
   private :
      Class2 *obj;
   public:
      Class1();
      Class1(const Class1 &other);
      Class1 & operator=(const Class1 & obj);
      ~Class1();
}


class Class2
{
  Class2();
  Class2(const Class2 &other);
  Class2 & operator=(const Class2 & obj);
  ~Class2();
}

Мы все согласны с тем, что конструктор копирования копирует данные из существующего объекта-источника в новый экземпляр объекта, и для большинства конструкторов копирования, которые я реализовал, они выглядят следующим образом:

Class1::Class1(const Class1 &other)
{
    *this = other;
}

Код выше подсказывает нам, как реализовать перегрузку оператора “=”, который в основном используется для копирования данных из одного существующего объекта в другой существующий объект, и его реализация:

Class1 & Class1::operator=(const Class1 & obj)
{
    if(this != &other)
      this->data = obj.data;
    return *this;
}

Теперь, предполагая, что мы хотим выделить внутри этого Class1 переменную типа Obj, эти два метода будут выглядеть так:

Class1::Class1(const Class1 &other)
{
     this->obj = new Class2(); 
     *this = other;
}


Class1 & Class1::operator=(const Class1 & obj)
{
   if(this != &other)
   {
       this->data = obj.data;
       this->obj = new Class2();
   }
   return *this;
}

Мой вопрос: Поскольку конструктор копирования использует *this = other, что будет копировать объект-источник в новый объект назначения (включая this->obj = new Class2(), который будет выделен для нового объекта), почему нам нужно снова выделять в конструкторе копирования, если оператор= об этом позаботится?

Я знаю, что это называется глубокое копирование, которое позволяет каждому экземпляру class1 иметь свою собственную переменную типа Class2 и избегать совместного использования адреса памяти, чтобы изменения одного объекта экземпляра Class1 не затрагивали другой экземпляр того же типа.

Ответ или решение

Ваша задача касается различий между выделением памяти для данных типа Class2 в перегруженной функции оператора присваивания и конструкторе копирования в контексте класса Class1. Давайте разберем это подробно.

Структура классов

У нас есть два класса: Class1 и Class2. Каждый из них имеет конструктор по умолчанию, конструктор копирования и оператор присваивания. Сам класс Class1 содержит указатель на объект типа Class2.

class Class1 {
private:
    Class2 *obj;
public:
    Class1();
    Class1(const Class1 &other);
    Class1 &operator=(const Class1 &obj);
    ~Class1();
};

class Class2 {
public:
    Class2();
    Class2(const Class2 &other);
    Class2 &operator=(const Class2 &obj);
    ~Class2();
};

Конструктор копирования

При вызове конструктора копирования Class1::Class1(const Class1 &other) создается новый объект Class1, который должен содержать копию объекта Class1, переданного в качестве параметра.

Ваша реализация:

Class1::Class1(const Class1 &other) {
    this->obj = new Class2();
    *this = other;  // Вызов оператора присваивания
}

Здесь важно подчеркнуть, что хотя оператор присваивания копирует данные из other в текущий объект, указатель this->obj уже был инициализирован новым объектом Class2. Таким образом, вы должны выделить память для this->obj, чтобы избежать совместного использования одного и того же указателя в разных экземплярах Class1.

Оператор присваивания

Ваша реализация оператора присваивания выглядит следующим образом:

Class1 &Class1::operator=(const Class1 &obj) {
    if (this != &obj) {  // Проверка на самоприсвоение
        this->data = obj.data; // Копирование других данных
        this->obj = new Class2(); // Выделение новой памяти для obj
    }
    return *this;
}

При выполнении оператора присваивания:

  1. Если объекты не идентичны (для предотвращения самоприсваивания), другие данные копируются.
  2. Затем выделяется новая память для this->obj, что обеспечивает глубокое копирование.

Объяснение

Теперь давайте ответим на ваш вопрос: почему выделение памяти для this->obj происходит как в конструкторе копирования, так и в операторе присваивания. Это связано с тем, что:

  1. Конструктор копирования: Он всегда создает новый объект. Для этого необходимо выделить новую память под Class2, чтобы новый объект класса Class1 не использовал указатель на объект Class2 из другого экземпляра.

  2. Оператор присваивания: Он может перезаписать существующий объект. Если obj уже указывает на выделенную память, ее необходимо сначала освободить, а затем выделить новую память для this->obj, чтобы избежать утечки памяти или повреждения данных.

Хотя оператор присваивания действительно обрабатывает копирование, нам все равно нужно выделить память для нового объекта Class2 в конструкторе копирования, чтобы гарантировать независимость экземпляров.

Заключение

Ответ на ваш вопрос заключается в том, что конструкция this->obj = new Class2(); в конструкторе копирования необходима для обеспечения выделения новой памяти для нового экземпляра Class2. Это делает ваш код более устойчивым, избегает ошибочного совместного использования указателей и позволяет держать данные различных экземпляров Class1 изолированными друг от друга. Таким образом, сам принцип глубокого копирования позволяет избежать потенциальных проблем с памятью и обеспечивает корректное поведение программного обеспечения.

Оцените материал
Добавить комментарий

Капча загружается...