Tôi thấy quá nhiều lập trình viên C ghét C ++. Tôi đã mất khá nhiều thời gian (năm) để từ từ hiểu được điều gì là tốt và điều gì là xấu về nó. Tôi nghĩ cách tốt nhất để diễn đạt nó là:
Mã ít hơn, không có chi phí thời gian chạy, an toàn hơn.
Chúng tôi viết càng ít mã càng tốt. Điều này nhanh chóng trở nên rõ ràng trong tất cả các kỹ sư phấn đấu cho sự xuất sắc. Bạn sửa một lỗi ở một nơi, không nhiều - bạn thể hiện một thuật toán một lần và sử dụng lại nó ở nhiều nơi, v.v. Người Hy Lạp thậm chí còn có một câu nói, truy nguyên từ người Sparta cổ đại: "nói một điều gì đó ít lời hơn, nghĩa là rằng bạn khôn ngoan về điều đó ". Và thực tế của vấn đề là, khi được sử dụng một cách chính xác , C ++ cho phép bạn thể hiện bản thân bằng mã ít hơn nhiều so với C, mà không tốn chi phí thời gian chạy, trong khi vẫn an toàn hơn (tức là bắt được nhiều lỗi hơn trong thời gian biên dịch) so với C là.
Đây là một ví dụ đơn giản từ trình kết xuất của tôi : Khi nội suy các giá trị pixel qua đường quét của tam giác. Tôi phải bắt đầu từ tọa độ X x1 và đạt tọa độ X x2 (từ bên trái sang bên phải của một tam giác). Và qua từng bước, qua từng pixel tôi chuyển qua, tôi phải nội suy các giá trị.
Khi tôi nội suy ánh sáng xung quanh đạt đến pixel:
typedef struct tagPixelDataAmbient {
int x;
float ambientLight;
} PixelDataAmbient;
...
// inner loop
currentPixel.ambientLight += dv;
Khi tôi nội suy màu (được gọi là bóng "Gouraud", trong đó các trường "đỏ", "xanh" và "xanh" được nội suy bởi một giá trị bước ở mỗi pixel):
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;
Khi tôi hiển thị trong bóng "Phong", tôi không còn nội suy cường độ (ambientLight) hoặc màu (đỏ / xanh / xanh) - Tôi nội suy một vectơ bình thường (nx, ny, nz) và ở mỗi bước, tôi phải tái - tính toán phương trình chiếu sáng, dựa trên vectơ bình thường nội suy:
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;
Bây giờ, bản năng đầu tiên của các lập trình viên C sẽ là "heck, viết ba hàm nội suy các giá trị và gọi chúng tùy thuộc vào chế độ cài đặt". Trước hết, điều này có nghĩa là tôi có một vấn đề kiểu - tôi làm việc với cái gì? Là pixel của tôi PixelDataAmbient? PixelDataGouraud? PixelDataPhong? Oh, đợi đã, lập trình viên C hiệu quả nói, hãy sử dụng một liên minh!
typedef union tagSuperPixel {
PixelDataAmbient a;
PixelDataGouraud g;
PixelDataPhong p;
} SuperPixel;
.. và sau đó, bạn có một chức năng ...
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;
}
...
Bạn có cảm thấy sự hỗn loạn trượt vào?
Trước hết, một lỗi đánh máy là tất cả những gì cần thiết để đánh sập mã của tôi, vì trình biên dịch sẽ không bao giờ ngăn tôi trong phần "Gouraud" của hàm, để thực sự truy cập vào ".a." (môi trường) giá trị. Một lỗi không được hệ thống loại C bắt gặp (nghĩa là trong quá trình biên dịch), có nghĩa là một lỗi xuất hiện trong thời gian chạy và sẽ yêu cầu gỡ lỗi. Bạn có để ý rằng tôi đang truy cập left.a.green
vào tính toán của "cây xanh" không? Trình biên dịch chắc chắn không nói với bạn như vậy.
Sau đó, có sự lặp lại ở khắp mọi nơi - for
vòng lặp ở đó nhiều lần như có các chế độ kết xuất, chúng tôi tiếp tục thực hiện "trừ đi bên trái chia cho các bước". Xấu xí, và dễ bị lỗi. Bạn có để ý tôi so sánh việc sử dụng "i" trong vòng lặp Gouraud không, khi nào tôi nên sử dụng "j"? Trình biên dịch là một lần nữa, im lặng.
Điều gì về if / other / lad cho các chế độ? Nếu tôi thêm một chế độ kết xuất mới, trong ba tuần thì sao? Tôi có nhớ xử lý chế độ mới trong tất cả "if mode ==" trong tất cả mã của mình không?
Bây giờ hãy so sánh sự xấu xí ở trên, với bộ cấu trúc C ++ này và một hàm mẫu:
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;
}
}
Bây giờ hãy nhìn vào điều này. Chúng tôi không còn tạo ra một loại súp kết hợp: chúng tôi có các loại cụ thể cho mỗi chế độ. Họ sử dụng lại những thứ phổ biến của họ (trường "x") bằng cách kế thừa từ một lớp cơ sở ( CommonPixelData
). Và khuôn mẫu làm cho trình biên dịch CREATE (nghĩa là tạo mã) ba hàm khác nhau mà chúng ta sẽ tự viết bằng C, nhưng đồng thời, rất nghiêm ngặt về các loại!
Vòng lặp của chúng tôi trong mẫu không thể đi và truy cập các trường không hợp lệ - trình biên dịch sẽ sủa nếu chúng tôi làm.
Mẫu thực hiện công việc chung (vòng lặp, tăng theo "bước" mỗi lần) và có thể thực hiện theo cách đơn giản KHÔNG THỂ gây ra lỗi thời gian chạy. Suy cho mỗi loại ( AmbientPixelData
, GouraudPixelData
, PhongPixelData
) được thực hiện với sự operator+=()
rằng chúng tôi sẽ thêm vào trong các cấu trúc - mà về cơ bản sai khiến như thế nào với từng loại được nội suy.
Và bạn có thấy những gì chúng tôi đã làm với WorkOnPixel <T> không? Chúng tôi muốn làm công việc khác nhau cho mỗi loại? Chúng tôi chỉ đơn giản gọi một chuyên ngành mẫu:
void WorkOnPixel<AmbientPixelData>(AmbientPixelData& p)
{
// use the p.ambientLight field
}
void WorkOnPixel<GouraudPixelData>(GouraudPixelData& p)
{
// use the p.red/green/blue fields
}
Đó là - chức năng để gọi, được quyết định dựa trên loại. Tại thời điểm biên dịch!
Để viết lại nó một lần nữa:
- chúng tôi thu nhỏ mã (thông qua mẫu), sử dụng lại các phần chung,
- chúng tôi không sử dụng các bản hack xấu xí, chúng tôi giữ một hệ thống loại nghiêm ngặt để trình biên dịch có thể kiểm tra chúng tôi mọi lúc.
- và tốt nhất của tất cả: không có gì chúng tôi đã làm có bất kỳ tác động thời gian chạy nào. Mã này sẽ chạy JUST nhanh như mã C tương đương - trên thực tế, nếu mã C đang sử dụng các con trỏ hàm để gọi các
WorkOnPixel
phiên bản khác nhau , mã C ++ sẽ FASTER hơn C, bởi vì trình biên dịch sẽ nội tuyến WorkOnPixel
hóa chuyên môn mẫu cụ thể gọi!
Mã ít hơn, không có chi phí thời gian chạy, an toàn hơn.
Điều này có nghĩa là C ++ là tất cả và cuối cùng của ngôn ngữ? Dĩ nhiên là không. Bạn vẫn phải đo lường sự đánh đổi. Những người thiếu hiểu biết sẽ sử dụng C ++ khi họ nên viết một tập lệnh Bash / Perl / Python. Những người mới sử dụng C ++ vui vẻ sẽ tạo ra các lớp lồng nhau sâu với nhiều kế thừa ảo trước khi bạn có thể dừng chúng và gửi chúng đóng gói. Họ sẽ sử dụng lập trình meta Boost phức tạp trước khi nhận ra rằng điều này là không cần thiết. Họ vẫn sẽ sử dụng char*
, strcmp
và macro, thay vì std::string
và các mẫu.
Nhưng điều này không nói gì hơn là ... xem bạn làm việc với ai. Không có ngôn ngữ để bảo vệ bạn khỏi những người dùng bất tài (không, thậm chí không phải Java).
Tiếp tục học và sử dụng C ++ - đừng quá tin tưởng.