Подтвердить что ты не робот

Саморазрушение: this-> MyClass:: ~ MyClass() vs. this-> ~ MyClass()

В моем стремлении изучить С++ я наткнулся на статью Написание конструкторов копий и операторов присваивания, в которой предлагается механизм, позволяющий избежать дублирования кода между конструкторами копирования и операторами присваивания.

Чтобы суммировать/дублировать содержание этой ссылки, предлагаемый механизм:

struct UtilityClass
{
  ...

  UtilityClass(UtilityClass const &rhs)
    : data_(new int(*rhs_.data_))
  {
    // nothing left to do here
  }

  UtilityClass &operator=(UtilityClass const &rhs)
  {
    //
    // Leaves all the work to the copy constructor.
    //

    if(this != &rhs)
    {
      // deconstruct myself    
      this->UtilityClass::~UtilityClass();

      // reconstruct myself by copying from the right hand side.
      new(this) UtilityClass(rhs);
    }

    return *this;
  }

  ...
};

Это похоже на хороший способ избежать дублирования кода, обеспечивая при этом "программную целостность", но при этом нужно учитывать риск потери усилий, а затем - выделение вложенной памяти, которая вместо этого может быть повторно использована (как указывает ее автор).

Но я не знаком с синтаксисом, лежащим в его основе:

this->UtilityClass::~UtilityClass()

Я предполагаю, что это способ вызвать деструктор объекта (уничтожить содержимое структуры объекта), сохраняя при этом структуру. Для новичков С++ синтаксис выглядит как странная смесь метода объекта и метода класса.

Может ли кто-нибудь объяснить этот синтаксис мне или указать на ресурс, который объясняет это?

Как этот вызов отличается от следующего?

this->~UtilityClass()

Это законный звонок? Это дополнительно разрушает структуру объекта (свободна от кучи, выталкивает стек)?

4b9b3361

Ответ 1

TL; DR НЕ ДЕЛАЙТЕ ЭТО.

Чтобы ответить на конкретный вопрос:

В этом конкретном примере нет никакой разницы. Как объясняется в статье, на которую вы ссылаетесь, было бы различие, если бы это был полиморфный базовый класс с виртуальным деструктором.

Квалифицированный вызов:

this->UtilityClass::~UtilityClass()

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

Неквалифицированный вызов:

this->~UtilityClass()

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

Писатель статьи утверждает, что первое - это то, что вы хотите, так что вы назначаете только базовый под-объект, оставляя производные части неповрежденными. Однако, что вы на самом деле переписываете часть объекта новым объектом базового типа; вы изменили динамический тип и просочились все, что было в производных частях старого объекта. В любом случае это плохо. Вы также внесли исключение: если конструкция нового объекта не удалась, тогда старый объект остается в недопустимом состоянии и даже не может быть безопасно уничтожен.

UPDATE: у вас также есть поведение undefined, поскольку, как описано в другом ответе, запрещено использовать place-new для создания объекта поверх (части) объекта с различной типизацией.

Для неполиморфных типов хороший способ написать оператор присваивания копий с идиомой копирования и свопинга. Это позволяет избежать дублирования путем повторного использования конструктора-копии и обеспечивает надежную гарантию исключения - если присваивание не выполняется, исходный объект не модифицируется.

Для полиморфных типов объекты копирования более активно и обычно не могут выполняться с помощью простого оператора присваивания. Общим подходом является виртуальная функция clone, которую каждый тип переопределяет, чтобы динамически распределять копию самого себя с правильным типом.

Ответ 2

TL; версия DR: НЕ ПОСЫЛАЙТЕ ЛЮБЫЕ КОНСУЛЬТАЦИИ, ПРЕДОСТАВЛЯЕМЫЕ АВТОРОМ, ЧТО LINK


Ссылка предполагает, что этот метод можно использовать в базовом классе, если вызов виртуального деструктора не используется, поскольку это приведет к уничтожению частей производного класса, который не несет ответственности за базовый класс operator=.

Эта линия рассуждений полностью терпит неудачу. Техника никогда не может использоваться в базовом классе. Причина в том, что стандарт С++ допускает замену объекта на месте другим объектом того же типа (см. Раздел 3.8 стандарта):

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

  • хранилище для нового объекта точно накладывает место хранения, в котором находился исходный объект, и
  • новый объект имеет тот же тип, что и исходный объект (игнорируя cv-qualifers верхнего уровня) и
  • тип исходного объекта не является const-quali fiified, и, если тип класса не содержит нестатического члена данных, тип которого является const-квалифицированным или ссылочным, и
  • исходный объект был самым производным объектом (1.8) типа T, а новый объект является наиболее производным объектом типа T (то есть они не являются подобъектами базового класса).

В исходном коде как return *this;, так и последующее использование объекта undefined; они получают доступ к объекту, который был уничтожен, а не только что созданному объекту.

На практике это тоже проблема: в новом вызове будет установлен v-table ptr, соответствующий базовому классу, а не правильный производный тип объекта.

Даже для классов листьев (не-базовые классы) метод очень сомнительный.

Ответ 3

Вы можете решить, как вызвать деструктор:

this->MyClass::~MyClass(); // Non-virtual call

this->~MyClass();          // Virtual call