16

オブジェクト指向 (C#、Java、Scala) 出身の私は、コードの再利用と型安全性の両方の原則を非常に高く評価しています。上記の言語の型引数はその役割を果たし、型安全でコードを「無駄」にしない汎用データ構造を有効にします。

C に行き詰まるにつれて、妥協しなければならないことを認識しており、それが正しいものであってほしいと思っています。データ構造のvoid *各ノード/要素に があり、型の安全性が失われるか、使用する型ごとに構造とコードを書き直す必要があります。

コードの複雑さは明らかな要因です。配列または連結リストを繰り返し処理することは簡単で*next、構造体に a を追加することは余分な労力ではありません。このような場合、構造とコードを再利用しようとしないことは理にかなっています。しかし、より複雑な構造の場合、答えはそれほど明白ではありません。

モジュール性とテスト容易性もあります。構造を使用するコードから型とその操作を分離すると、テストが容易になります。逆もまた真です。他のことをしようとしている間に構造体に対するコードの反復をテストすると、面倒になります。

それで、あなたのアドバイスは何ですか?void *再利用または型安全性と重複コードはありますか? 一般原則はありますか?適合しない場合、オブジェクト指向を手続き型に強制しようとしていますか?

編集: C++ をお勧めしないでください、私の質問は C についてです!

4

11 に答える 11

13

void *コードを再利用できるように使用します。リスト内のデータを適切に取得/設定することを確認するよりも、リンクされたリストなどを再実装する方が手間がかかります。

glibからできるだけ多くのヒントを得てください。それらのデータ構造は非常に素晴らしく、使いやすく、タイプ セーフが失われているためにほとんど問題がありませんでした。

于 2009-12-14T09:46:36.410 に答える
6

ご指摘のとおり、両者のバランスを取る必要があると思います。コードがほんの数行で些細な場合は複製しますが、より複雑な場合はvoid*、いくつかの場所で潜在的なバグ修正やメンテナンスを行う必要がないように、またコード サイズを縮小するために、協力することを検討します。

C ランタイム ライブラリを見ると、 で動作する「一般的な」関数がいくつかあります。void*一般的な例の 1 つは、 での並べ替えqsortです。ソートしたいすべてのタイプに対してこのコードを複製するのは狂気の沙汰です。

于 2009-12-14T09:47:37.503 に答える
4

void ポインターを使用しても問題はありません。変換は内部で行われるため、ポインター型の変数に割り当てるときにキャストする必要さえありません。これを見る価値があるかもしれません: http://www.cpax.org.uk/prg/writings/casting.php

于 2009-12-14T09:44:38.767 に答える
3

この質問への答えは、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);

などなど

于 2009-12-14T13:35:35.967 に答える
2

ここでは C でオブジェクト指向をよく使用しますが、カプセル化と抽象化のためだけであり、ポリモーフィズムなどはありません。

つまり、FooBar(Foo a, ...) のような特定の型がありますが、コレクションの「クラス」には void * を使用します。複数の型を使用できる場合は void * を使用するだけですが、そうすることで、引数を特定の型にする必要がなくなります。コレクションごとに、コレクションはタイプを気にしないため、 void * を持つことは問題ありません。ただし、関数が型 a と型 b を受け入れることができ、それ以外は受け入れられない場合は、2 つのバリアント (1 つは a 用、もう 1 つは b 用) を作成します。

重要なポイントは、タイプを気にしない場合にのみ void * を使用することです。

ここで、同じ基本構造 (すべての型の最初のメンバーとして int a; int b; としましょう) を持つ 50 の型があり、それらの型に対して機能する関数が必要な場合は、共通の最初のメンバーを単独で型にします。 、次に関数にこれを受け入れさせ、 object->ab または (AB*)object を渡します。タイプは不透明です。ab が構造体の最初のフィールドである場合、両方とも機能します。

于 2009-12-14T13:12:39.753 に答える
1

マクロを使用できます。それらは任意の型で機能し、コンパイラは展開されたコードを静的にチェックします。欠点は、(バイナリでの) コード密度が低下し、デバッグがより困難になることです。

少し前にジェネリック関数についてこの質問をしましたが、その答えが役に立ちます。

于 2009-12-14T09:51:59.690 に答える
1

型情報、継承、ポリモーフィズムを C データ構造に効率的に追加できます。これが C++ の機能です。( http://www.embedded.com/97/fe29712.htm )

于 2009-12-14T09:52:26.320 に答える
0

ジェネリックコンテナーは、タイプセーフバージョンでインスタンス化できるように、少しの作業でラップできます。以下にリンクされている完全なヘッダーの例を示します。

/*ジェネリック実装*/

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

特定のラップされたタイプの両端キューをインスタンス化するために使用できるヘッダーから

#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);
}
于 2011-11-04T16:01:05.167 に答える
0

Java では、java.utilパッケージからのすべてのコレクションは、実質的にvoid*ポインター ( Object) と同等のものを保持します。

はい、ジェネリック (1.5 で導入) は構文糖衣を追加し、安全でない代入をコーディングできないようにしますが、ストレージ タイプはそのままObjectです。

void*ですので、ジェネリックフレームワーク型に使ってもオブジェクト指向の罪は無いと思います。

コードでこれを頻繁に行う場合は、一般的な構造からデータを割り当て/取得するタイプ固有のインラインまたはマクロ ラッパーも追加します。

PS してはいけないことの 1 つは、void**割り当てられた/再割り当てされたジェネリック型を返すために使用することです。署名を確認すると、恐ろしいポインターmalloc/reallocなしで正しいメモリ割り当てを実現できることがわかります。void**ここで名前を挙げたくないオープンソースプロジェクトでこれを見たので、私はこれを言っているだけです.

于 2009-12-14T18:44:21.690 に答える
0

C を使用して (一種の) OO フレームワークを構築することはできますが、コンパイラが理解する OO 型システムのような多くの利点を逃してしまいます。オブジェクト指向を C に似た言語で行うことに固執する場合は、C++ を選択することをお勧めします。通常の C よりも複雑ですが、少なくとも OO に対する適切な言語サポートが得られます。

編集: わかりました ... C++ を推奨しないと主張する場合は、C で OO を実行しないことをお勧めします。よろしいですか? オブジェクト指向の習慣に関する限り、おそらく「オブジェクト」の観点から考える必要がありますが、継承とポリモーフィズムは実装戦略から除外してください。汎用性 (関数ポインターを使用) は慎重に使用する必要があります。

void *EDIT 2: 実際、一般的な C リストでの使用は合理的だと思います。マクロ、関数ポインター、ディスパッチ、および私が悪い考えだと思うその種のナンセンスを使用して、モック OO フレームワークを構築しようとしているだけです。

于 2009-12-14T09:42:53.663 に答える