2016-06-27 10 views
4

Ich entwerfe eine Iteratorschnittstelle für meine hashmap Datenstruktur. Das aktuelle Design sieht wie folgt aus:Opaque struct ohne Definition

// map.h 
typedef struct map_iterator map_iterator; 

// map.c 
struct map_iterator 
{ 
    // Implementation details 
}; 

// client.c 
map *m = map_new(); 
map_iterator *it = map_iterator_new(m); 
void *key, *value; 
while (map_iterator_next(it, &key, &value)) { 
    // Use key, value 
} 
map_iterator_free(it); 

Dies erfordert jedoch eine Heapzuordnung für das Iterator-Objekt, und der Kunde muss daran denken, die Iterator zu befreien, wenn sie fertig sind. Wenn ich den Iterator auf dem Stapel zurückkehren machen map_iterator_new, sieht der Code wie folgt aus:

// map.h 
typedef struct map_iterator 
{ 
    // Implementation details 
}; 

// client.c 
map *m = map_new(); 
map_iterator it = map_iterator_new(m); 
void *key, *value; 
while (map_iterator_next(&it, &key, &value)) { 
    // Use key, value 
} 

Dies erfordert jedoch, dass ich die Definition des map_iterator Struktur zu Client-Code zur Verfügung stellen (sonst bekomme ich einen unvollständigen Typ Fehler). Ich möchte diese Definition ausblenden und nur die Erklärung liefern.

Gibt es eine Möglichkeit, dies zu erreichen? Im Wesentlichen suche ich nach einer Möglichkeit, dem Client-Code mitzuteilen, dass "diese Struktur X Bytes einnimmt, damit Sie sie auf dem Stack zuweisen können, aber ich sage Ihnen nicht, wie Sie auf ihre Mitglieder zugreifen".

Edit: Nur Standard C, bitte! (keine Compiler-Erweiterungen/plattformspezifische Funktionen)

+0

Was ist unter 'Implementierungsdetails' im Iterator? – 2501

+0

@ 2501 Iteratorzustand, z.B. Zeiger auf hashmap, aktueller Bucket-Index, Änderungszähler usw. –

+1

Ich denke nicht, dass es ein Problem ist, einen Zeiger auf Ihren Iterator zu setzen und ihn danach freizugeben. Viele Bibliotheken, wie zum Beispiel Curl etc., stellen ebenfalls solche Schnittstellen bereit. Ich würde lieber die erste Option als die zweite verwenden. – ckruczek

Antwort

-1

Es gibt eine Funktion namens alloca, die Speicher auf dem Stack des Aufrufers reserviert. So könnte Ihr Code wie folgt aussehen:

// map.h 
typedef struct map_iterator map_iterator; 
extern int map_iterator_size; 
// map.c 
struct map_iterator 
{ 
    // Implementation details 
}; 
int map_iterator_size = sizeof(struct map_iterator); 
// client.c 
map *m = map_new(); 
map_iterator *it = alloca(map_iterator_size); 
map_iterator_new(m, it); 
void *key, *value; 
while (map_iterator_next(it, &key, &value)) { 
    // Use key, value 
} 
+0

Danke, aber ich habe vergessen zu erwähnen, dass ich eine strikt C99-konforme Lösung suche, tut mir leid. –

+0

'alloca' ist eine der Funktionen, die Sie nie in Betracht ziehen sollten, da es sich um Schwachstellen in Stack-Korruption handelt. Außerdem hat dies sehr wenig mit API-Design zu tun, da es sich um einen Codierungsfehler handelt, den der API-Benutzer macht. Schließlich haben unvollständige Typen keine 'sizeof()'. –

+0

Ich sehe keinen Unterschied (von Stack Korruption Perspektive) zwischen 'alloca()' und mit dem Objekt direkt auf Stapel. die 'sizeof' wird am Platz mit vollständiger Definition berechnet. – nothrow

0

Verwenden Sie die Serialisierung.

Sie dürfen immer ein Objekt des T in ein Array von unsigned char kopieren und dann zurück zu einem Objekt vom Typ T. Dieses Objekt ist das gleiche wie das ursprüngliche Objekt. T kann jeder Objekttyp sein, und dies kann mit automatischer Speicherdauer (Stack) erfolgen:

int value = 568; 
unsigned char store[sizeof(int)]; 
memcpy(store , &value , sizeof(int)); 
int other; 
memcpy(&other , store , sizeof(int)); 
assert(other == value), 

sein (ab) Dies kann verwendet, um die Implementierung von dem Benutzer zu verbergen. Definieren Sie zwei Strukturen, die die tatsächliche Implementierung definieren und für den Benutzer nicht sichtbar sind, und eine, die sichtbar ist und nur ein Array von unsigniertem Zeichen der entsprechenden Größe enthält.

implementation.c

#include "implementation.h" 

struct invisible 
{ 
    int element1; 
    char element2 
    float element3; 
    void** element4; 
}; 

_Static_assert(sizeof(struct invisible) <= VISIBLE_SIZE); 

struct visible New(void) 
{ 
    struct invisible i = { 1 , '2' , 3.0F , NULL }; 
    struct visible v = { 0 }; 
    memcpy(&v , &i , sizeof(i)); 
    return v; 
} 

void Next(struct visible* v) 
{ 
    struct invisible i = { 0 }; 
    memcpy(&i , &v , sizeof(i)); 
    i.element1++; //make some changes 
    const int next = i.element; 
    memcpy(&v , &i , sizeof(i)); 
    return next; 
} 

implementation.h

#define VISIBLE_SIZE 24 //make sure it greater or equal to the size of struct invisible 
struct visible 
{ 
    unsigned char[VISIBLE_SIZE]; 
}; 

struct visible New(void); 
int Next(struct visible* v); 

user.c

#include "implementation.h" 

void Func(void) 
{ 
    struct visible v = New(); 
    while(1) 
    { 
     const int next = Next(&v); 
     if(next == 10) 
     { 
       break; 
     } 
     printf("%d\n" , next); 
    } 
} 

Dieses Beispiel berührt nur das Element element1. In einer realen Implementierung können Sie die unsichtbare Struktur frei modifizieren.