ฉันเห็นโปรแกรมเมอร์ C มากเกินไปที่เกลียด C ++ ฉันใช้เวลาค่อนข้างนานหลายปีในการเข้าใจสิ่งที่ดีและสิ่งที่ไม่ดีเกี่ยวกับเรื่องนี้อย่างช้าๆ ฉันคิดว่าวิธีที่ดีที่สุดในการใช้วลีนี้คือ:
รหัสน้อยไม่มีค่าใช้จ่ายในการดำเนินการความปลอดภัยมากขึ้น
ยิ่งโค้ดน้อยที่เราเขียนก็จะยิ่งดี สิ่งนี้จะชัดเจนในทุกวิศวกรที่มุ่งมั่นสู่ความเป็นเลิศ คุณแก้ไขข้อผิดพลาดในที่เดียวไม่มาก - คุณแสดงอัลกอริทึมหนึ่งครั้งและนำกลับมาใช้ใหม่ในหลาย ๆ ที่ ฯลฯ ชาวกรีกยังมีคำพูดย้อนกลับไปที่สปาร์ตันโบราณ: "พูดอะไรบางอย่างด้วยคำพูดน้อย ว่าคุณฉลาดเกี่ยวกับเรื่องนี้ " และความจริงของเรื่องนี้คือเมื่อใช้อย่างถูกต้อง C ++ จะช่วยให้คุณสามารถแสดงรหัสที่น้อยกว่า C ได้โดยไม่ต้องเสียค่าใช้จ่ายในการรันไทม์ในขณะที่มีความปลอดภัยมากขึ้น (เช่นการจับข้อผิดพลาดในเวลารวบรวม) มากกว่า C คือ
ต่อไปนี้เป็นตัวอย่างที่เรียบง่ายจากตัวแสดงผลของฉัน: เมื่อทำการแก้ไขค่าพิกเซลข้าม scanline ของรูปสามเหลี่ยม ฉันต้องเริ่มต้นจากพิกัด X X และถึงพิกัด X x2 (จากซ้ายไปทางด้านขวาของรูปสามเหลี่ยม) และในแต่ละขั้นตอนในแต่ละพิกเซลที่ฉันผ่านไปฉันต้องแก้ไขค่า
เมื่อฉันแก้ไขแสงโดยรอบที่มาถึงพิกเซล:
typedef struct tagPixelDataAmbient {
int x;
float ambientLight;
} PixelDataAmbient;
...
// inner loop
currentPixel.ambientLight += dv;
เมื่อฉันสอดแทรกสี (เรียกว่า "การแรเงา" Gouraud "โดยที่ฟิลด์" สีแดง "," สีเขียว "และ" สีฟ้า "ถูกแก้ไขโดยค่าขั้นตอนในแต่ละพิกเซล):
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;
เมื่อฉันแสดงเป็นเงาพงษ์ฉันจะไม่สอดแทรกความเข้ม (ambientLight) หรือสี (แดง / เขียว / น้ำเงิน) อีกต่อไป - ฉันสอดแทรกเวกเตอร์ปกติ (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 คือ "heck เขียนสามฟังก์ชั่นที่สอดแทรกค่าและเรียกมันขึ้นอยู่กับโหมด set" ก่อนอื่นนี่หมายความว่าฉันมีปัญหาประเภท - ฉันจะทำงานกับอะไร 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;
}
...
คุณรู้สึกถึงความโกลาหลที่เข้ามาหรือไม่?
ก่อนอื่นหนึ่งการพิมพ์ผิดคือสิ่งที่จำเป็นในการทำให้รหัสของฉันผิดเนื่องจากคอมไพเลอร์จะไม่หยุดฉันในส่วน "Gouraud" ของฟังก์ชันเพื่อเข้าถึง ".a" อย่างแท้จริง ค่า (แวดล้อม) ข้อผิดพลาดที่ไม่ถูกจับโดยระบบชนิด C (นั่นคือระหว่างการรวบรวม) หมายถึงข้อผิดพลาดที่ปรากฏในเวลาทำงานและจะต้องมีการดีบัก คุณสังเกตเห็นว่าฉันกำลังเข้าสู่left.a.green
การคำนวณ "dgreen" หรือไม่? ผู้แปลไม่ได้บอกคุณอย่างแน่นอน
จากนั้นมีการทำซ้ำทุกที่ - for
ลูปมีหลายครั้งที่มีโหมดการเรนเดอร์เราทำต่อไป "ลบด้วยการหารซ้ายขวาด้วยขั้นตอน" น่าเกลียดและผิดพลาดได้ง่าย คุณสังเกตเห็นฉันเปรียบเทียบโดยใช้ "i" ใน Gouraud loop เมื่อใดที่ฉันควรใช้ "j" คอมไพเลอร์นั้นเงียบอีกครั้ง
สิ่งที่เกี่ยวกับ if / else / ladder สำหรับโหมด? จะเกิดอะไรขึ้นถ้าฉันเพิ่มโหมดการแสดงผลใหม่ในสามสัปดาห์ ฉันจะจำได้หรือไม่ว่าจะจัดการโหมดใหม่ใน "if mode ==" ในรหัสของฉันทั้งหมดหรือไม่
ตอนนี้เปรียบเทียบความอัปลักษณ์ข้างต้นด้วยชุด C ++ structs และฟังก์ชันเทมเพลต:
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;
}
}
ตอนนี้ดูที่นี้ เราไม่ได้ทำซุปรวมยูเนี่ยนอีกต่อไป: เรามีประเภทเฉพาะต่อแต่ละโหมด พวกเขาใช้สิ่งทั่วไป (ฟิลด์ "x") อีกครั้งโดยสืบทอดจากคลาสพื้นฐาน ( CommonPixelData
) และเทมเพลตทำให้คอมไพเลอร์สร้าง (นั่นคือรหัสสร้าง) สามฟังก์ชั่นที่แตกต่างกันเราจะได้เขียนตัวเองใน C แต่ในเวลาเดียวกันการเข้มงวดมากเกี่ยวกับประเภท!
การวนซ้ำของเราในเทมเพลตไม่สามารถทำให้ผิดและเข้าถึงฟิลด์ที่ไม่ถูกต้อง - คอมไพเลอร์จะเห่าถ้าเราทำ
เทมเพลตดำเนินงานทั่วไป (ลูปเพิ่มขึ้นโดย "ขั้นตอน" ในแต่ละครั้ง) และสามารถทำได้ในลักษณะที่ไม่สามารถทำให้เกิดข้อผิดพลาดรันไทม์ แก้ไขต่อประเภท ( AmbientPixelData
, GouraudPixelData
, PhongPixelData
) จะทำกับoperator+=()
ว่าเราจะเพิ่มใน structs - ซึ่งโดยทั่วไปกำหนดวิธีการแต่ละประเภทจะถูกหยัน
และคุณเห็นสิ่งที่เราทำกับ 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
รุ่นต่าง ๆรหัส C ++ จะเร็วกว่า C เนื่องจากคอมไพเลอร์จะอินไลน์WorkOnPixel
แม่แบบเฉพาะประเภท โทร!
รหัสน้อยไม่มีค่าใช้จ่ายในการดำเนินการความปลอดภัยมากขึ้น
สิ่งนี้หมายความว่า C ++ เป็นภาษาที่เป็นทั้งหมดและสิ้นสุดหรือไม่ ไม่แน่นอน คุณยังต้องวัดการแลกเปลี่ยน คนที่โง่เขลาจะใช้ C ++ เมื่อพวกเขาควรเขียนสคริปต์ Bash / Perl / Python มือใหม่ C ++ ที่มีความสุขในการเรียกใช้จะสร้างคลาสที่ซ้อนกันอย่างลึกล้ำด้วยการสืบทอดหลาย ๆ แบบเสมือนจริงก่อนที่คุณจะหยุดและส่งพวกมันออก พวกเขาจะใช้การเขียนโปรแกรมBoost meta ที่ซับซ้อนก่อนที่จะรู้ว่าสิ่งนี้ไม่จำเป็น พวกเขาจะยังคงใช้char*
, strcmp
และแมโครแทนstd::string
และแม่แบบ
แต่นี่ไม่ได้พูดอะไรมากไปกว่า ... ดูว่าคุณทำงานกับใคร ไม่มีภาษาใดที่จะป้องกันคุณจากผู้ใช้ที่ไร้ความสามารถ (ไม่แม้แต่ Java)
ศึกษาและใช้ C ++ ต่อไป - อย่าเพิ่งออกแบบมากเกินไป