ホームページ > 記事 > ウェブフロントエンド > C++ でのジェネリックスの使用によって引き起こされる拡張の問題
数日前、あるブロガーが、生成された実行可能ファイル内のコードが肥大化する原因となる C++ のジェネリックスを批判する記事を読みました。
ブロガーは長年 C++ ソフトウェア開発に従事していましたが、以前の開発環境は十分なリソースのあるサーバーであったため、ディスク容量の問題を考慮する必要はありませんでした。最近、スマート ホーム ホストの組み込みプラットフォームでの開発に C++ を使用する予定です。 FLASH のストレージ容量は限られているため、これは考慮する必要があり、注意を払う必要がある要素です。
要素の型が異なる 2 つのリストを次のように定義します:
list<int> l1; list<string> l2;
C 言語を使用する場合はどうすればよいですか? listbd43222e33876353aff11e13a7dc75f6 に対して 1 つのコード セットを書き込み、list98c455a79ddfebb79781bff588e7b37e に対して別のコード セットを書き込みます。各セットには同じメンバー関数があり、変数の型が異なるだけです。
以下は listbd43222e33876353aff11e13a7dc75f6 の C 言語実装です:
//! code-1struct list_int_item { int value; struct list_int_item *next; };struct list_int { struct list_int_item *head; size_t size; }; void list_int_insert(struct list_int *p, int value);int list_int_sort(struct list_int *p);bool list_int_empty(struct list_int *p); ...
以下は list98c455a79ddfebb79781bff588e7b37e の C 言語実装です:
//! code-2 struct list_string_item { string value; struct list_string_item *next; }; struct list_string { struct list_string_item *head; size_t size; }; void list_string_insert(struct list_int *p, string value); int list_string_sort(struct list_int *p); bool list_string_empty(struct list_int *p); ...
2 つの違いは型です。 C 言語では、次のようにマクロを使用してその型を置き換えることがよくあります:
//! code-3 #define LIST_DECLARE(TYPE) \ struct list_##TYPE##_item { \ TYPE## value; \ struct list_##TYPE##_item *next; \ }; \ \ struct list_##TYPE { \ struct list_##TYPE##_item *head; \ size_t size; \ }; \ \ void list_##TYPE##_insert(struct list_##TYPE *p, ##TYPE## value); \ int list_##TYPE##_sort(struct list_##TYPE *p); \ bool list_##TYPE##_empty(struct list_##TYPE *p); \ ...
次に、list229a20c20174f89abe8fab2ad31639d8 が次のようにヘッダー ファイルで定義されます:
//! code-4 LIST_DECLARE(double)
したがって、ジェネリックスを使用して冗長なコードを生成することは不可能です。回避可能ですが、少なくともそのようなジェネリックスを実行するために C を使用することは避けられません。
それは避けられないので、上記の問題をできるだけ回避する方法を見てみましょう。 「Effective C++」には、次のことについて特に言及している章があります。テンプレート内で不要なパラメータを使用しないでください。コンパイラは、異なるパラメータごとに対応するコードのセットを生成するためです。
コード内にデータ型が 1 つしかない場合、この型を使用して複数の変数が定義されている場合でも、コンパイラーは関連するコードのセットのみを生成しますか? (こうあるべきです)。
比較用の例を書きます: (不要なコードは省略します)
test1.cpp、これにはmapd0cb00eb53176e0fb76e0d413b9941f1しかありませんが、m1、m2、m3が定義されています。
//! code-5 map<int, string> m1; map<int, string> m2; map<int, string> m3; m1.insert(std::make_pair(1, "hello")); m2.insert(std::make_pair(1, "hi")); m3.insert(std::make_pair(1, "lichunjun"));
test2.cppは、test1.cppと比較して、次の3種類があります:
//! code-6 map<int, string> m1; map<int, double> m2; map<int, int> m3; m1.insert(std::make_pair(1, "hello")); m2.insert(std::make_pair(1, 1.2)); m3.insert(std::make_pair(1, 44));
その結果、コンパイルされた実行ファイルのサイズ比較:
[hevake_lcj@Hevake tmp]$ ll test1 test2 -rwxrwxr-x. 1 18784 Mar 19 22:01 test1 -rwxrwxr-x. 1 35184 Mar 19 22:03 test2
test2は、test1の2倍になります。理由は言うまでもありません。 。
別の質問: ポインターは型とみなされますか?
上記の listbd43222e33876353aff11e13a7dc75f6 と list98c455a79ddfebb79781bff588e7b37e は同じコードのセットを共有できません。その理由は、2 つの型の int と string はスペースのサイズと代入方法が異なるためです。したがって、これを実現するには 2 セットのコードを生成する必要があります。
そして、ポインタは、それが何であれ、同じです。 void* を使用してすべてのポインター型を表すことができます。
そこで、上記のコードを変更して再度テストしました:
//! code-7 map<int, string*> m1; map<int, string*> m2; map<int, string*> m3; m1.insert(std::make_pair(1, new string("hello"))); m2.insert(std::make_pair(1, new string("hi"))); m3.insert(std::make_pair(1, new string("lichunjun")));
と
//! code-8 map<int, string*> m1; map<int, double*> m2; map<int, int*> m3; m1.insert(std::make_pair(1, new string("hello"))); m2.insert(std::make_pair(1, new double(1.2))); m3.insert(std::make_pair(1, new int(44)));
結果は次のようになります:
-rwxrwxr-x. 1 18736 Mar 19 23:05 test1 -rwxrwxr-x. 1 35136 Mar 19 23:05 test2
期待される結果 test1 と test2 は似ていますが、結果からは最適化が行われていないのが少し残念です。 ~
考え: C++ にはこれを最適化できるパラメーターはありますか?
そうでない場合は、スペースを節約するために、すべてのポインターを void* 型として定義し、使用時にキャストすることしかできません。
//! code-9 map<int, void*> m1; map<int, void*> m2; map<int, void*> m3; m1.insert(std::make_pair(1, new string("hello"))); m2.insert(std::make_pair(1, new double(1.2))); m3.insert(std::make_pair(1, new int(44))); cout << *static_cast<string*>(m1[1]) << endl; cout << *static_cast<double*>(m2[1]) << endl; cout << *static_cast<int*>(m3[1]) << endl;
上記のコードはコード 8 に基づいており、すべての仕様を void* として定義し、使用時に対応するポインター型への変換を強制するために static_cast を使用しています。
code-7 と比較すると、この方法で得られるコード サイズは 16 バイト増加するだけです。
しかし、このアプローチは非常に望ましくありません。 void* ポインターを使用する必要があると、コンパイラーは型をチェックしなくなり、型を混同しやすくなります。