Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

The C++ Standard, [expr.delete], paragraph 3 [ISO/IEC 14882-2014], states the following:

In the first alternative (delete object), if the static type of the object to be deleted is different from its dynamic type, the static type shall be a base class of the dynamic type of the object to be deleted and the static type shall have a virtual destructor or the behavior is undefined. In the second alternative (delete array) if the dynamic type of the object to be deleted differs from its static type, the behavior is undefined.

Do not delete an object of derived class type through a pointer to its base class type that has a non-virtual destructor. Instead, the base class should be defined with a virtual destructor. Deleting an object through a pointer to a type without a virtual destructor results in undefined behavior.

Noncompliant Code Example

In this noncompliant example, b is a polymorphic pointer type whose static type is Base * and whose dynamic type is Derived *. When b is deleted, it results in undefined behavior because Base does not have a virtual destructor. The C++ Standard, [class.dtor], paragraph 4 [ISO/IEC 14882-2014], states the following:

If a class has no user-declared destructor, a destructor is implicitly declared as defaulted. An implicitly declared destructor is an inline public member of its class.

The implicitly declared destructor is not declared as virtual even in the presence of other virtual functions.

Code Block
bgColor#FFCCCC
langcpp
struct Base {
  virtual void f();
};
 
struct Derived : Base {};
 
void f

Calling the destructor of a derived class in an inheritance hierarchy should invoke the destructors that class and all of its base classes. However, if the derived class is referenced by a pointer of a type higher up the class hierarchy, then the destructor of the pointer's type will be called rather than the destructor of the class being pointing to. As a result the derived class's destructor will not be called leading to resource mismanagement and possibly unintended program behavior.

Non-Compliant Code Example

In this non-compliant example, a reference to the parent class Base is used to instantiate a Derived object. When b is deleted, the destructor for Base is invoked rather than the destructor for Derived. As a result, the object b refers to will be improperly destroyed.

Code Block

class Base {
public:
   Base() {
  Base *b = new Derived();
  // Build Base object
   }
   ~Base ...
  delete b;
}

Noncompliant Code Example

In this noncompliant example, the explicit pointer operations have been replaced with a smart pointer object, demonstrating that smart pointers suffer from the same problem as other pointers. Because the default deleter for std::unique_ptr calls delete on the internal pointer value, the resulting behavior is identical to the previous noncompliant example.

Code Block
bgColor#FFCCCC
langcpp
#include <memory>
 
struct Base {
  virtual void f();
};
 
struct Derived : Base {};
 
void f() {
  std::unique_ptr<Base> b = std::make_unique<Derived()>();
}

Compliant Solution

In this compliant solution, the destructor for Base has an explicitly declared virtual destructor, ensuring that the polymorphic delete operation results in well-defined behavior.

Code Block
bgColor#ccccff
langcpp
struct Base {
  // Destroy Base objectvirtual ~Base() = default;
   }virtual void f();
};

classstruct Derived : public Base {};
public:
void   Derivedf() {
  Base *b = new Derived();
  // Build Derived object
   }
   ~Derived() {
      // Destroy Derived object
   }
};

void function(void) {
   Base* b = new Derived();
   // ...
   delete b;
}

Compliant Solution

To correct this example, the destructor for Base should be declared virtual. This ensures that the object b refers to will be correctly evaluated at runtime thus, deleting b will call the destructor for class Derived.

...
  delete b;
}

Exceptions

OOP52-CPP:EX0: Deleting a polymorphic object without a virtual destructor is permitted if the object is referenced by a pointer to its class, rather than via a pointer to a class it inherits from.

Code Block
bgColor#ccccff
langcpp
class Base {
public:
  // ...
  virtual void AddRef() = 0;
  virtual void Destroy() = 0;
};

class Derived final : public Base {
public:
  // ...
  virtual void AddRef() { /* ... */ }
  virtual void Destroy() { delete this; }
private:
  ~Derived() {}
};

Note that if Derived were not marked as final, then delete this could actually reference a subclass of Derived, violating this rule.

OOP52-CPP:EX1: Deleting a polymorphic object without a virtual destructor is permitted if its base class has a destroying operator delete that will figure out the correct derived class's destructor to call by other means.

Code Block
bgColor#ccccff
langcpp
#include <new>

class Base {
  const int whichDerived;

protected:
  Base(int whichDerived) : whichDerived(whichDerived) {}

public:
  Base() : Base(0) {}
  void operator delete(Base *, std::destroying_delete_t);
};

struct Derived1 final : Base {
  Derived1() : Base(1) {}
};

struct Derived2 final : Base {
  Derived2() : Base(2) {}
};

void Base::operator delete(Base *b, std::destroying_delete_t) {
  switch (b->whichDerived) {
  case 0:
    b->~Base();
    break;
  case 1:
    static_cast<Derived1 *>(b)->~Derived1();
    break;
  case 2:
    static_cast<Derived2 *>(b)->~Derived2();
  }
  ::operator delete(b);
}

void f() {
  Base *b = new Derived1();
Code Block

cclass Base {
public:
   Base() {
      // Build Base object
   }
   virtual ~Base() {
      // Destroy Base object
   }
};

class Derived : public Base {
public:
   Derived() {
      // Build Derived object
   }
   ~Derived() {
      // Destroy Derived object
   }
};

void function(void) {
   Base* b = new Derived();
   // ...
   delete b;
}


Risk Assessment

Attempting to destruct a polymorphic object that does not have a virtual destructor declared results in undefined behavior. In practice, potential consequences include abnormal program termination and memory leaks.

Rule

Severity

Likelihood

Remediation Cost

Priority

Level

OOP52-CPP

Low

Likely

Low

P9

L2

Automated Detection

Tool

Version

Checker

Description

Astrée

Include Page
Astrée_V
Astrée_V

non-virtual-public-destructor-in-non-final-class
Partially checked
Axivion Bauhaus Suite

Include Page
Axivion Bauhaus Suite_V
Axivion Bauhaus Suite_V

CertC++-OOP52
Clang
Include Page
Clang_V
Clang_V
-Wdelete-non-virtual-dtor
CodeSonar
Include Page
CodeSonar_V
CodeSonar_V

LANG.STRUCT.DNVD

delete with Non-Virtual Destructor

Helix QAC

Include Page
Helix QAC_V
Helix QAC_V

C++3402, C++3403, C++3404


Klocwork
Include Page
Klocwork_V
Klocwork_V

CL.MLK.VIRTUAL
CWARN.DTOR.NONVIRT.DELETE


LDRA tool suite
Include Page
LDRA_V
LDRA_V

303 S

Partially implemented

Parasoft C/C++test
Include Page
Parasoft_V
Parasoft_V

CERT_CPP-OOP52-a

Define a virtual destructor in classes used as base classes which have virtual functions

Polyspace Bug Finder

Include Page
Polyspace Bug Finder_V
Polyspace Bug Finder_V

CERT C++: OOP52-CPPChecks for situations when a class has virtual functions but not a virtual destructor (rule partially covered)
PVS-Studio

Include Page
PVS-Studio_V
PVS-Studio_V

V599, V689
RuleChecker
Include Page
RuleChecker_V
RuleChecker_V
non-virtual-public-destructor-in-non-final-class
Partially checked
SonarQube C/C++ Plugin
Include Page
SonarQube C/C++ Plugin_V
SonarQube C/C++ Plugin_V
S1235

Related Vulnerabilities

Search for other vulnerabilities resulting from the violation of this rule on the CERT website.

Related Guidelines

Bibliography

[ISO/IEC 14882-2014]

Subclause 5.3.5, "Delete"
Subclause 12.4, "Destructors" 

[Stroustrup 2006]"Why Are Destructors Not Virtual by Default?"


...

Image Added Image Added Image Added