Cでのオブジェクト指向


157

Cである種の醜い(しかし使用可能な)オブジェクト指向を可能にする気の利いたプリプロセッサハック(ANSI C89 / ISO C90互換)のセットは何ですか?

私はいくつかの異なるオブジェクト指向言語に精通しているので、「C ++を学ぶ!」のような答えを返さないでください。私は「ANSI Cによるオブジェクト指向プログラミング」(注意:PDF形式)と他のいくつかの興味深い解決策を読みましたが、主にあなたの解決策に興味があります:-)!


Cでオブジェクト指向コードを記述できますか?も参照してください


1
私はDを学び、C.はあなたが本当に必要な場所のためのC互換ABI使用に対応できるdigitalmars.com/d
ティム・マシューズ

2
@Dinah:「また見る」をありがとう。その投稿は面白かった。

1
興味深い質問は、なぜCでOOPのプリプロセッサハックが必要なのかということです
Calyth

3
@Calyth:OOPは有用であり、「Cコンパイラのみを実際に使用できる一部の組み込みシステムで動作します」(上記から)。さらに、気の利いたプリプロセッサのハックが面白いと思いませんか?

回答:


31

Cオブジェクトシステム(COS)は有望に聞こえます(まだアルファ版です)。シンプルさと柔軟性のために、利用可能な概念を最小限に抑えようとします。オープンクラス、メタクラス、プロパティメタクラス、ジェネリック、マルチメソッド、委任、所有権、例外、コントラクト、およびクロージャを含む、均一なオブジェクト指向プログラミングです。それを説明するドラフトペーパー(PDF)があります。

Cの例外は、他のオブジェクト指向言語にあるTRY-CATCH-FINALLYのC89実装です。テストスイートといくつかの例が付属しています。

どちらもCOOPに取り組んでいるLaurent Deniauによるものです。


@vonbrand COSは、最後のコミットが昨年の夏であるgithubに移行しました。成熟度はコミットの欠如を説明できます。
フィランテ

185

私は、プリプロセッサの(誤用)を避けて、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

ポリモーフィズム(仮想関数)を取得するには、関数ポインターを使用し、オプションで仮想テーブルまたはvtableとも呼ばれる関数ポインターテーブルを使用します。

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でのポリモーフィズムの方法です。見栄えはよくありませんが、機能します。基本クラスと派生クラス間のポインターキャストに関連するいくつかの厄介な問題があります。これは、基本クラスが派生クラスの最初のメンバーである限り安全です。多重継承ははるかに困難です。その場合、最初のクラス以外の基本クラス間で大文字と小文字を区別するには、適切なオフセットに基づいてポインターを手動で調整する必要があります。

実行できるもう1つの(トリッキーな)ことは、実行時にオブジェクトの動的タイプを変更することです!新しいvtableポインターを再割り当てするだけです。仮想機能の一部を選択的に変更し、他の機能を維持して、新しいハイブリッドタイプを作成することもできます。グローバルvtableを変更するのではなく、新しいvtableを作成するように注意してください。そうしないと、指定したタイプのすべてのオブジェクトに誤って影響を及ぼします。


6
Adam、型のグローバルvtableを変更する楽しみは、Cでのダック型付けをシミュレートすることです。:)
jmucchiello

今度はC ++に同情します...もちろんC ++構文はより明確ですが、それは簡単な構文ではないので軽減されています。C ++とCの間のハイブリッドが実現できるかどうか疑問に思うので、void *は引き続き有効なキャスト可能型になります。のある部分struct derived {struct base super;};は、バイト順で正しいため、どのように機能するかを推測するのは明らかです。
jokoon

2
エレガントなコードの+1、よく書かれています。これがまさに私が探していたものです!
Homunculus Reticulli

3
よくやった。これがまさに私のやり方ですが、それも正しい方法です。構造体/オブジェクトへのポインタを要求する代わりに、整数(アドレス)へのポインタを渡す必要があります。これにより、あらゆる種類のオブジェクトを無制限の多態性メソッド呼び出しに渡すことができます。また、不足しているのは、構造体(オブジェクト/クラス)を初期化する関数だけです。これはmalloc関数を含み、ポインターを返します。多分私はCでメッセージパッシング(objective-c)を行う方法の一部を追加します

1
これはわらがC ++を壊してしまい、Cをさらに使用するために(継承にC ++のみを使用する前に)ありがとう
アンクイン2013年

31

私はかつて、非常にエレガントな印象を与える方法で実装された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ファイルは各オブジェクトで実行できることを正確に定義しました。


「オブジェクト」のすべての内部がパブリックであることを除いて、私はこのソリューションが本当に好きです。
Lawrence Dol、

6
@Software Monkey:Cにはアクセス制御がありません。実装の詳細を非表示にする唯一の方法は、不透明ポインターを介してやり取りすることです。これは、おそらくインライン化できないアクセサーメソッドを介してすべてのフィールドにアクセスする必要があるため、かなり苦痛になる可能性があります。
Adam Rosenfield、

1
@Adam:リンク時の最適化をサポートするコンパイラはそれらをうまくインライン化します...
Christoph

9
これを行う場合は、パブリックとして定義されていない.cファイル内のすべての関数が静的として定義されていることを確認して、オブジェクトファイル内で名前付き関数として機能しないようにする必要があります。これにより、リンクフェーズで自分の名前を見つけることができなくなります。
jmucchiello

2
@Marcel:自律システム用のさまざまなプロセッサを実行する低レベルのボードにコードがデプロイされたため、Cが使用されました。これらはすべて、Cからそれぞれのネイティブバイナリへのコンパイルをサポートしていました。このアプローチにより、コードが何をしようとしているかを理解すると、コードが非常に読みやすくなりました。
キエーヴェリ2013年

18

LinuxのGNOMEデスクトップはオブジェクト指向のCで記述されており、プロパティ、継承、ポリモーフィズム、および参照、イベント処理(「シグナル」と呼ばれる)などの他の便利な機能をサポートする「GObject」と呼ばれるオブジェクトモデルがあります。タイピング、プライベートデータなど

これには、クラス階層での型キャストなどの処理を行うためのプリプロセッサハックが含まれています。以下は、GNOME用に記述したクラスの例です(gcharのようなものはtypedefです)。

クラスソース

クラスヘッダー

GObject構造の内部には、GLibの動的型付けシステムのマジックナンバーとして使用されるGType整数があります(構造全体を「GType」にキャストして、その型を見つけることができます)。


残念ながら、read me / tutorialファイル(wikiリンク)が機能せず、そのためのリファレンスマニュアルしかありません(私はGTKではなくGObjectについて話している)。同じようにいくつかのチュートリアルファイルを提供してください...
FL4SOF '06 / 01/06

リンクが修正されました。
ジェームズケープ

4
リンクは再び壊れます。
SeanRamey

6

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

PS:vintは単にintのtypedefでした-私はそれを使用して、長さがプラットフォーム間で(移植のために)可変であることを思い出させました。


7
聖なるモリー、これは難読化されたCコンテストに勝つ可能性があります!私はそれが好きです!:)
horseyguy 2009

@horseyguyいいえ、できませんでした。公開されました。また、彼らは、iocccsizeツールに対する悪用ヘッダーファイルを含めることを検討しています。また、完全なプログラムでもありません。2009年はコンテストがなかったため、iocccsizeを比較できません。CPPは何度も悪用されているため、かなり古いものです。申し訳ありません。私は否定的であるつもりはありませんが、現実的です。私はあなたの意味をある程度理解し、それは良い読み物であり、私はそれを賛成投票しました。(はい、私はそれに参加し、はい、私も勝利します。)
プリフタン

6

少し話題から外れましたが、元のC ++コンパイラCfrontはC ++をCにコンパイルしてからアセンブラにコンパイルしました。

ここに保存されています


実際に見たことがあります。いい作品だったと思います。

@Anthony Cuozzo:Stan Lippmanは、「C ++-Inside the object model」と呼ばれる素晴らしい本を書いており、c-frontの作成と保守に関する多くの経験と設計上の決定を関連付けています。それはまだ良い読み取りだとCからCへの移行時に非常に私を助けてくれ++長年バック
zebrabox

5

オブジェクトで呼び出されたメソッドを、暗黙の ' this'を関数に渡す静的メソッドと考えると、CでOOを考えるのが簡単になります。

例えば:

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

になる:

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

またはそのようなもの。


6
@Artelius:もちろんですが、それが明記されるまでは明らかな場合もあります。+1。
Lawrence Dol、

1
さらに良いでしょうstring->length(s);
OozeMeister 2014

4

ffmpeg(ビデオ処理用のツールキット)は、ストレートC(およびアセンブリ言語)で記述されていますが、オブジェクト指向のスタイルを使用しています。関数ポインタを持つ構造体でいっぱいです。適切な「メソッド」ポインタで構造体を初期化する一連のファクトリ関数があります。


その中にはファクトリー関数はありません(ffmpeg)。むしろ、ポリモーフィズム/継承を使用しているようには見えません(上で提案した簡単な方法)。
FL4SOF 2009年

avcodec_openは1つのファクトリ関数です。関数ポインターをAVCodecContext構造体(draw_horiz_bandなど)に詰め込みます。avcodec.hのFF_COMMON_FRAMEマクロの使用法を見ると、データメンバーの継承に似たものが表示されます。私見、ffmpegは、OOPはCではなくC ++で行うのが最善であることを証明しています
Fooz氏、2009年

3

-あなたが本当にcatefully考えている場合でも、標準Cライブラリの使用OOPは考慮するFILE *例として:fopen()初期化しFILE *たオブジェクトを、そしてあなたはそれがメンバー・メソッドを使用使用してfscanf()fprintf()fread()fwrite()などを、そして最終的にそれを完成しますfclose()

同様に難しくない疑似Objective-Cの方法を使用することもできます。

typedef void *Class;

typedef struct __class_Foo
{
    Class isa;
    int ivar;
} Foo;

typedef struct __meta_Foo
{
    Foo *(*alloc)(void);
    Foo *(*init)(Foo *self);
    int (*ivar)(Foo *self);
    void (*setIvar)(Foo *self);
} meta_Foo;

meta_Foo *class_Foo;

void __meta_Foo_init(void) __attribute__((constructor));
void __meta_Foo_init(void)
{
    class_Foo = malloc(sizeof(meta_Foo));
    if (class_Foo)
    {
        class_Foo = {__imp_Foo_alloc, __imp_Foo_init, __imp_Foo_ivar, __imp_Foo_setIvar};
    }
}

Foo *__imp_Foo_alloc(void)
{
    Foo *foo = malloc(sizeof(Foo));
    if (foo)
    {
        memset(foo, 0, sizeof(Foo));
        foo->isa = class_Foo;
    }
    return foo;
}

Foo *__imp_Foo_init(Foo *self)
{
    if (self)
    {
        self->ivar = 42;
    }
    return self;
}
// ...

使用するには:

int main(void)
{
    Foo *foo = (class_Foo->init)((class_Foo->alloc)());
    printf("%d\n", (foo->isa->ivar)(foo)); // 42
    foo->isa->setIvar(foo, 60);
    printf("%d\n", (foo->isa->ivar)(foo)); // 60
    free(foo);
}

これは、かなり古いObjective-C-to-Cトランスレータが使用されている場合、次のようなObjective-Cコードから生じる可能性があります。

@interface Foo : NSObject
{
    int ivar;
}
- (int)ivar;
- (void)setIvar:(int)ivar;
@end

@implementation Foo
- (id)init
{
    if (self = [super init])
    {
        ivar = 42;
    }
    return self;
}
@end

int main(void)
{
    Foo *foo = [[Foo alloc] init];
    printf("%d\n", [foo ivar]);
    [foo setIvar:60];
    printf("%d\n", [foo ivar]);
    [foo release];
}

何をし__attribute__((constructor))ますvoid __meta_Foo_init(void) __attribute__((constructor))か?
AE Drew

1
これは、バイナリがメモリにロードされるときにマークされた関数が確実に呼び出されるようにするGCC拡張です。@AEDrew
Maxthon Chan

popen(3)FILE *別の例のも返します。
プリフタン

3

Adam Rosenfieldが投稿したのは、CでOOPを行う正しい方法だと思います。彼が示しているのはオブジェクトの実装であることを付け加えておきます。つまり、実際の実装は.cファイルに入れられ、インターフェースはヘッダー.hファイルに入れられます。たとえば、上記のサルの例を使用すると、次のようになります。

インターフェイスは次のようになります。

//monkey.h

    struct _monkey;

    typedef struct _monkey monkey;

    //memory management
    monkey * monkey_new();
    int monkey_delete(monkey *thisobj);
    //methods
    void monkey_dance(monkey *thisobj);

インターフェイス.hファイルで、プロトタイプのみを定義していることがわかります。次に、実装部分「.cファイル」を静的または動的ライブラリにコンパイルできます。これによりカプセル化が作成され、実装を自由に変更することもできます。オブジェクトのユーザーは、その実装についてほとんど何も知る必要はありません。これは、オブジェクトの全体的なデザインにも焦点を当てています。

oopはコード構造と再利用性を概念化する方法であり、オーバーロードやテンプレートなどのc ++に追加される他のこととはまったく関係がないと私は個人的に信じています。はい、これらは非常に便利な機能ですが、オブジェクト指向プログラミングが実際に何であるかを表すものではありません。


構造体は、typedef struct Monkey {} Monkey; 作成後にtypedefする意味は何ですか?
MarcusJ 2016年

1
@MarcusJこれstruct _monkeyは単なるプロトタイプです。実際の型定義は、実装ファイル(.cファイル)で定義されています。これによりカプセル化効果が生まれ、API開発者はAPIを変更せずにサルの構造を再定義できます。APIのユーザーは、実際のメソッドのみを考慮する必要があります。APIデザイナは、オブジェクト/構造体のレイアウト方法を含む実装を処理します。したがって、オブジェクト/構造体の詳細はユーザーから隠されます(不透明なタイプ)。

構造体をヘッダーで定義していますが、これは標準ではありませんか?まあ、私は時々そのライブラリの外の構造体のメンバーにアクセスする必要があるので、私はそのようにします。
MarcusJ 2016年

1
@MarcusJ必要に応じて、ヘッダーで構造体を定義できます(標準はありません)。しかし、将来的に内部構造を変更したい場合は、コードを壊す可能性があります。カプセル化は、コードを壊すことなく実装を簡単に変更できるようにするコーディングのスタイルにすぎません。int getCount(ObjectType obj)実装ファイルで構造体を定義することを選択した場合、etcなどのアクセサメソッドを介していつでもメンバーにアクセスできます。

2

私の推奨事項:シンプルにしてください。私が抱えている最大の問題の1つは、古いソフトウェア(場合によっては10年以上)の保守です。コードが単純でない場合は、難しい場合があります。はい、Cでポリモーフィズムを使用して非常に便利なOOPを作成できますが、読みにくい場合があります。

私はいくつかの明確に定義された機能をカプセル化する単純なオブジェクトを好みます。これの良い例はGLIB2、例えばハッシュテーブルです:

GHastTable* my_hash = g_hash_table_new(g_str_hash, g_str_equal);
int size = g_hash_table_size(my_hash);
...

g_hash_table_remove(my_hash, some_key);

キーは次のとおりです。

  1. シンプルなアーキテクチャとデザインパターン
  2. 基本的なOOPカプセル化を実現します。
  3. 実装、読み取り、理解、保守が簡単

1

CIでOOPを作成する場合は、おそらく疑似Pimplデザインを使用します。構造体にポインタを渡す代わりに、構造体へのポインタにポインタを渡すことになります。これにより、コンテンツが不透明になり、ポリモーフィズムと継承が容易になります。

CでのOOPの実際の問題は、変数がスコープを出るときに何が起こるかです。コンパイラが生成するデストラクタはなく、問題が発生する可能性があります。マクロが役立つ可能性がありますが、常に見づらいものになります。


1
Cでプログラミングするときは、ifステートメントを使用してスコープを処理し、最後に解放します。例if ( (obj = new_myObject()) ) { /* code using myObject */ free_myObject(obj); }

1

Cを使用してオブジェクト指向スタイルでプログラミングする別の方法は、ドメイン固有の言語をCに変換するコードジェネレーターを使用することです。TypeScriptとJavaScriptを使用してOOPをjsに変換します。


0
#include "triangle.h"
#include "rectangle.h"
#include "polygon.h"

#include <stdio.h>

int main()
{
    Triangle tr1= CTriangle->new();
    Rectangle rc1= CRectangle->new();

    tr1->width= rc1->width= 3.2;
    tr1->height= rc1->height= 4.1;

    CPolygon->printArea((Polygon)tr1);

    printf("\n");

    CPolygon->printArea((Polygon)rc1);
}

出力:

6.56
13.12

これは、CによるOOプログラミングとは何かを示しています。

これは本物の純粋なCであり、プリプロセッサマクロはありません。継承、ポリモーフィズム、およびデータのカプセル化(クラスまたはオブジェクトにプライベートなデータを含む)があります。保護された修飾子に相当する可能性はありません。つまり、プライベートデータは継承チェーンの下でもプライベートです。しかし、私はそれが必要だとは思わないので、これは不便ではありません。

CPolygon これはインスタンス化されていません。なぜなら、共通の側面を持つがそれらの実装が異なる継承連鎖のオブジェクトを操作するためだけに使用するためです(ポリモーフィズム)。


0

@Adam Rosenfieldは、CでOOPを達成する方法について非常に良い説明をしています

その上、私はあなたに読むことをお勧めします

1)pjsip

VoIPに最適なCライブラリ。構造体と関数ポインタテーブルを介してOOPを実現する方法を学ぶことができます

2)iOSランタイム

iOSランタイムがObjective Cにどのように役立つかを学びます。それはisaポインター、メタクラスを通じてOOPを実現します


0

私にとって、Cのオブジェクト指向には次の機能が必要です。

  1. カプセル化とデータ非表示(構造体/不透明ポインターを使用して実現できます)

  2. 継承とポリモーフィズムのサポート(単一継承は構造体を使用して実現できます-抽象ベースがインスタンス化できないことを確認してください)

  3. コンストラクタとデストラクタの機能(実現は容易ではない)

  4. 型チェック(少なくともユーザー定義型の場合、Cは強制しないため)

  5. 参照カウント(またはRAIIを実装するためのもの)

  6. 例外処理の制限付きサポート(setjmpおよびlongjmp)

上記に加えて、ANSI / ISO仕様に依存する必要があり、コンパイラ固有の機能に依存しないようにする必要があります。


番号(5)の場合-デストラクタがない言語ではRAIIを実装できません(つまり、RAIIはCまたはJavaでコンパイラーがサポートする手法ではありません)。
トム

コンストラクタとデストラクタは、cベースのオブジェクト用に作成できます-GObjectはそれを行うと思います。もちろんRAAI(単純ではなく、醜く、実用的である必要はない)-私が探していたのは、Cベースのセマンティクスを特定して上記を実現することだけです。
FL4SOF 2009年

Cはデストラクタをサポートしていません。あなたは入力する必要が何かを彼らの仕事をするために。つまり、彼らは自分たちを片付けません。GObjectは言語を変更しません。
トム

0

見てくださいhttp://ldeniau.web.cern.ch/ldeniau/html/oopc/oopc.html。ドキュメンテーション全体を読むことが他に何もない場合は、啓発的な経験です。


3
共有しているリンクのコンテキストを入力してください。あなたが共有したリンクは確かに非常に役立つかもしれませんが、質問に答える共有記事の主要な側面をキャプチャすることをお勧めします。このようにして、リンクが削除された場合でも、回答は関連性があり、役に立ちます。
ishmaelMakitla 16

0

私はここでのパーティーに少し遅れますが、私は両方の極端なマクロを避けたいです-コードが多すぎたり難すぎたりしますが、いくつかの明白なマクロはOOPコードの開発と読み取りを容易にします。

/*
 * OOP in C
 *
 * gcc -o oop oop.c
 */

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

struct obj2d {
    float x;                            // object center x
    float y;                            // object center y
    float (* area)(void *);
};

#define X(obj)          (obj)->b1.x
#define Y(obj)          (obj)->b1.y
#define AREA(obj)       (obj)->b1.area(obj)

void *
_new_obj2d(int size, void * areafn)
{
    struct obj2d * x = calloc(1, size);
    x->area = areafn;
    // obj2d constructor code ...
    return x;
}

// --------------------------------------------------------

struct rectangle {
    struct obj2d b1;        // base class
    float width;
    float height;
    float rotation;
};

#define WIDTH(obj)      (obj)->width
#define HEIGHT(obj)     (obj)->height

float rectangle_area(struct rectangle * self)
{
    return self->width * self->height;
}

#define NEW_rectangle()  _new_obj2d(sizeof(struct rectangle), rectangle_area)

// --------------------------------------------------------

struct triangle {
    struct obj2d b1;
    // deliberately unfinished to test error messages
};

#define NEW_triangle()  _new_obj2d(sizeof(struct triangle), triangle_area)

// --------------------------------------------------------

struct circle {
    struct obj2d b1;
    float radius;
};

#define RADIUS(obj)     (obj)->radius

float circle_area(struct circle * self)
{
    return M_PI * self->radius * self->radius;
}

#define NEW_circle()     _new_obj2d(sizeof(struct circle), circle_area)

// --------------------------------------------------------

#define NEW(objname)            (struct objname *) NEW_##objname()


int
main(int ac, char * av[])
{
    struct rectangle * obj1 = NEW(rectangle);
    struct circle    * obj2 = NEW(circle);

    X(obj1) = 1;
    Y(obj1) = 1;

    // your decision as to which of these is clearer, but note above that
    // macros also hide the fact that a member is in the base class

    WIDTH(obj1)  = 2;
    obj1->height = 3;

    printf("obj1 position (%f,%f) area %f\n", X(obj1), Y(obj1), AREA(obj1));

    X(obj2) = 10;
    Y(obj2) = 10;
    RADIUS(obj2) = 1.5;
    printf("obj2 position (%f,%f) area %f\n", X(obj2), Y(obj2), AREA(obj2));

    // WIDTH(obj2)  = 2;                                // error: struct circle has no member named width
    // struct triangle  * obj3 = NEW(triangle);         // error: triangle_area undefined
}

私はこれは良いバランスを持っていると思います、そしてそれは(少なくともデフォルトのgcc 6.3オプションで)それが生成するエラーはいくつかのより起こりそうな間違いのために混乱するのではなく役立つでしょう。重要なのは、プログラマーの生産性を向上させることです。


0

小さなコードを記述する必要がある場合は、次のことを試してください。https//github.com/fulminati/class-framework

#include "class-framework.h"

CLASS (People) {
    int age;
};

int main()
{
    People *p = NEW (People);

    p->age = 10;

    printf("%d\n", p->age);
}

2
回答としてツールやライブラリを投稿しないでください。少なくとも、それ自体が問題をどのように解決するかを答え自体で示します。
Baum mit Augen

0

私はまた、マクロソリューションに基づいてこれに取り組んでいます。ですから、それは勇敢な人のためだけだと思います;-)しかし、それはすでにかなり素晴らしいです、そして私はすでにそれに加えていくつかのプロジェクトに取り組んでいます。まず、クラスごとに個別のヘッダーファイルを定義します。このような:

#define CLASS Point
#define BUILD_JSON

#define Point__define                            \
    METHOD(Point,public,int,move_up,(int steps)) \
    METHOD(Point,public,void,draw)               \
                                                 \
    VAR(read,int,x,JSON(json_int))               \
    VAR(read,int,y,JSON(json_int))               \

クラスを実装するには、クラスのヘッダーファイルと、メソッドを実装するCファイルを作成します。

METHOD(Point,public,void,draw)
{
    printf("point at %d,%d\n", self->x, self->y);
}

クラス用に作成したヘッダーには、必要な他のヘッダーを含め、クラスに関連するタイプなどを定義します。クラスヘッダーとCファイルの両方に、クラス仕様ファイル(最初のコード例を参照)とXマクロを含めます。これらX-マクロ(123など)は、実際のクラス構造体や他の宣言にコードを拡大します。

クラスを継承し、クラス定義の最初の行として#define SUPER supername追加supername__define \します。両方ともそこにいるはずです。JSONサポート、シグナル、抽象クラスなどもあります。

オブジェクトを作成するには、単にを使用しますW_NEW(classname, .x=1, .y=2,...)。初期化は、C11で導入された構造体の初期化に基づいています。それはうまく機能し、リストされていないものはすべてゼロに設定されています。

メソッドを呼び出すには、を使用しますW_CALL(o,method)(1,2,3)。上位の関数呼び出しのように見えますが、これは単なるマクロです。それは((o)->klass->method(o,1,2,3))本当に素晴らしいハックであるものに拡張されます。

ドキュメントコード自体を参照してください。

フレームワークにはボイラープレートコードが必要なので、この仕事を行うPerlスクリプト(wobject)を作成しました。それを使うなら、あなたはただ書くことができます

class Point
    public int move_up(int steps)
    public void draw()
    read int x
    read int y

そして、クラス仕様ファイル、クラスヘッダー、およびPoint_impl.cクラスの実装場所を含むCファイルを作成します。多くの単純なクラスがあっても、すべてがCにある場合は、かなりの作業を節約できます。wobjectは非常に単純な正規表現ベースのスキャナーで、特定のニーズに簡単に適合したり、ゼロから書き直したりできます。



弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.