Саморазрушение: 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()
Это законный звонок? Это дополнительно разрушает структуру объекта (свободна от кучи, выталкивает стек)?
Ответы
Ответ 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