C++ でのジェネリックスの使用によって引き起こされる拡張の問題

高洛峰
リリース: 2016-11-22 17:33:41
オリジナル
1351 人が閲覧しました

数日前、あるブロガーが、生成された実行可能ファイル内のコードが肥大化する原因となる C++ のジェネリックスを批判する記事を読みました。

ブロガーは長年 C++ ソフトウェア開発に従事していましたが、以前の開発環境は十分なリソースのあるサーバーであったため、ディスク容量の問題を考慮する必要はありませんでした。最近、スマート ホーム ホストの組み込みプラットフォームでの開発に C++ を使用する予定です。 FLASH のストレージ容量は限られているため、これは考慮する必要があり、注意を払う必要がある要素です。

要素の型が異なる 2 つのリストを次のように定義します:

list<int> l1;
list<string> l2;
ログイン後にコピー

C 言語を使用する場合はどうすればよいですか? list に対して 1 つのコード セットを書き込み、list に対して別のコード セットを書き込みます。各セットには同じメンバー関数があり、変数の型が異なるだけです。

以下は list の 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);
...
ログイン後にコピー

以下は list の 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); \
    ...
ログイン後にコピー

次に、list が次のようにヘッダー ファイルで定義されます:

//! code-4

LIST_DECLARE(double)
ログイン後にコピー

したがって、ジェネリックスを使用して冗長なコードを生成することは不可能です。回避可能ですが、少なくともそのようなジェネリックスを実行するために C を使用することは避けられません。

それは避けられないので、上記の問題をできるだけ回避する方法を見てみましょう。 「Effective C++」には、次のことについて特に言及している章があります。テンプレート内で不要なパラメータを使用しないでください。コンパイラは、異なるパラメータごとに対応するコードのセットを生成するためです。

コード内にデータ型が 1 つしかない場合、この型を使用して複数の変数が定義されている場合でも、コンパイラーは関連するコードのセットのみを生成しますか? (こうあるべきです)。

比較用の例を書きます: (不要なコードは省略します)

test1.cpp、これにはmapしかありませんが、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倍になります。理由は言うまでもありません。 。


別の質問: ポインターは型とみなされますか?

上記の list と list は同じコードのセットを共有できません。その理由は、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* ポインターを使用する必要があると、コンパイラーは型をチェックしなくなり、型を混同しやすくなります。


関連ラベル:
c++
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート
私たちについて 免責事項 Sitemap
PHP中国語ウェブサイト:福祉オンライン PHP トレーニング,PHP 学習者の迅速な成長を支援します!