Cプログラムのオブジェクト指向ベストプラクティス[終了]


19

「オブジェクト指向の砂糖が本当に必要な場合は、C ++を使用してください」 -これを聞いたとき、友人の1人からすぐに返事をもらいました。ここでは2つのことが間違っていることを知っています。最初のOOは「砂糖」ではありません。2番目に、C ++はCを吸収していません。

サーバーをC(Pythonのフロントエンド)で記述する必要があるため、大規模なCプログラムを管理するためのより良い方法を模索しています。

オブジェクトの観点から大規模なシステムをモデリングし、オブジェクトの相互作用により、システムの管理、保守、拡張がより容易になります。しかし、このモデルをオブジェクト(およびそのすべて)を持たないCに変換しようとすると、いくつかの重要な決定に挑戦することになります。

システムに必要なオブジェクト指向の抽象化を提供するカスタムライブラリを作成しますか?オブジェクト、カプセル化、継承、ポリモーフィズム、例外、pub / sub(イベント/シグナル)、名前空間、イントロスペクションなど(GObjectCOSなど)。

または、基本的なC構造(structおよび関数)を使用して、すべてのオブジェクトクラス(およびその他の抽象化)をアドホックな方法で近似します。(たとえば、SOに関するこの質問に対する回答の一部)

最初のアプローチは、モデル全体をCで実装するための構造化された方法を提供しますが、維持する必要がある複雑さの層も追加します。(複雑さは、最初にオブジェクトを使用することで軽減したかったことを思い出してください)。

2番目のアプローチについては知りません。また、あなたが必要とする可能性のあるすべての抽象化を近似するのにどれほど効果的かはわかりません。

したがって、私の簡単な質問は次のとおりです。Cでオブジェクト指向設計を実現するためのベストプラクティスは何ですか。それを行う方法を求めているのではないことに注意してください。これこの質問はそれについて話す、とさえあります本は、この上。私がもっと興味を持っているのは、これを解決するときに現れる実際の問題に対処する現実的なアドバイス/例です。

注:C ++を支持してCを使用すべきではない理由をアドバイスしないでください。私たちはその段階を十分に過ぎました。


3
C ++サーバーを記述して、外部インターフェースがextern "C"pythonから使用できるようにすることができます。手動で行うことも、SWIGに支援してもらうこともできます。したがって、Pythonフロントエンドへの欲求は、C ++を使用しない理由にはなりません。Cにとどまる正当な理由がないと言っているわけではありません。
Jan Hudec

1
この質問には説明が必要です。現在、パラグラフ4と5は基本的にどのアプローチを取るべきかを尋ねていますが、「あなたはそれを行う方法を求めていない」と言い、代わりにベストプラクティス(リスト?)が必要です。Cでそれを行う方法を探していない場合、OOP全般に関連する「ベストプラクティス」のリストを求めていますか?もしそうなら、それは言うが、質問は主観的であるために閉じられる可能性が高いことに注意してください。
カレブ

:)実際の例(コードなど)がどこで行われたのか、そしてそれを行ったときに遭遇した問題を求めています。
ツリーコーダー

4
あなたの要件は紛らわしいようです。あなたは私が見る理由なしにオブジェクト指向の使用を主張します(一部の言語では、プログラムをより保守しやすくする方法ですが、Cでは使用しません)、Cの使用を主張します。オブジェクト指向は手段であり、終わりでも万能薬でもありません。さらに、言語サポートによって大きなメリットが得られます。実際にオブジェクト指向が必要な場合は、言語の選択時にそれを考慮する必要があります。Cで大規模なソフトウェアシステムを作成する方法についての質問は、はるかに意味があります。
デヴィッドソーンリー

「オブジェクト指向のモデリングと設計」をご覧ください。(Rumbaughら):マッピングOO設計上のセクションは、Cなどの言語に存在する
Giorgioの

回答:


16

Cで複雑なプロジェクトどのように構成すべきかという私の答えから(オブジェクト指向ではなく、Cでの複雑さの管理について):

キーはモジュール性です。これは、設計、実装、コンパイル、および保守が簡単です。

  • OOアプリのクラスなど、アプリのモジュールを識別します。
  • 各モジュールのインターフェイスと実装を分離し、他のモジュールに必要なものだけをインターフェイスに配置します。Cには名前空間がないため、インターフェイスのすべてを一意にする必要があります(たとえば、プレフィックスを付ける)。
  • 実装でグローバル変数を非表示にし、アクセサー関数を読み取り/書き込みに使用します。
  • 継承の観点からではなく、構成の観点から考えてください。一般的なルールとして、CでC ++を模倣しようとしないでください。これは、読み取りと保守が非常に困難です。

私の答えから、OO Cのパブリックおよびプライベート関数の一般的な命名規則は何ですか(これはベストプラクティスだと思います):

私が使用する規則は次のとおりです。

  • パブリック関数(ヘッダーファイル内):

    struct Classname;
    Classname_functionname(struct Classname * me, other args...);
    
  • プライベート関数(実装ファイル内で静的)

    static functionname(struct Classname * me, other args...)

さらに、多くのUMLツールはUML図からCコードを生成できます。オープンソースのものはTopcasedです。



1
素晴らしい答え。モジュール性を目指します。オブジェクト指向はそれを提供することになっていますが、1)実際には、オブジェクト指向スパゲッティで終わるのはあまりにも一般的であり、2)それが唯一の方法ではありません。実際のいくつかの例については、Linuxカーネル(Cスタイルのモジュール方式)とglib(Cスタイルのオブジェクト指向)を使用するプロジェクトを見てください。私は両方のスタイルで作業する機会があり、IMO Cスタイルのモジュール性が勝ちました。
ヨハ

そして、なぜ正確に構成が継承よりも優れたアプローチであるのですか?根拠と参考資料は大歓迎です。または、Cプログラムのみを参照していましたか?
アレクサンドルブレフ14年

1
@AleksandrBlekh-はい、私はCのみに言及しています。
mouviciel 14年

16

この議論では、オブジェクト指向とC ++を区別する必要があると思います。

Cでオブジェクトを実装することは可能であり、非常に簡単です。関数ポインタを使用して構造体を作成するだけです。それがあなたの「第二のアプローチ」であり、私はそれで行くでしょう。別のオプションは、構造体で関数ポインターを使用せず、「コンテキスト」ポインターとして直接呼び出しでデータの構造体を関数に渡すことです。読みやすく、より簡単に追跡でき、構造体を変更せずに関数を追加できるため(データが追加されない場合は継承が容易)、IMHOの方が優れています。それが実際、C ++が通常どのようにthisポインターを実装するかです。

組み込みの継承と抽象化のサポートがないため、ポリモーフィズムはより複雑になります。そのため、子クラスに親構造体を含めるか、コピーペーストを大量に行う必要があります。シンプル。大量のバグが発生するのを待っています。

仮想関数は、必要に応じてさまざまな関数を指す関数ポインターを使用して簡単に実現できます。手動で行うと非常にバグが発生しやすく、ポインターを正しく初期化するのは面倒です。

名前空間、例外、テンプレートなどについては、Cに限定されている場合、それらを放棄する必要があると思います。私はOOをCで書くことに取り組んでいますが、選択肢があればそれをしません(その職場で文字通りC ++の導入に取り組み、マネージャーはそれを統合するのがどれほど簡単かを「驚いた」最後に残りのCモジュールを使用します。)。

言うまでもなく、C ++を使用できる場合は、C ++を使用します。しない理由はありません。


実際には、構造体から継承してデータを追加できます。子構造体の最初の項目を、型が親構造体である変数として宣言するだけです。その後、必要に応じてキャストします。
mouviciel

1
@mouviciel-はい。私は言った。" ...したがって、子クラスに親構造体を含める必要があります。または... "
littleadv

5
継承を実装しようとする理由はありません。コードの再利用を実現する手段として、そもそも欠陥のあるアイデアです。オブジェクトの構成が簡単で優れています。
KaptajnKold

@KaptajnKold-同意する。
littleadv

8

Cでオブジェクトの向きを作成する方法の基本を次に示します。

1.オブジェクトの作成とカプセル化

通常-次のようなオブジェクトを作成します

object_instance = create_object_typex(parameter);

メソッドは、ここで2つの方法のいずれかで定義できます。

object_type_method_function(object_instance,parameter1)
OR
object_instance->method_function(object_instance_private_data,parameter1)

ほとんどの場合、object_instance (or object_instance_private_data)返されるのは型であることに注意してくださいvoid *.。アプリケーションは、この個々のメンバーまたは関数を参照できません。

さらに、各メソッドはこれらのobject_instanceを後続のメソッドに使用します。

2.ポリモーフィズム

多くの関数と関数ポインターを使用して、実行時に特定の機能をオーバーライドできます。

たとえば、すべてのobject_methodsは、パブリックメソッドとプライベートメソッドに拡張できる関数ポインターとして定義されます。

またvar_args 、printfで定義された引数の可変数に非常に似ている使用方法により、限られた意味で関数のオーバーロードを適用することもできます。はい、これはC ++ではそれほど柔軟ではありませんが、これが最も近い方法です。

3.継承の定義

継承の定義は少し難しいですが、構造を使用して次のことができます。

typedef struct { 
     int age,
     int sex,
} person; 

typedef struct { 
     person p,
     enum specialty s;
} doctor;

typedef struct { 
     person p,
     enum subject s;
} engineer;

// use it like
engineer e1 = create_engineer(); 
get_person_age( (person *)e1); 

ここでdoctorengineerは人から派生したもので、より高いレベルに型キャストすることが可能ですperson

これの最良の例は、GObjectおよびそれから派生したオブジェクトで使用されます。

4.仮想クラスの作成 すべてのブラウザーでjpegデコードに使用されるlibjpegというライブラリーによる実例を引用しています。error_managerと呼ばれる仮想クラスを作成します。これにより、アプリケーションは具体的なインスタンスを作成して返すことができます-

struct djpeg_dest_struct {
  /* start_output is called after jpeg_start_decompress finishes.
   * The color map will be ready at this time, if one is needed.
   */
  JMETHOD(void, start_output, (j_decompress_ptr cinfo,
                               djpeg_dest_ptr dinfo));
  /* Emit the specified number of pixel rows from the buffer. */
  JMETHOD(void, put_pixel_rows, (j_decompress_ptr cinfo,
                                 djpeg_dest_ptr dinfo,
                                 JDIMENSION rows_supplied));
  /* Finish up at the end of the image. */
  JMETHOD(void, finish_output, (j_decompress_ptr cinfo,
                                djpeg_dest_ptr dinfo));

  /* Target file spec; filled in by djpeg.c after object is created. */
  FILE * output_file;

  /* Output pixel-row buffer.  Created by module init or start_output.
   * Width is cinfo->output_width * cinfo->output_components;
   * height is buffer_height.
   */
  JSAMPARRAY buffer;
  JDIMENSION buffer_height;
};

ここで、JMETHODは、それぞれ正しいメソッドでロードする必要があるマクロを介して関数ポインターで展開することに注意してください。


私はあまり個人的な説明なしに多くのことを言おうとしました。しかし、私は人々が自分のことを試すことができると思います。ただし、私の意図は、物事がどのようにマッピングされるかを示すことです。

また、これがC ++の同等の正確なプロパティではないという多くの議論があります。Cのオブジェクト指向は、その定義にそれほど厳密ではないことを知っています。しかし、そのように働くことは、いくつかのコア原則を理解するでしょう。

重要なことは、オブジェクト指向がC ++やJAVAほど厳密ではないということです。オブジェクト指向の考え方を念頭に置いてコードを構造的に整理し、そのように操作できるということです。

libjpegの実際のデザインと次のリソースを参照することを強くお勧めします

a。Cでのオブジェクト指向プログラミング
b。これは人々がアイデアを交換するのに適した場所
ですc。そして、ここに完全な本があります


3

オブジェクト指向は、次の3つに要約されます。

1)自律クラスを備えたモジュール式プログラム設計。

2)プライベートカプセル化によるデータの保護。

3)継承/ポリモーフィズム、およびコンストラクタ/デストラクタ、テンプレートなどのさまざまな便利な構文

1は断然最も重要であり、また完全に言語に依存せず、すべてプログラム設計に関するものです。Cでは、1つの.hファイルと1つの.cファイルで構成される自律的な「コードモジュール」を作成することでこれを行います。これをオブジェクト指向クラスに相当すると見なしてください。このモジュール内に何を配置するかは、常識、UML、またはC ++プログラムに使用しているOOデザインの任意の方法によって決定できます。

2は、プライベートデータへの意図的なアクセスを保護するだけでなく、意図しないアクセス、つまり「名前空間の乱雑」からも保護するためにも非常に重要です。C ++はこれをCよりも洗練された方法で行いますが、staticキーワードを使用することでCでも実現できます。C ++クラスでプライベートとして宣言した変数はすべて、Cで静的として宣言し、ファイルスコープに配置する必要があります。それらは、独自のコードモジュール(クラス)内からのみアクセスできます。C ++の場合と同様に、「セッター/ゲッター」を記述できます。

3は役立ちますが、必須ではありません。継承なしで、またはコンストラクタ/デストラクタなしでオブジェクト指向プログラムを作成できます。これらは持っておくと便利です。確かにプログラムをよりエレガントに、そしておそらくより安全にすることができます(不注意に使用する場合は逆です)。しかし、それらは必要ありません。Cはこれらの便利な機能のどちらもサポートしていないため、それらを使用せずに実行するだけです。コンストラクターは、init / destruct関数に置き換えることができます。

継承はさまざまな構造体のトリックを介して行うことができますが、おそらくあなたのプログラムを何の利得も持たずに複雑にするだけなので、私はそれに対してアドバイスします(継承は一般にCだけでなく任意の言語で慎重に適用する必要があります)。

最後に、すべてのOOトリック Cで行うことできます。90年代前半のAxel-Tobias Schreinerの著書「ANSI Cによるオブジェクト指向プログラミング」はこれを証明しています。しかし、私はその本を誰にも勧めません。それはあなたのCプログラムに不快で奇妙な複雑さを追加するだけで、大騒ぎする価値はありません。(この本は、私の警告にもかかわらず、まだ興味がある人のためにここで無料入手できます。)

したがって、1)および2)を実装し、残りはスキップすることをお勧めします。これは、20年以上にわたって成功を収めてきたCプログラムの作成方法です。


2

さまざまなObjective-Cランタイムからいくつかの経験を借りて、Cで動的なポリモーフィックなオブジェクト指向機能を書くのはそれほど難しくありません(一方で、25年後もまだ高速で使いやすいようになっています)。ただし、言語構文を拡張せずにObjective-Cスタイルのオブジェクト機能を実装すると、最終的には面倒なコードになります。

  • すべてのクラスは、そのスーパークラス、準拠するインターフェイス、実装するメッセージ(「セレクター」、メッセージ名、「実装」へのマップ、動作を提供する関数)、およびクラスのインスタンスを宣言する構造体によって定義されます可変レイアウト。
  • すべてのインスタンスは、そのクラスへのポインタとそのインスタンス変数を含む構造体によって定義されます。
  • メッセージ送信は、次のような関数を使用して実装されます(特定のケースを与えるか、いくつかの特殊なケースを取ります)objc_msgSend(object, selector, …)。オブジェクトがどのクラスのインスタンスであるかを知ることで、セレクターに一致する実装を見つけて、正しい機能を実行できます。

これはすべて、複数の開発者が互いのクラスを使用および拡張できるように設計された汎用OOライブラリの一部であるため、独自のプロジェクトではやりすぎになる可能性があります。私はよくCプロジェクトを構造と関数を使用して「静的な」クラス指向のプロジェクトとして設計しました。-各クラスはivarレイアウトを指定するC構造の定義です-各インスタンスは対応する構造の単なるインスタンスです-オブジェクトはできません「メッセージ」ですが、メソッドに似た関数MyClass_doSomething(struct MyClass *object, …)が定義されています。これにより、ObjCアプローチよりもコードの内容が明確になりますが、柔軟性が低くなります。

トレードオフの場所はあなた自身のプロジェクトに依存します:他のプログラマがあなたのCインターフェースを使用しないように聞こえますが、選択は内部の好みに帰着します。もちろん、objcランタイムライブラリのようなものが必要だと判断した場合は、クロスプラットフォームのobjcランタイムライブラリが提供されます。


1

GObjectは実際に複雑さを隠しておらず、独自の複雑さをもたらします。アドホックなことは、シグナルやインターフェイスマシンなどの高度なGObjectを必要としない限り、GObjectよりも簡単だと思います。

COSでは、OO構文を使用してC構文を拡張するプリプロセッサが付属しているため、COSとは少し異なります。GObjectには同様のプリプロセッサであるG Object Builderがあります。

また、Valaプログラミング言語を試すこともできます。これは、Cにコンパイルされ、プレーンなCコードからValaライブラリを使用できるようにする完全な高レベル言語です。GObject、独自のオブジェクトフレームワーク、またはアドホックな方法(機能が制限されています)を使用できます。


1

まず、これが宿題であるか、ターゲットデバイス用のC ++コンパイラがない場合を除き、Cを使用する必要はないと思いますが、C ++からのCリンケージを含むCインターフェイスを十分に簡単に提供できます。

第二に、多態性と例外、およびフレームワークが提供できる他の機能にどれだけ依存するかを見ていきます。関連する関数を含む単純な構造体は、完全な機能を備えたフレームワークよりもはるかに簡単です。設計にはそれらが必要であるため、弾丸を噛んでフレームワークを使用するので、自分で機能を実装する必要はありません。

まだ決定を下すデザインがまだない場合は、スパイクを実行して、コードが何を示しているかを確認してください。

最後に、それは1つまたは他の選択肢である必要はありません(最初からフレームワークに行く場合は、おそらくそれに固執するかもしれませんが)、単純な部分の単純な構造で開始し、ライブラリを追加するだけで可能です必要に応じて。

編集:だから、その管理権による決定は、最初のポイントを無視することができます。

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