Saya melihat terlalu banyak programmer C yang membenci C ++. Butuh beberapa waktu (bertahun-tahun) untuk perlahan memahami apa yang baik dan apa yang buruk tentang itu. Saya pikir cara terbaik untuk mengungkapkannya adalah ini:
Lebih sedikit kode, tidak ada overhead run-time, lebih aman.
Semakin sedikit kode yang kita tulis, semakin baik. Ini dengan cepat menjadi jelas di semua insinyur yang berjuang untuk keunggulan. Anda memperbaiki bug di satu tempat, tidak banyak - Anda mengekspresikan algoritma sekali, dan menggunakannya kembali di banyak tempat, dll. Orang Yunani bahkan memiliki pepatah, ditelusuri kembali ke Spartan kuno: "untuk mengatakan sesuatu dengan sedikit kata, berarti bahwa Anda bijak tentang hal itu ". Dan faktanya, adalah bahwa ketika digunakan dengan benar , C ++ memungkinkan Anda untuk mengekspresikan diri Anda dalam kode yang jauh lebih sedikit daripada C, tanpa biaya kecepatan runtime, sementara lebih aman (yaitu menangkap lebih banyak kesalahan pada waktu kompilasi) daripada C.
Berikut ini contoh sederhana dari penyaji saya : Saat menginterpolasi nilai piksel melintasi garis pindai segitiga. Saya harus mulai dari koordinat X x1, dan mencapai koordinat X x2 (dari kiri ke sisi kanan segitiga). Dan di setiap langkah, di setiap piksel yang saya lewati, saya harus menginterpolasi nilai.
Saat saya menginterpolasi cahaya sekitar yang mencapai piksel:
typedef struct tagPixelDataAmbient {
int x;
float ambientLight;
} PixelDataAmbient;
...
// inner loop
currentPixel.ambientLight += dv;
Ketika saya menginterpolasi warna (disebut shading "Gouraud", di mana bidang "merah", "hijau" dan "biru" diinterpolasi oleh nilai langkah di setiap piksel):
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;
Ketika saya membuat warna "Phong", saya tidak lagi menyisipkan intensitas (ambientLight) atau warna (merah / hijau / biru) - Saya menginterpolasi vektor normal (nx, ny, nz) dan pada setiap langkah, saya harus kembali -menghitung persamaan pencahayaan, berdasarkan vektor normal yang diinterpolasi:
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;
Sekarang, insting pertama programmer C adalah "heck, tulis tiga fungsi yang menginterpolasi nilai-nilai, dan memanggil mereka tergantung pada mode yang ditetapkan". Pertama-tama, ini berarti saya memiliki masalah tipe - apa yang saya kerjakan? Apakah piksel saya PixelDataAmbient? PixelDataGouraud? PixelDataPhong? Oh, tunggu, programmer C yang efisien mengatakan, gunakan serikat pekerja!
typedef union tagSuperPixel {
PixelDataAmbient a;
PixelDataGouraud g;
PixelDataPhong p;
} SuperPixel;
..dan kemudian, Anda memiliki fungsi ...
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;
}
...
Apakah Anda merasakan kekacauan itu menyelinap masuk?
Pertama-tama, satu kesalahan ketik adalah semua yang diperlukan untuk mem-crash kode saya, karena kompiler tidak akan pernah menghentikan saya di bagian "Gouraud" dari fungsi, untuk benar-benar mengakses ".a." nilai (ambient). Bug yang tidak ditangkap oleh sistem tipe C (yaitu, selama kompilasi), berarti bug yang bermanifestasi pada saat run-time, dan akan membutuhkan debugging. Apakah Anda memperhatikan bahwa saya mengakses left.a.green
dalam perhitungan "dgreen"? Kompiler pasti tidak memberi tahu Anda.
Lalu, ada pengulangan di mana-mana - for
loop ada di sana sebanyak yang ada mode rendering, kami terus melakukan "kanan minus kiri dibagi dengan langkah-langkah". Jelek, dan rawan kesalahan. Apakah Anda perhatikan saya membandingkan menggunakan "i" di loop Gouraud, padahal seharusnya saya menggunakan "j"? Kompiler sekali lagi, diam.
Bagaimana dengan if / else / ladder untuk mode? Bagaimana jika saya menambahkan mode rendering baru, dalam tiga minggu? Akankah saya ingat untuk menangani mode baru di semua "jika mode ==" di semua kode saya?
Sekarang bandingkan keburukan di atas, dengan rangkaian C ++ structs dan fungsi templat ini:
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;
}
}
Sekarang lihat ini. Kami tidak lagi membuat sup jenis persatuan: kami memiliki jenis khusus per setiap mode. Mereka menggunakan kembali barang-barang umum mereka (bidang "x") dengan mewarisi dari kelas dasar ( CommonPixelData
). Dan templatnya membuat kompilator MENCIPTAKAN (yaitu, menghasilkan kode) tiga fungsi berbeda yang akan kita tulis sendiri dalam C, tetapi pada saat yang sama, menjadi sangat ketat tentang jenisnya!
Perulangan kami di templat tidak dapat membuang waktu dan mengakses bidang yang tidak valid - kompiler akan menggonggong jika kami melakukannya.
Template melakukan pekerjaan umum (loop, meningkat dengan "langkah" di setiap waktu), dan dapat melakukannya dengan cara yang TIDAK BISA menyebabkan kesalahan runtime. Interpolasi per jenis ( AmbientPixelData
, GouraudPixelData
, PhongPixelData
) dilakukan dengan operator+=()
yang kita akan menambahkan dalam struct - yang pada dasarnya menentukan bagaimana masing-masing jenis interpolasi.
Dan apakah Anda melihat apa yang kami lakukan dengan WorkOnPixel <T>? Kami ingin melakukan pekerjaan yang berbeda per jenis? Kami cukup memanggil spesialisasi templat:
void WorkOnPixel<AmbientPixelData>(AmbientPixelData& p)
{
// use the p.ambientLight field
}
void WorkOnPixel<GouraudPixelData>(GouraudPixelData& p)
{
// use the p.red/green/blue fields
}
Artinya - fungsi untuk memanggil, diputuskan berdasarkan jenisnya. Pada waktu kompilasi!
Untuk mengulanginya lagi:
- kami meminimalkan kode (melalui templat), menggunakan kembali bagian-bagian umum,
- kami tidak menggunakan hacks jelek, kami menjaga sistem tipe ketat, sehingga kompiler dapat memeriksa kami setiap saat.
- dan yang terbaik: tidak ada yang kami lakukan yang memiliki dampak runtime APA PUN. Kode ini akan berjalan HANYA secepat kode C yang setara - pada kenyataannya, jika kode C menggunakan pointer fungsi untuk memanggil berbagai
WorkOnPixel
versi, kode C ++ akan LEBIH CEPAT daripada C, karena kompiler akan menyejajarkan WorkOnPixel
spesialisasi templat jenis-spesifik panggilan!
Lebih sedikit kode, tidak ada overhead run-time, lebih aman.
Apakah ini berarti bahwa C ++ adalah bahasa be-all dan end-all? Tentu saja tidak. Anda masih harus mengukur trade-off. Orang yang bodoh akan menggunakan C ++ ketika mereka seharusnya menulis skrip Bash / Perl / Python. Pemula-senang C ++ pemula akan membuat kelas bersarang mendalam dengan beberapa warisan virtual sebelum Anda dapat menghentikan mereka dan mengirim mereka berkemas. Mereka akan menggunakan meta-pemrograman Boost yang kompleks sebelum menyadari bahwa ini tidak perlu. Mereka akan MASIH menggunakan char*
, strcmp
dan makro, bukan std::string
dan template.
Tapi ini tidak lebih dari ... awasi dengan siapa kamu bekerja. Tidak ada bahasa untuk melindungi Anda dari pengguna yang tidak kompeten (tidak, bahkan Java).
Terus belajar dan menggunakan C ++ - hanya saja jangan overdesign.