Domanda

Per chi proviene da OO (C #, Java, Scala) apprezzo molto altamente i principi di entrambi riutilizzo del codice e il tipo di sicurezza. Digitare argomenti nelle lingue di cui sopra fare il lavoro e consentire strutture di dati generici che sono sia di tipo-sicuri e non il codice 'rifiuti'.

Come vengo bloccato in C, io sono consapevole che devo fare un compromesso e mi piacerebbe che fosse quella giusta. In entrambi i miei strutture di dati hanno un void * in ogni nodo / elemento e ho perso la sicurezza di tipo o devo ri-scrivere le mie strutture e codice per ogni tipo che voglio usarli con.

La complessità del codice è un fattore evidente: iterazione attraverso un array o una lista collegata è banale e aggiungendo un *next ad una struct è sforzo supplementare; in questi casi non ha senso cercare di strutture e riutilizzo del codice. Ma per le strutture più complesse la risposta non è così ovvia.

C'è anche modularità e controllabilità: separare il tipo e le sue operazioni dal codice che utilizza la struttura rende il test più facile. L'inverso è anche vero:. Testare l'iterazione del codice su una struttura, mentre si sta cercando di fare altre cose diventa disordinato

Allora, qual è il tuo consiglio? void * e il riutilizzo o il tipo di sicurezza e duplicati codice? Ci sono dei principi generali? Sto cercando di forzare OO su procedurale quando non si adatta?

Modifica :! Si prega di non raccomandare C ++, la mia domanda è di circa C

È stato utile?

Soluzione

Direi uso void * in modo da poter riutilizzare il codice. E 'più di lavoro di re-implementare per esempio una lista collegata, che per essere sicuri di ottenere / impostare i dati nella lista in modo appropriato.

Prendere come molti suggerimenti da glib possibile, trovo le loro strutture di dati molto piacevole e facile da usare, e hanno avuto pochi problemi a causa della perdita di sicurezza tipo.

Altri suggerimenti

Credo che si dovrà trovare un equilibrio tra i due, proprio come lei suggerisce. Se il codice è solo poche righe e banale avrei duplicarlo, ma se è più complesso, vorrei prendere in considerazione lavorare con void* per evitare di dover fare qualsiasi potenziale di fissaggio e manutenzione bug in più punti e anche per ridurre la dimensione del codice.

Se si guarda alla libreria di runtime C, c'è diverse funzioni "generici" che lavorano con void*, un esempio comune è l'ordinamento con qsort. Sarebbe follia duplicare il codice per ogni tipo che desideri ordinare.

Non c'è niente di sbagliato con l'utilizzo di puntatori void. Non hanno nemmeno bisogno di lanciare loro quando assegnandoli a una variabile di tipo di puntatore in quanto la conversione è fatta internamente. Si migtht essere la pena di avere uno sguardo a questo: http: //www.cpax. org.uk/prg/writings/casting.php

La risposta a questa domanda è lo stesso di ottenere modelli efficienti per elenco di link in C ++.

a) creare una versione astratta del algoritmo che utilizza void * o qualche Sottratto tipo

b) Creazione di un'interfaccia pubblica leggero per chiamare gli algoritmi di tipo astratto e di casta tra di loro.

Ad esempio.

typedef struct simple_list
  {
  struct simple_list* next;
  } SimpleList;

void add_to_list( SimpleList* listTop, SimpleList* element );
SimpleList* get_from_top( SimpleList* listTop );
// the rest

#define ListType(x) \
    void add_ ## x ( x* l, x* e ) \
        { add_to_list( (SimpleList*)l, (SimpleList*)x ); } \
    void get_ ## x ( x* l, x* e ) \
        { return (x*) get_from_to( (SimpleList*)l ); } \
   /* the rest */

typedef struct my_struct
  {
  struct my_struct* next;
  /* rest of my stuff */
  } MyStruct;

ListType(MyStruct)

MyStruct a;
MyStruct b;

add_MyStruct( &a, &b );
MyStruct* c = get_MyStruct(&a);

ecc ecc.

Usiamo OO in C molto qui, ma solo per l'incapsulamento e astrazione, senza il polimorfismo o giù di lì.

Il che significa che abbiamo tipi specifici, come FooBar (Foo una, ...), ma, per la nostra collezione "classi", usiamo void *. Basta usare void * in cui potrebbero essere utilizzati più tipi, ma, così facendo, assicurarsi che non è necessario l'argomento di essere di un tipo specifico. Come da collezione, avendo * vuoto è a posto, perché la raccolta non si preoccupa il tipo. Ma se la funzione può accettare di tipo A e tipo B, ma nessun altro, fare due varianti, una per una e una per b.

Il punto principale è quello di utilizzare un void * solo se non vi interessa circa il tipo.

Ora, se si dispone di 50 tipi con la struttura stessa base (diciamo, int a, int b; come primi membri di tutti i tipi), e vogliono una funzione di agire su quei tipi, basta fare i primi membri comuni di tipo di per sé, quindi effettuare la funzione accettare questo, e passare object-> ab o (AB *) oggetto è il tipo è opaco, entrambi funziona se ab è il primo campo nella struct.

È possibile utilizzare le macro, che funziona con qualsiasi tipo e il compilatore controllerà staticamente il codice espanso. Il rovescio della medaglia è che la densità di codice (in binario) destinata a peggiorare e sono più difficili da eseguire il debug.

Ho fatto questa domanda su funzioni generiche po 'di tempo fa e le risposte potrebbero aiutare.

void* Sicuramente generica, non duplicare il codice!

Si tenga conto del fatto che questo dilemma è stato considerato da molti un programmatore C, e molti grandi progetti C. Tutti i progetti C gravi che abbia mai incontrato, sia open source o commerciale, scelto il void* generico. Quando viene utilizzato con cura e avvolto in una buona API, è appena un onere per l'utente della biblioteca. Inoltre, è void* idiomatica C, consigliato direttamente in K e R2. E 'il modo in cui la gente si aspetta di codice da scrivere, e qualsiasi altra cosa sarebbe sorprendente e male accettato.

È possibile costruire una (specie di) quadro OO in C, ma perdere un sacco di benefici ... come un sistema di tipo OO che il compilatore capisce. Se ti ostini a fare OO in un linguaggio simile al C, C ++ è una scelta migliore. E 'più complicato di vaniglia C, ma almeno si ottiene un adeguato supporto linguistico per OO.

EDIT: Ok ... se insistete che noi sconsigliamo C ++, vi consiglio di non fare OO in C. Felice? Per quanto riguarda le vostre abitudini OO sono interessati, si dovrebbe probabilmente pensare in termini di "oggetti", ma lasciare ereditarietà e polimorfismo fuori della vostra strategia di implementazione. Genericità (usando puntatori a funzione) dovrebbe essere usato con parsimonia.

EDIT 2: In realtà, penso che l'uso di void * in un elenco C generica è ragionevole. Si sta solo cercando di costruire un quadro OO finta utilizzando le macro, puntatori a funzione, dispacciamento e quel tipo di sciocchezze che secondo me è una cattiva idea.

In Java tutte le collezioni di pacchetto java.util a effetto stiva equivalente di puntatore void* (la Object).

Sì, farmaci generici (introdotto nel 1.5) aggiungere lo zucchero sintattico e vi impedisce di codifica assegnazioni non sicuri, ma il tipo di archiviazione rimane Object.

Quindi, penso che non c'è crimine OO commesso quando si utilizza void* per generico tipo di quadro.

Vorrei anche aggiungere inlines tipo-specifici o involucri macro che assegnano / recuperano i dati dalle strutture generiche se fate questo spesso nel codice.

P.S. L'unica cosa che non si deve fare è usare void** per tornare assegnate tipi generici / riassegnati. Se si controlla le firme di malloc/realloc si vedrà che si può ottenere allocazioni di memoria corretto senza puntatore void** temuta. Sto solo dicendo questo perché ho visto questo in qualche progetto open-source, che non voglio nominare qui.

Un contenitore generico può essere spostato con un po 'di lavoro in modo che possa essere istanziato nelle versioni type-safe. Ecco un esempio, intestazione completa legati seguito:

/ * generico implementazione * /

struct deque *deque_next(struct deque *dq);

void *deque_value(const struct deque *dq);

/* Prepend a node carrying `value` to the deque `dq` which may
 * be NULL, in which case a new deque is created.
 * O(1)
 */
void deque_prepend(struct deque **dq, void *value); 

Dal intestazione che può essere utilizzato per creare un'istanza specifici tipi di avvolto deque

#include "deque.h"

#ifndef DEQUE_TAG
#error "Must define DEQUE_TAG to use this header file"
#ifndef DEQUE_VALUE_TYPE
#error "Must define DEQUE_VALUE_TYPE to use this header file"
#endif
#else

#define DEQUE_GEN_PASTE_(x,y) x ## y
#define DEQUE_GEN_PASTE(x,y) DEQUE_GEN_PASTE_(x,y)
#define DQTAG(suffix) DEQUE_GEN_PASTE(DEQUE_TAG,suffix)

#define DQVALUE DEQUE_VALUE_TYPE

#define DQREF DQTAG(_ref_t)

typedef struct {
    deque_t *dq;
} DQREF;

static inline DQREF DQTAG(_next) (DQREF ref) {
    return (DQREF){deque_next(ref.dq)};
}

static inline DQVALUE DQTAG(_value) (DQREF ref) {
    return deque_value(ref.dq);
}

static inline void DQTAG(_prepend) (DQREF *ref, DQVALUE val) {
    deque_prepend(&ref->dq, val);
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top