Давайте знакомиться.
Я — Серега. (На фото — не я). Работаю в Intel. Вместе с коллегами пишу GPA. Программирую вот уже скоро 20 лет как. Ну, это если считать со школы. Последнее время накопилось много разных мыслей, которыми хочется с кем-то поделиться. Рассказать кому-то о том, что такое хорошо, а что такое плохо. Рассказывать можно и пустоте (так даже спокойней, никто не отвлекает и не суется со Своим Самым Правильным мнением), но это не очень эффективно. Поэтому буду сливать свои мысли сюда. Вдруг кому-нибудь пригодится…
В качестве введения
Многие считают, что есть два родственных языка — C и C++. При этом C++ — это якобы тот же C, только с двумя плюсами, т.е. ООП. Это очень распространенное заблуждение. «На самом деле все не так». C и С++ — это совершенно разные языки, не имеющие между собой практически ничего общего. Однако исторически так сложилось, что C++ синтаксически совместим с C, т.е. может компилировать программы, написанные на C. Из-за этой особенности четкая грань между языками отсутствует и существует множество кода, написанного на жуткой смеси этих языков. Причем пропорция этой смеси может меняться даже в рамках одной программы у одного автора. Будем называть подобное творчество языком «Ц с классами». Иногда использование «Ц с классами» может быть осознанным выбором, продиктованным обстоятельствами и решаемой задачей. К сожалению, чаще всего подобный код просто демонстрирует огромную разницу между амбициями и реальными способностями его автора. Если вы еще только в самом начале пути, лучше возьмите что-то более строгое, например чистый C или Pascal. А потом приходите сюда, когда в этих языках станет тесно.
Кстати об обстоятельствах и задачах. Как вы думаете, кто такой «настоящий программист»? Нет, не хакер. И не тот, кто знает о языках программирования ВСЕ. И даже не тот, кто способен написать самую «крутую» программу. Программист — это тот, кто может решить поставленную алгоритмическую задачу в заявленный срок с заданным уровнем качества. Перечитайте это определение несколько раз медленно, обдумывая каждое слово. Попытайтесь понять такую простую вещь, что без задачи, сроков и качества программирования не бывает! Это может быть творчество, обучение, эксперимент, исследование, что угодно еще, но только не программирование. Потому что результатом программирования является программа, которая служит людям, которая, начиная с момента своего создания, изо дня в день, многократно решает для них свою задачу. Даже википедия в определении термина программист 3 раза упоминает слово “задача”! Поймите, никому не нужна идеальная программа, написанная за бесконечно долгое время. Люди с большей охотой будут пользоваться хоть чем-то, что решает их проблемы здесь и сейчас, ну, в крайнем случае, завтра. Так же мало кто будет пользоваться дурно пахнущей поделкой, сляпанной кое-как, когда вокруг уже существует множество более простых и приятных решений ихних проблем. Практически никто не будет пользоваться «прикольной безделушкой» долгое время. Разнообразные бегающие по экрану звери, навороченные спецэффекты анимации окон и менюшек, Самые Правильные Операционные Системы и т.п. — все это барахло обречено создавать ВАУ эффект у молодого поколения, после чего с почетом уходить в небытие. Я могу рассказать про обеспечение качества. Объяснить кое-что про сроки. Но задачи придется искать самостоятельно.
Думаю, настало время поговорить за C++. Надеюсь вы уже прочли толстые и умные книжки про этот язык, написали и скомпилировали Свою Первую Программу? Если еще нет — идите, читайте и пишите, поговорим, когда будете понимать хотя бы синтаксис языка. Если вы еще тут, то наверное уже знаете — этот язык поддерживает ООП на уровне синтаксиса. Однако следует для себя понять одну важную вещь, чтобы не скатиться в «Ц с классами». В C++ все есть класс. Попробую на пальцах:
void foo();
...
foo();
Это самый что ни на есть C. Даже без классов. Если мы пишем программу на C++, то правильнее то же самое написать так:
class Foo
{
public:
static void foo();
};
...
Foo::foo();
Еще иногда бывает удобно сделать вот так:
class foo
{
public:
foo();
};
...
foo();
Или даже вот так:
class Foo
{
public:
void operator()();
};
...
Foo foo;
foo();
В исключительных случаях, можно и так:
namespace Foo
{
void foo();
}
...
Foo::foo();
За исключением последнего варианта (он мне не нравится, но иногда таки он бывает полезен) вы наверняка везде заметили ключевое слово class. Дело в том, что class — вовсе не означает объект из ООП, как обычно пишут в толстых и умных книжках. Это просто конструкция языка, которая объединяет в единое целое несколько разных сущностей и наделяет их особенностями. Например, очень распространено использование классов для определения «интерфейса» — поведения объекта.
class IFoo
{
public:
virtual void foo() = 0;
};
В подобном классе нет «полей», которые могли бы чего-нибудь хранить. Также тут нет методов, которые могли бы чего-то выполнить. Идея в чистом, концентрированном виде. Однако именно через такие интерфейсы и «общаются» модули в больших программных проектах.
Вот еще один неоднозначный пример использования класса:
template <typename TypeA> class DoIt
{
private:
TypeA m_it;
public:
template<typename TypeB> DoIt(TypeB it) : m_it(it) {}
operator TypeA() {return m_it;}
};
...
TypeC c;
TypeD d = DoIt< TypeD >(c);
Зачем так сложно? А затем, чтобы разделить шаблонный аргумент на две части и одну из них задавать явно, а другую заставить компилятор брать из аргументов вызова конструктора. Это очень красивый способ написать «шаблонную функцию» с произвольными типами аргументов и заданными типом возвращаемого значения.
Есть такая игра слов: Чем больше сыра — тем больше в нем дыр, чем больше в сыре дыр — тем меньше в нем собственно сыра. В результате чем больше сыра — тем меньше сыра. С C++ все именно так и происходит. На этом языке простые и банальные вещи пишутся очень громоздкими и запутанными конструкциями. Однако именно эти конструкции помогают сделать в этом языке сложные вещи просто. Возьмем например вашу первую программу на «Ц с классами»
#include <iostream>
int main(void)
{
std::cout << "Здраствуй Мир!!!n";
std::cout << "До свиданьяn";
return 0;
}
и сделаем из нее программу на C++:
#include <iostream>
class App
{
public:
int Run()
{
std::cout << "Здраствуй Мир!!!n";
return 0;
}
~App()
{
std::cout << "До свиданья.n";
}
};
int main(void)
{
try
{
App().Run();
}
catch(...)
{
throw;
}
return 0;
}
Функция main для нас неизбежное наследие С и избавиться от нее без лишних проблем тяжело. Будем считать такую ее реализацию частью языка. Теперь представим, что нам нужно добавить в нашу программу сложную вещь:
#include <iostream>
#include <stdexcept>
class App
{
public:
int Run()
{
std::cout << "Здраствуй Мир!!!n";
throw std::runtime_error("Что-то плохое в нашей программе случилось.");
return 0;
}
~App()
{
std::cout << "До свиданья.n";
}
};
int main(void)
{
try
{
App().Run();
}
catch(...)
{
throw;
}
return 0;
}
В последнем варианте наша программа будет обрушена исключением, выброшенным откуда-то изнутри. Однако, несмотря на плохой финал, она сможет корректно выполнить свою завершающую часть. Как можно заметить, сложность никак не изменила нашей программы. Именно такие фокусы и позволяет делать «настоящий» C++. Если вы думаете, что всегда и все можно обложить секциями try… catch — у меня для вас плохие новости. Такой код будет практически невозможно поддерживать.
Домашнее задание
Прочитать описание таких вещей, как Паттерны.
Автор: Softogen