12 Special member functions [special]

12.4 Destructors [class.dtor]

In a declaration of a destructor, the declarator is a function declarator ([dcl.fct]) of the form

ptr-declarator ( parameter-declaration-clause ) noexcept-specifieropt attribute-specifier-seqopt

where the ptr-declarator consists solely of an id-expression, an optional attribute-specifier-seq, and optional surrounding parentheses, and the id-expression has one of the following forms:

The class-name shall not be a typedef-name. A destructor shall take no arguments ([dcl.fct]). Each decl-specifier of the decl-specifier-seq of a destructor declaration (if any) shall be friend, inline, or virtual.

A destructor is used to destroy objects of its class type. The address of a destructor shall not be taken. A destructor can be invoked for a const, volatile or const volatile object. const and volatile semantics ([dcl.type.cv]) are not applied on an object under destruction. They stop being in effect when the destructor for the most derived object ([intro.object]) starts.

Note: A declaration of a destructor that does not have a noexcept-specifier has the same exception specification as if had been implicitly declared ([except.spec]).  — end note ]

If a class has no user-declared destructor, a destructor is implicitly declared as defaulted ([dcl.fct.def]). An implicitly-declared destructor is an inline public member of its class.

A defaulted destructor for a class X is defined as deleted if:

  • X is a union-like class that has a variant member with a non-trivial destructor,

  • any potentially constructed subobject has class type M (or array thereof) and M has a deleted destructor or a destructor that is inaccessible from the defaulted destructor,

  • or, for a virtual destructor, lookup of the non-array deallocation function results in an ambiguity or in a function that is deleted or inaccessible from the defaulted destructor.

A destructor is trivial if it is not user-provided and if:

  • the destructor is not virtual,

  • all of the direct base classes of its class have trivial destructors, and

  • for all of the non-static data members of its class that are of class type (or array thereof), each such class has a trivial destructor.

Otherwise, the destructor is non-trivial.

A destructor that is defaulted and not defined as deleted is implicitly defined when it is odr-used ([basic.def.odr]) or when it is explicitly defaulted after its first declaration.

Before the defaulted destructor for a class is implicitly defined, all the non-user-provided destructors for its base classes and its non-static data members shall have been implicitly defined.

After executing the body of the destructor and destroying any automatic objects allocated within the body, a destructor for class X calls the destructors for X's direct non-variant non-static data members, the destructors for X's non-virtual direct base classes and, if X is the type of the most derived class ([class.base.init]), its destructor calls the destructors for X's virtual base classes. All destructors are called as if they were referenced with a qualified name, that is, ignoring any possible virtual overriding destructors in more derived classes. Bases and members are destroyed in the reverse order of the completion of their constructor (see [class.base.init]). A return statement ([stmt.return]) in a destructor might not directly return to the caller; before transferring control to the caller, the destructors for the members and bases are called. Destructors for elements of an array are called in reverse order of their construction (see [class.init]).

A destructor can be declared virtual ([class.virtual]) or pure virtual ([class.abstract]); if any objects of that class or any derived class are created in the program, the destructor shall be defined. If a class has a base class with a virtual destructor, its destructor (whether user- or implicitly-declared) is virtual.

Note: some language constructs have special semantics when used during destruction; see [class.cdtor].  — end note ]

A destructor is invoked implicitly

In each case, the context of the invocation is the context of the construction of the object. A destructor is also invoked implicitly through use of a delete-expression ([expr.delete]) for a constructed object allocated by a new-expression ([expr.new]); the context of the invocation is the delete-expression. [ Note: An array of class type contains several subobjects for each of which the destructor is invoked.  — end note ] A destructor can also be invoked explicitly. A destructor is potentially invoked if it is invoked or as specified in [expr.new], [class.base.init], and [except.throw]. A program is ill-formed if a destructor that is potentially invoked is deleted or not accessible from the context of the invocation.

At the point of definition of a virtual destructor (including an implicit definition ([class.copy])), the non-array deallocation function is determined as if for the expression delete this appearing in a non-virtual destructor of the destructor's class (see [expr.delete]). If the lookup fails or if the deallocation function has a deleted definition ([dcl.fct.def]), the program is ill-formed. [ Note: This assures that a deallocation function corresponding to the dynamic type of an object is available for the delete-expression ([class.free]).  — end note ]

In an explicit destructor call, the destructor is specified by a ~ followed by a type-name or decltype-specifier that denotes the destructor's class type. The invocation of a destructor is subject to the usual rules for member functions ([class.mfct]); that is, if the object is not of the destructor's class type and not of a class derived from the destructor's class type (including when the destructor is invoked via a null pointer value), the program has undefined behavior. [ Note: invoking delete on a null pointer does not call the destructor; see [expr.delete].  — end note ] [ Example:

struct B {
  virtual ~B() { }
};
struct D : B {
  ~D() { }
};

D D_object;
typedef B B_alias;
B* B_ptr = &D_object;

void f() {
  D_object.B::~B();             // calls B's destructor
  B_ptr->~B();                  // calls D's destructor
  B_ptr->~B_alias();            // calls D's destructor
  B_ptr->B_alias::~B();         // calls B's destructor
  B_ptr->B_alias::~B_alias();   // calls B's destructor
}

 — end example ] [ Note: An explicit destructor call must always be written using a member access operator ([expr.ref]) or a qualified-id ([expr.prim]); in particular, the unary-expression ~X() in a member function is not an explicit destructor call ([expr.unary.op]).  — end note ]

Note: explicit calls of destructors are rarely needed. One use of such calls is for objects placed at specific addresses using a placement new-expression. Such use of explicit placement and destruction of objects can be necessary to cope with dedicated hardware resources and for writing memory management facilities. For example,

void* operator new(std::size_t, void* p) { return p; }
struct X {
  X(int);
  ~X();
};
void f(X* p);

void g() {                      // rare, specialized use:
  char* buf = new char[sizeof(X)];
  X* p = new(buf) X(222);       // use buf[] and initialize
  f(p);
  p->X::~X();                   // cleanup
}

 — end note ]

Once a destructor is invoked for an object, the object no longer exists; the behavior is undefined if the destructor is invoked for an object whose lifetime has ended ([basic.life]). [ Example: if the destructor for an automatic object is explicitly invoked, and the block is subsequently left in a manner that would ordinarily invoke implicit destruction of the object, the behavior is undefined.  — end example ]

Note: the notation for explicit call of a destructor can be used for any scalar type name ([expr.pseudo]). Allowing this makes it possible to write code without having to know if a destructor exists for a given type. For example,

typedef int I;
I* p;
p->I::~I();

 — end note ]