Я вижу слишком много программистов на C, которые ненавидят C ++. Мне потребовалось некоторое время (годы), чтобы постепенно понять, что хорошо, а что плохо. Я думаю, что лучший способ выразить это так:
Меньше кода, никаких накладных расходов, больше безопасности.
Чем меньше кода мы напишем, тем лучше. Это быстро становится ясно всем инженерам, которые стремятся к совершенству. Вы исправляете ошибку в одном месте, а не во многих - вы выражаете алгоритм один раз и повторно используете его во многих местах и т. Д. У греков даже есть высказывание, восходящее к древним спартанцам: «сказать что-то меньшим количеством слов означает что вы мудры в этом ". И дело в том, что при правильном использовании 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;
При рендеринге с затенением «Фонг» я больше не интерполирую интенсивность (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;
Теперь первым инстинктом программистов на Си будет «черт возьми, напиши три функции, которые интерполируют значения и вызывают их в зависимости от установленного режима». Прежде всего, это означает, что у меня есть проблема с типом - с чем я работаю? Являются ли мои пиксели 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;
}
...
Чувствуете ли вы хаос?
Прежде всего, одна опечатка - это все, что нужно для сбоя моего кода, так как компилятор никогда не остановит меня в разделе функции «Гуро», чтобы фактически получить доступ к «.a». (окружающие) значения. Ошибка, не обнаруженная системой типов C (то есть во время компиляции), означает ошибку, которая проявляется во время выполнения и требует отладки. Вы заметили, что я обращаюсь left.a.green
в расчет "dgreen"? Компилятор, конечно, не сказал вам этого.
Затем повсюду повторение - for
цикл существует столько раз, сколько существует режимов рендеринга, мы продолжаем делать «правый минус левый, разделенный на шаги». Гадкий и подверженный ошибкам. Вы заметили, что я сравниваю, используя «i» в цикле Гуро, когда я должен был использовать «j»? Компилятор опять молчит.
А как насчет if / else / ladder для модов? Что если я добавлю новый режим рендеринга через три недели? Я буду помнить, чтобы обрабатывать новый режим во всем "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;
}
}
Теперь посмотри на это. Мы больше не делаем объединение типов супов: у нас есть определенные типы для каждого режима. Они повторно используют свой общий материал (поле «x»), наследуя от базового класса ( CommonPixelData
). И шаблон заставляет компилятор CREATE (то есть генерировать код) тремя различными функциями, которые мы написали бы сами в C, но в то же время очень строго относимся к типам!
Наш цикл в шаблоне не может работать и обращаться к недопустимым полям - компилятор будет лаять, если мы это сделаем.
Шаблон выполняет обычную работу (цикл, увеличивающийся на «шаг» в каждый раз) и может делать это так, что просто НЕ МОЖЕТ вызывать ошибки во время выполнения. Интерполяция по типу ( 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
}
То есть - функция для вызова определяется по типу. Во время компиляции!
Чтобы перефразировать это снова:
- мы минимизируем код (через шаблон), повторно используя общие части,
- мы не используем уродливые хаки, мы придерживаемся строгой системы типов, чтобы компилятор мог всегда проверять нас.
- и самое главное: ни одно из того, что мы сделали, не оказывает никакого влияния на время выполнения. Этот код будет работать JUST так же быстро, как и эквивалентный код C - фактически, если код C использовал указатели функций для вызова различных
WorkOnPixel
версий, код C ++ будет БЫСТРЕЕ, чем C, потому что компилятор встроит WorkOnPixel
специализацию шаблона конкретного типа. вызов!
Меньше кода, никаких накладных расходов, больше безопасности.
Означает ли это, что C ++ является основным и конечным языком? Конечно, нет. Вы все еще должны измерить компромиссы. Невежественные люди будут использовать C ++, когда им следовало написать скрипт на Bash / Perl / Python. Новички в C ++, удовлетворяющие триггерам, создадут глубоко вложенные классы с виртуальным множественным наследованием, прежде чем вы сможете их остановить и отправить упаковку. Они будут использовать сложное метапрограммирование Boost, прежде чем поймут, что в этом нет необходимости. Они будут по- прежнему использовать char*
, strcmp
и макросы, а std::string
и шаблоны.
Но это говорит только о том, что ... смотри, с кем ты работаешь. Там нет языка, чтобы оградить вас от некомпетентных пользователей (нет, даже не Java).
Продолжайте изучать и использовать C ++ - только не переусердствуйте.