[c] C의 객체 지향

C에서 어떤 종류의 추악하지만 사용 가능한 객체 지향을 가능하게하는 멋진 전 처리기 핵 (ANSI C89 / ISO C90 호환) 세트는 무엇입니까?

몇 가지 다른 객체 지향 언어에 익숙하므로 “Learn C ++!”와 같은 답변으로 응답하지 마십시오. ” ANSI C를 사용한 객체 지향 프로그래밍 “(주의 : PDF 형식 ) 및 기타 여러 가지 흥미로운 솔루션 을 읽었 지만 대부분 귀하의 관심에 관심이 있습니다. :-)!


참조 하면 C에서 객체 지향 코드를 작성할 수 있습니까?



답변

COS (C Object System) 가 유망하게 들립니다 (아직 알파 버전 임). 개방형 클래스, 메타 클래스, 속성 메타 클래스, 제네릭, 멀티 메소드, 위임, 소유권, 예외, 계약 및 폐쇄를 포함한 균일 한 객체 지향 프로그래밍을 통해 단순성과 유연성을 위해 사용 가능한 개념을 최소화하려고합니다. 이를 설명 하는 초안 (PDF)이 있습니다.

C의 예외는 다른 OO 언어에서 발견 된 TRY-CATCH-FINALLY의 C89 구현입니다. 테스트 슈트와 예제가 함께 제공됩니다.

둘 다 C의 OOP에서 많은 작업을하고있는 Laurent Deniau의 작품 입니다.


답변

전 처리기 (ab)를 사용하여 C 구문을 다른 객체 지향 언어의 구문과 비슷하게 만들도록 권장합니다. 가장 기본적인 수준에서는 평범한 구조체를 객체로 사용하고 포인터로 전달합니다.

struct monkey
{
    float age;
    bool is_male;
    int happiness;
};

void monkey_dance(struct monkey *monkey)
{
    /* do a little dance */
}

상속과 다형성과 같은 것을 얻으려면 조금 더 열심히 노력해야합니다. 구조체의 첫 번째 멤버가 슈퍼 클래스의 인스턴스가되도록하여 수동 상속을 수행 한 다음 기본 및 파생 클래스에 대한 포인터를 자유롭게 캐스트 할 수 있습니다.

struct base
{
    /* base class members */
};

struct derived
{
    struct base super;
    /* derived class members */
};

struct derived d;
struct base *base_ptr = (struct base *)&d;  // upcast
struct derived *derived_ptr = (struct derived *)base_ptr;  // downcast

다형성 (즉, 가상 함수)을 얻으려면 함수 포인터를 사용하고 선택적으로 가상 테이블 또는 가상 테이블이라고도하는 함수 포인터 테이블을 사용합니다.

struct base;
struct base_vtable
{
    void (*dance)(struct base *);
    void (*jump)(struct base *, int how_high);
};

struct base
{
    struct base_vtable *vtable;
    /* base members */
};

void base_dance(struct base *b)
{
    b->vtable->dance(b);
}

void base_jump(struct base *b, int how_high)
{
    b->vtable->jump(b, how_high);
}

struct derived1
{
    struct base super;
    /* derived1 members */
};

void derived1_dance(struct derived1 *d)
{
    /* implementation of derived1's dance function */
}

void derived1_jump(struct derived1 *d, int how_high)
{
    /* implementation of derived 1's jump function */
}

/* global vtable for derived1 */
struct base_vtable derived1_vtable =
{
    &derived1_dance, /* you might get a warning here about incompatible pointer types */
    &derived1_jump   /* you can ignore it, or perform a cast to get rid of it */
};

void derived1_init(struct derived1 *d)
{
    d->super.vtable = &derived1_vtable;
    /* init base members d->super.foo */
    /* init derived1 members d->foo */
}

struct derived2
{
    struct base super;
    /* derived2 members */
};

void derived2_dance(struct derived2 *d)
{
    /* implementation of derived2's dance function */
}

void derived2_jump(struct derived2 *d, int how_high)
{
    /* implementation of derived2's jump function */
}

struct base_vtable derived2_vtable =
{
   &derived2_dance,
   &derived2_jump
};

void derived2_init(struct derived2 *d)
{
    d->super.vtable = &derived2_vtable;
    /* init base members d->super.foo */
    /* init derived1 members d->foo */
}

int main(void)
{
    /* OK!  We're done with our declarations, now we can finally do some
       polymorphism in C */
    struct derived1 d1;
    derived1_init(&d1);

    struct derived2 d2;
    derived2_init(&d2);

    struct base *b1_ptr = (struct base *)&d1;
    struct base *b2_ptr = (struct base *)&d2;

    base_dance(b1_ptr);  /* calls derived1_dance */
    base_dance(b2_ptr);  /* calls derived2_dance */

    base_jump(b1_ptr, 42);  /* calls derived1_jump */
    base_jump(b2_ptr, 42);  /* calls derived2_jump */

    return 0;
}

이것이 C에서 다형성을하는 방법입니다. 예쁘지는 않지만 일을합니다. 기본 클래스와 파생 클래스 사이의 포인터 캐스트와 관련된 몇 가지 끈적한 문제가 있습니다. 기본 클래스가 파생 클래스의 첫 번째 멤버 인 한 안전합니다. 다중 상속은 훨씬 어렵습니다.이 경우 첫 번째 클래스가 아닌 다른 기본 클래스 사이를 사용하려면 적절한 오프셋을 기반으로 포인터를 수동으로 조정해야합니다. 이는 실제로 까다 롭고 오류가 발생하기 쉽습니다.

런타임에 객체의 동적 유형을 변경하는 것이 또 하나의 까다로운 작업입니다! 새로운 vtable 포인터를 다시 할당하기 만하면됩니다. 가상 기능 중 일부를 선택적으로 변경하면서 다른 기능을 유지하면서 새로운 하이브리드 유형을 만들 수도 있습니다. 전역 vtable을 수정하는 대신 새 vtable을 작성하면됩니다. 그렇지 않으면 주어진 유형의 모든 객체에 실수로 영향을 미칩니다.


답변

나는 한때 나에게 아주 우아한 방식으로 구현 된 C 라이브러리로 작업했다. 그들은 C로 객체를 정의하는 방법을 작성한 다음 C ++ 객체만큼 확장 가능하도록 객체에서 상속했습니다. 기본 아이디어는 다음과 같습니다.

  • 각 객체에는 자체 파일이 있습니다
  • 공용 함수 및 변수는 객체의 .h 파일에 정의되어 있습니다.
  • 개인 변수와 함수는 .c 파일에만 있습니다
  • 구조체의 첫 번째 멤버가 상속 할 객체 인 새 구조체를 “상속”하기 위해

상속은 설명하기 어렵지만 기본적으로 다음과 같습니다.

struct vehicle {
   int power;
   int weight;
}

그런 다음 다른 파일에서 :

struct van {
   struct vehicle base;
   int cubic_size;
}

그런 다음 밴을 메모리에 만들어 차량에 대해서만 알고있는 코드로 사용할 수 있습니다.

struct van my_van;
struct vehicle *something = &my_van;
vehicle_function( something );

그것은 아름답게 작동했으며 .h 파일은 각 객체로 할 수있는 것을 정확하게 정의했습니다.


답변

Linux 용 그놈 데스크탑은 객체 지향 C로 작성되었으며, 속성, 상속, 다형성 및 참조, 이벤트 처리 ( “신호”라고 함), 런타임과 같은 다른 기능을 지원하는 ” GObject ” 라는 객체 모델을 가지고 있습니다. 타이핑, 개인 정보 등

여기에는 클래스 계층 구조에서 typecasting과 같은 작업을 수행하는 전 처리기 해킹이 포함되어 있습니다. 다음은 그놈에 대해 작성한 클래스의 예입니다 (gchar와 같은 것들은 typedef입니다).

클래스 소스

클래스 헤더

GObject 구조에는 GLib의 동적 타이핑 시스템을위한 매직 넘버로 사용되는 GType 정수가 있습니다 (전체 구조체를 “GType”으로 캐스팅하여 유형을 찾을 수 있습니다).


답변

나는 OOP가 무엇인지 알기 전에 C에서 이런 종류의 일을 했었습니다.

다음은 최소 크기, 증분 및 최대 크기가 주어지면 필요에 따라 증가하는 데이터 버퍼를 구현하는 예입니다. 이 특정 구현은 “요소”를 기반으로했으며 가변 길이 바이트 버퍼뿐만 아니라 모든 C 유형의 목록과 같은 컬렉션을 허용하도록 설계되었습니다.

xxx_crt ()를 사용하여 객체를 인스턴스화하고 xxx_dlt ()를 사용하여 객체를 삭제하는 것이 좋습니다. 각 “멤버”메소드는 조작하기 위해 특별히 유형이 지정된 포인터를 사용합니다.

이 방법으로 연결 목록, 순환 버퍼 및 기타 여러 가지를 구현했습니다.

나는이 방법으로 상속을 구현하는 방법에 대해 전혀 생각해 본 적이 없다고 고백해야한다. Kieveli가 제공하는 것들이 좋은 길이라고 생각합니다.

dtb.c :

#include <limits.h>
#include <string.h>
#include <stdlib.h>

static void dtb_xlt(void *dst, const void *src, vint len, const byte *tbl);

DTABUF *dtb_crt(vint minsiz,vint incsiz,vint maxsiz) {
    DTABUF          *dbp;

    if(!minsiz) { return NULL; }
    if(!incsiz)                  { incsiz=minsiz;        }
    if(!maxsiz || maxsiz<minsiz) { maxsiz=minsiz;        }
    if(minsiz+incsiz>maxsiz)     { incsiz=maxsiz-minsiz; }
    if((dbp=(DTABUF*)malloc(sizeof(*dbp))) == NULL) { return NULL; }
    memset(dbp,0,sizeof(*dbp));
    dbp->min=minsiz;
    dbp->inc=incsiz;
    dbp->max=maxsiz;
    dbp->siz=minsiz;
    dbp->cur=0;
    if((dbp->dta=(byte*)malloc((vuns)minsiz)) == NULL) { free(dbp); return NULL; }
    return dbp;
    }

DTABUF *dtb_dlt(DTABUF *dbp) {
    if(dbp) {
        free(dbp->dta);
        free(dbp);
        }
    return NULL;
    }

vint dtb_adddta(DTABUF *dbp,const byte *xlt256,const void *dtaptr,vint dtalen) {
    if(!dbp) { errno=EINVAL; return -1; }
    if(dtalen==-1) { dtalen=(vint)strlen((byte*)dtaptr); }
    if((dbp->cur + dtalen) > dbp->siz) {
        void        *newdta;
        vint        newsiz;

        if((dbp->siz+dbp->inc)>=(dbp->cur+dtalen)) { newsiz=dbp->siz+dbp->inc; }
        else                                       { newsiz=dbp->cur+dtalen;   }
        if(newsiz>dbp->max) { errno=ETRUNC; return -1; }
        if((newdta=realloc(dbp->dta,(vuns)newsiz))==NULL) { return -1; }
        dbp->dta=newdta; dbp->siz=newsiz;
        }
    if(dtalen) {
        if(xlt256) { dtb_xlt(((byte*)dbp->dta+dbp->cur),dtaptr,dtalen,xlt256); }
        else       { memcpy(((byte*)dbp->dta+dbp->cur),dtaptr,(vuns)dtalen);   }
        dbp->cur+=dtalen;
        }
    return 0;
    }

static void dtb_xlt(void *dst,const void *src,vint len,const byte *tbl) {
    byte            *sp,*dp;

    for(sp=(byte*)src,dp=(byte*)dst; len; len--,sp++,dp++) { *dp=tbl[*sp]; }
    }

vint dtb_addtxt(DTABUF *dbp,const byte *xlt256,const byte *format,...) {
    byte            textÝ501¨;
    va_list         ap;
    vint            len;

    va_start(ap,format); len=sprintf_len(format,ap)-1; va_end(ap);
    if(len<0 || len>=sizeof(text)) { sprintf_safe(text,sizeof(text),"STRTOOLNG: %s",format); len=(int)strlen(text); }
    else                           { va_start(ap,format); vsprintf(text,format,ap); va_end(ap);                     }
    return dtb_adddta(dbp,xlt256,text,len);
    }

vint dtb_rmvdta(DTABUF *dbp,vint len) {
    if(!dbp) { errno=EINVAL; return -1; }
    if(len > dbp->cur) { len=dbp->cur; }
    dbp->cur-=len;
    return 0;
    }

vint dtb_reset(DTABUF *dbp) {
    if(!dbp) { errno=EINVAL; return -1; }
    dbp->cur=0;
    if(dbp->siz > dbp->min) {
        byte *newdta;
        if((newdta=(byte*)realloc(dbp->dta,(vuns)dbp->min))==NULL) {
            free(dbp->dta); dbp->dta=null; dbp->siz=0;
            return -1;
            }
        dbp->dta=newdta; dbp->siz=dbp->min;
        }
    return 0;
    }

void *dtb_elmptr(DTABUF *dbp,vint elmidx,vint elmlen) {
    if(!elmlen || (elmidx*elmlen)>=dbp->cur) { return NULL; }
    return ((byte*)dbp->dta+(elmidx*elmlen));
    }

dtb.h

typedef _Packed struct {
    vint            min;                /* initial size                       */
    vint            inc;                /* increment size                     */
    vint            max;                /* maximum size                       */
    vint            siz;                /* current size                       */
    vint            cur;                /* current data length                */
    void            *dta;               /* data pointer                       */
    } DTABUF;

#define dtb_dtaptr(mDBP)                (mDBP->dta)
#define dtb_dtalen(mDBP)                (mDBP->cur)

DTABUF              *dtb_crt(vint minsiz,vint incsiz,vint maxsiz);
DTABUF              *dtb_dlt(DTABUF *dbp);
vint                dtb_adddta(DTABUF *dbp,const byte *xlt256,const void *dtaptr,vint dtalen);
vint                dtb_addtxt(DTABUF *dbp,const byte *xlt256,const byte *format,...);
vint                dtb_rmvdta(DTABUF *dbp,vint len);
vint                dtb_reset(DTABUF *dbp);
void                *dtb_elmptr(DTABUF *dbp,vint elmidx,vint elmlen);

추신 : vint는 단순히 int의 typedef였습니다. 길이가 플랫폼마다 다를 수 있음을 상기시키기 위해 사용했습니다 (포팅).


답변

약간의 주제는 아니지만 원래 C ++ 컴파일러 인 Cfront 는 C ++를 C로 컴파일 한 다음 어셈블러로 컴파일했습니다.

여기에 보존 됩니다 .


답변

객체에서 호출 된 메소드를 this함수에 암시적인 ‘ ‘를 전달하는 정적 메소드 로 생각하면 C에서 OO를 쉽게 생각할 수 있습니다.

예를 들면 다음과 같습니다.

String s = "hi";
System.out.println(s.length());

된다 :

string s = "hi";
printf(length(s)); // pass in s, as an implicit this

아니면 그런 것.