IssueConversion or deletion of incomplete class pointer 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; }
// ...
};
RiskWhen 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).
FixWhen 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.
Example - Deletion of Pointer to Incomplete Classclass Handle {
class Body *impl;
public:
~Handle() { delete impl; } //Noncompliant
// ...
};
In this example, the definition of class Body
is not visible when the
pointer to Body
is deleted.
Correction — Define Class Before DeletionOne 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() {}
// ...
};
Example - Downcasting to Pointer to Incomplete ClassFile1.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)); //Noncompliant
}
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 DowncastingOne 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;
}