Pergunta

Vindo de OO (C #, Java, Scala) Eu valorizo ??muito bem os princípios de ambos reutilização de código e tipo de segurança. argumentos de tipo nas línguas acima fazer o trabalho e permitir estruturas de dados genéricos que são tanto de tipo seguro e não codificam 'resíduos'.

Como eu ficar preso em C, estou ciente de que eu tenho que fazer um compromisso e eu gostaria que fosse o caminho certo. Ou minhas estruturas de dados têm um void * em cada nó / elemento e I tipo de segurança perder ou eu tenho que re-escrever minhas estruturas e código para cada tipo eu quero usá-los com.

A complexidade do código é um fator óbvio: a iteração através de uma matriz ou uma lista ligada é trivial e adicionando um *next a um struct é nenhum esforço extra; nestes casos não faz sentido tentar e estruturas de reutilização e código. Mas para estruturas mais complicadas, a resposta não é tão óbvia.

Há também modularidade e capacidade de teste: separar o tipo e as suas operações a partir do código que usa a estrutura faz testá-lo mais fácil. O inverso também é verdadeiro:. Testando a iteração de algum código sobre uma estrutura, enquanto ele está tentando fazer outras coisas fica confuso

Então, qual é o seu conselho? void * e reutilização nem a recepção de segurança e código duplicado? Existem princípios gerais? Am I tentar forçar OO sobre processual quando não se encaixam?

Editar : Por favor, não recomendo C ++, a minha pergunta é sobre C

Foi útil?

Solução

Eu diria uso void * assim você pode reutilizar o código. É mais trabalho para re-implementar por exemplo uma lista ligada, do que ter certeza de obter / definir os dados na lista corretamente.

Tome-se como muitas sugestões de glib possível, acho suas estruturas de dados muito agradável e fácil de usar, e tiveram pouca dificuldade por causa da perda de segurança de tipo.

Outras dicas

Eu acho que você vai ter que encontrar um equilíbrio entre os dois, assim como você sugere. Se o código for apenas algumas linhas e I trivial seria duplicá-lo, mas se é mais complexa, eu consideraria trabalhar com void* para evitar ter que fazer qualquer correção de bugs potencial e manutenção em vários lugares e também para reduzir o tamanho do código.

Se você olhar para a biblioteca C runtime, há várias funções "genéricos" que o trabalho com void*, um exemplo comum é a classificação com qsort. Seria uma loucura para duplicar este código para cada tipo que você gostaria de tipo.

Não há nada de errado em usar ponteiros void. Você não precisa nem lançá-los ao atribuir-lhes a uma variável do tipo de ponteiro já que a conversão é feita internamente. Ele migtht valer a pena ter um olhar para este: http: //www.cpax. org.uk/prg/writings/casting.php

A resposta a esta pergunta é a mesma como a obtenção de modelos eficientes para a lista de links no C ++.

a) Criar uma versão abstrata do algoritmo que usa void * ou algum abstraída Tipo

b) Criar uma interface pública peso leve para chamar os algoritmos abstraída Tipo e casta entre eles.

Por exemplo.

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

etc etc.

Nós usamos OO em C muito aqui, mas apenas para encapsulamento e abstração, nenhum polimorfismo ou assim.

que significa que temos tipos específicos, como FooBar (Foo um, ...), mas, para nossos coleção "classes", usamos void *. Basta usar * vazio onde múltiplos tipos poderia ser usado, mas, ao fazê-lo, certifique-se que você não precisa o argumento de ser de um tipo específico. Como por coleção, tendo void * está bem, porque a coleção não se preocupa com o tipo. Mas se a sua função pode aceitar tipo A e tipo b, mas nenhum outro, fazer duas variantes, um para um e um para b.

O ponto principal é usar a * nula somente quando você não se preocupam com o tipo.

Agora, se você tem 50 tipos com a mesma estrutura de base (digamos, int a; int b; como primeiros membros de todos os tipos), e quer uma função de agir sobre esses tipos, basta fazer o comum primeira membros de uma digite por si só, em seguida, fazer a função aceitar isso, e passar object-> ab ou (AB *) objeto é o seu tipo é opaco, ambos irão funcionar se ab é o primeiro campo em sua estrutura.

Você pode usar macros, eles vão trabalhar com qualquer tipo e o compilador irá verificar estaticamente o código expandido. A desvantagem é que a densidade do código (no binário) vai piorar e eles são mais difíceis de depuração.

Eu fiz esta pergunta sobre funções genéricas há algum tempo e as respostas poderia ajudá-lo.

Você pode eficientemente adicionar informações de tipo, herança e polimorfismo de estruturas de dados de C, que é o que C ++ faz. ( http://www.embedded.com/97/fe29712.htm )

void* Definitivamente genérico, código nunca duplicado!

Leve em conta que este dilema foi considerado por muitos um programador C, e muitos grandes projetos C. Todos os projetos C graves que eu já encontrei, se open-source ou comercial, escolheu o void* genérico. Quando usado com cuidado e envolto em uma boa API, é apenas um fardo para o usuário da biblioteca. Além disso, é void* C idiomática, recomendado directamente em K & R2. É a maneira como as pessoas esperam código a ser escrito, e qualquer outra coisa seria surpreendente e mal aceite.

Você pode construir um quadro OO (tipo de) usando C, mas você perder um monte de benefícios ... como um sistema do tipo OO que o compilador entende. Se você insistir em fazer OO em um C-como a linguagem, C ++ é uma escolha melhor. É mais complicado do que baunilha C, mas pelo menos você obter apoio linguístico adequado para OO.

EDIT: Ok ... se você insistir que não recomendamos C ++, eu recomendo que você não fizer OO em C. feliz? Tanto quanto seus hábitos OO estão em causa, você provavelmente deve pensar em termos de "objetos", mas herança licença e polimorfismo fora de sua estratégia de implementação. Genericity (usando ponteiros de função) deve ser usado com moderação.

EDIT 2: Na verdade, eu acho que o uso de void * em uma lista C genérico é razoável. Ele está apenas tentando construir um quadro OO simulada usando macros, ponteiros de função e expedição e que tipo de absurdo que eu acho que é uma má idéia.

Em Java todas as coleções de pacote java.util em vigor espera equivalente do ponteiro void* (a Object).

Sim, os genéricos (introduzido no 1.5) adicionar açúcar sintático e impedi-lo de codificação atribuições inseguras, no entanto o tipo de armazenamento permanece Object.

Então, eu acho que não há crime OO comprometida quando você usa void* para genérico tipo quadro.

Eu também acrescentaria inlines específicos do tipo ou invólucros de macro que atribuir / recuperar dados do estruturas genéricas se você fizer isso muitas vezes em seu código.

P.S. A única coisa que você não deve fazer é usar void** para retornar alocado / realocados tipos genéricos. Se você verificar as assinaturas de malloc/realloc você vai ver que você pode conseguir alocações de memória corretos sem ponteiro void** temido. Eu só estou dizendo isso porque eu já vi isso em algum projeto de código aberto, que eu não desejo para citar aqui.

Um contêiner genérico pode ser enrolado com um pouco de trabalho para que ele possa ser instanciado em versões tipo seguro. Aqui está um exemplo, cabeçalhos completos ligados abaixo:

/ * implementação genérica * /

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

A partir do cabeçalho que pode ser usado para tipos embrulhadas instanciar específicas de 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);
}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top