2015-08-06 14 views
10

prepararsi per una domanda un po '"contorta" ...vuoto * come generico in C, è sicuro?

Ho implementato in passato molta struttura dati (albero, lista, tabella hash, grafico pure), usando la macro che posso implementare un po 'o generico. Comunque stavo vagando se è possibile implementare una struttura dati generica usando il puntatore vuoto, ma in qualche modo mi piacerebbe essere in grado di usare un typecheking ...

Non so se è chiaro quello che sto cercando di diciamo ... ma fondamentalmente non penso sia sempre sicuro mettere "void *" come generico, allo stesso tempo non penso sia sempre una buona idea usare la macro come modo per creare una struttura dati generica (poiché fondamentalmente ciò che fa un preprocessore con la macro è la sostituzione del codice), perché se si guardano intorno al web è possibile trovare tali esempi.

Una buona idea potrebbe essere, a mio parere ma probabilmente non ho ragione, è utilizzare la macro per creare un'interfaccia standard per i dati memorizzati in una struttura dati, tra le funzioni di interfaccia metterei il codice per il corretto tipo di controllo, dato un vuoto *. Ispirato dalla tecnica dell'ingegnere del software, questo potrebbe essere un buon modo per procedere.

È sicuramente vero che probabilmente per cose troppo sofisticate sarebbe meglio cambiare linguaggio (C++/Java) ma è anche vero che questo non è sempre possibile.

Quindi in sintesi ... come viene gestito il problema del "generico" in C? mi affido alla tua esperienza per una risposta!

+0

È possibile utilizzare 'void *' come un puntatore generico, ma è necessario inoltre memorizzare informazioni su ciò che viene indicato, se si desidera recuperarlo effettivamente. –

+0

Non mi è mai piaciuto 'void qsort (void * base, size_t nitems, size_t size, int (* compar) (const void *, const void *))', ma non c'è davvero niente di meglio per fare il lavoro. http://www.tutorialspoint.com/c_standard_library/c_function_qsort.htm – user3528438

+0

non è per niente contorto. è un uso diffuso per implementare ds generici usando 'void *' per puntare a un altro blocco di dati. – HuStmpHrrr

risposta

6

In breve, non c'è modo conveniente per ottenere strutture di dati generici type-safe e funzioni in C.

non generico:

struct node { 
    int value; 
    struct node *next; 
}; 

generico, ma non sicuro, un void* non ha informazioni sul tipo:

struct node { 
    void *value; 
    struct node *next; 
}; 

sicuro, ma brutto:

#define DECLARE_NODE_TYPE(type) \ 
    struct node_##type { \ 
    type value; \ 
    struct node_##type *next; \ 
    }; 

DECLARE_NODE_TYPE(int) 
node_int *x = ... 

stessa idea, ma un po 'meno brutto:

// declare_node_type.h 

struct node_##NODE_TYPE { 
    NODE_TYPE value; 
    struct node_##NODE_TYPE *next; 
}; 

#undef NODE_TYPE 

// elsewhere 

#define NODE_TYPE int 
#include "declare_node_type.h" 

node_int *x = ... 

Generico e sicuro, ma C++, non C:

template<typename T> 
struct node { 
    T value; 
    node<T> *next; 
}; 

node<int> *x = ... 
+0

generalmente la terza via non è raccomandata. ha generato un mucchio di codice che potrebbe non essere richiamato. 'void *' è un modo migliore. – HuStmpHrrr

+1

@HuStmpHrrr: È un compromesso. Se vuoi sicurezza, devi anche assicurarti di non generare codice inutilizzato, o almeno che stai rimuovendo simboli inutilizzati e combinando simboli duplicati nel file binario. Questo è vero anche in C++. –

+1

Ho visto che la terza via è molto usata. Direi che è quasi il modo standard di fare modelli in C. –

1

Si può fare più sicuro roba con void*; tornare all'esempio collegata di Jon Purdy:

typedef struct { 
    union { 
     void* data; // generic data 
     int idata; // int is not stored dynamically 
    }; 
    int type; // additional type information 
    Node* next; // link 
} Node; 

#define NODE_TYPE_INT 0 
Node* createNodeInt(Node* self, Node* next, int value) { 
    self->idata = value; 
    self->type = NODE_TYPE_INT; 
    self->next = next; 
    return self; 
} 

// in this case relying on user defined types... 
Node* createNodeGeneric(Node* self, Node* next, void* data, int type) { 
    assert(type != NODE_TYPE_INT && ..); 
    self->data = data; 
    self->type = type; 
    self->next = next; 
    return self; 
} 

Un altro approccio è quello di utilizzare il primo membro comune come il tipo di base:

typedef struct { 
    int type; 
} Node; 

#define TYPE_BINARY 0 
typedef struct { 
    Node base; 
    Node* left; 
    Node* right; 
    int op; 
} BinaryOp; 

#define TYPE_LEAF_INT 1 
typedef struct { 
    Node base; 
    int a; 
} LeafInt; 

#define TYPE_LEAF_FLOAT 2 
typedef struct { 
    Node base; 
    float b; 
} LeafFloat; 

void op(BinaryOp* node) { 
    switch(node->left.type) { 
    case TYPE_BINARY: 
     op((BinaryOp*)node->left); 
     break; 
    case TYPE_LEAF_INT: 
     evalInt((LeafInt*)node->left); 
     break; 
    ... 
    } 
} 

Node* foo() { 
    LeafInt* left; 
    LeafFloat* right; 
    BinaryOp* op; 
    // allocate 
    ... 
    // init 
    createLeafInt(left, 42); 
    createLeafFloat(right, 13.37); 
    createBinaryOp(op, &left->base, &right->base); 
    // and return 
    return &op->base; 
} 
} 
+0

Questo è un modo per memorizzare le informazioni insieme ai dati, ma voglio dire era qualcosa come "questa lista può solo memorizzare questo tipo qui" vorrei solo passare i dati, e internamente la logica della lista dovrebbe essere in modo tale che il controllo sia fornito. – user8469759

+0

@Lukkio questo è esattamente il modo in cui fornisci un'API che esegue il controllo, ma il controllo avviene sul lato client, chiamando la funzione corretta nell'API o fornendo i dati necessari per la struttura dati ('type' fa semplicemente parte dei tuoi dati) – BeyelerStudios