c++ - Optionally safety-checked cast on possibly incomplete type -
pursuant simple, intrusively reference-counted object system, have template<typename t> class handle
, meant instantiated subclass of countedbase
. handle<t>
holds pointer t
, , destructor calls decref
(defined in countedbase
) on pointer.
normally, cause problems when trying limit header dependencies using forward declarations:
#include "handle.h" class foo; // forward declaration struct mystruct { handle<foo> foo; // okay, but... }; void bar() { mystruct ms; } // ...there's error here, implicit ~mystruct calls // handle<foo>::~handle(), wants foo complete // type can call foo::decref(). solve this, have // #include definition of foo.
as solution, rewrote handle<t>::~handle()
follows:
template<typename t> handle<t>::~handle() { reinterpret_cast<countedbase*>(m_ptr)->decref(); }
note i'm using reinterpret_cast
here instead of static_cast
, since reinterpret_cast
doesn't require definition of t
complete. of course, won't perform pointer adjustment me... long i'm careful layouts (t
must have countedbase
leftmost ancestor, must not inherit virtually, , on couple of unusual platforms, vtable magic necessary), it's safe.
what really nice, though, if layer of static_cast
safety possible. in practice, definition of t
usually complete @ point handle::~handle
instantiated, making perfect moment double-check t
inherits countedbase
. if it's incomplete, there's not can do... if it's complete, sanity check good.
which brings us, finally, question: is there way compile-time check t
inherits countedbase
not cause (spurious) error when t
not complete?
[usual disclaimer: i'm aware there potentially unsafe and/or ub aspects use of incomplete types in way. nevertheless, after great deal of cross-platform testing , profiling, i've determined practical approach given unique aspects of use case. i'm interested in compile-time checking question, not general code review.]
using sfinae on sizeof
check whether type complete :
struct countedbase { void decref() {} }; struct incomplete; struct complete : countedbase {}; template <std::size_t> struct size_tag; template <class t> void decref(t *ptr, size_tag<sizeof(t)>*) { std::cout << "static\n"; static_cast<countedbase*>(ptr)->decref(); } template <class t> void decref(t *ptr, ...) { std::cout << "reinterpret\n"; reinterpret_cast<countedbase*>(ptr)->decref(); } template <class t> struct handle { ~handle() { decref(m_ptr, nullptr); } t *m_ptr = nullptr; }; int main() { handle<incomplete> h1; handle<complete> h2; }
output (note order of destruction reversed) :
static reinterpret
trying complete type not derive countedbase
yields :
main.cpp:16:5: error: static_cast 'oops *' 'countedbase *' not allowed
that being said, think more elegant (and more explicit) approach introduce class template incomplete<t>
, such handle<incomplete<foo>>
compiles reinterpret_cast
, , else tries static_cast
.
Comments
Post a Comment