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 Deleting a derived class object using a pointer to a base class that has a non-virtual destructor results in undefined behavior. To correct this situation destructor. Instead, the base class should be defined with a virtual destructor.
...
destructor. Deleting an object through a pointer to a type without a virtual destructor results in undefined behavior.
Noncompliant Code Example
In this non-compliant noncompliant example, b is a pointer to the base class Base is used to refer to a Derived object. When polymorphic pointer type whose static type is Base * and whose dynamic type is Derived *. When b is deleted, it results in undefined behavior results. Typically, the destructor for Base is invoked rather than the destructor for Derived. As a result, the object b refers to is not properly destroyed 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 publicmember of its class.
The implicitly declared destructor is not declared as virtual even in the presence of other virtual functions.
| Code Block | ||||
|---|---|---|---|---|
| ||||
struct class Base { public: Base virtual void f(); }; struct Derived : Base {}; void f() { 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 | ||||
|---|---|---|---|---|
| ||||
#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 | ||||
|---|---|---|---|---|
| ||||
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
...
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 | ||||
|---|---|---|---|---|
| ||||
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 meansTo correct this example, the destructor for Base should be declared virtual. This ensures that the object b refers to is correctly evaluated at runtime and that deleting b will call the destructor for class Derived.
| Code Block | ||||
|---|---|---|---|---|
| ||||
#include <new> class Base { public const int whichDerived; protected: Base(Base(int whichDerived) : whichDerived(whichDerived) {} public: Base() : Base(0) // Build Base object } virtual ~Base() { // Destroy Base object {} void operator delete(Base *, std::destroying_delete_t); }; struct Derived1 final : Base { Derived1() : Base(1) {} }; struct Derived2 final : Base { Derived2() : Base(2) {} }; class Derived : public Base { public:void Base::operator delete(Base *b, std::destroying_delete_t) { switch Derived(b->whichDerived) { case 0: // Build Derived object b->~Base(); break; case }1: ~Derived static_cast<Derived1 *>(b)->~Derived1() {; break; case 2: // Destroy Derived objectstatic_cast<Derived2 *>(b)->~Derived2(); } }::operator delete(b); }; void functionf(void) { Base *b = new DerivedDerived1(); // ... delete b; } |
Destructors are not virtual by default because many classes are not designed to be used as base classes. Additionally, many base classes are used only for reasons of implementation, and are not used in a polymorphic fashion. Virtual functions only make sense in classes that act as interfaces to objects of derived classes.
Risk Assessment
Risk Assessment
Attempting to destruct a polymorphic object that does not have a virtual destructor declared results in Calling the wrong destructor for a polymorphic object may lead to undefined behavior. In practice, potential consequences include abnormal program termination and resource mismanagementmemory leaks.
Rule | Severity | Likelihood | Detectable |
|---|
Repairable | Priority | Level |
|---|
OOP52-CPP |
2 (medium)
2 (probable)
2 (medium)
P8
L2
Automated Detection
Klocwork can detect violations of this rule with the CL.MLK.VIRTUAL checker. See Klocwork Cross Reference
References
| Wiki Markup |
|---|
\*\[[Stroustrup 06|AA. Bibliography#Stroustrup 06]\] Why are destructors not virtual by default? |
Low | Likely | No | No | P3 | L3 |
Automated Detection
Tool | Version | Checker | Description | ||||||
|---|---|---|---|---|---|---|---|---|---|
| Astrée |
| non-virtual-public-destructor-in-non-final-class | Partially checked | ||||||
| Axivion Bauhaus Suite |
| CertC++-OOP52 | |||||||
| Clang |
| -Wdelete-non-virtual-dtor | |||||||
| CodeSonar |
| LANG.STRUCT.DNVD | delete with Non-Virtual Destructor | ||||||
| Helix QAC |
| C++3402, C++3403, C++3404 | |||||||
| Klocwork |
| CL.MLK.VIRTUAL | |||||||
| LDRA tool suite |
| 303 S | Partially implemented | ||||||
| Parasoft C/C++test |
| CERT_CPP-OOP52-a | Define a virtual destructor in classes used as base classes which have virtual functions | ||||||
| Polyspace Bug Finder |
| CERT C++: OOP52-CPP | Checks for situations when a class has virtual functions but not a virtual destructor (rule partially covered) | ||||||
| PVS-Studio |
| V599, V689 | |||||||
| RuleChecker |
| non-virtual-public-destructor-in-non-final-class | Partially checked | ||||||
| SonarQube C/C++ Plugin |
| S1235 |
Related Vulnerabilities
Search for other vulnerabilities resulting from the violation of this rule on the CERT website.
Related Guidelines
| SEI CERT C++ Coding Standard | EXP51-CPP. Do not delete an array through a pointer of the incorrect type |
Bibliography
| [ISO/IEC 14882-2014] | Subclause 5.3.5, "Delete" |
| [Stroustrup 2006] | "Why Are Destructors Not Virtual by Default?" |
...
OOP33-CPP. Do not slice polymorphic objects 13. Object Oriented Programming (OOP) VOID 14. Templates and the STL (STL)