Optionally, with a security check on a possibly incomplete type

According to a simple, intrusive scoring system, I have a template<typename T> class Handle that needs to be created with a subclass of CountedBase . Handle<T> contains a pointer to T , and its destructor calls DecRef (defined in CountedBase ) on that pointer.

This can usually cause problems when trying to limit header dependencies with forward declarations:

 #include "Handle.h" class Foo; // forward declaration struct MyStruct { Handle<Foo> foo; // This is okay, but... }; void Bar() { MyStruct ms; } // ...there an error here, as the implicit ~MyStruct calls // Handle<Foo>::~Handle(), which wants Foo to be a complete // type so it can call Foo::DecRef(). To solve this, I have // to #include the definition of Foo. 

As a solution, I rewrote Handle<T>::~Handle() as follows:

 template<typename T> Handle<T>::~Handle() { reinterpret_cast<CountedBase*>(m_ptr)->DecRef(); } 

Note that I use reinterpret_cast here instead of static_cast , since reinterpret_cast does not require the definition of T be complete. Of course, he will also not perform pointer adjustment for me ... but for now, I am careful with layouts ( T should have CountedBase as its left ancestor, should not inherit from it practically, and for a couple of unusual platforms, additional vtable magic is required), this safely.

Which would be really nice, if only if I could get an additional level of static_cast security, where possible. In practice, the definition of T usually ends at the point where Handle::~Handle is created, making the perfect moment to double check that T really inheriting from CountedBase . If it's incomplete, I can't do much ... but if it completes, a health check will be good.

Which brings us finally to my question: Is there a way to perform a compile-time check that T inherits from CountedBase that will not cause a (false) error when T not completed?

[Usually disclaimer: I know that there are potentially dangerous and / or UB aspects for using incomplete types in this way. However, after a large number of cross-platform tests and profiling, I decided that this is the most practical approach, given some unique aspects of my use. I'm interested in compile-time checking, not a general overview of the code.]

+5
source share
1 answer

Using SFINAE on sizeof to check if the type is 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; } 

Exit (note that the destruction order is canceled):

 static reinterpret 

Live on coliru

Trying with a full type that cannot be obtained from CountedBase yields:

 main.cpp:16:5: error: static_cast from 'Oops *' to 'CountedBase *' is not allowed 

I think a more elegant (and more explicit) approach would be to introduce an incomplete<T> class template, so that Handle<incomplete<Foo>> compiles into reinterpret_cast , and something else tries static_cast .

+2
source

All Articles