The use of incomplete class declarations (also known as "forward" declarations) is common. While it is possible to declare pointers and references to incomplete classes, because the class definition is not available it is not possible to access a member of the class, determine the size of the class object, and so on. However, it is possible to cast and delete a pointer to an incomplete class, but this is never a good idea.
Non-Compliant Code Example (deleting)
Consider a common application of a handle/body idiom that implements an abstract data type by a handle that contains a pointer to an implementation class.
Code Block | ||||
---|---|---|---|---|
| ||||
class Body; // incomplete class declaration class Handle { public: Handle(); ~Handle() { delete impl_; } // deletion of pointer to incomplete class // ... private: Body *impl_; }; |
Because impl_
is a pointer to an undefined class, its deletion in Handle
's destructor results in undefined behavior if Body
has a non-trivial destructor. Even in the case where Body
does have a trivial destructor, this practice should be avoided. During maintenance a non-trivial destructor could be added to Body
, resulting in undefined behavior in the destructor for Handle
. Typical behavior in this case is that no destructor is called and the memory for Body
is released via a call to the usual global operator delete
. This may result in a resource leak or other bug if the destructor manages resources, or if the definition of Body
defines or inherits a member operator delete
.
Compliant Solution 1
The deletion of impl_
should be moved to a part of the code where Body
is defined.
Code Block | ||||
---|---|---|---|---|
| ||||
class Body { // ... }; Handle::~Handle() { delete impl_; } // correct. |
Compliant Solution 2
Alternatively, an appropriate smart pointer may be used in place of a raw pointer.
Code Block | ||||
---|---|---|---|---|
| ||||
class Handle { public: Handle(); ~Handle() {} // correct. // ... private: std::tr1::shared_ptr<Body> impl_; }; |
Note that we used a shared_ptr
to refer to the Body
. Other common smart pointers, including std::auto_ptr
, will still produce undefined behavior.
Non-Compliant Code Example (casting)
Similarly, while it is possible to cast a pointer or reference to an incomplete class, it is never a good idea to do so. Casting a class address often involves an adjustment of the address by a fixed amount that can only be determined after the layout and inheritance structure of the class is known, and this information is not available in the case of an incomplete class.
Code Block | ||||
---|---|---|---|---|
| ||||
class B { // ... }; B *getMeSomeSortOfB(); // ... class D; // incomplete declaration // ... B *bp = getMeSomeSortOfB(); D *dp = (D *)bp; // old-stlye cast: legal, but inadvisable dp = reinterpret_cast<D *>(bp); // new-style cast: legal, but inadvisable |
Both an old-style cast and a reinterpret_cast
may be used to cast a pointer to an incomplete class. However, the cast may result in a bad address.
Code Block | ||||
---|---|---|---|---|
| ||||
class D : public SomeClass, public B { // ... }; B *getMeSomeSortOfB() { return new D; } |
In this case, it is likely that a correct cast of a B *
to a D *
would have to adjust the address by a fixed amount. However, at the point the cast is translated by the compiler the required information is not available and the address adjustment will not take place.
In the case of an old-style cast, the address adjustment will, however, take place if the cast is performed at a point where the structure of the class D
is known. This different, context-dependent behavior of the old-style cast can result in very challenging bugs.
Compliant Solution
Avoid casting to or from pointers to incomplete classes. If such a cast is, for some reason, absolutely necessary, prefer reinterpret_cast
to an old-style cast because it will exhibit the same behavior whether or not the class is incomplete.
Risk Assessment
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 destructor. This can result in program termination, a runtime signal, or resource leaks.
Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
---|---|---|---|---|---|
EXP39-CPP | medium | unlikely | medium | P4 | L3 |
Automated Detection
Tool | Version | Checker | Description |
---|---|---|---|
Coverity | 6.5 | DELETE_VOID | Fully Implemented |
Bibliography
[Dewhurst 03] Gotcha 39: Casting Incomplete Types
EXP38-CPP. Do not modify constant values 03. Expressions (EXP) 04. Integers (INT)