Чистый C++

в 12:34, , рубрики: c++, Блог компании Intel, Программирование, учебник, метки: ,

Чистый C++

Давайте знакомиться.

Я — Серега. (На фото — не я). Работаю в 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

Источник

Поделиться

* - обязательные к заполнению поля