문제

OO (C#, Java, Scala)에서 나온 나는 코드 재사용 및 유형 안전성의 원칙을 매우 높게 평가합니다. 위의 언어에서 인수 유형은 작업을 수행하고 유형-안전하고 코드를 '폐기'하지 않는 일반 데이터 구조를 활성화합니다.

C에 갇히면서 타협해야한다는 것을 알고 있으며 그것이 올바른 것이기를 원합니다. 내 데이터 구조물에는 a가 있습니다 void * 각 노드 / 요소에서 유형 안전을 잃거나 사용하려는 각 유형에 대한 구조와 코드를 다시 작성해야합니다.

코드의 복잡성은 명백한 요소입니다. 배열 또는 링크 목록을 통한 반복은 사소하고 추가됩니다. *next 구조물에는 추가 노력이 없습니다. 이 경우 구조와 코드를 재사용하지 않는 것이 합리적입니다. 그러나 더 복잡한 구조의 경우 그 대답은 분명하지 않습니다.

모듈성 및 테스트 가능성도 있습니다. 구조를 사용하는 코드에서 유형과 작업을 분리하면 테스트가 더 쉬워집니다. 역수도 사실입니다. 다른 일을하려고하는 동안 구조를 통해 일부 코드의 반복을 테스트하는 것은 지저분 해집니다.

그래서 당신의 조언은 무엇입니까? void * 재사용 또는 유형 안전성 및 복제 된 코드? 일반적인 원칙이 있습니까? 맞지 않을 때 OO를 절차에 강요하려고합니까?

편집하다: C ++를 추천하지 마십시오. 제 질문은 C에 관한 것입니다!

도움이 되었습니까?

해결책

나는 사용한다고 말할 것이다 void * 따라서 코드를 재사용 할 수 있습니다. 목록의 데이터를 올바르게 얻거나 설정하는 것보다 링크 된 목록을 다시 구현하는 것이 더 많은 작업입니다.

많은 힌트를 가져 가십시오 말 잘하는 가능한 한, 나는 그들의 데이터 구조가 매우 좋고 사용하기 쉽고, 유형 안전 상실로 인해 문제가 거의 없었습니다.

다른 팁

나는 당신이 제안한대로 둘 사이의 균형을 잡아야한다고 생각합니다. 코드가 단지 몇 줄이고 사소한 경우, 나는 그것을 복제하지만 더 복잡한 경우 작업을 고려할 것입니다. void* 여러 곳에서 잠재적 인 버그 수정 및 유지 관리를 수행하지 않아도 코드 크기를 줄입니다.

C 런타임 라이브러리를 보면 다음과 같은 몇 가지 "일반"기능이 있습니다. void*, 한 가지 일반적인 예는 정렬하는 것입니다 qsort. 정렬하려는 모든 유형에 대해이 코드를 복제하는 것은 광기입니다.

void 포인터를 사용하는 데 아무런 문제가 없습니다. 변환이 내부적으로 수행되기 때문에 변수 유형의 포인터에 할당 할 때 시전 할 필요조차 없습니다. 이것을 살펴볼 가치가 있습니다. http://www.cpax.org.uk/prg/writings/casting.php

답변이 질문은 C ++의 링크 목록에 대한 효율적인 템플릿을 얻는 것과 동일합니다.

a) void* 또는 일부 초록 유형을 사용하는 알고리즘의 초록 버전 생성

b) 초록 유형 알고리즘을 호출하고 그들 사이의 카스트를 호출하는 가벼운 공개 인터페이스를 만듭니다.

예를 들어.

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

우리는 여기서 C에서 OO를 많이 사용하지만 캡슐화 및 추상화, 다형성 등만 사용합니다.

즉, foobar (foo a, ...)와 같은 특정 유형을 가지고 있지만 컬렉션 "클래스"의 경우 void *를 사용합니다. 여러 유형을 사용할 수있는 경우 void * 만 사용하지만 그렇게함으로써 특정 유형의 인수가 필요하지 않도록하십시오. 컬렉션에 따라 컬렉션이 유형에 신경 쓰지 않기 때문에 void *를 갖는 것은 괜찮습니다. 그러나 함수가 유형 A와 유형 B를 허용 할 수 있지만 다른 사람은 없다면, 하나는 A와 하나는 b에 대해 하나를 만듭니다.

요점은 유형을 신경 쓰지 않을 때만 공허 *를 사용하는 것입니다.

이제 동일한 기본 구조를 가진 50 가지 유형이 있다면 (int a; int b; 모든 유형의 첫 번째 구성원으로서, 그러한 유형의 기능을 원한다면, 공통 첫 번째 멤버를 유형 자체로 만드십시오. , 그런 다음 함수를 받아들이고, 객체-> ab 또는 (ab*) 객체를 통과하십시오. 객체는 유형이 불투명하다.

매크로를 사용할 수 있고 모든 유형과 함께 작동하며 컴파일러는 확장 된 코드를 정적으로 확인합니다. 단점은 코드 밀도 (이진에서)가 악화되고 디버깅하기가 더 어렵다는 것입니다.

나는 이것을 물었다 일반 기능에 대한 질문 얼마 전과 답이 도움이 될 수 있습니다.

C 데이터 구조에 유형 정보, 상속 및 다형성을 효율적으로 추가 할 수 있습니다. 이것이 C ++가하는 일입니다. (http://www.embedded.com/97/fe29712.htm)

확실히 일반 void*, 절대로 코드를 복제하지 마십시오!

이 딜레마는 많은 C 프로그래머와 많은 주요 C 프로젝트에 의해 고려되었다는 점을 고려하십시오. 오픈 소스 또는 광고에 관계없이 내가 만난 모든 심각한 C 프로젝트는 제네릭을 선택했습니다. void*. 조심스럽게 사용하고 좋은 API로 싸면 라이브러리 사용자에게는 거의 부담이되지 않습니다. 더구나, void* k & r2에서 직접 권장되는 관용 C입니다. 사람들이 코드를 작성하기를 기대하는 방식이며, 다른 모든 것은 놀랍고 심하게 받아 들여질 것입니다.

C를 사용하여 (일종의) OO 프레임 워크를 구축 할 수 있지만 컴파일러가 이해하는 OO 유형 시스템과 같이 많은 이점을 놓치게됩니다. C와 같은 언어로 OO를 수행한다고 주장한다면 C ++가 더 나은 선택입니다. 바닐라 C보다 더 복잡하지만 적어도 OO에 대한 적절한 언어 지원을받습니다.

편집 : 좋아 ... C ++를 추천하지 않는다고 주장하면 C에서 OO를하지 않는 것이 좋습니다. 당신의 OO 습관에 관한 한, 당신은 아마도 생각한다 "객체"의 관점에서 볼 때, 구현 전략에서 상속과 다형성을 남겨 두십시오. 제어 (기능 포인터 사용)는 드물게 사용해야합니다.

편집 2 : 사실, 나는 그것이 void * 일반적인 C 목록에서 합리적입니다. 그것은 단지 매크로, 기능 포인터, 파견 및 내가 나쁜 생각이라고 생각하는 그런 종류의 말도 안되는 모의 OO 프레임 워크를 구축하려고 노력하고 있습니다.

Java에서 모든 컬렉션 java.util 사실상 패키지는 동일합니다 void* 포인터 (the Object ).

예, 제네릭 (1.5로 소개) 구문 설탕을 추가하고 안전하지 않은 과제를 코딩하는 것을 방지하지만 저장 유형은 남아 있습니다. Object.

그래서 나는 당신이 사용할 때 헌신적 인 범죄가 없다고 생각합니다. void* 일반 프레임 워크 유형의 경우.

또한 코드에서 자주이 작업을 수행하면 일반 구조에서 데이터를 할당/검색하는 유형 별 인라인 또는 매크로 포장지를 추가합니다.

추신 당신이하지 말아야 할 일은 사용하는 것입니다. void** 할당/재 할당 된 일반 유형을 반환합니다. 서명을 확인하면 malloc/realloc 당신은 당신이 두려워하지 않고 올바른 메모리 할당을 달성 할 수 있음을 알게 될 것입니다. void** 바늘. 나는 오픈 소스 프로젝트에서 이것을 보았 기 때문에 여기에 이름을 말하고 싶지 않기 때문에 이것을 말하고 있습니다.

일반 컨테이너는 작은 작업으로 포장하여 유형 안전 버전으로 인스턴스화 할 수 있습니다. 다음은 아래에 링크 된 전체 헤더입니다.

/ * 일반 구현 */

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);
}
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top