1

私はCライブラリに取り組んでおり、その一部はいくつかの数学タイプを扱い、それらを操作しています。各タイプには、動的に割り当ておよび解放するファクトリコンストラクタ/デストラクタ関数があります。例えば:

/* Example type, but illustrates situation very well. */
typdef struct {
    float x;
    float y;
    float z;
} Vector3D;

/* Constructor */
Vector* Vector3D_new(float x, float y, float z) {
     Vector3D* vector = (Vector3D*) malloc(sizeof(Vector3D));
     /* Initialization code here...*/

     return vector;
}

/* Destructor */
void Vector3D_destroy(Vector3D* vector) {
    free(vector);
}

素晴らしくシンプルで、ユーザーの適切な初期化の負荷を軽減します。

ここでの私の主な関心事は、これらの型を操作する関数の処理方法(具体的には結果値を返す方法)です。ほとんどすべての二項演算は同じ型の新しいインスタンスになるため、これをどのように与えるかを検討する必要があります。ユーザーに戻ります。で返すこともできますが、より高速で、construct / destructorメソッドと互換性があり、ユーザーにそれほど負担をかけないため、ポインターを渡すことをお勧めします。

現在、関数に結果を動的に割り当てて、それへのポインターを返すことで実装しています。

/* Perform an operation, and dynamically return resultant vector */
Vector3D* addVectors(Vector3D* a, Vector3D* b) {
    Vector3D* c = Vector3D_new(
        a->x + b->x,
        a->y + b->y,
        a->z + b->z);

    return c; 
}

値をユーザーに直接返すことにより、次のようにチェーン化できる(たとえば、パラメーターとして別の関数に直接渡される)ことができるという利点があります。

/* Given three Vector3D*s : a, b, & c */
float dot = dotProduct(crossProduct(a, addVectors(b, c));

しかし、現在の方法を考えると、結果がにaddVectors()直接渡されcrossProduct()、ユーザーがそれに渡される機会がないため、これによりメモリリークが発生します(そして、に渡される結果free()と同じことが起こります)。これを機能させるには、値を保持するためのポインターを作成し、それを使用してから、そのポインターを介してそれを使用する必要があります。crossProduct()dotProduct()free()

Vector3D* d = addVectors(b, c);
Vector3D* e = crossProduct(a, d);
float dot = dotProduct(e);

Vector3D_destroy(d);
Vector3d_destroy(e);

これは機能しますが、直感的ではなく、私が望む連鎖効果を失います。

もう1つの可能性は、操作関数に3つの引数を取るようにすることです。2つはオペランド用で、もう1つは結果を格納するためのものですが、やはりあまり直感的ではありません。

私の質問は次のとおりです 。二項演算で動的メモリを操作するためのエレガントで生産的な方法は何ですか?ボーナスとして、実際のライブラリで使用されているソリューションはかなりクールです。何か案は?:)

4

3 に答える 3

2

あなたが言及したメモリリークに加えて、現在のシステムには他にもいくつかの問題があります。

  • ヒープへの割り当ては、プレーンスタック操作よりも大幅に遅くなります。
  • すべての割り当てもfree()dである必要があります。つまり、すべてのインスタンスで少なくとも2つの関数呼び出しが必要になりますが、スタックベースの設計を使用するだけでは何も必要ありません。
  • メモリは手動で管理する必要があるため、メモリリークの余地がはるかに大きくなります。
  • メモリの割り当てが失敗する可能性があります。スタックベースのシステムはこれを軽減します。
  • ポインターを使用するには、間接参照が必要になります。これは直接アクセスよりも遅く、より多くの(おそらくずさんな)sytaxを必要とします。

これに加えて、多くのコンパイラはプログラムのスタックに使用されるメモリをキャッシュし、ヒープを大幅に改善できます(キャッシュされることはほとんどありません(可能な場合))。

つまり、パフォーマンスだけでなく、保守とクリーンなコードについても、すべてをスタックに依存することが最善です。覚えておくべき唯一のことは、スタック有限であり、気が狂うのも簡単かもしれないということです。短期データ(この場合は2項演算の結果)にはスタックを使用し、より重い長期データにはヒープを使用します。

これがお役に立てば幸いです。:)

注:この回答の情報の多くは、@ Justinのおかげです。

于 2012-06-15T05:01:33.863 に答える
1

私は同じくらい簡単にします

Vector3D addVectors(Vector3D a, Vector3D b) {
    Vector3D c;
    c.x = a.x + b.x;
    c.y = a.y + b.y;
    c.z = a.z + b.z;
    return c;
}

呼び出し元が本当にヒープ上でそれを必要とする場合、彼はそれを自分でコピーすることができます。

于 2012-06-11T19:25:27.810 に答える
1

オペレーター内での割り当ては、見た目ほど便利ではありません。
これは主に、ガベージコレクションがないためであり、割り当ての失敗について心配する必要があるためです。

このコードを考えてみましょう:

Vector3D *v1,*v2,*v3;
Vector3d v4 = addVectors(v1,multiplyVectors(v2,v3));

いい感じ。
しかし、から返されたベクトルはどうなりmultiplyVectorsますか?メモリリーク。
そして、割り当てが失敗した場合はどうなりますか?他の関数のクラッシュ。

インプレースで追加します:
void addVectors(Vector3D *target, const Vector3D *src);
これは。と同等target += src;です。

于 2012-06-11T18:51:08.527 に答える