私はそのための「コンストラクタ」を作成したいこの構造を持っています。
struct example {
int x, y, z; /* various members */
struct another *another; /* pointer to another structure */
}
私が知っている2つの異なる方法
次のように、ヒープにそれらを割り当てる関数を作成することで、構造を作成できます。
/* create example */
struct example *example_new(int x, int y, int z) {
struct example *p = malloc(sizeof *p);
p->x = x;
p->y = y;
p->z = z;
p->another = another_new();
}
/* delete example */
void example_new(struct example *p) {
another_del(p->another);
free(p);
}
または、ポインタを関数に渡して構造を初期化し、メモリの割り当てと割り当て解除をユーザーに任せることもできます。
/* initalize example */
void example_init(struct example *p, int x, int y, int z) {
assert(p);
p->x = x;
p->y = y;
p->z = z;
p->another = another_new();
}
/* free memory allocated for example */
void example_free(struct example *p) {
another_del(p->another);
}
私は通常、TreesやLinked Lists)のような再帰的な構造を作成するときに最初のアプローチを好みますが、他のすべてのケースでは2番目のアプローチを使用します。
私は再帰的な構造に対して2番目のアプローチを使用しようとしましたが、かなり面倒であることがわかりました。
これらの2つの方法からどのように選択しますか?この問題を解決するために使用する3番目の方法はありますか?
プログラムによってニーズが異なるため、常に正しい答えはありません。インフォームドコールを実行するのに十分な情報がある場合は、それが適切な方法です。 (たとえば、プログラムが可能な限り高いスループットを必要とする場合、いくつかの関数が構造を使用する必要があるたびにmalloc()
/free()
ペアを強制することはおそらく飛ぶことはありません。)
汎用ライブラリをビルドする場合、両方をサポートする方法でビルドするための追加の作業はそれほど多くありません。
void example_init(struct example *p, int x, int y, int z)
-p
が指す構造を初期化します。これは、自動、ヒープ上での割り当て、プールからの引き出しのいずれであっても、_struct example
_を持っている人なら誰でも呼び出すことができます。
void example_destroy(struct example *p)
-p
が指す構造を初期化解除するために必要なことをすべて実行します。 (あなたの場合、これはanother
メンバーに割り当てられたメモリを解放します。)not free p
を行うことに注意してください。構造体がヒープ上にあるかどうか、または呼び出し側がexample_init()
を再度呼び出して再利用しない場合。
struct example * example_new(int x, int y, int z)
-ヒープ上の構造体にスペースを割り当て、それに対してexample_init()
を呼び出し、ポインターを返します。
void example_del(struct example * p)
-example_destroy(p)
を呼び出してから、p
を解放します。
このアプローチには危険があります。つまり、誰かがexample_init()
/example_del()
またはexample_new()
/example_destroy()
呼び出しのペアを実行しようとする可能性があります。 valgrind
のようなツールとfree()
の一部の実装は、これらの問題を自動的に取り除くことができます。別のアプローチは、構造体がヒープ上にあるときに設定できる構造体のどこかを脇に置いておき、アサーションを使用して誰かがそれを間違った方法で行った場合に不平を言うことです。 (example_del()
は、example_destroy()
を呼び出す前にビットをクリアする必要があります。)
最初のオプションの主な利点は、メモリ割り当てを「新規」と「削除」にカプセル化することです。あなただけの構造を作成し、破壊することを覚えておく必要があります。 2番目のオプションでは、オブジェクトを作成/破棄および割り当て/解放するために、個別に覚えておく必要があります。
2番目のオプションの利点は、スタック上にオブジェクトを作成できることです。
struct example;
example_init(&example, 1,2,3);
これには、ヒープへの割り当てよりも大きなパフォーマンス上の利点があります。ただし、オブジェクトがスコープから外れる前に、example_free
を呼び出すように十分注意する必要があります。
したがって、オプション1は一般にエラーが発生しにくく、要件にこれらのオブジェクトをスタックに置くことによるパフォーマンス上の利点が必要でない限り、推奨されるはずです。
使用するアプローチの答えは、APIのユーザーが採用するメモリ管理戦略によって異なります。たとえば、 オブジェクトプール または リージョンベースのメモリ を使用するには、戦略2を使用する必要があります。APIのコンシューマーがメモリ管理をより詳細に制御する必要がある可能性がある場合は、戦略2が好ましい。
malloc
とfree
がAPIのすべてのコンシューマーにとって十分であると確信している場合は、戦略1が適切です。この場合でも、2の関数を提供し、malloc
とfree
のバージョンを定義することは合理的ですそれらの機能の用語。これには、メモリの割り当てとそのメモリの初期化という論理的に異なるタスクを分離するという利点があります。