Подтвердить что ты не робот

Универсальность и безопасность типов? Использование void * в C

Начиная с OO (С#, Java, Scala), я очень высоко ценю принципы повторного использования кода и безопасности типов. Аргументы типа на указанных языках выполняют задание и разрешают создание общих структур данных, которые являются безопасными по типу и не являются "ненужными".

Как я застрял в C, я знаю, что я должен пойти на компромисс, и я бы хотел, чтобы он был правильным. Либо мои структуры данных имеют void * в каждом элементе node/, и я теряю безопасность типа, или мне приходится переписывать свои структуры и код для каждого типа, с которым я хочу их использовать.

Сложность кода является очевидным фактором: итерация через массив или связанный список тривиальна, и добавление *next к структуре не требует дополнительных усилий; в этих случаях имеет смысл не пытаться повторно использовать структуры и код. Но для более сложных структур ответ не столь очевиден.

Кроме того, модульность и проверяемость: разделение типа и его операций с кодом, использующим структуру, упрощает тестирование. Обратное также верно: тестирование итерации какого-либо кода по структуре, пока он пытается сделать другие вещи, становится беспорядочным.

Так что твой совет? void * и повторное использование или тип безопасности и дублированный код? Существуют ли общие принципы? Я пытаюсь заставить OO на процедурный, когда он не подходит?

Изменить. Пожалуйста, не рекомендуем С++, мой вопрос о C!

4b9b3361

Ответ 1

Я бы сказал, используйте void *, чтобы вы могли повторно использовать код. Это больше работает для повторной реализации, например. связанный список, чем убедиться, что вы правильно загружаете/устанавливаете данные в списке.

Возьмите как можно больше советов от glib, я считаю, что их структуры данных очень приятные и удобные в использовании, и у них были небольшие проблемы из-за потери безопасности типа.

Ответ 2

Я думаю, вам придется найти баланс между ними, как вы предлагаете. Если код всего несколько строк и тривиальный, я бы дублировал его, но если бы он был более сложным, я бы подумал о работе с void*, чтобы избежать необходимости исправлять и исправлять ошибки в нескольких местах, а также уменьшить размер кода.

Если вы посмотрите на библиотеку времени выполнения C, есть несколько "общих" функций, которые работают с void*, один общий пример сортируется с помощью qsort. Было бы безумием дублировать этот код для каждого типа, который вы хотите сортировать.

Ответ 3

Нет ничего плохого в использовании указателей void. Вам даже не нужно бросать их при назначении им переменной типа указателя, поскольку преобразование выполняется внутри. Это должно выглядеть так: http://www.cpax.org.uk/prg/writings/casting.php

Ответ 4

Ответ на этот вопрос такой же, как получение эффективных шаблонов для списка ссылок в С++.

a) Создайте абстрактную версию алгоритма, который использует void * или некоторый абстрактный тип

b) Создайте легкий публичный интерфейс для вызова алгоритмов Abstracted Type и касты между ними.

Например.

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

и т.д.

Ответ 5

Вы можете использовать макросы, они будут работать с любым типом, а компилятор будет статически проверять расширенный код. Недостатком является то, что плотность кода (в двоичном формате) ухудшится, и их сложнее отладить.

Я задал этот вопрос об общих функциях некоторое время назад, и ответы могли помочь вам.

Ответ 6

Вы можете эффективно добавлять информацию о типе, наследование и полиморфизм в структуры данных C, что делает С++. (http://www.embedded.com/97/fe29712.htm)

Ответ 7

Мы используем OO в C здесь много, но только для инкапсуляции и абстракции, никакого полиморфизма или так.

Это означает, что у нас есть определенные типы, такие как FooBar (Foo a,...), но для нашей коллекции "classes" мы используем void *. Просто используйте void *, где можно использовать несколько типов, НО, сделав это, убедитесь, что вам не нужен аргумент определенного типа. В соответствии с коллекцией наличие void * в порядке, потому что коллекция не заботится о типе. Но если ваша функция может принимать тип a и тип b, но ни один другой, сделайте два варианта: один для a и один для b.

Главное - использовать void * только тогда, когда вам не нужен тип.

Теперь, если у вас есть 50 типов с одной и той же базовой структурой (скажем, int a; int b; как первые члены всех типов) и хотите, чтобы функция воздействовала на эти типы, просто сделайте общих первых членов тип сам по себе, затем заставьте функцию принять это, и передать объект object → ab или (AB *) - ваш тип непрозрачен, оба будут работать, если ab - это первое поле в вашей структуре.

Ответ 8

Определенно общий void*, никогда не дублируйте код!

Учтите, что эта дилемма рассматривалась многими программистами C и многими крупными проектами C. Все серьезные проекты C, с которыми я когда-либо сталкивался, будь то open-source или commercial, выбрали общий void*. При использовании тщательно и завернутый в хороший API, это едва ложится на пользователя библиотеки. Более того, void* является идиоматическим C, рекомендованным непосредственно в K & R2. Это то, как люди ожидают, что код будет написан, и все остальное будет удивительно и плохо принято.

Ответ 9

Вы можете создать (вид) OO-рамки с помощью C, но вы пропустите много преимуществ... как система типа OO, которую понимает компилятор. Если вы настаиваете на выполнении OO на языке C, С++ - лучший выбор. Это сложнее, чем ваниль C, но по крайней мере вы получаете правильную лингвистическую поддержку OO.

EDIT: Хорошо... если вы настаиваете на том, что мы не рекомендуем С++, я рекомендую вам не делать OO на C. Happy? Что касается ваших привычек OO, вы, вероятно, должны думать о "объектах", но оставляйте наследование и полиморфизм из своей стратегии реализации. Обобщение (используя указатели функций) следует использовать экономно.

EDIT 2: На самом деле, я думаю, что использование void * в общем списке C является разумным. Он просто пытается создать фальшивую OO-инфраструктуру с использованием макросов, указателей функций, диспетчеризации и таких глупостей, которые, по моему мнению, являются плохими идеями.

Ответ 10

В Java все коллекции из пакета java.util фактически содержат эквивалент указателя void* (Object).

Да, generics (введенные в 1.5) добавляют синтаксический сахар и препятствуют кодированию небезопасных назначений, однако тип хранения остается Object.

Итак, я думаю, что преступление OO не совершается, когда вы используете void* для универсального типа рамки.

Я бы также добавил встроенные строки или макро-обертки, которые назначают/извлекают данные из общих структур, если вы часто это делаете в своем коде.

P.S. Единственное, что вы НЕ должны делать, это использовать void** для возврата выделенных/перераспределенных общих типов. Если вы проверите подписи malloc/realloc, вы увидите, что вы можете добиться правильного выделения памяти без страшного указателя void**. Я только говорю это, потому что я видел это в каком-то проекте с открытым исходным кодом, который я не хочу называть здесь.

Ответ 11

Общий контейнер можно обернуть небольшим количеством работы, чтобы его можно было создать в безопасных версиях. Ниже приведен пример полных заголовков, связанных ниже:

/* общая реализация */

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

Из заголовка, который может использоваться для создания экземпляров определенных обернутых типов 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);
}