2015-08-05 2 views
5

Gemäß einem einfachen, intrusiv referenzierten Objektsystem habe ich eine template<typename T> class Handle, die mit einer Unterklasse von CountedBase instanziiert werden soll. Handle<T> enthält einen Zeiger auf T, und sein Destruktor ruft DecRef (definiert in CountedBase) auf diesem Zeiger auf.Optional sicherheitsgeprüft bei möglicherweise unvollständigem Typ

Normalerweise würde dies zu Problemen führen, wenn sie versuchen, Header-Abhängigkeiten zu begrenzen, indem Vorwärtsdeklarationen mit:

#include "Handle.h" 

class Foo; // forward declaration 

struct MyStruct { 
    Handle<Foo> foo; // This is okay, but... 
}; 

void Bar() { 
    MyStruct ms; 
} // ...there's 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. 

Als Lösung schrieb ich Handle<T>::~Handle() wie folgt:

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

Bitte beachte, dass ich bin Verwenden Sie reinterpret_cast hier anstelle von static_cast, da reinterpret_cast erfordert die Definition von T nicht vollständig sein. Natürlich wird es auch keine Zeigeranpassung für mich durchführen ... aber so lange ich mit Layouts vorsichtig bin (T muss CountedBase als seinen am weitesten links stehenden Vorfahren haben, darf nicht virtuell von ihm erben, und auf einigen ungewöhnlichen Plattformen , einige zusätzliche Magie ist notwendig), es ist sicher.

Was wäre wirklich schön wäre, wenn ich diese zusätzliche Schicht von static_cast Sicherheit wo möglich bekommen könnte. In der Praxis ist die Definition Tnormalerweise vollständig an dem Punkt, wo Handle::~Handle instanziiert ist, so dass es ein perfekter Moment, um zu überprüfen, dass T tatsächlich erbt von CountedBase. Wenn es unvollständig ist, gibt es nicht viel, was ich tun kann ... aber wenn es vollständig ist, wäre eine Plausibilitätsprüfung gut.

Das bringt uns schließlich zu meiner Frage: Gibt es eine Möglichkeit, eine Kompilierung-Check, dass T erbt von CountedBase, die einen (falschen) Fehler führen nicht zu tun, wenn T unvollständig erscheinen?

[Üblicher Haftungsausschluss: Ich bin mir bewusst, dass es auf diese Weise potentiell unsichere und/oder UB-Aspekte für die Verwendung von unvollständigen Typen gibt. Nach vielen plattformübergreifenden Tests und Profilergebnissen habe ich jedoch festgestellt, dass dies der praktikabelste Ansatz ist, wenn man einige einzigartige Aspekte meines Anwendungsfalls berücksichtigt. Ich habe Interesse an der Kompilierung-Prüfung Frage, nicht ein allgemeiner Code-Review]

+1

Sie möchten also die Kompilierzeitprüfung nur anwenden, wenn 'T' abgeschlossen ist? –

+0

Leider können Sie nicht eine 'is_complete'-Eigenschaft (die nicht UB ist) als diese [one] (http://coliru.stacked-crooked.com/a/56bb09c812f521c6) – Jarod42

+0

@ m.s tun. Ja das ist richtig. – Sneftel

Antwort

2

Mit SFINAE auf sizeof zu prüfen, ob die Art abgeschlossen ist.

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; 
} 

Ausgang (beachten Sie, dass die Reihenfolge der Zerstörung umgekehrt ist):

static 
reinterpret 

Live on Coliru

es mit einem kompletten Art Versuch, der nicht vonnicht herleitenergibt:

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

aber sagen, dass ich glaube, eine elegantere (und mehr expliziten) Ansatz solche einzuführen wäre, eine Klassenvorlage incomplete<T>, dass Handle<incomplete<Foo>> zum reinterpret_cast kompiliert, und alles andere versucht zu static_cast.

+0

Ich denke, es ist UB als '~ Handle()' wird ODR brechen, wenn irgendwann 'T' abgeschlossen ist und irgendwann nicht. – Jarod42

+0

@ Jarod42 Ich verstehe, was du meinst, aber ich habe keine Ahnung. Aber wäre das nicht der Fall bei einer Umsetzung der OP-Anforderungen? – Quentin

+0

Ich denke so, wie ich im Kommentar der Frage gesagt habe. – Jarod42

Verwandte Themen