2

C++のテンプレートについて2つの質問があります。単純なリストを作成し、それをプログラムで使用して、さまざまなオブジェクトタイプ(A *、B * ... ALot *)へのポインターを格納するとします。私の同僚は、実際にはすべてのポインターのサイズが同じであっても、タイプごとに専用のコードが生成されると言っています。

これが本当なら、誰かが私に理由を説明できますか?たとえば、Javaのジェネリックは、C++のポインタのテンプレートと同じ目的を持っています。ジェネリックスは、コンパイル前の型チェックにのみ使用され、コンパイル前に削除されます。そしてもちろん、すべてに同じバイトコードが使用されます。

2番目の質問は、charとshortの専用コードも生成されるかどうかです(両方とも同じサイズであり、特殊化されていないことを考慮して)。

これが何か違いを生むのであれば、私たちは組み込みアプリケーションについて話しているのです。

同様の質問を見つけましたが、私の質問に完全には答えていませんでした。C++テンプレートクラスは、使用されるポインター型ごとにコードを複製しますか?

どうもありがとう!

4

2 に答える 2

5

答えは多分です。一般に、テンプレートの各インスタンス化は一意のタイプであり、一意の実装があり、コードの完全に独立したインスタンスになります。インスタンスのマージは可能ですが、「最適化」(「あたかも」ルールの下で)と見なされ、この最適化は広く普及していません。

Javaとの比較に関しては、次の点に注意してください。

  • C ++は、デフォルトで値セマンティクスを使用します。std::vectorたとえば、は実際にコピーを挿入します。また、をコピーするかコピーするshortdoubleによって、生成されるコードに違いが生じます。Javaでは、shortボックスdouble化され、生成されたコードはボックス化されたインスタンスを何らかの方法で複製します。クローン作成はの仮想関数を呼び出すため、別のコードは必要ありませんがObject、物理的にコピーする必要があります。

  • C++はJavaよりもはるかに強力です。特に、関数のアドレスなどを比較することができ、テンプレートのさまざまなインスタンス化の関数がさまざまなアドレスを持っている必要があります。通常、これは重要なポイントではありません。このポイントを無視し、バイナリレベルで同一のインスタンスをマージするように指示するオプションを備えたコンパイラを簡単に想像できます。(VC ++にはこのようなものがあると思います。)

もう1つの問題は、C++でのテンプレートの実装がヘッダーファイルに存在する必要があることです。もちろん、Javaでは、すべてが常に存在している必要があるため、この問題は、テンプレートだけでなく、すべてのクラスに影響します。もちろん、これがJavaが大規模なアプリケーションに適していない理由の1つです。ただし、テンプレートに複雑な機能は必要ないということです。そうすることで、Java(および他の多くの言語)と比較して、C++の主要な利点の1つが失われます。実際、テンプレートに複雑な機能を実装する場合、ほとんどの実装を行う非テンプレートクラスからテンプレートを継承することは珍しくありませんvoid*。の観点からコードの大きなブロックを実装している間void*決して楽しいことではありません。クライアントに両方の長所を提供するという利点があります。実装はコンパイルされたファイルに隠され、クライアントにはどのような形、形、方法でも見えません。

于 2013-03-24T14:28:41.073 に答える
5

C++のテンプレートについて2つの質問があります。単純なリストを作成し、それをプログラムで使用して、さまざまなオブジェクトタイプ(A *、B * ... ALot *)へのポインターを格納するとします。私の同僚は、実際にはすべてのポインターのサイズが同じであっても、タイプごとに専用のコードが生成されると言っています。

はい、これは両方の関数を作成することと同じです。

一部のリンカーは、同一の機能を検出し、それらを排除します。一部のライブラリは、リンカーにこの機能がないことを認識しており、共通コードを1つの実装に分解し、共通コードの周りにキャストラッパーのみを残します。つまり、スペシャライゼーションはstd::vector<T*>すべての作業を転送std::vector<void*>し、途中でキャストを行う場合があります。

現在、comdatフォールディングはデリケートです。同じと思われる関数を作成するのは比較的簡単ですが、同じではなくなるため、2つの関数が生成されます。おもちゃの例として、を使用してタイプ名を出力することができますtypeid(x).name()。現在、関数の各バージョンは区別されており、削除することはできません。

場合によっては、実行時のプロパティが異なると考えてこのようなことを行うことがあります。そのため、同じコードが作成され、同じ関数が削除されます。ただし、スマートC ++コンパイラは、ユーザーが何をしたかを理解し、 as-ifルールを使用してコンパイル時のチェックに変換し、実際には同一ではない関数が同一として扱われるのをブロックします。

これが本当なら、誰かが私に理由を説明できますか?たとえば、Javaのジェネリックは、C++のポインタのテンプレートと同じ目的を持っています。ジェネリックスは、コンパイルごとの型チェックにのみ使用され、コンパイル前に削除されます。そしてもちろん、すべてに同じバイトコードが使用されます。

いいえ、そうではありません。std::function<void()>ジェネリックスは、呼び出し可能なオブジェクトを格納するために行うことなど、型消去のC++手法とほぼ同等です。C ++では、型消去はテンプレートを介して行われることがよくありますが、テンプレートのすべての使用が型消去であるとは限りません。

本質的に型消去されていないテンプレートでC++が行うことは、Javaジェネリックでは一般的に不可能です。

C ++では、テンプレートを使用して型消去されたポインターのコンテナーを作成できstd::vectorますが、それはできません。実際のポインターのコンテナーが作成されます。これの利点は、のすべての型チェックがstd::vectorコンパイル時に行われるため、実行時チェックをstd::vector行う必要がないことです。安全な型消去には、実行時型チェックとそれに関連するオーバーヘッドが必要になる場合があります。

2番目の質問は、charとshortの専用コードも生成されるかどうかです(両方とも同じサイズであり、特殊化されていないことを考慮して)。

それらは異なるタイプです。charorの値で動作が異なるコードを書くことができshortます。例として:

std::cout << x << "\n";

xがaの場合、これは値が--であるshort整数を出力します。これは、に対応する文字を出力します。xxcharx

現在、ほとんどすべてのテンプレートコードはヘッダーファイルに存在し、暗黙的にinlineです。ほとんどinlineの人がそれが何を意味すると思うかを意味するわけではありませんが、コンパイラがコードを呼び出し元のコンテキストに簡単に引き上げることができることを意味します。

これが何か違いを生むのであれば、私たちは組み込みアプリケーションについて話しているのです。

実際に違いを生むのは、特定のコンパイラとリンカ、およびそれらがアクティブにしている設定とフラグです。

于 2013-03-24T15:32:54.017 に答える