Handle Handling

Hmm … da gibt es so ein Thema, wo ich mit mir selbst lange diskutieren könnte und nie am Ende ankäme …

Und deshalb gibt es im GATE Projekt eine Entscheidung:

Verweise auf System-Ressourcen (unter Windows gerne HANDLE genannt) wandern in eine eigene Struktur, die dann darüber entscheiden darf, ob STACK oder HEAP der richtige Platz ist.


Wenn wir viel mit dem Betriebssystem reden, erhalten wird eine Vielzahl an unterschiedlichen Datentypen, die so wie Banknoten unseren Anspruch auf irgend etwas im System repräsentieren.

Und das ist ärgerlich, denn “unser eigenes” Plattform-unabhängiges Objekt würde dann auf jeder Platform anders aussehen.
Schlimmer: wir müssten auch die Platform-Header in unsere Header aufnehmen.

Wer so dreist ist, <windows.h> in seinen Header reinzuschreiben, wird zum Duell mit dem Degen herausgefordert! Denn so laden wir nämlich tausende Funktionsdeklarationen, die wir gar nicht brauchen, in “unseren” Headern, die vor allem das Kompilieren stark verlangsamen.

Das andere Extrem ist mit PIMPL verwandt und bedient sich eines void* Pointers. Die Systemressource wird per malloc oder new im Speicher allokiert und der Pointer verweist darauf.

… auch nicht gut, denn besonders wenn der Ressourcen-Typ selbst nur ein Pointer ist, pflastern wir den Heap mit kleinen Hilfsverweisen völlig zu.

Nur ein Sith kennt nichts als Extreme.

Und damit hatte Obi-Wan Kenobi recht.

Die Lösung liegt dazwischen:

Stellt euch vor, wir haben einen Datentypen, der im Falle kleiner Objekte diese in sich selbst speichert, bei größeren aber zum Pointer mutiert und die Daten am Heap allokiert, so haben wir eine Komponente, die sich dynamisch in die nächste-beste Lösung verwandelt.

In C++ wäre wieder alles so leicht:
Ein Template -> zwei Spezialisierungen -> perfekter Code

  1typedef void*  handlestore_t;
  2
  3template<class T, bool HEAP> struct HandleAccessor;
  4
  5/* HANDLE is on the Heap */
  6template<class T> struct HandleAccessor<T, true>
  7{
  8private:
  9    handlestore_t&        store;
 10public:
 11    HandleAccessor(handlestore_t& handle)
 12    : store(handle)
 13    { }
 14
 15    void create(T const& src)
 16    {
 17        T* ptr = new T(src);  // create a new Object on Heap
 18        this->store = ptr;    // store IS the pointer to the object
 19    }
 20    void destroy()
 21    {
 22        T* ptr = static_cast<T*>(this->store);
 23        delete ptr;  // delete the heap object
 24        this->store = NULL;
 25    }
 26    T& get()
 27    {
 28        T* ptr = static_cast<T*>(this->store);
 29        return *ptr;  // return a pointer to the heap object
 30    }
 31};
 32
 33/* HANDLE is embedded */
 34template<class T> struct HandleAccessor<T, false>
 35{
 36private:
 37    handlestore_t&        store;
 38public:
 39    HandleAccessor(handlestore_t& handle)
 40    : store(handle)
 41    { }
 42        
 43    void create(T const& src)
 44    {
 45        new (&this->store) T(src);  //create object IN the store
 46    }
 47    void destroy()
 48    {
 49        T* ptr = reinterpret_cast<T*>(&this->store);
 50        ptr->~T();  // explicitely call the destructor of T
 51        this->store = NULL;
 52    }
 53
 54    T& get()
 55    {
 56        // get a T-Pointer from store address
 57        T* ptr = reinterpret_cast<T*>(&this->store);
 58        return *ptr;
 59    }
 60};
 61
 62/* tiny helper trait-class: value is true, if sizeof(T) > sizeof(U) */
 63template<class T, class U> struct is_typesize_greater
 64{
 65    static const bool value = (sizeof(T) > sizeof(U));
 66};
 67
 68template<class T> struct Handle 
 69: HandleAccessor<T, is_typesize_greater<T, handlestore_t>::value>
 70{
 71    typedef HandleAccessor<T, 
 72          is_typesize_greater<T, handlestore_t>::value> baseclass_t;
 73          
 74    Handle(handlestore_t& store)
 75    : baseclass_t(store)
 76    { }
 77};
 78
 79// usage:
 80
 81typedef int small_type_t;
 82
 83typedef struct something
 84{
 85    int         x;
 86    double y;
 87    void*         c;
 88} big_type_t;
 89
 90int main()
 91{
 92    handlestore_t store1, store2;
 93
 94    Handle<small_type_t> h1(store1);
 95    Handle<big_type_t> h2(store2);
 96
 97    h1.create(42);
 98    h2.create(big_type_t());
 99
100    small_type_t& r1 = h1.get();
101    big_type_t& r2 = h2.get();
102
103    h1.destroy();
104    h2.destroy();
105        
106    return 0;
107}

Und in C?

Dafür haben wir ja das GATE Projekt … wir brauchen zwar einen weiteren Pointer im Store, weil C leider keine templates hat, aber ansonsten ist es ähnlich.

Jetzt kann es uns egal sein, ob ein Thread, Mutex oder eine Semaphore nur ein Pointer oder eine fette Struktur ist. “Unser Handle” sucht sich die richtige Art der Speicherung automatisch aus. In unseren Headern finden wir nur handlestore_t, und die Implementierungen ziehen sich den eigentlichen Datensatz aus dem Handle-Store heraus.

Der Coding-Aufwand hält sich in Grenzen und der Speicher- und Übersetzungsaufwand ebenso.