web-dev-qa-db-ja.com

あなたはCでオブジェクト指向のコードを書くことができますか?

あなたはCでオブジェクト指向のコードを書くことができますか?特に多相に関して。


スタックオーバーフローの質問もご覧ください。Cにおけるオブジェクト指向

463
Dinah

はい。実際、Axel Schreinerは 彼の著書 "ANSI-Cでのオブジェクト指向プログラミング"を無料で提供しています。

341
mepcotterell

多態性について話しているのですから、そのとおりです。C++が登場する何年も前から、そうしたことをやっていました。

基本的には、データとそのデータに関連する関数を指す関数ポインタのリストの両方を保持するためにstructを使います。

したがって、コミュニケーションクラスでは、オブジェクトのデータと並んで、構造体の4つの関数ポインタとして維持されるopen、read、write、closeの呼び出しがあります。

typedef struct {
    int (*open)(void *self, char *fspec);
    int (*close)(void *self);
    int (*read)(void *self, void *buff, size_t max_sz, size_t *p_act_sz);
    int (*write)(void *self, void *buff, size_t max_sz, size_t *p_act_sz);
    // And data goes here.
} tCommClass;

tCommClass commRs232;
commRs232.open = &rs232Open;
: :
commRs232.write = &rs232Write;

tCommClass commTcp;
commTcp.open = &tcpOpen;
: :
commTcp.write = &tcpWrite;

もちろん、上記のコードセグメントは実際にはrs232Init()のような「コンストラクタ」にあります。

そのクラスから「継承」するときは、ポインタを自分の関数を指すように変更するだけです。それらの関数を呼び出した人は誰でもあなたがあなたの多態性を与えて、関数ポインタを通してそれをするでしょう:

int stat = (commTcp.open)(commTcp, "bigiron.box.com:5000");

手動のvtableのようなもの。

ポインタをNULLに設定して仮想クラスを作成することもできます。動作はC++と多少異なります(コンパイル時のエラーではなく実行時のコアダンプ)。

これを示すサンプルコードを次に示します。まずトップレベルのクラス構造

#include <stdio.h>

// The top-level class.

typedef struct sCommClass {
    int (*open)(struct sCommClass *self, char *fspec);
} tCommClass;

それからTCP 'サブクラス'の関数があります。

// Function for the TCP 'class'.

static int tcpOpen (tCommClass *tcp, char *fspec) {
    printf ("Opening TCP: %s\n", fspec);
    return 0;
}
static int tcpInit (tCommClass *tcp) {
    tcp->open = &tcpOpen;
    return 0;
}

そしてHTTPも同様です。

// Function for the HTTP 'class'.

static int httpOpen (tCommClass *http, char *fspec) {
    printf ("Opening HTTP: %s\n", fspec);
    return 0;
}
static int httpInit (tCommClass *http) {
    http->open = &httpOpen;
    return 0;
}

そして最後にそれを実際に動作させるためのテストプログラム:

// Test program.

int main (void) {
    int status;
    tCommClass commTcp, commHttp;

    // Same 'base' class but initialised to different sub-classes.

    tcpInit (&commTcp);
    httpInit (&commHttp);

    // Called in exactly the same manner.

    status = (commTcp.open)(&commTcp, "bigiron.box.com:5000");
    status = (commHttp.open)(&commHttp, "http://www.Microsoft.com");

    return 0;
}

これにより出力が生成されます。

Opening TCP: bigiron.box.com:5000
Opening HTTP: http://www.Microsoft.com

そのため、サブクラスに応じてさまざまな関数が呼び出されていることがわかります。

319
paxdiablo

名前空間はしばしば以下のようにして行われます。

stack_Push(thing *)

の代わりに

stack::Push(thing *)

C 構造体を C++ クラスのようなものにするには、変えることができます:

class stack {
     public:
        stack();
        void Push(thing *);
        thing * pop();
        static int this_is_here_as_an_example_only;
     private:
        ...
};

struct stack {
     struct stack_type * my_type;
     // Put the stuff that you put after private: here
};
struct stack_type {
     void (* construct)(struct stack * this); // This takes uninitialized memory
     struct stack * (* operator_new)(); // This allocates a new struct, passes it to construct, and then returns it
     void (*Push)(struct stack * this, thing * t); // Pushing t onto this stack
     thing * (*pop)(struct stack * this); // Pops the top thing off the stack and returns it
     int this_is_here_as_an_example_only;
}Stack = {
    .construct = stack_construct,
    .operator_new = stack_operator_new,
    .Push = stack_Push,
    .pop = stack_pop
};
// All of these functions are assumed to be defined somewhere else

そして、やります:

struct stack * st = Stack.operator_new(); // Make a new stack
if (!st) {
   // Do something about it
} else {
   // You can use the stack
   stack_Push(st, thing0); // This is a non-virtual call
   Stack.Push(st, thing1); // This is like casting *st to a Stack (which it already is) and doing the Push
   st->my_type.Push(st, thing2); // This is a virtual call
}

デストラクタも削除もしませんでしたが、同じパターンに従います。

this_is_here_as_an_example_onlyは静的クラス変数のようなものです - 型のすべてのインスタンス間で共有されます。すべてのメソッドは本当に静的ですが、いくつかはthis *を取ります。

81
nategoose

私はそれ自身で有用であることに加えて、CでOOPを実装することは学びOOPそしてその内部を理解するための優れた方法であると信じます働きます。多くのプログラマーの経験から、テクニックを効率的かつ自信を持って使用するには、プログラマーは基礎となる概念が最終的にどのように実装されるかを理解しなければならないことがわかっています。 Cでクラス、継承、およびポリモーフィズムをエミュレートすることは、まさにこれを教えています。

元の質問に答えるために、CでOOPを実行する方法を教える2、3のリソースがあります。

EmbeddedGurus.comのブログ記事「Cでのオブジェクトベースプログラミング」は、移植可能なCでクラスと単一継承を実装する方法を示しています。 http://embeddedgurus.com/state-space/2008/01/ C言語でのオブジェクトベースプログラミング/

アプリケーションノート "" C++ - Cのオブジェクト指向プログラミング "はプリプロセッサマクロを使ってCでクラス、単一継承、そしてレイトバインディング(多態性)を実装する方法を示しています。 http://www.state -machine.com/resources/cplus_3.0_manual.pdf 、サンプルコードは http://www.state-machine.com/resourcesから入手できます。 /cplus_3.0.Zip

53
Miro Samek

私はそれが終わったのを見ました。お勧めしません。 C++はもともとこのように中間ステップとしてCコードを生成するプリプロセッサとして始まりました。

基本的にあなたがやろうとしていることはあなたがあなたの関数参照を格納するあなたのメソッドすべてのためのディスパッチテーブルを作成することです。クラスを派生させるには、このディスパッチテーブルをコピーしてオーバーライドするエントリを置き換えます。新しい「メソッド」は、ベースメソッドを呼び出す場合は元のメソッドを呼び出す必要があります。結局、あなたはC++を書き直すことになります。

32
tvanfosson

もちろん可能です。これが GObjectGTK +GNOME のすべてが基づいているフレームワークです。 。

C stdio FILEサブライブラリは、純粋なC言語で抽象化、カプセル化、およびモジュール化を作成する方法の優れた例です。

継承と多態性 - OOPにとって不可欠であると考えられる他の側面 - は必ずしも約束された生産性の向上をもたらすわけではなく、 妥当なarguments have 彼らは実際に開発を妨げ、問題領域について考えることができるようになっています

17
msw

動物と犬との些細な例:あなたはC++のvtableメカニズムを反映しています(とにかく)。また、割り当てとインスタンス化(Animal_Alloc、Animal_New)を分けているので、malloc()を複数回呼び出すことはありません。また、明示的にthisポインタを渡す必要があります。

あなたが非仮想的な機能をやろうとしていたならば、それはtrivalです。あなたはそれらをvtableに追加するだけではなく、静的関数はthisポインタを必要としません。多重継承は一般的にあいまいさを解決するために複数のvtableを必要とします。

また、setjmp/longjmpを使って例外処理を行うことができるはずです。

struct Animal_Vtable{
    typedef void (*Walk_Fun)(struct Animal *a_This);
    typedef struct Animal * (*Dtor_Fun)(struct Animal *a_This);

    Walk_Fun Walk;
    Dtor_Fun Dtor;
};

struct Animal{
    Animal_Vtable vtable;

    char *Name;
};

struct Dog{
    Animal_Vtable vtable;

    char *Name; // Mirror member variables for easy access
    char *Type;
};

void Animal_Walk(struct Animal *a_This){
    printf("Animal (%s) walking\n", a_This->Name);
}

struct Animal* Animal_Dtor(struct Animal *a_This){
    printf("animal::dtor\n");
    return a_This;
}

Animal *Animal_Alloc(){
    return (Animal*)malloc(sizeof(Animal));
}

Animal *Animal_New(Animal *a_Animal){
    a_Animal->vtable.Walk = Animal_Walk;
    a_Animal->vtable.Dtor = Animal_Dtor;
    a_Animal->Name = "Anonymous";
    return a_Animal;
}

void Animal_Free(Animal *a_This){
    a_This->vtable.Dtor(a_This);

    free(a_This);
}

void Dog_Walk(struct Dog *a_This){
    printf("Dog walking %s (%s)\n", a_This->Type, a_This->Name);
}

Dog* Dog_Dtor(struct Dog *a_This){
    // Explicit call to parent destructor
    Animal_Dtor((Animal*)a_This);

    printf("dog::dtor\n");

    return a_This;
}

Dog *Dog_Alloc(){
    return (Dog*)malloc(sizeof(Dog));
}

Dog *Dog_New(Dog *a_Dog){
    // Explict call to parent constructor
    Animal_New((Animal*)a_Dog);

    a_Dog->Type = "Dog type";
    a_Dog->vtable.Walk = (Animal_Vtable::Walk_Fun) Dog_Walk;
    a_Dog->vtable.Dtor = (Animal_Vtable::Dtor_Fun) Dog_Dtor;

    return a_Dog;
}

int main(int argc, char **argv){
    /*
      Base class:

        Animal *a_Animal = Animal_New(Animal_Alloc());
    */
    Animal *a_Animal = (Animal*)Dog_New(Dog_Alloc());

    a_Animal->vtable.Walk(a_Animal);

    Animal_Free(a_Animal);
}

PS。これはC++コンパイラでテストされていますが、Cコンパイラで動作させるのは簡単なはずです。

15
Jasper Bekkers

GObject を調べてください。それはCのOOとあなたが探しているものの一つの実装です。本当にOOがほしいのであれば、C++や他のOOP言語を使ってください。 GObjectは、OO言語を扱うのに慣れている人にとっては、時には非常に難しい作業になる可能性がありますが、他のものと同様に、規則とフローに慣れるでしょう。

13
NG.

これは読むのが面白かったです。私は同じ質問を自分自身で考えてきました、そしてそれについて考えることの利点はこれです:

  • OOP以外の言語でOOPの概念を実装する方法を想像してみると、OOp言語(私の場合はC++)の長所を理解するのに役立ちます。これにより、特定の種類のアプリケーションでCとC++のどちらを使用するかについて、より適切な判断を下すことができます。一方の利点が他方の利点よりも重要です。

  • これに関する情報や意見を私のウェブで閲覧したところ、私は組み込みプロセッサ用のコードを書いていてCコンパイラしか利用できなかった作者を見つけました。 http://www.eetimes.com/ Discussion/other/4024626 /オブジェクト指向C作成基礎クラス - パート1

彼の場合、プレーンCでOOP概念を分析して適応させることは有効な追求でした。 Cでそれらを実装しようとした結果生じるパフォーマンスのオーバーヘッドのため、彼はいくつかのOOPの概念を犠牲にしても構わなかったようです。

私が取った教訓は、そうです、それはある程度ある程度することができます、そして、はい、それを試みるためのいくつかの正当な理由があります。

最後に、マシンはスタックポインタビットを回転させ、プログラムカウンタを飛び回らせてメモリアクセス操作を計算させます。効率の観点からは、プログラムによるこれらの計算の数は少ないほど良いのですが、人的ミスの影響を受けにくい方法でプログラムを編成できるように、単純にこの税金を支払わなければならない場合があります。 OOP言語コンパイラは、両方の側面を最適化するように努めています。プログラマーはこれらの概念をCのような言語でもっと慎重に実装する必要があります。

12
RJB

使用できる手法はいくつかあります。最も重要なのは、プロジェクトを分割する方法です。私たちのプロジェクトでは、.hファイルで宣言されたインターフェイスと、.cファイルでのオブジェクトの実装を使用します。重要な点は、.hファイルを含むすべてのモジュールがvoid *としてオブジェクトのみを認識し、.cファイルが構造の内部を知っている唯一のモジュールであるということです。

例としてFOOと名付けたクラスのためのこのような何か:

.hファイル内

#ifndef FOO_H_
#define FOO_H_

...
 typedef struct FOO_type FOO_type;     /* That's all the rest of the program knows about FOO */

/* Declaration of accessors, functions */
FOO_type *FOO_new(void);
void FOO_free(FOO_type *this);
...
void FOO_dosomething(FOO_type *this, param ...):
char *FOO_getName(FOO_type *this, etc);
#endif

C実装ファイルはそのようなものになるでしょう。

#include <stdlib.h>
...
#include "FOO.h"

struct FOO_type {
    whatever...
};


FOO_type *FOO_new(void)
{
    FOO_type *this = calloc(1, sizeof (FOO_type));

    ...
    FOO_dosomething(this, );
    return this;
}

だから私はそのモジュールのすべての関数にオブジェクトへのポインタを明示的に渡します。 C++コンパイラはそれを暗黙的に行い、Cでは明示的に書き出します。

私のプログラムがC++でコンパイルされないようにするために、私は私のプログラムで本当にthisを使用しています。それは私の構文強調表示エディタでは別の色になるという素晴らしい特性を持っています。

FOO_structのフィールドは1つのモジュールで変更することができ、別のモジュールはまだ使用可能にするために再コンパイルする必要すらありません。

そのスタイルで、私はOOP(データのカプセル化)の利点の大部分をすでに処理しています。関数ポインタを使用することで、継承のようなものを実装するのはさらに簡単ですが、正直なところ、本当に役に立ちません。

10

Core FoundationのAPIセットについては、Appleのドキュメントを参照すると便利です。これは純粋なC APIですが、型の多くはObjective-Cオブジェクトと同等のものにブリッジされています。

Objective-C自体の設計を見てみると便利です。オブジェクトシステムがCの関数で定義されているという点でC++とは少し異なります。オブジェクトのメソッドを呼び出すobjc_msg_send。コンパイラーは角括弧構文をそれらの関数呼び出しに変換するので、それを知っている必要はありませんが、あなたの質問を考えるとそれがどうやって内部でどのように機能するかを学ぶのが役に立つかもしれません。

10
benzado

オブジェクト指向Cができます、韓国でその種類のコードを見たことがありますが、それは私が長年見ていた最も恐ろしいモンスターでした(これは私がコードを見た昨年(2007年)のようでした)。それで、そうすることができます、そして、はい、人々は前にそれをしました、そしてそれでもこの日と年齢でそれをします。しかし、私はC++またはObjective-Cをお勧めします。どちらもCから生まれた言語であり、オブジェクト指向を異なるパラダイムで提供することを目的としています。

7
Robert Gould

あなたが解決しようとしている問題に対してOOPアプローチが優れていると確信しているなら、なぜあなたはそれを非OOP言語で解決しようとしているのでしょうか?あなたがその仕事に間違った道具を使っているようです。 C++または他のオブジェクト指向のC言語を使用してください。

Cで書かれた既存の大規模プロジェクトのコーディングを始めようとしているのであれば、自分の(または他の誰かの)OOPパラダイムをプロジェクトのインフラストラクチャに無理やり押し込まないでください。プロジェクトに既に存在しているガイドラインに従ってください。一般的に、クリーンなAPIと独立したライブラリおよびモジュールは、クリーンなOOP - ish設計を実現するための長い道のりです。

結局のところ、あなたが本当にOOP Cをやろうとしているのであれば、これを読んでください(PDF)。

7
RarrRarrRarr

関数ポインタを使ってそれを偽造することができます、そして実際、私はC++プログラムをCにコンパイルすることが理論的に可能であると思います.

ただし、パラダイムを使用する言語を選択するのではなく、ある言語にパラダイムを強制することが理にかなっていることはめったにありません。

7
Uri

はい、できます。人々はC++や Objective-C が登場する前にオブジェクト指向のCを書いていました。 C++とObjective-Cの両方とも、部分的には、Cで使用されているOO概念の一部を採用し、それらを言語の一部として形式化することを試みていました。

これは、メソッド呼び出しのように見えるものを作成する方法を示す、非常に単純なプログラムです(これを実行するより良い方法があります。これは、言語が概念をサポートしていることを証明するものです)。

#include<stdio.h>

struct foobarbaz{
    int one;
    int two;
    int three;
    int (*exampleMethod)(int, int);
};

int addTwoNumbers(int a, int b){
    return a+b;
}

int main()
{
    // Define the function pointer
    int (*pointerToFunction)(int, int) = addTwoNumbers;

    // Let's make sure we can call the pointer
    int test = (*pointerToFunction)(12,12);
    printf ("test: %u \n",  test);

    // Now, define an instance of our struct
    // and add some default values.
    struct foobarbaz fbb;
    fbb.one   = 1;
    fbb.two   = 2;
    fbb.three = 3;

    // Now add a "method"
    fbb.exampleMethod = addTwoNumbers;

    // Try calling the method
    int test2 = fbb.exampleMethod(13,36);
    printf ("test2: %u \n",  test2);

    printf("\nDone\n");
    return 0;
}
6
Alan Storm

追加する小さなOOCコード

#include <stdio.h>

struct Node {
    int somevar;
};

void print() {
    printf("Hello from an object-oriented C method!");
};

struct Tree {
    struct Node * NIL;
    void (*FPprint)(void);
    struct Node *root;
    struct Node NIL_t;
} TreeA = {&TreeA.NIL_t,print};

int main()
{
    struct Tree TreeB;
    TreeB = TreeA;
    TreeB.FPprint();
    return 0;
}
6
user922475

もちろん、サポートが組み込まれている言語を使うのと同じくらいきれいになるわけではありません。私も「オブジェクト指向アセンブラ」を書いた。

6
Darron

私はこれを1年間掘り下げてきました:

GObjectシステムは純粋なCでは使いにくいので、CでOOスタイルを簡単にするためにいくつかのNiceマクロを書いてみました。

#include "OOStd.h"

CLASS(Animal) {
    char *name;
    STATIC(Animal);
    vFn talk;
};
static int Animal_load(Animal *THIS,void *name) {
    THIS->name = name;
    return 0;
}
ASM(Animal, Animal_load, NULL, NULL, NULL)

CLASS_EX(Cat,Animal) {
    STATIC_EX(Cat, Animal);
};
static void Meow(Animal *THIS){
    printf("Meow!My name is %s!\n", THIS->name);
}

static int Cat_loadSt(StAnimal *THIS, void *PARAM){
    THIS->talk = (void *)Meow;
    return 0;
}
ASM_EX(Cat,Animal, NULL, NULL, Cat_loadSt, NULL)


CLASS_EX(Dog,Animal){
    STATIC_EX(Dog, Animal);
};

static void Woof(Animal *THIS){
    printf("Woof!My name is %s!\n", THIS->name);
}

static int Dog_loadSt(StAnimal *THIS, void *PARAM) {
    THIS->talk = (void *)Woof;
    return 0;
}
ASM_EX(Dog, Animal, NULL, NULL, Dog_loadSt, NULL)

int main(){
    Animal *animals[4000];
    StAnimal *f;
    int i = 0;
    for (i=0; i<4000; i++)
    {
        if(i%2==0)
            animals[i] = NEW(Dog,"Jack");
        else
            animals[i] = NEW(Cat,"Lily");
    };
    f = ST(animals[0]);
    for(i=0; i<4000; ++i) {
        f->talk(animals[i]);
    }
    for (i=0; i<4000; ++i) {
        DELETE0(animals[i]);
    }
    return 0;
}

これが私のプロジェクトサイトです(en。docを書くのに十分な時間がありませんが、中国語のdocの方がはるかに優れています)。

OOC-GCC

5
dameng

Jim Larsonの1996年の講演でCを使用した継承の例があります。 セクション312プログラミングランチタイムセミナー高レベルCと低レベルC

4
Judge Maygarden

Cを使ってC++スタイルをエミュレートしようとしているようです。オブジェクト指向プログラミングを行うことは実際には構造指向プログラミングを行うことです。ただし、遅延バインディング、カプセル化、および継承などを実現できます。継承のためにあなたは明示的にあなたのサブ構造体の中で基本構造体へのポインタを定義します、そしてこれは明らかに多重継承の形式です。あなたがあなたの

//private_class.h
struct private_class;
extern struct private_class * new_private_class();
extern int ret_a_value(struct private_class *, int a, int b);
extern void delete_private_class(struct private_class *);
void (*late_bind_function)(struct private_class *p);

//private_class.c
struct inherited_class_1;
struct inherited_class_2;

struct private_class {
  int a;
  int b;
  struct inherited_class_1 *p1;
  struct inherited_class_2 *p2;
};

struct inherited_class_1 * new_inherited_class_1();
struct inherited_class_2 * new_inherited_class_2();

struct private_class * new_private_class() {
  struct private_class *p;
  p = (struct private_class*) malloc(sizeof(struct private_class));
  p->a = 0;
  p->b = 0;
  p->p1 = new_inherited_class_1();
  p->p2 = new_inherited_class_2();
  return p;
}

    int ret_a_value(struct private_class *p, int a, int b) {
      return p->a + p->b + a + b;
    }

    void delete_private_class(struct private_class *p) {
      //release any resources
      //call delete methods for inherited classes
      free(p);
    }
    //main.c
    struct private_class *p;
    p = new_private_class();
    late_bind_function = &implementation_function;
    delete_private_class(p);

c_compiler main.c inherited_class_1.obj inherited_class_2.obj private_class.objでコンパイルしてください。

だからアドバイスは純粋なCスタイルに固執することであり、C++スタイルに強制しようとしないことです。またこの方法は、APIを作成するための非常にクリーンな方法にも役立ちます。

4
user2074102

質問に対する答えは「はい、できます」です。

オブジェクト指向C(OOC)キットはオブジェクト指向の方法でプログラムしたい人のためのものですが、古き良きCにもこだわります。 OOCは、クラス、単一および多重継承、例外処理を実装します。

機能

•Cのマクロと関数のみを使用します。言語の拡張は不要です。 (ANSI-C)

•あなたのアプリケーションの読みやすいソースコード。物事をできるだけ単純にするように注意を払った。

•クラスの単一継承

インターフェイスおよびミックスインによる多重継承(バージョン1.3以降)

•(純粋なC言語で)例外を実装する

•クラスの仮想機能

•簡単なクラス実装のための外部ツール

詳細については、 http://ooc-coding.sourceforge.net/ をご覧ください。

4
Sachin Mhetre

どの記事や本がCでOOPの概念を使うのが良いですか?

Dave Hansonの Cインタフェースと実装 は、カプセル化と命名において優れたであり、関数ポインタの使用に関して非常に優れています。 Daveは継承をシミュレートしようとしません。

4
Norman Ramsey

したいことの1つは、 X WindowXt ツールキットの実装を調べることです。確かに歯の中では長くなりますが、使用される構造の多くは、従来のC内でOOのように機能するように設計されました。その他。

このようにCにあるOOの方法で本当に多くのことができますが、時々OO概念は#include<favorite_OO_Guru.h>の精神から完全に形成されたようには思えません。彼らは本当に当時の確立されたベストプラクティスの多くを構成していました。 OO言語とシステムは、その日のプログラミング時代の一部を蒸留して増幅しただけです。

4
Ukko

OOPは、プログラム内のコードよりもデータの重要性が高いパラダイムにすぎません。 OOPは言語ではありません。したがって、プレーンCが単純な言語であるのと同様に、プレーンCのOOPも単純です。

4
anonyme

私はそれを試したところ小さなライブラリーを作りました、そして私にとってそれは本当にうまく機能します。だから私は経験を共有すると思いました。

https://github.com/thomasfuhringer/oxygen

単一継承は、構造体を使用してそれを他のすべての子クラスに拡張することで、非常に簡単に実装できます。親構造への単純なキャストは、すべての子孫で親メソッドを使用することを可能にします。変数がこの種のオブジェクトを保持する構造体を指していることを知っている限り、いつでもルートクラスにキャストしてイントロスペクションを行うことができます。

すでに述べたように、仮想メソッドはややトリッキーです。しかしそれらは実行可能です。簡単にするために、必要に応じてすべての子クラスが個々のスロットをコピーして再生成するクラス記述構造内の関数の配列を使用しています。

多重継承は実装がかなり複雑になり、パフォーマンスに大きな影響を与えます。だから私はそれを残します。実生活の状況を明確にモデル化することは、かなりの数のケースで望ましいと考えていますが、おそらく90%のケースで単一の継承がニーズをカバーしています。また、単一継承は単純で、費用はかかりません。

また、私は型の安全性を気にしません。プログラミングのミスを防ぐために、コンパイラに頼るべきではないと思います。とにかくそれはエラーのかなり小さい部分からのみあなたを保護します。

通常、オブジェクト指向環境では、可能な限りメモリ管理を自動化するために参照カウントを実装することも必要です。だから私はまた、 "Object"ルートクラスとヒープメモリの割り当てと割り当て解除をカプセル化するためのいくつかの機能に参照カウントを入れました。

それはすべて非常に単純で無駄のないもので、C [+]のモンスターに対処することを強いられることなく、OOの要点を教えてくれます。そして私はCランドでの滞在の柔軟性を保持しています。それはとりわけ、サードパーティのライブラリを統合することをより簡単にします。

2
Thomas F.

私はパーティーには少し時間がかかりますが、このトピックに関する私の経験を共有したいと思います。私は最近組み込みのものを扱っています。 Cで書かれた私の組み込みプロジェクトでのアプローチ.

私が今まで見てきた解決策のほとんどは型キャストを多用するので、型安全性を失います。間違えてもコンパイラは役に立ちません。これはまったく受け入れられません。

私が持っている要件:

  • 型キャストをできるだけ避けて、型の安全性を失わないようにします。
  • ポリモーフィズム:私たちは仮想メソッドを使うことができるはずです、そしてクラスのユーザーはある特定のメソッドが仮想であるかどうかを意識するべきではありません。
  • 多重継承:私はそれをあまり使いませんが、時には私は本当にあるクラスに複数のインターフェースを実装したい(または複数のスーパークラスを拡張したい)ことを望みます。

この記事では、私のアプローチについて詳しく説明しました。 Cでのオブジェクト指向プログラミング ;加えて、基本クラスと派生クラスの定型コードの自動生成のためのユーティリティがあります。

2
Dmitry Frank

CのOOPのもう1つの工夫については、 http://slkpg.byethost7.com/instance.html を参照してください。ネイティブCを使用した再入可能性のデータ。関数のラッパーを使用して手動で多重継承が行われます。型安全性は維持されます。これは小さなサンプルです。

typedef struct _peeker
{
    log_t     *log;
    symbols_t *sym;
    scanner_t  scan;            // inherited instance
    peek_t     pk;
    int        trace;

    void    (*Push) ( SELF *d, symbol_t *symbol );
    short   (*peek) ( SELF *d, int level );
    short   (*get)  ( SELF *d );
    int     (*get_line_number) ( SELF *d );

} peeker_t, SlkToken;

#define Push(self,a)            (*self).Push(self, a)
#define peek(self,a)            (*self).peek(self, a)
#define get(self)               (*self).get(self)
#define get_line_number(self)   (*self).get_line_number(self)

INSTANCE_METHOD
int
(get_line_number) ( peeker_t *d )
{
    return  d->scan.line_number;
}

PUBLIC
void
InitializePeeker ( peeker_t  *peeker,
                   int        trace,
                   symbols_t *symbols,
                   log_t     *log,
                   list_t    *list )
{
    InitializeScanner ( &peeker->scan, trace, symbols, log, list );
    peeker->log = log;
    peeker->sym = symbols;
    peeker->pk.current = peeker->pk.buffer;
    peeker->pk.count = 0;
    peeker->trace = trace;

    peeker->get_line_number = get_line_number;
    peeker->Push = Push;
    peeker->get = get;
    peeker->peek = peek;
}
2
slkpg

CのスーパーセットであるObjective-Cを使用することを提案します。

Objective-Cは30歳ですが、エレガントなコードを書くことができます。

http://en.wikipedia.org/wiki/Objective-C

1
SteAp

はい、しかし私は誰もがCと何らかの種類の多態性を実装しようとするのを見たことがありません。

0
Paul Morel

はい、可能です。

これは純粋なCで、マクロの前処理はありません。継承、多態性、データのカプセル化(プライベートデータを含む)があります。これには、保護された同等の修飾子がありません。これは、プライベートデータも継承チェーンの下でプライベートであることを意味します。

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

/*output:
6.56
13.12
*/
0
rogergc

最初に言うべきことは(少なくとも私見では)Cの関数ポインタの実装は本当に使うのが難しいということです。私は関数ポインタを避けるためにたくさんのフープを飛び越えるでしょう...

そうは言っても、他の人が言ったことはかなり良いと思います。 foo->method(a,b,c)の代わりに構造体を持ち、モジュールを持ち、method(foo,a,b,c)で終わるのです。もしあなたが "method"メソッドを持つ複数の型を持っているなら、他の人が言っているようにFOO_method(foo,a,b,c)を前置できます。 .hファイルをうまく利用することで、プライベートやパブリックなどにすることができます。

さて、このテクニックでは得られないことがいくつかあります。それはあなたのプライベートデータフィールドを与えることはありません。それは、私が思うに、あなたは意志の力と優れたコーディングの衛生状態に関係していなければなりません...また、これを継承する簡単な方法はありません。

これらは少なくとも簡単な部分です...残りの部分、私は状況の90/10の種類だと思います。利益の10%が仕事の90%を必要とします...

0
Brian Postow