Versions Compared

Key

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

Referring to objects of incomplete class type, also known as forward declarations, is a common practice. One such common usage is with the "pimpl idiom" [Sutter 00] where whereby an opaque pointer is used to hide implementation details from a public-facing API. However, attempting to delete a pointer to an object of incomplete class type can lead to undefined behavior. The C++ Standard, [expr.delete], paragraph 5, states [ISO/IEC 14882-2014]:

...

Do not attempt to delete a pointer to an object of incomplete type. While Although it is well-formed if the class has no nontrivial destructor and no associated deallocation function, it would become undefined behavior were a nontrivial destructor or deallocation function added later. It would be possible to check for a nontrivial destructor at compile time using a static_assert and the std::is_trivially_destructible type trait, not but no such type trait exists to test for the presence of a deallocation function.

...

In this noncompliant code example, a class attempts to implement the pimpl idiom , but deletes a pointer to an incomplete class type, resulting in undefined behavior if Body has a nontrivial destructor:

Code Block
bgColor#FFcccc
langcpp
class Handle {
  class Body *Impl;  // Declaration of a pointer to an incomplete class.
public:
  ~Handle() { delete Impl; } // Deletion of pointer to an incomplete class.
  // ...
};

Compliant Solution (delete)

...

Code Block
bgColor#ccccff
langcpp
class Handle {
  class Body *Impl;  // Declaration of a pointer to an incomplete class.
public:
  ~Handle();
  // ...
};

// Elsewhere.
class Body { /* ... */ };
 
Handle::~Handle() {
  delete Impl;
}

...

Pointer downcasting (casting a pointer to a base class into a pointer to a derived class) may require adjusting the address of the pointer by a fixed amount that can only be determined only when the layout of the class inheritance structure is known. In this noncompliant code example, f() retrieves a polymorphic pointer of complete type B from getD(). That pointer is then cast to a pointer of incomplete type D, before being passed to g(). Casting to a pointer to the derived class may fail to properly adjust the resulting pointer, resulting in causing undefined behavior when the pointer is dereferenced by calling d->doSomething().

...

When compiled with Clang 3.5, the noncompliant code example prints the following:

Code Block
f: 1.89367e-40, d: 5.27183e-315, s: 0

Similarly, unexpected values are printed when the example is run in Microsoft Visual Studio 2013 and GCC 4.9.0.

...

This compliant solution assumes that the intent is to hide implementation details by using incomplete class types. Instead of requiring a D * to be passed to g(), it expects a B * type instead:

Code Block
bgColor#ccccff
langcpp
// File1.h
class B {
protected:
  double d;
public:
  B() : d(1.0) {}
};
 
// File2.h
void g(class B *); // Accepts a B object, expects a D object
class B *getD(); // Returns a D object

// File1.cpp
#include "File1.h"
#include "File2.h"

void f() {
  B *v = getD();
  g(v);
}
 
// File2.cpp
#include "File2.h"
#include "File1.h"
#include <iostream>

class Hah {
protected:
  short s;
public:
  Hah() : s(12) {}
};

class D : public Hah, public B {
  float f;
public:
  D() : Hah(), B(), f(1.2f) {}
  void doSomething() { std::cout << "f: " << f << ", d: " << d << ", s: " << s << std::endl; }
};

void g(B *d) {
  D *t = static_cast<D *>(d);
  if (t) {
    t->doSomething();
  } else {
    // Handle error.
  }
}

B *getD() {
  return new D;
}

...

Casting pointers or references to incomplete classes can result in bad addresses. Deleting a pointer to an incomplete class results in undefined behavior if the class has a non-trivial nontrivial destructor. This Doing so can result in cause program termination, a runtime signal, or resource leaks.

...

Tool

Version

Checker

Description

Coverity6.5DELETE_VOIDFully Implemented
Clang
Include Page
Clang_V
Clang_V
-Wdelete-incomplete 

Related Vulnerabilities

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

Related Guidelines

...

Bibliography

[ISO/IEC 14882-2014]

5.3.5, "Delete"
5.2.9, "Static Cast"
5.2.10, "Reinterpret Cast"
5.4, "Explicit Type Conversion (Cast Notation)
4.10, "Pointer Conversions"

[Sutter 00]"Compiler Firewalls and the Pimpl Idiom"
[Dewhurst 03]Gotcha 39, "Casting Incomplete Types"

...