C ++が嫌いなCプログラマが多すぎると思います。何が良いのか、何が悪いのかをゆっくりと理解するのにかなりの時間(年)かかりました。それを表現する最良の方法はこれだと思います:
コードの削減、ランタイムのオーバーヘッドなし、安全性の向上。
記述するコードが少なければ少ないほど良いです。これは、卓越性を追求するすべてのエンジニアですぐに明らかになります。バグを1か所ではなく、1か所で修正します。一度アルゴリズムを表現し、それを多くの場所で再利用します。あなたがそれについて賢明であること」。事実、C ++を正しく使用すると、ランタイム速度を犠牲にすることなく、Cよりも安全に(つまり、コンパイル時に多くのエラーをキャッチして)、Cよりはるかに少ないコードで表現できます。
レンダラーの簡単な例を次に示します。三角形のスキャンラインでピクセル値を補間する場合。X座標x1から開始し、X座標x2(三角形の左側から右側)に到達する必要があります。そして、各ステップで、通過する各ピクセルで、値を補間する必要があります。
ピクセルに到達する環境光を補間すると:
typedef struct tagPixelDataAmbient {
int x;
float ambientLight;
} PixelDataAmbient;
...
// inner loop
currentPixel.ambientLight += dv;
色を補間すると(「グーロー」シェーディングと呼ばれ、「赤」、「緑」、「青」フィールドは各ピクセルのステップ値によって補間されます):
typedef struct tagPixelDataGouraud {
int x;
float red;
float green;
float blue; // The RGB color interpolated per pixel
} PixelDataGouraud;
...
// inner loop
currentPixel.red += dred;
currentPixel.green += dgreen;
currentPixel.blue += dblue;
「Phong」シェーディングでレンダリングするとき、強度(ambientLight)または色(red / green / blue)を補間しなくなりました-通常のベクトル(nx、ny、nz)を補間し、各ステップで、 -補間された法線ベクトルに基づいて、照明方程式を計算します:
typedef struct tagPixelDataPhong {
int x;
float nX;
float nY;
float nZ; // The normal vector interpolated per pixel
} PixelDataPhong;
...
// inner loop
currentPixel.nX += dx;
currentPixel.nY += dy;
currentPixel.nZ += dz;
さて、Cプログラマーの最初の本能は「一体、値を補間する3つの関数を書き、設定モードに応じてそれらを呼び出す」でしょう。まず第一に、これはタイプの問題があることを意味します。私のピクセルはPixelDataAmbientですか?PixelDataGouraud?PixelDataPhong?ああ、待って、効率的なCプログラマーはユニオンを使うと言った!
typedef union tagSuperPixel {
PixelDataAmbient a;
PixelDataGouraud g;
PixelDataPhong p;
} SuperPixel;
..そして、あなたは機能を持っています...
RasterizeTriangleScanline(
enum mode, // { ambient, gouraud, phong }
SuperPixel left,
SuperPixel right)
{
int i,j;
if (mode == ambient) {
// handle pixels as ambient...
int steps = right.a.x - left.a.x;
float dv = (right.a.ambientLight - left.a.ambientLight)/steps;
float currentIntensity = left.a.ambientLight;
for (i=left.a.x; i<right.a.x; i++) {
WorkOnPixelAmbient(i, dv);
currentIntensity+=dv;
}
} else if (mode == gouraud) {
// handle pixels as gouraud...
int steps = right.g.x - left.g.x;
float dred = (right.g.red - left.g.red)/steps;
float dgreen = (right.g.green - left.a.green)/steps;
float dblue = (right.g.blue - left.g.blue)/steps;
float currentRed = left.g.red;
float currentGreen = left.g.green;
float currentBlue = left.g.blue;
for (j=left.g.x; i<right.g.x; j++) {
WorkOnPixelGouraud(j, currentRed, currentBlue, currentGreen);
currentRed+=dred;
currentGreen+=dgreen;
currentBlue+=dblue;
}
...
混が入り込むのを感じますか?
まず、コードをクラッシュさせるのに必要なのは1つのタイプミスだけです。これは、コンパイラが関数の「Gouraud」セクションで実際に「.a」にアクセスするのを止めないためです。(アンビエント)値。C型システムによって(つまり、コンパイル中に)キャッチされないバグは、実行時に現れるバグを意味し、デバッグが必要になります。left.a.green
「dgreen」の計算でアクセスしていることに気づきましたか?コンパイラーは確かにそう言わなかった。
その後、あらゆる場所に繰り返しがあります- for
ループはレンダリングモードと同じ回数だけ存在し、「右から左をステップで割ったもの」を繰り返します。glyい、エラーが発生しやすい。「j」を使用すべきだったときに、グーループで「i」を使用して比較したことに気づきましたか?コンパイラーは再びサイレントです。
モードのif / else /ラダーはどうですか?3週間で新しいレンダリングモードを追加するとどうなりますか?すべてのコードのすべての「if mode ==」で新しいモードを処理することを覚えていますか?
次に、上記のさを、このC ++構造体のセットとテンプレート関数と比較します。
struct CommonPixelData {
int x;
};
struct AmbientPixelData : CommonPixelData {
float ambientLight;
};
struct GouraudPixelData : CommonPixelData {
float red;
float green;
float blue; // The RGB color interpolated per pixel
};
struct PhongPixelData : CommonPixelData {
float nX;
float nY;
float nZ; // The normal vector interpolated per pixel
};
template <class PixelData>
RasterizeTriangleScanline(
PixelData left,
PixelData right)
{
PixelData interpolated = left;
PixelData step = right;
step -= left;
step /= int(right.x - left.x); // divide by pixel span
for(int i=left.x; i<right.x; i++) {
WorkOnPixel<PixelData>(interpolated);
interpolated += step;
}
}
これを見てください。ユニオンタイプスープはもう作成しません。各モードごとに特定のタイプがあります。基本クラス(CommonPixelData
)から継承することにより、共通のもの(「x」フィールド)を再利用します。そして、このテンプレートにより、コンパイラーは、Cで記述した3つの異なる関数をCREATE(つまり、コード生成)しますが、同時に、型については非常に厳格です!
テンプレート内のループでは、無効なフィールドにアクセスしてアクセスすることはできません。コンパイラが実行すると、barえます。
テンプレートは一般的な作業(ループ、毎回「ステップ」で増加)を実行し、実行時エラーを発生させない方法で実行できます。タイプごとの補間は(AmbientPixelData
、GouraudPixelData
、PhongPixelData
)で行われoperator+=()
、我々は構造体に追加すること-基本的に指示するか、各タイプが補間されます。
そして、WorkOnPixel <T>で行ったことがわかりますか?タイプごとに異なる作業を行いたいですか?テンプレートの専門化を単に呼び出します:
void WorkOnPixel<AmbientPixelData>(AmbientPixelData& p)
{
// use the p.ambientLight field
}
void WorkOnPixel<GouraudPixelData>(GouraudPixelData& p)
{
// use the p.red/green/blue fields
}
つまり、呼び出す関数は、タイプに基づいて決定されます。コンパイル時に!
言い換えると:
- コードを(テンプレートを介して)最小化し、共通部分を再利用し、
- いハックを使用せず、厳密な型システムを維持するため、コンパイラは常に私たちをチェックできます。
- そして何よりも:実行したことによる影響はありません。このコードは、同等のCコードと同じ速さで実行されます-実際、Cコードが関数ポインターを使用してさまざまな
WorkOnPixel
バージョンを呼び出す場合、コンパイラーは型固有のWorkOnPixel
テンプレート特殊化をインライン化するため、C ++コードはCより高速になりますコール!
コードの削減、ランタイムのオーバーヘッドなし、安全性の向上。
これは、C ++がすべての言語であり、すべての言語であるということですか?もちろん違います。それでもトレードオフを測定する必要があります。無知な人は、Bash / Perl / Pythonスクリプトを書くべきだったときにC ++を使用します。トリガー対応のC ++初心者は、それらを停止してパッキングを送信する前に、仮想多重継承を使用して深くネストされたクラスを作成します。彼らは、これが必要ではないことに気付く前に、複雑なBoostメタプログラミングを使用します。テンプレートとの代わりにchar*
、strcmp
とマクロをまだ使用しますstd::string
。
しかし、これは、あなたと一緒に仕事をしている人に注目してください。無能なユーザーからあなたを守る言語はありません(いいえ、Javaさえも)。
C ++の学習と使用を続けてください-過剰に設計しないでください。