...
Pointer downcasting to a pointer of incomplete class type has similar caveats. Pointer upcasting (casting from a more derived type to a less derived type) is a standard implicit conversion operation. C++ allows static_cast
to perform the inverse operation, pointer downcasting, via [expr.static.cast], paragraph 7. However, when the pointed-to type is incomplete, the compiler is unable to make any class offset adjustments that may be required in the presence of multiple inheritanceinheritances, resulting in a pointer that cannot be validly dereferenced.
The reinterpret_cast
of a pointer type is defined by [expr.reinterpret.cast], paragraph 7, as being static_cast<cv T *>(static_cast<cv void *>(PtrValue))
, meaning that reinterpret_cast
is simply a sequence of static_cast
operations. C-style casts of a pointer to incomplete object type are defined as using either static_cast
or reinterpret_cast
(which is picked is unspecified) in [expr.cast], paragraph 5.
...
In this compliant solution, the deletion of impl
is moved to a part of the code where Body
is defined:.
Code Block | ||||
---|---|---|---|---|
| ||||
class Handle { class Body *impl; // Declaration of a pointer to an incomplete class public: ~Handle(); // ... }; // Elsewhere class Body { /* ... */ }; Handle::~Handle() { delete impl; } |
...
In this compliant solution, a std::shared_ptr
is used to own the memory to impl
. Note that a A std::shared_ptr
is capable of referring to an incomplete type, but a std::unique_ptr
is not.
...
Code Block | ||||
---|---|---|---|---|
| ||||
// File1.h class B { protected: double d; public: B() : d(1.0) {} }; // File2.h void g(class D *); class B *get_d(); // Returns a pointer to a D object // File1.cpp #include "File1.h" #include "File2.h" void f() { B *v = get_d(); g(reinterpret_cast<class D *>(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 do_something() { std::cout << "f: " << f << ", d: " << d << ", s: " << s << std::endl; } }; void g(D *d) { d->do_something(); } B *get_d() { return new D; } |
Implementation Details
When compiled with Clang 3.8 and the function f()
is executed, the noncompliant code example prints the following:.
Code Block |
---|
f: 1.89367e-40, d: 5.27183e-315, s: 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:.
Code Block | ||||
---|---|---|---|---|
| ||||
// File1.h -- contents identical. // File2.h void g(class B *); // Accepts a B object, expects a D object class B *get_d(); // Returns a pointer to a D object // File1.cpp #include "File1.h" #include "File2.h" void f() { B *v = get_d(); g(v); } // File2.cpp // ... all contents are identical until ... void g(B *d) { D *t = dynamic_cast<D *>(d); if (t) { t->do_something(); } else { // Handle error } } B *get_d() { return new D; } |
...