C ++를 싫어하는 C 프로그래머가 너무 많습니다. 좋은 점과 나쁜 점을 천천히 이해하는 데 상당한 시간 (년)이 걸렸습니다. 나는 그것을 표현하는 가장 좋은 방법은 이것이라고 생각합니다.
코드가 적고 런타임 오버 헤드가 없으며 안전성이 향상되었습니다.
우리가 작성하는 코드가 적을수록 좋습니다. 이는 우수성을 위해 노력하는 모든 엔지니어에게 신속하게 분명해집니다. 한 곳에서 버그를 고치지 마십시오. 한 번만 알고리즘을 표현하고 여러 곳에서 다시 사용하십시오. 그리스인들은 심지어 고대 스파르타 인으로 거슬러 올라가는 말을합니다. "당신은 그것에 대해 현명하다". 그리고 중요한 것은 C ++을 올바르게 사용하면 런타임 속도를 높이 지 않고 C보다 훨씬 적은 코드로 자신을 표현할 수 있으며 C보다 안전합니다 (예 : 컴파일 타임에 더 많은 오류를 잡을 수 있음).
다음은 렌더러 의 간단한 예입니다 . 삼각형의 스캔 라인에서 픽셀 값을 보간 할 때. X 좌표 x1에서 시작하여 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;
"Phong"음영으로 렌더링 할 때 더 이상 강도 (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 프로그래머의 첫 번째 본능은 "허크, 값을 보간하는 세 가지 함수를 작성하고 설정된 모드에 따라 호출하는 것"입니다. 우선, 이것은 유형 문제가 있음을 의미합니다. 무엇을 사용합니까? 내 픽셀은 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
됩니다. 렌더링 모드가있는 횟수만큼 루프가 존재합니다. "오른쪽 빼기 왼쪽을 단계로 나눕니다". 추악하고 오류가 발생하기 쉽습니다. "j"를 사용해야했을 때 Gouraud 루프에서 "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로 작성한 세 가지 함수를 CREATE (즉, 코드 생성)로 만들지 만 동시에 유형에 대해 매우 엄격합니다!
템플릿의 루프가 잘못되어 유효하지 않은 필드에 액세스 할 수 없습니다. 컴파일러는 우리가 짖 으면 짖습니다.
템플릿은 일반적인 작업 (루프, 매번 "단계"씩 증가)을 수행하며 단순히 런타임 오류를 유발할 수없는 방식으로 수행 할 수 있습니다. 종류 당 보간 ( 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 ++를 사용하게됩니다. Trigger-happy C ++ 초보자는 중지하고 포장을 보내기 전에 가상 다중 상속으로 중첩 된 클래스를 만듭니다. 그들은 이것이 필요하지 않다는 것을 깨닫기 전에 복잡한 Boost 메타 프로그래밍 을 사용할 것 입니다. 그들은 여전히 사용 char*
, strcmp
매크로, 대신 std::string
템플릿.
그러나 이것은 당신이 누구와 일하는지 지켜 보는 것 이상의 의미가 없습니다. 무능한 사용자로부터 당신을 보호 할 언어가 없습니다 (Java도 아님).
C ++을 계속 연구하고 사용하십시오. 지나치게 디자인하지 마십시오.