Main Content

Conversion or deletion of incomplete class pointer

You delete or cast to a pointer to an incomplete class

Description

This defect occurs when you delete or cast to a pointer to an incomplete class. An incomplete class is one whose definition is not visible at the point where the class is used.

For instance, the definition of class Body is not visible when the delete operator is called on a pointer to Body:

class Handle {
  class Body *impl;  
public:
  ~Handle() { delete impl; }
  // ...
};

Risk

When you delete a pointer to an incomplete class, it is not possible to call any nontrivial destructor that the class might have. If the destructor performs cleanup activities such as memory deallocation, these activities do not happen.

A similar problem happens, for instance, when you downcast to a pointer to an incomplete class (downcasting is casting from a pointer to a base class to a pointer to a derived class). At the point of downcasting, the relationship between the base and derived class is not known. In particular, if the derived class inherits from multiple classes, at the point of downcasting, this information is not available. The downcasting cannot make the necessary adjustments for multiple inheritance and the resulting pointer cannot be dereferenced.

A similar statement can be made for upcasting (casting from a pointer to derived class to a pointer to a base class).

Fix

When you delete or downcast to a pointer to a class, make sure that the class definition is visible.

Alternatively, you can perform one of these actions:

  • Instead of a regular pointer, use the std::shared_ptr type to point to the incomplete class.

  • When downcasting, make sure that the result is valid. Write error-handling code for invalid results.

Examples

expand all

class Handle {
  class Body *impl;  
public:
  ~Handle() { delete impl; } 
  // ...
};

In this example, the definition of class Body is not visible when the pointer to Body is deleted.

Correction — Define Class Before Deletion

One possible correction is to make sure that the class definition is visible when a pointer to the class is deleted.

class Handle {
  class Body *impl;  
public:
  ~Handle();
  // ...
};
 
// Elsewhere
class Body { /* ... */ };
  
Handle::~Handle() {
  delete impl;
}
Correction — Use std::shared_ptr

Another possible correction is to use the std::shared_ptr type instead of a regular pointer.

#include <memory>
  
class Handle {
  std::shared_ptr<class Body> impl;
  public:
    Handle();
    ~Handle() {}
    // ...
};

File1.h:

class Base {
protected:
  double var;
public:
  Base() : var(1.0) {}
  virtual void do_something();
  virtual ~Base();
};

File2.h:

void funcprint(class Derived *);
class Base *get_derived(); 

File1.cpp:

#include "File1.h"
#include "File2.h"
 
void getandprint() {
  Base *v = get_derived();
  funcprint(reinterpret_cast<class Derived *>(v));
}

File2.cpp:

#include "File2.h"
#include "File1.h"
#include <iostream>
 
class Base2 {
protected:
  short var2;
public:
  Base2() : var2(12) {}
};
 
class Derived : public Base2, public Base {
  float var_derived;
public:
    Derived() : Base2(), Base(), var_derived(1.2f) {}
    void do_something()
    {
        std::cout << "var_derived: "
                  << var_derived << ", var : " << var
                  << ", var2: " << var2 << std::endl;
    }
 };
 
void funcprint(Derived *d) {
  d->do_something();
}
 
Base *get_derived() {
  return new Derived;
}

In this example, the definition of class Derived is not visible in File1.cpp when a Base* pointer to downcast to a Derived* pointer.

In File2.cpp, class Derived derives from two classes, Base and Base2. This information about multiple inheritance is not available at the point of downcasting in File1.cpp. The result of downcasting is passed to the function funcprint and dereferenced in the body of funcprint. Because the downcasting was done with incomplete information, the dereference can be invalid.

Correction — Define Class Before Downcasting

One possible correction is to define the class Derived before downcasting a Base* pointer to a Derived* pointer.

In this corrected example, the downcasting is done in File2.cpp in the body of funcprint at a point where the definition of class Derived is visible. The downcasting is not done in File1.cpp where the definition of Derived is not visible. The changes from the previous incorrect example are highlighted.

File1_corr.h:

class Base {
protected:
  double var;
public:
  Base() : var(1.0) {}
  virtual void do_something();
  virtual ~Base();
};

File2_corr.h:

void funcprint(class Base *);
class Base *get_derived(); 

File1.cpp:

#include "File1_corr.h"
#include "File2_corr.h"
 
void getandprint() {
  Base *v = get_derived();
  funcprint(v);
}

File2.cpp:

#include "File2_corr.h"
#include "File1_corr.h"
#include <iostream>
 
class Base2 {
protected:
  short var2;
public:
  Base2() : var2(12) {}
};
 
class Derived : public Base2, public Base {
  float var_derived;

public:
    Derived() : Base2(), Base(), var_derived(1.2f) {}
    void do_something()
    {
        std::cout << "var_derived: "
                  << var_derived << ", var : " << var
                  << ", var2: " << var2 << std::endl;
    }
};
 
void funcprint(Base *d) {
  Derived *temp = dynamic_cast<Derived*>(d);
  if(temp)  {
     d->do_something();
  }
  else {
      //Handle error
  }
}
 
Base *get_derived() {
  return new Derived;
}

Result Information

Group: Object Oriented
Language: C++
Default: On for handwritten code, off for generated code
Command-Line Syntax: INCOMPLETE_CLASS_PTR
Impact: Medium

Version History

Introduced in R2018b