Self destruction: this->MyClass::~MyClass() vs. th

2019-03-23 03:28发布

In my quest to learn C++ I stumbled across the article Writing Copy Constructors and Assignment Operators which proposes a mechanism to avoid code duplication across copy constructors and assignment operators.

To summarise/duplicate the content of that link, the proposed mechanism is:

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 seems like a nice way to avoid code duplication whilst ensuring "programatic integrity" but needs weighed against the risk of wasting effort freeing-then-allocating nested memory that could, instead, be re-used (as its author points out).

But I'm not familiar with the syntax that lies at its core:

this->UtilityClass::~UtilityClass()

I assume that this is a way to call the object's destructor (to destroy the contents of the object structure) whilst keeping the structure itself. To a C++ newbie, the syntax looks like a strange mixture of an object method and a class method.

Could someone please explain this syntax to me, or point me to a resource which explains it?

How does that call differ from the following?

this->~UtilityClass()

Is this a legitimate call? Does this additionally destroy the object structure (free from heap; pop off the stack)?

3条回答
来,给爷笑一个
2楼-- · 2019-03-23 03:59

TL;DR DO NOT DO THIS.

To answer the specific question:

In this particular example, there's no difference. As explained in the article you link to, there would be a difference if this were a polymorphic base class, with a virtual destructor.

A qualified call:

this->UtilityClass::~UtilityClass()

would specifically call the destructor of this class, not that of the most derived class. So it only destroys the subobject being assigned to, not the entire object.

An unqualified call:

this->~UtilityClass()

would use virtual dispatch to call the most derived destructor, destroying the complete object.

The article writer claims that the first is what you want, so that you only assign to the base sub-object, leaving the derived parts intact. However, what you actually do overwrite part of the object with a new object of the base type; you've changed the dynamic type, and leaked whatever was in the derived parts of the old object. This is a bad thing to do in any circumstances. You've also introduced an exception issue: if the construction of the new object fails, then the old object is left in an invalid state, and can't even be safely destroyed.

UPDATE: you also have undefined behaviour since, as described in another answer, it's forbidden to use placement-new to create an object on top of (part of) a differently-typed object.

For non-polymorphic types, a good way to write a copy-assignment operator is with the copy-and-swap idiom. That both avoids duplication by reusing the copy-constructor, and provides a strong exception guarantee - if the assignment fails, then the original object is unmodified.

For polymorphic types, copying objects is more involved, and can't generally be done with a simple assignment operator. A common approach is a virtual clone function, which each type overrides to dynamically allocate a copy of itself with the correct type.

查看更多
smile是对你的礼貌
3楼-- · 2019-03-23 04:03

You can decide to how to call the destructor:

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

this->~MyClass();          // Virtual call
查看更多
戒情不戒烟
4楼-- · 2019-03-23 04:06

TL;DR version: DO NOT FOLLOW ANY ADVICE GIVEN BY THE AUTHOR OF THAT LINK


The link suggests that this technique can be used in a base class as long as a virtual destructor call is not used, because doing so would destruct parts of the derived class, which isn't the responsibility of the base class operator=.

This line of reasoning totally fails. The technique can never ever be used in a base class. The reason is that the C++ Standard only allows in-place replacement of an object with another object of the exact same type (see section 3.8 of the Standard):

If, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, a new object is created at the storage location which the original object occupied, a pointer that pointed to the original object, a reference that referred to the original object, or the name of the original object will automatically refer to the new object and, once the lifetime of the new object has started, can be used to manipulate the new object, if:

  • the storage for the new object exactly overlays the storage location which the original object occupied, and
  • the new object is of the same type as the original object (ignoring the top-level cv-qualifiers), and
  • the type of the original object is not const-qualified, and, if a class type, does not contain any non-static data member whose type is const-qualified or a reference type, and
  • the original object was a most derived object (1.8) of type T and the new object is a most derived object of type T (that is, they are not base class subobjects).

In the original code, both return *this; and subsequent use of the object are undefined behavior; they access an object which has been destroyed, not the newly created object.

This is a problem in practice as well: the placement-new call will set up a v-table ptr corresponding to the base class, not the correct derived type of the object.

Even for leaf classes (non-base classes) the technique is highly questionable.

查看更多
登录 后发表回答