C++

C++
C++
Семантика:

мультипарадигмальный: объектно-ориентированное, обобщённое, процедурное, метапрограммирование

Тип исполнения:

компилируемый

Появился в:

1983

Автор(ы):

Бьёрн Страуструп

Расширение файлов:

.c++ .cpp .cxx .cc .h++ .hpp .hxx .hh .h

Типизация данных:

статическая

Основные реализации:

GNU C++, Microsoft Visual C++, Intel C++ compiler, Clang, Comeau C/C++, Embarcadero (Borland) C++ Builder, Watcom C++ compiler, Digital Mars C++, Oracle Solaris Studio C++ compiler, Turbo C++

Диалекты:

ISO/IEC 14882 C++

C++ — компилируемый статически типизированный язык программирования общего назначения.

Поддерживает такие парадигмы программирования как процедурное программирование, объектно-ориентированное программирование, обобщенное программирование, обеспечивает модульность, раздельную компиляцию, обработку исключений, абстракцию данных, объявление типов (классов) объектов, виртуальные функции. Стандартная библиотека включает, в том числе, общеупотребительные контейнеры и алгоритмы. C++ сочетает свойства как высокоуровневых, так и низкоуровневых языков.[1][2] В сравнении с его предшественником — языком C, — наибольшее внимание уделено поддержке объектно-ориентированного и обобщённого программирования.[2]

Название «C++» происходит от названия языка C, в котором унарный оператор ++ обозначает инкремент переменной.

Являясь одним из самых популярных языков программирования,[3][4] C++ широко используется для разработки программного обеспечения. Область его применения включает создание операционных систем, разнообразных прикладных программ, драйверов устройств, приложений для встраиваемых систем, высокопроизводительных серверов, а также развлекательных приложений (игр). Существует множество реализаций языка C++, как бесплатных, так и коммерческих и для различных платформ. Например, на платформе x86 это GCC, Visual C++, Intel C++ Compiler, Embarcadero (Borland) C++ Builder и другие. C++ оказал огромное влияние на другие языки программирования, в первую очередь на Java и C#.

Синтаксис C++ унаследован от языка C. Одним из принципов разработки было сохранение совместимости с C. Тем не менее, C++ не является в строгом смысле надмножеством C; множество программ, которые могут одинаково успешно транслироваться как компиляторами C, так и компиляторами C++, довольно велико, но не включает все возможные программы на C.

Содержание

История

Исторический этап развития[5] Год
Язык BCPL 1966
Язык Би (оригинальная разработка Томпсона под UNIX) 1969
Язык Си 1973
Си с классами 1979
C84 1984
Cfront (выпуск E) 1984
Cfront (выпуск 1.0) 1985
Множественное/виртуальное наследование 1988
Обобщенное программирование (шаблоны) 1991
ANSI C++ / ISO-C++ 1996
ISO/IEC 14882:1998 1998
ISO/IEC 14882:2003 2003
C++/CLI 2005
TR1 2005
C++11 2011

Создание

Язык возник в начале 1980-х годов, когда сотрудник фирмы Bell Labs Бьёрн Страуструп придумал ряд усовершенствований к языку C под собственные нужды. До начала официальной стандартизации язык развивался в основном силами Страуструпа в ответ на запросы программистского сообщества. В 1998 году был ратифицирован международный стандарт языка C++: ISO/IEC 14882:1998 «Standard for the C++ Programming Language»; после принятия технических исправлений к стандарту в 2003 году — нынешняя версия этого стандарта — ISO/IEC 14882:2003.[6]

Ранние версии языка, известные под именем «C с классами», начали появляться с 1980 года.[7] Идея создания нового языка берёт начало от опыта программирования Страуструпа для диссертации. Он обнаружил, что язык моделирования Симула имеет такие возможности, которые были бы очень полезны для разработки большого программного обеспечения, но работает слишком медленно. В то же время язык BCPL достаточно быстр, но слишком близок к языкам низкого уровня и не подходит для разработки большого программного обеспечения. Страуструп начал работать в Bell Labs над задачами теории очередей (в приложении к моделированию телефонных вызовов). Попытки применения существующих в то время языков моделирования оказались неэффективными. Вспоминая опыт своей диссертации, Страуструп решил дополнить язык C (преемник BCPL) возможностями, имеющимися в языке Симула. Язык C, будучи базовым языком системы UNIX, на которой работали компьютеры Bell, является быстрым, многофункциональным и переносимым. Страуструп добавил к нему возможность работы с классами и объектами. В результате, практические задачи моделирования оказались доступными для решения как с точки зрения времени разработки (благодаря использованию Симула-подобных классов) так и с точки зрения времени вычислений (благодаря быстродействию C). Вначале в C были добавлены классы (с инкапсуляцией), производные классы, строгая проверка типов, inline-функции и аргументы по умолчанию.

Разрабатывая C с классами (позднее C++), Страуструп также написал программу cfront — транслятор, перерабатывающий исходный код C с классами в исходный код простого C. Новый язык, неожиданно для автора, приобрёл большую популярность среди коллег и вскоре Страуструп уже не мог лично поддерживать его, отвечая на тысячи вопросов.

В 1983 году произошло переименование языка из C с классами в C++. Кроме того, в него были добавлены новые возможности, такие как виртуальные функции, перегрузка функций и операторов, ссылки, константы, пользовательский контроль над управлением свободной памятью, улучшенная проверка типов и новый стиль комментариев (//). Его первый коммерческий выпуск состоялся в октябре 1985 года.

Развитие и стандартизация языка

В 1985 году вышло первое издание «Языка программирования C++», обеспечивающее первое описание этого языка, что было чрезвычайно важно из-за отсутствия официального стандарта. В 1989 году состоялся выход C++ версии 2.0. Его новые возможности включали множественное наследование, абстрактные классы, статические функции-члены, функции-константы и защищённые члены. В 1990 году вышло «Комментированное справочное руководство по C++», положенное впоследствии в основу стандарта. Последние обновления включали шаблоны, исключения, пространства имён, новые способы приведения типов и булевский тип.

Стандартная библиотека C++ также развивалась вместе с ним. Первым добавлением к стандартной библиотеке C++ стали потоки ввода/вывода, обеспечивающие средства для замены традиционных функций C printf и scanf. Позднее самым значительным развитием стандартной библиотеки стало включение в неё Стандартной библиотеки шаблонов.

В 1998 году был опубликован стандарт языка ISO/IEC 14882:1998 (известный как C++98),[8] разработанный комитетом по стандартизации C++ (ISO/IEC JTC1/SC22/WG21 working group). Стандарт C++ не описывает способы именования объектов, некоторые детали обработки исключений и другие возможности, связанные с деталями реализации, что делает несовместимым объектный код, созданный различными компиляторами. Однако для этого третьими лицами создано множество стандартов для конкретных архитектур и операционных систем.

В 2003 году был опубликован стандарт языка ISO/IEC 14882:2003, где были исправлены выявленные ошибки и недочёты предыдущей версии стандарта.

В 2005 году был выпущен отчёт Library Technical Report 1 (кратко называемый TR1). Не являясь официально частью стандарта, отчёт описывает расширения стандартной библиотеки, которые, как ожидалось авторами, должны быть включены в следующую версию языка C++. Степень поддержки TR1 улучшается почти во всех поддерживаемых компиляторах языка C++.

С 2009 года велась работа по обновлению предыдущего стандарта, предварительной версией нового стандарта сперва был C++99, а спустя год C++0x, сегодня — C++11, куда были включены дополнения в ядро языка и расширение стандартной библиотеки, в том числе большую часть TR1.

C++ продолжает развиваться, чтобы отвечать современным требованиям. Одна из групп, разрабатывающих язык C++ и направляющих комитету по стандартизации C++ предложения по его улучшению — это Boost, которая занимается, в том числе, совершенствованием возможностей языка путём добавления в него особенностей метапрограммирования.

Никто не обладает правами на язык C++, он является свободным. Однако сам документ стандарта языка (за исключением черновиков) не доступен бесплатно.

История названия

Имя языка, получившееся в итоге, происходит от оператора унарного постфиксного инкремента C ++ (увеличение значения переменной на единицу). Имя C+ не было использовано потому, что является синтаксической ошибкой в C и, кроме того, это имя было занято другим языком. Язык также не был назван D, поскольку «является расширением C и не пытается устранять проблемы путём удаления элементов C».[7]

Философия C++

Общие принципы

В книге «Дизайн и эволюция C++» Бьёрн Страуструп описывает принципы, которых он придерживался при проектировании C++.[9] Эти принципы объясняют, почему C++ именно такой, какой он есть. Некоторые из них:

  • Получить универсальный язык со статическими типами данных, эффективностью и переносимостью языка C.
  • Непосредственно и всесторонне поддерживать множество стилей программирования, в том числе процедурное программирование, абстракцию данных, объектно-ориентированное программирование и обобщённое программирование.
  • Дать программисту свободу выбора, даже если это даст ему возможность выбирать неправильно.
  • Максимально сохранить совместимость с C, тем самым делая возможным лёгкий переход от программирования на C.
  • Избежать разночтений между C и C++: любая конструкция, допустимая в обоих языках, должна в каждом из них обозначать одно и то же и приводить к одному и тому же поведению программы.
  • Избегать особенностей, которые зависят от платформы или не являются универсальными.
  • «Не платить за то, что не используется» — никакое языковое средство не должно приводить к снижению производительности программ, не использующих его.
  • Не требовать слишком усложнённой среды программирования.

Совместимость с языком С

Выбор именно C в качестве базы для создания нового языка программирования объясняется тем, что язык C:

  1. является многоцелевым, лаконичным и относительно низкоуровневым языком;
  2. подходит для решения большинства системных задач;
  3. исполняется везде и на всём;
  4. стыкуется со средой программирования UNIX.

— Б. Страуструп. Язык программирования C++. Раздел 1.6[10]

Несмотря на ряд известных недостатков языка C, Страуструп пошёл на его использование в качестве основы, так как «в C есть свои проблемы, но их имел бы и разработанный с нуля язык, а проблемы C нам известны». Кроме того, это позволило быстро получить прототип компилятора (cfront), который лишь выполнял трансляцию добавленных синтаксических элементов в оригинальный язык C.

По мере разработки C++ в него были включены другие средства, которые перекрывали возможности конструкций C, в связи с чем неоднократно поднимался вопрос об отказе от совместимости языков путём удаления устаревших конструкций. Тем не менее, совместимость была сохранена из следующих соображений:

  • сохранение действующего кода, написанного изначально на C и прямо перенесённого в C++;
  • исключение необходимости переучивания программистов, ранее изучавших C (им требуется только изучить новые средства C++);
  • исключение путаницы между языками при их совместном использовании («если два языка используются совместно, их различия должны быть или минимальными, или настолько большими, чтобы языки было невозможно перепутать»).

Обзор языка

Стандарт C++ на 2003 год состоит из двух основных частей: описание ядра языка и описание стандартной библиотеки.

Кроме того, существует огромное количество библиотек C++, не входящих в стандарт. В программах на C++ можно использовать многие библиотеки C.

Стандартизация определила язык программирования C++, однако за этим названием могут скрываться также неполные, ограниченные, достандартные варианты языка. Первое время язык развивался вне формальных рамок, спонтанно, по мере встававших перед ним задач. Развитию языка сопутствовало развитие кросс-компилятора cfront. Новшества в языке отражались в изменении номера версии кросс-компилятора. Эти номера версий кросс-компилятора распространялись и на сам язык, но применительно к настоящему времени речь о версиях языка C++ не ведут.

Необъектно-ориентированные возможности

В этом разделе описываются возможности, непосредственно не связанные с объектно-ориентированным программированием (ООП), но многие из них, однако, особенно важны в сочетании с ООП.

Комментарии

С++ поддерживает как комментарии в стиле C:

 /*
 это комментарий, который может состоять
 из нескольких строчек
 */

так и однострочные:

 // вся оставшаяся часть строки является комментарием

где // обозначает начало комментария, а ближайший последующий символ новой строки, который не предварён символом \ (либо эквивалентным ему обозначением ??/), считается окончанием комментария.

Типы

В C++ доступны следующие встроенные типы:

  • Символьные: char, wchar_t (char16_t и char32_t, в стандарте C++11).
  • Целочисленные знаковые: signed char, short int, int, long intlong long int, в стандарте C++11).
  • Целочисленные беззнаковые: unsigned char, unsigned short int, unsigned int, unsigned long intunsigned long long int, в стандарте C++11).
  • С плавающей точкой: float, double, long double.
  • Логический: bool, имеющий значения true и false.

Операции сравнения возвращают тип bool. Выражения в скобках после if, while приводятся к типу bool.[11]

Функции могут принимать аргументы по ссылке. Например, функция void f(int &x) {x=3;} присваивает своему аргументу значение 3. Функции также могут возвращать результат по ссылке, и ссылки могут быть вне всякой связи с функциями. Например, {double &b=a[3]; b=sin(b);} эквивалентно a[3]=sin(a[3]);. При программировании ссылки в определённой степени сходны с указателями, со следующими особенностями: перед использованием ссылка должна быть инициализирована; ссылка пожизненно указывает на один и тот же адрес; в выражении ссылка обозначает непосредственно тот объект или ту функцию, на которую она указывает, обращение же к объекту или функции через указатель требует разыменование указателя. Существуют и другие отличия в использовании указателей и ссылок. Концептуально ссылка — другое имя переменной или функции, другое название одного и того же адреса, существует лишь только в тексте программы, заменяемое адресом при компиляции; а указатель — переменная, хранящая адрес, к которому обращаются.

Разное

  • Спецификатор inline позволяет объявлять inline-функции. Функция, определённая внутри тела класса, является inline по умолчанию. Изначально inline-функции задумывались как функции, являющиеся хорошими кандидатами на оптимизацию, при которой в местах обращения к функции компилятор вставит тело этой функции, а не код вызова. В действительности компилятор не обязан реализовывать подстановку тела для inline-функций, но может, исходя из заданных критериев оптимизации, выполнять подстановку тела для функций, которые не объявлены как inline. Пожалуй, наиболее значимой особенностью inline-функции является то, что она может многократно определяться в нескольких единицах трансляции (при этом inline-функция должна быть определена во всех единицах трансляции, где она используется), в то время как функция, не являющаяся inline, может определяться в программе не более одного раза. Пример:

inline double Sqr(double x) {return x*x;}.

  • Описатель volatile используется в описании переменных и информирует компилятор, что значение данной переменной может быть изменено способом, который компилятор не в состоянии отследить. Для переменных, объявленных volatile, компилятор не должен применять средства оптимизации, изменяющие положение переменной в памяти (например, помещающие её в регистр) или полагающиеся на неизменность значения переменной в промежутке между двумя присваиваниями ей значения. В мультиядерной системе volatile помогает избегать барьеров памяти 2-го типа[12].
  • Если описана структура, класс, объединение (union) или перечисление (enum), её имя является именем типа, например:
struct Time {
    int hh, mm, ss;
};
Time t1, t2;
namespace Foo
{
   const int x=5;
   typedef int** T;
   void f(int y) {return y*x};
   double g(T);
   ...
}

то вне фигурных скобок следует обращаться к T, x, f, g как Foo::T, Foo::x, Foo::f и Foo::g соответственно. Если в каком-то файле нужно обратиться к ним непосредственно, можно написать

using namespace Foo;

Или же

using Foo::T;

Пространства имён нужны, чтобы не возникало коллизий между пакетами, имеющими совпадающие имена глобальных переменных, функций и типов. Специальным случаем является безымянное пространство имён

namespace
{
    ...
}

Все имена, описанные в нём, доступны в текущей единице трансляции и больше нигде.

  • Один или несколько последних аргументов функции могут задаваться по умолчанию. К примеру, если функция описана как void f(int x, int y=5, int z=10), вызовы f(1), f(1,5) и f(1,5,10) эквивалентны.
  • При описании функций отсутствие аргументов в скобках означает, в отличие от C, что аргументов нет, а не то, что они неизвестны. Если аргументы неизвестны, надо пользоваться многоточием, например, int printf(const char* fmt, ...).
  • Внутри структуры или класса можно описывать вложенные типы, как через typedef, так и через описание других классов, а также перечислений. Для доступа к таким типам вне класса, к имени типа добавляется имя структуры или класса и два двоеточия:
struct S
{
    typedef int** T;
    T x;
};
S::T y;
  • Могут быть несколько функций с одним и тем же именем, но разными типами или количеством аргументов (перегрузка функций; при этом тип возвращаемого значения на перегрузку не влияет). Например, вполне можно писать:
void Print(int x);
void Print(double x);
void Print(int x, int y);
  • Смысл некоторых операторов применительно к пользовательским типам можно определять через объявление соответствующих операторных функций. К примеру, так:
struct Date {int day, month, year;};
void operator ++(struct Date& date);

Операторные функции во многом схожи с обычными (неоператорными) функциями. За исключением операторов new, new[], delete и delete[], нельзя переопределять поведение операторов для встроенных типов (скажем, переопределять умножение значений типа int); нельзя создавать новые операторы, которых нет в C++ (скажем, **); нельзя менять количество операндов, предусмотренное для оператора, а также нельзя менять существующие приоритеты и ассоциативность операторов (скажем, в выражении a+b*c сначала будет выполняться умножение, а потом сложение, к каким бы типам ни принадлежали a, b и c). Можно переопределить операции [] (с одним параметром) и () (с любым числом параметров).

  • Добавлены шаблоны (template). Например, template<class T> T Min(T x, T y) {return x<y?x:y;} определяет функцию Min для любых типов. Шаблоны могут задавать не только функции, но и типы. Например, template<class T> struct Array{int len; T* val;}; определяет массив значений любого типа, после чего мы можем писать Array<float> x;
  • В дополнение к функциям malloc и free введены операторные функции operator new, operator new[], operator delete и operator delete[], а также операторы new, new[], delete и delete[]. Если T — произвольный объектный тип, не являющийся типом массива, X — произвольный объектный тип и A — тип массива из некоторого количества n элементов, имеющих тип X, то
    • new T выделяет память (посредством вызова функции operator new), достаточную для размещения одного объекта типа Т, возможно, инициализирует объект в этой памяти, и возвращает указатель типа Т* (например, Т* p = new T).
    • new X[n] и new A выделяют память (посредством вызова функции operator new[]), достаточную для размещения n объектов типа X, возможно, инициализируют каждый объект в этой памяти, и возвращают указатель типа X* (например, X* p = new X[n]).
    • delete p — разрушает объект (не являющийся массивом), на который ссылается указатель p, и освобождает область памяти (посредством вызова функции operator delete), ранее выделенную для него new-выражением.
    • delete [] p — разрушает каждый объект в массиве, на который ссылается указатель p, и освобождает область памяти (посредством вызова функции operator delete[]), ранее выделенную для этого массива new-выражением.

Операция delete проверяет, что её аргумент не является нулевым указателем, в противном случае она ничего не делает. Для инициализации объекта non-POD классового типа new-выражение вызывает конструктор; для уничтожения объекта классового типа delete-выражение вызывает деструктор (см. ниже).

Объектно-ориентированные особенности языка

C++ добавляет к C объектно-ориентированные возможности. Он вводит классы, которые обеспечивают три самых важных свойства ООП: инкапсуляцию, наследование и полиморфизм.

В стандарте C++ под классом (class) подразумевается пользовательский тип, объявленный с использованием одного из ключевых слов class, struct или union, под структурой (structure) подразумевается класс, определённый через ключевое слово struct, и под объединением (union) подразумевается класс, определённый через ключевое слово union.

Описание функций в теле класса

В теле класса можно указать только заголовок функции, а можно описать всю функцию (см. пример с функцией Alloc ниже. В этом случае она считается встраиваемой (inline))

Константные функции-члены

Нестатические функции-члены (и только они) могут иметь описатель const

class Array
{
...
    inline double operator[] (int n) const;

Такие функции не имеют права изменять поля класса (кроме полей, определённых как mutable). Если они пытаются это сделать, компилятор должен выдать сообщение об ошибке.

Наследование

В C++ при наследовании одного класса от другого наследуется реализация класса, плюс класс-наследник может добавлять свои поля и функции или переопределять функции базового класса. Множественное наследование разрешено.

Конструктор наследника вызывает конструкторы базовых классов, а затем конструкторы нестатических членов-данных, являющихся экземплярами классов. Деструктор работает в обратном порядке.

Наследование бывает публичным, защищённым и закрытым (то есть закрытого типа):

Доступ члена базового класса/режим наследования private-член protected-член public-член
private-наследование недоступен private private
protected-наследование недоступен protected protected
public-наследование недоступен protected public

Наследник — это больше чем базовый класс, поэтому, если наследование открытое, то он может использоваться везде, где используется базовый класс, но не наоборот.

Полиморфизм

Полиморфизмом в программировании называется переопределение наследником функций-членов базового класса, например:

class Figure
{
    ...
    void Draw() const;
    ...
};
 
class Square : public Figure
{
    ...
    void Draw() const;
    ...
};
 
class Circle : public Figure
{
    ...
    void Draw() const;
    ...
};

Какая именно из функций будет вызвана — Figure::Draw(), Square::Draw() или Circle::Draw() — определяется во время компиляции. Например:

Circle *c = new Circle(0,0,5);
Figure *f = c; // Всё ok: Figure — базовый класс для Circle
c->Draw();
f->Draw(); // Указатели друг другу равны, но для f будет вызвана другая функция, чем для c

Несмотря на то что оба указателя указывают на один и тот же объект класса Circle, для c будет вызвана Circle::Draw(), а для f — Figure::Draw(), поскольку f — указатель на объект класса Figure. Такой полиморфизм называется статическим.

Но в C++ есть и динамический полиморфизм, когда вызываемая функция определяется во время выполнения. Для этого функции-члены базового класса должны быть объявлены виртуальными.

class Figure
{
    ...
    virtual void Draw() const;
    ...
};
 
class Square : public Figure
{
    ...
    void Draw() const;
    ...
};
 
class Circle : public Figure
{
    ...
    void Draw() const;
    ...
};
 
Figure * figures[10];
figures[0] = new Square(1, 2, 10);
figures[1] = new Circle(3, 5, 8);
...
for (int i = 0; i < 10; i++)
    figures[i]->Draw();

В этом случае для каждого элемента массива будет вызвана Square::Draw() или Circle::Draw() в зависимости от вида фигуры.

Чисто виртуальной функцией называется виртуальная функция-член, которая объявлена со спецификатором = 0:

class Figure
{
    ...
    virtual void Draw() const = 0;
);

Чисто виртуальная функция может быть оставлена без определения, кроме случая, когда требуется произвести её вызов.

Абстрактным классом называется такой, у которого есть хотя бы одна чисто виртуальная функция-член. Объекты таких классов создавать запрещено. Абстрактные классы часто используются как интерфейсы. В отличие от чистых интерфейсов других языков, абстрактные классы С++ могут иметь невиртуальные функции и члены-данные.

Инкапсуляция

Основным способом организации информации в C++ являются классы. В отличие от структуры (struct) языка C, которая может состоять только из полей и вложенных типов, класс (class) C++ может состоять из полей, вложенных типов и функций-членов (member functions). Инкапсуляция в С++ реализуется через указание уровня доступа к членам класса: они бывают публичными (открытыми, public), защищёнными (protected) и собственными (закрытыми, приватными, private). В C++ структуры формально отличаются от классов лишь тем, что по умолчанию члены и базовые классы у структуры публичные, а у класса — собственные.

Доступ private protected public
Сам класс да да да
Друзья да да да
Наследники нет да да
Извне нет нет да

Проверка доступа происходит во время компиляции, попытка обращения к недоступному члену класса вызовет ошибку компиляции.

Пример класса, реализующего одномерный массив (это просто иллюстрация, а не образец дизайна!):

class Array
{
    public:
        Array() :
            len(0),
            val(NULL)
        {}
 
        Array(int _len) :
            len(_len)
        {
            val = new double[_len];
        }
 
        Array(const Array & a);
 
        ~Array()
        {
            Free();
        }
 
        inline const double & Elem(int i) const
        {
            return val[i];
        }
 
        inline void ChangeElem(int i, double x)
        {
            val[i] = x;
        }
 
    protected:
        void Alloc(int _len)
        {
            if (len == 0)
                Free();
 
            len = _len;
            val = new double[len];
        }
 
        void Free()
        {
            delete [] val;
            len = 0;
        }
 
        int len;
        double * val;
};

Здесь класс Array имеет 2 публичных функции-члена, 2 защищённых поля, 3 публичных конструктора и публичный деструктор. Описатель inline означает подсказку компилятору, что вместо вызова функции её код следует встроить в точку вызова, чем часто можно достичь большей эффективности.

Друзья

Функции-друзья — это функции, не являющиеся функциями-членами и тем не менее имеющие доступ к защищённым и закрытым членам класса. Они должны быть объявлены в теле класса как friend. Например:

class Matrix {
    ...
    friend Matrix Multiply(Matrix m1, Matrix m2);
    ...
};
 
Matrix Multiply(Matrix m1, Matrix m2) {
    ...
}

Здесь функция Multiply может обращаться к любым полям и функциям-членам класса Matrix.

Существуют также классы-друзья. Если класс A — друг класса B, то все его собственные (не унаследованные) функции-члены могут обращаться к любым членам класса B. Например:

class Matrix {
    ...
    int t;
    void SomeFunction();
    friend class Vector;
    ...
};
...
class Vector
{
    int GetNum(){ return 1;}
};

Пример обращения:

void Matrix::SomeFunction()
{
    t=Vector::GetNum();
}

Однако в C++ не действует правило «друг моего друга — мой друг».

По действующему стандарту C++ вложенный класс не имеет прав доступа к закрытым членам объемлющего класса и не может быть объявлен его другом (последнее следует из определения термина друг как нечлена класса). В будущем стандарте C++0x эти ограничения будут устранены. В данном отношении современные версии компиляторов VC++, GNU C++ и Comeau C++ даже с отключенными расширениями следуют новым правилам, сформулированным в последних версиях черновика C++0x.

Конструкторы и деструкторы

В классах всегда есть специальные функции — конструкторы и деструкторы, которые могут быть объявлены явно или неявно.

Конструктор вызывается для инициализации объекта (соответствующего типа) при его создании, а деструктор — для уничтожения объекта. В частности, конструктор может быть вызван для выполнения преобразования к классовому типу.

Конструкторы обозначаются как одноимённые классу функции (например, Array::Array), деструкторы — как имя класса, предварённое тильдой (например, Array::~Array). Для конструкторов и деструкторов нельзя указывать тип возвращаемого значения. Деструктор нельзя объявлять как принимающий аргументы. Класс может иметь сколько угодно конструкторов (с разными наборами параметров), в том числе шаблонных, и только один (причём нешаблонный) деструктор.

Конструктор без параметров или конструктор, все параметры которого имеют аргументы по умолчанию, называется конструктором по умолчанию, нешаблонный конструктор с первым параметром-ссылкой на тот же класс (например, Array::Array(const Array&)) и остальными параметрами (если таковые есть), имеющими аргументы по умолчанию, — конструктором копирования, он вызывается при создании нового объекта, являющегося копией уже существующего объекта:

Array a(5); // вызывается Array::Array(int)
Array b;    // вызывается Array::Array()
Array c(a); // вызывается Array::Array(const Array&)
Array d=a;  // вызывается Array::Array(const Array&)
b=c;        // происходит вызов оператора =
            // если он не определён (как в данном случае), то вызывается сгенерированный компилятором оператор присваивания, который
            // осуществляет копирование базовых подобъектов и почленное копирование нестатических членов-данных.
            // как правило конструктор копий и оператор присваивания переопределяются попарно

Если в классе нет явно объявленных конструкторов, класс имеет неявно объявленный конструктор без параметров, который конструирует подобъекты классов-родителей и инициализирует поля класса, вызывая для них конструкторы по умолчанию. Если в классе нет явно объявленных копирующих конструкторов, то класс имеет неявно объявленный копирующий конструктор, который выполняет прямое копирование всех объявленных полей объекта-источника в соответствующие поля объекта-приёмника с помощью соответствующих копирующих конструкторов. Если в классе нет явно объявленного деструктора, то класс имеет неявно объявленный деструктор.

Конструкторы в C++ не могут быть объявлены виртуальными, а деструкторы — могут, и обычно так и объявляются, чтобы гарантировать правильное уничтожение доступного по ссылке или указателю объекта независимо от того, какого типа ссылка или указатель.

Перегрузка операторов

Функции-члены могут быть операторами:

class Array {
...
    inline double& operator[] (int n) { return val[n]; }

И далее

Array a(10);
...
double b = a[5];

Стандартная библиотека

Общая структура

Стандартная библиотека C++ включает в себя набор средств, которые должны быть доступны для любой реализации языка, чтобы обеспечить программистам удобное пользование языковыми средствами и создать базу для разработки как прикладных приложений самого широкого спектра, так и специализированных библиотек. Стандартная библиотека С++ включает в себя часть стандартной библиотеки C. Стандарт C++ содержит нормативную ссылку на стандарт C от 1990 года и не определяет самостоятельно те функции стандартной библиотеки, которые заимствуются из стандартной библиотеки C.

Доступ к возможностям стандартной библиотеки C++ обеспечивается с помощью включения в программу (посредством директивы #include) соответствующих стандартных заголовочных файлов. Всего в стандарте C++11 определено 79 таких файлов. Средства стандартной библиотеки объявляются как входящие в пространство имён std. Заголовочные файлы, имена которых соответствуют шаблону «cX», где X — имя заголовочного файла стандартной библиотеки C без расширения (cstdlib, cstring, cstdio и пр.), содержат определения, соответствующие данной части стандартной библиотеки C, при этом программист может воспользоваться заголовочными файлами с именами по шаблону «cX.h» (добавлено стандартное расширение заголовочных файлов C «.h»), в которых те же средства определены как относящиеся к глобальному пространству имён.

Для использования следующих функций стандартной библиотеки

void* operator new(std::size_t) throw(std::bad_alloc);
void* operator new[](std::size_t) throw(std::bad_alloc);
void operator delete(void*) throw();
void operator delete[](void*) throw();

подключение каких-либо заголовочных файлов не требуется.

Состав

Стандартная библиотека включает в себя следующие разделы:

  • Поддержка языка. Включает средства, которые необходимы для работы программ, а также сведения об особенностях реализации. Выделение памяти, RTTI, базовые исключения, пределы значений для числовых типов данных, базовые средства взаимодействия со средой, такие как системные часы, обработка сигналов UNIX, завершение программы.
  • Стандартные контейнеры. В стандартную библиотеку входят шаблоны для следующих контейнеров: одномерные массивы, списки, одно- и двунаправленные очереди, стеки, ассоциативные массивы, множества, очереди с приоритетом.
  • Основные утилиты. В этот раздел входит описание основных базовых элементов, применяемых в стандартной библиотеке, распределителей памяти и поддержка времени и даты в стиле C.
  • Итераторы. Обеспечивают шаблоны итераторов, с помощью которых в стандартной библиотеке реализуется стандартный механизм группового применения алгоритмов обработки данных к элементам контейнеров.
  • Алгоритмы. Шаблоны для описания операций обработки, которые с помощью механизмов стандартной библиотеки могут применяться к любой последовательности элементов, в том числе к элементам в контейнерах. Также в этот раздел входят описания функций bsearch() и qsort() из стандартной библиотеки C.
  • Строки. Шаблоны строк в стиле C++. Также в этот раздел попадает часть библиотек для работы со строками и символами в стиле C.
  • Ввод/вывод. Шаблоны и вспомогательные классы для потоков ввода/вывода общего вида, строкового ввода/вывода, манипуляторы (средства управления форматом потокового ввода/вывода в стиле C++).
  • Локализация. Определения, используемые для поддержки национальных особенностей и форматов представления (дат, валют и т. д.) в стиле C++ и в стиле C.
  • Диагностика. Определения ряда исключений и механизмов проверки утверждений во время выполнения (assert). Поддержка обработки ошибок в стиле C.
  • Числа. Определения для работы с комплексными числами, математическими векторами, поддержка общих математических функций, генератор случайных чисел.

Контейнеры, строки, алгоритмы, итераторы и основные утилиты, за исключением заимствований из библиотеки C, собирательно называются STL (Stanard Template Library — стандартная шаблонная библиотека). Изначально эта библиотека была отдельным продуктом и её аббревиатура расшифровывалась иначе, но потом она вошла в стандартную библиотеку C++ в качестве неотъемлемого элемента. В названии отражено то, что для реализации средств общего вида (контейнеров, строк, алгоритмов) использованы механизмы обобщённого программирования (шаблоны C++ — template). В работах Страуструпа подробно описываются причины, по которым был сделан именно такой выбор. Основными из них являются бо́льшая универсальность выбранного решения (шаблонные контейнеры, в отличие от объектных, могут легко использоваться для не объектных типов и не требуют наличия общего предка у типов элементов) и его техническая эффективность (как правило, операции шаблонного контейнера не требуют вызовов виртуальных функций и могут легко встраиваться (inline), что в итоге даёт столь же производительный код, как и при ручном кодировании).

Реализации

STL до включения в стандарт C++ была сторонней разработкой, в начале — фирмы HP, а затем SGI. Стандарт языка не называет её «STL», так как эта библиотека стала неотъемлемой частью языка, однако многие люди до сих пор используют это название, чтобы отличать её от остальной части стандартной библиотеки (потоки ввода/вывода (iostream), подраздел C и другие).

Проект под названием STLport[13], основанный на SGI STL, осуществляет постоянное обновление STL, IOstream и строковых классов. Некоторые другие проекты также занимаются разработкой частных применений стандартной библиотеки.

Отличия от языка C

Новые возможности

Нововведениями C++ в сравнении с C являются:

Новые возможности C++ включают объявления в виде выражений, преобразования типов в виде функций, операторы new и delete, тип bool, ссылки, расширенное понятие константности, подставляемые функции, аргументы по умолчанию, переопределения, пространства имён, классы (включая и все связанные с классами возможности, такие как наследование, функции-члены, виртуальные функции, абстрактные классы и конструкторы), переопределения операторов, шаблоны, оператор ::, обработку исключений, динамическую идентификацию и многое другое. Язык C++ также во многих случаях строже относится к проверке типов, чем C.

В C++ появились комментарии в виде двойной косой черты (//), которые были в предшественнике C — языке BCPL.

Некоторые особенности C++ позднее были перенесены в C, например, ключевые слова const и inline, объявления в циклах for и комментарии в стиле C++ (//). В более поздних реализациях C также были представлены возможности, которых нет в C++, например макросы va_arg и улучшенная работа с массивами-параметрами.

C++ не включает в себя C

Несмотря на то, что большая часть кода C будет справедлива и для C++, C++ не является надмножеством C и не включает его в себя. Существует и такой верный для C код, который неверен для C++. Это отличает его от Objective C, ещё одного усовершенствования C для ООП, как раз являющегося надмножеством C.

Существуют и другие различия. Например, C++ не разрешает вызывать функцию main() внутри программы, в то время как в C это действие правомерно. Кроме того, C++ более строг в некоторых вопросах; например, он не допускает неявное приведение типов между несвязанными типами указателей и не разрешает использовать функции, которые ещё не объявлены.

Более того, код, верный для обоих языков, может давать разные результаты в зависимости от того, компилятором какого языка он оттранслирован. Например, на большинстве платформ следующая программа печатает «С», если компилируется компилятором C, и «C++» — если компилятором C++. Так происходит из-за того, что символьные константы в C (например, 'a') имеют тип int, а в C++ — тип char, а размеры этих типов обычно различаются.

#include <stdio.h>
 
int main()
{
    printf("%s\n", (sizeof('a') == sizeof(char)) ? "C++" : "C");
    return 0;
}

Средства C, которых рекомендуется избегать

По замечанию Страуструпа, «чем лучше вы знаете C, тем труднее вам будет избежать программирования на C++ в стиле C, теряя при этом потенциальные преимущества C++». В связи с этим он даёт следующий набор рекомендаций для программистов на C, чтобы в полной мере воспользоваться преимуществами C++:

  • Не использовать макроопределения #define. Для объявления констант применять const, групп констант (перечислений) — enum, для прямого включения функций — inline, для определения семейств функций или типов — template.
  • Не использовать предварительные объявления переменных. Объявлять переменные в блоке, где они реально используются, всегда совмещая объявление с инициализацией.
  • Отказаться от использования malloc() в пользу оператора new, от realloc() — в пользу типа vector.
  • Избегать бестиповых указателей, арифметики указателей, неявных приведений типов, объединений, за исключением, возможно, низкоуровневого кода. Использовать «новые» преобразования типов, как более точно выражающие действительные намерения программиста и более безопасные.
  • Свести к минимуму использование массивов символов и строк в стиле C, заменив их на типы string и vector из STL. Вообще не стремиться создавать собственные реализации того, что уже имеется в стандартной библиотеке.

Дальнейшее развитие

Текущий стандарт языка ISO/IEC 14882:2003(E) был принят в 2003 году. Неофициально его обозначают как C++03. Следующая версия стандарта имеет неофициальное обозначение C++11.

Дополнения в ядре языка

Явно определяемые константные функции и выражения constexpr.
В язык вводится ключевое слово constexpr, которым может быть помечено объявление функции, если она возвращает константу времени компиляции, или константной переменной, которая инициализируется выражением, содержащим только константы времени компиляции. Константные выражения могут использоваться везде, где по семантике программы требуется константа времени компиляции, например — в инициализаторах статических объектов. Предполагается, что константные выражения позволят программисту проще и точнее описывать семантику программы, а компилятору — шире применять предвычисление выражений на этапе трансляции кода.
Универсальная инициализация.
Инициализация массивов и объектов списками константных значений, заключёнными в фигурные скобки. Значительно упростит инициализацию сложных объектов.
Конструкторы и операторы присваивания с семантикой переноса.
Отдельное описание для конструкторов и присваиваний, реализующих перенос значения из источника в приёмник (то есть не гарантирующих сохранность объекта-источника после завершения операции). Во многих случаях такие конструкторы и присваивания могут быть описаны значительно более эффективно, чем обычные копирующие конструкторы и полноценные присваивания. Наличие их позволяет компилятору использовать эти более эффективные описания при работе с временными объектами (представляющими результаты выражений, аргументы или возвращаемые значения функций) что, в итоге, увеличивает производительность программ, работающих со сложными объектами.
Конструкторы и операторы преобразования типов — новые возможности.

Разрешён прямой вызов одного конструктора из другого, что уменьшает дублирование кода и исключает необходимость создания скрытых методов-инициализаторов. Для операторов преобразования типов разрешено описание с ключевым словом explicit — запрет на неявное преобразование типа параметра при вызове.

Вывод типов.
Для применения в шаблонах, там, где затруднительно указать конкретный тип переменной, введены два новых механизма: переменные типа auto и описание decltype. Оба используются на месте типа переменной при её объявлении. Переменная типа auto должна быть явно инициализирована некоторым выражением и её тип компилятор определяет по типу значения выражения-инициализатора. Соответственно, если в инициализации участвуют значения шаблонных типов или вызовы шаблонных функций, тип переменной определяется с учётом конкретных подставленных типов-параметров этих шаблонов. Описание вида «decltype(x) y» понимается как «y имеет тип выражения x», то есть компилятор выводит тип выражения x и определяет y как переменную этого типа. Все эти операции производятся только на этапе компиляции, то есть статическая типизация не нарушается. Чтобы обеспечить возможность указывать через decltype тип возвращаемого значения функции, когда он зависит от типов параметров, введён альтернативный синтаксис объявления функции: auto func(params) -> type соответствует type func(params).
Цикл по коллекции.
Вслед за многими современными языками в C++ введена конструкция «цикл по коллекции» вида for(type &x : array){...}. Здесь тело цикла выполняется для каждого элемента коллекции array, а x в каждой итерации будет ссылаться на очередной элемент коллекции. В качестве коллекции может выступать C-массив или любой контейнер стандартной библиотеки, для которого определены итераторы begin и end.
Лямбда-выражения.
Добавлена возможность объявлять лямбда-выражения (безымянные функции, определяемые в точке применения), в том числе зависящие от внешних переменных (замыкания). Лямбда-выражения могут присваиваться переменным и использоваться везде, где требуется функция соответствующего типа, например, в алгоритмах стандартной библиотеки.
Изменения в описании виртуальных методов.
Добавлен необязательный модификатор override, который употребляется в объявлении метода, замещающего виртуальный метод родительского класса. Описание замещения с override вызывает проверку на наличие в родительском классе замещаемого метода и на совпадение сигнатур методов.
Добавлен также модификатор final, как и в Java, запрещающий дальнейшее замещение помеченного им метода. Также final может быть объявлен класс — в таком случае от него запрещено наследовать новые классы.
Изменения в механизме шаблонов.
Добавлена возможность описания шаблонов с неопределённым числом параметров и возможность объявления синонимов для неспециализированных или частично специализированных шаблонов. Ключевое слово export при объявлении специализации шаблона указывает компилятору на то, что фактически специализация сделана в другой единице компиляции, что позволяет избежать повторной специализации одних и тех же шаблонов одними и теми же параметрами.
Различные синтаксические дополнения.
Определено ключевое слово для константы — нулевого указателя: nullptr. Его использование позволяет избежать путаницы между целым нулевым значением и пустым указателем, которая возникает при использовании константы 0, как требовалось ранее.
Внесены изменения в семантику и, частично, синтаксис перечислений и объединений. Добавлена возможность создавать типобезопасные перечисления, с объединений снят ряд ограничений на структуру.
От компилятора требуется правильный лексический разбор текста программы с несколькими закрывающимися угловыми скобками подряд (ранее последовательность «>>» воспринималась однозначно как операция вывода в поток, поэтому в записи вложенных шаблонных конструкций требовалось обязательно разделять знаки «больше» пробелами или переводами строк).
sizeof теперь может возвращать размер члена класса по его имени, без указания конкретного экземпляра (sizeof(ClassA::b)).

Изменения в стандартной библиотеке

  • Добавлена библиотека <regex>, реализующая общепринятые механизмы поиска и подстановки с помощью регулярных выражений.
  • Добавлена поддержка многопоточности.

Проблемные моменты

Одним из камней преткновения стало ключевое слово export в шаблонах, где оно используется для разделения объявления и определения спецификации шаблона (нововведение стандарта «C++98»). Герб Саттер, секретарь комитета по стандартизации C++, рекомендовал убрать export из будущих версий стандарта по причине серьёзных сложностей в полноценной реализации, однако впоследствии его решили оставить.

Первым компилятором, поддерживающим export в шаблонах, стал Comeau C++ в начале 2003 года (спустя 5 лет после выхода стандарта C++98). В 2004 году бета-версия компилятора Borland C++ Builder X также начала его поддержку. Оба этих компилятора основаны на фронт-энде EDG. Другие компиляторы, такие как Microsoft Visual C++ или GCC (до версии 3.4.4), вообще этого не поддерживают. Поддерживают export: Microsoft Visual C++ 7.0, GCC 3.4.4, Microsoft Visual Studio 2010 и другие.

Из списка других проблем, связанных с шаблонами, можно привести вопросы конструкций частичной специализации шаблонов, которые плохо поддерживались в течение многих лет после выхода стандарта C++.

Примеры программ

Пример создания простой программы на языке C++ в Ubuntu GNU/Linux

Пример № 1

Это пример программы, которая ничего не делает. Она начинает выполняться и немедленно завершается. Она состоит из основного потока: функции main(), которая обозначает точку начала выполнения программы на C++.

int main()
{
    return 0;
}

Стандарт C++ требует, чтобы функция main() возвращала тип int. Программа, которая имеет другой тип возвращаемого значения функции main(), не соответствует стандарту C++.

Стандарт не говорит о том, что на самом деле означает возвращаемое значение функции main(). Традиционно оно интерпретируется как код возврата программы. Стандарт гарантирует, что возвращение 0 из функции main() показывает, что программа была завершена успешно.

Завершение программы на C++ с ошибкой традиционно обозначается путём возврата ненулевого значения.

Пример № 2

Эта программа также ничего не делает, но более лаконична.

int main(){}

В C++ (как и в C), если выполнение программы доходит до конца функции main(), то это эквивалентно return 0;. Это неверно для любой другой функции кроме main().

Пример № 3

Это пример программы Hello, world!, которая выводит сообщение, используя стандартную библиотеку, и завершается.

#include <iostream> // это включает библиотеку iostream
 
using namespace std;
 
int main()
{
    cout << "Hello, world!" << endl; // можно и "...\n" вместо endl
    return 0;
}

Пример № 4

Современный C++ позволяет решать простым способом и более сложные задачи. Этот пример демонстрирует кроме всего прочего использование контейнеров стандартной библиотеки шаблонов (STL).

#include <iostream>   // для использования std::cout
#include <vector>     // для std::vector<>
#include <map>        // для std::map<> и std::pair<>
#include <algorithm>  // для std::for_each()
#include <string>     // для std::string
 
using namespace std;  // используем пространство имён "std"
 
void display_item_count(pair < string const, vector<string> > const& person)
{
   // person - это пара двух объектов: person.first - это его имя,
   // person.second - это список его предметов (вектор строк)
   cout << person.first << " is carrying " << person.second.size() << " items" << endl;
}
 
int main()
{
   // Объявляем карту со строковыми ключами и данными в виде векторов строк
   map< string, vector<string> > items;
 
   // Добавим в эту карту пару человек и дадим им несколько предметов
   items["Anya"].push_back("scarf");
   items["Dmitry"].push_back("tickets");
   items["Anya"].push_back("puppy");
 
   // Переберём все объекты в контейнере
   for_each(items.begin(), items.end(), display_item_count);
}

В этом примере для простоты используется директива использования пространства имён, в настоящей же программе обычно рекомендуется использовать объявления, которые аккуратнее директив:

#include <vector>
 
int main()
{
    using std::vector;
 
    vector<int> my_vector;
}

Здесь директива помещена в область функции, что уменьшает шансы столкновений имён (это и стало причиной введения в язык пространств имён). Использование объявлений, сливающих разные пространства имён в одно, разрушает саму концепцию пространства имён.[источник не указан 1029 дней]

Пример № 5

Популярные библиотеки boost в сочетании со стандартными средствами языка позволяют очень лаконично и наглядно записывать код. В приведённом ниже примере вычисляется скалярное произведение векторов нечётных чисел и квадратов. В коде вектора значений представлены ленивыми STL-подобными последовательностями.

#include <iostream>
#include <numeric>
#include <boost/iterator/counting_iterator.hpp>
#include <boost/iterator/transform_iterator.hpp>
 
int odd(int i)
{
  return 2 * i + 1;
}
 
int square(int i)
{
  return i * i;
}
 
typedef boost::counting_iterator <int> counter;
typedef boost::transform_iterator <int (*)(int), counter> transformer;
 
transformer odds(int n)
{
  return transformer(counter(n), odd);
}
 
transformer squares(int n)
{
  return transformer(counter(n), square);
}
 
int main()
{
  using namespace std;
 
  cout << "Enter vector length: ";
  int n; cin >> n;
 
  cout << inner_product( odds(0), odds(n), squares(0), 0 ) << endl;
}

Данный пример демонстрирует так называемый «плоский» стиль записи. Это название связано с тем, что алгоритмы STL позволяют записывать код без циклов, соответственно ширина отступов в отформатированном коде примерно постоянна. Сторонники такого подхода считают, что программисту, знакомому со стандартной библиотекой С++, достаточно строчки с вызовом inner_product(), чтобы понять, что делает программа. С этой точки зрения вызов inner_product близок к словесному описанию задачи: «вычислить скалярное произведение векторов нечётных чисел и квадратов для значений от нуля до n».

Достоинства языка и его критика

Прежде всего, необходимо подчеркнуть, что оценивать достоинства и, в особенности, недостатки C++ необходимо в контексте тех принципов, на которых строился язык, и требований, которые к нему изначально предъявлялись.

Достоинства

C++ — чрезвычайно мощный язык, содержащий средства создания эффективных программ практически любого назначения, от низкоуровневых утилит и драйверов до сложных программных комплексов самого различного назначения. В частности:

  • Поддерживаются различные стили и технологии программирования, включая традиционное директивное программирование, ООП, обобщённое программирование, метапрограммирование (шаблоны, макросы).
  • Предсказуемое выполнение программ является важным достоинством для построения систем реального времени. Весь код, неявно генерируемый компилятором для реализации языковых возможностей (например, при преобразовании переменной к другому типу), определён в стандарте. Также строго определены места программы, в которых этот код выполняется. Это даёт возможность замерять или рассчитывать время реакции программы на внешнее событие.
  • Автоматический вызов деструкторов объектов при их уничтожении, причём в порядке, обратном вызову конструкторов. Это упрощает (достаточно объявить переменную) и делает более надёжным освобождение ресурсов (память, файлы, семафоры и т. п.), а также позволяет гарантированно выполнять переходы состояний программы, не обязательно связанные с освобождением ресурсов (например, запись в журнал).
  • Пользовательские функции-операторы позволяют кратко и ёмко записывать выражения над пользовательскими типами в естественной алгебраической форме.
  • Язык поддерживает понятия физической (const) и логической (mutable) константности. Это делает программу надёжнее, так как позволяет компилятору, например, диагностировать ошибочные попытки изменения значения переменной. Объявление константности даёт программисту, читающему текст программы дополнительное представление о правильном использовании классов и функций, а также может являться подсказкой для оптимизации. Перегрузка функций-членов по признаку константности позволяет определять изнутри объекта цели вызова метода (константный для чтения, неконстантный для изменения). Объявление mutable позволяет сохранять логическую константность при использовании кэшей и ленивых вычислений.
  • Используя шаблоны, возможно создавать обобщённые контейнеры и алгоритмы для разных типов данных, а также специализировать и вычислять на этапе компиляции.
  • Возможность имитации расширения языка для поддержки парадигм, которые не поддерживаются компиляторами напрямую. Например, библиотека Boost.Bind позволяет связывать аргументы функций.
  • Возможность создания встроенных предметно-ориентированных языков программирования. Такой подход использует, например библиотека Boost.Spirit, позволяющая задавать EBNF-грамматику парсеров прямо в коде C++.
  • Используя шаблоны и множественное наследование можно имитировать классы-примеси и комбинаторную параметризацию библиотек. Такой подход применён в библиотеке Loki, класс SmartPtr которой позволяет, управляя всего несколькими параметрами времени компиляции, сгенерировать около 300 видов «умных указателей» для управления ресурсами.
  • Кроссплатформенность: стандарт языка накладывает минимальные требования на ЭВМ для запуска скомпилированных программ. Для определения реальных свойств системы выполнения в стандартной библиотеке присутствуют соответствующие возможности (например, std::numeric_limits <T>). Доступны компиляторы для большого количества платформ, на языке C++ разрабатывают программы для самых различных платформ и систем.
  • Эффективность. Язык спроектирован так, чтобы дать программисту максимальный контроль над всеми аспектами структуры и порядка исполнения программы. Ни одна из языковых возможностей, приводящая к дополнительным накладным расходам, не является обязательной для использования — при необходимости язык позволяет обеспечить максимальную эффективность программы.
  • Имеется возможность работы на низком уровне с памятью, адресами.
  • Высокая совместимость с языком C, позволяющая использовать весь существующий C-код (код на C может быть с минимальными переделками скомпилирован компилятором C++; библиотеки, написанные на C, обычно могут быть вызваны из C++ непосредственно без каких-либо дополнительных затрат, в том числе и на уровне функций обратного вызова, позволяя библиотекам, написанным на C, вызывать код, написанный на С++).

Критика

Критику языка C++ можно разделить на несколько групп заявлений. Критики полагают, что:

  • Синтаксис, унаследованный от C, неудобен.
  • Язык содержит слишком много возможностей, они могут быть опасны.
  • Наоборот, язык не содержит некоторых возможностей.
  • Языку присущи проблемы производительности.

Синтаксис

  • Операция присваивания обозначается как =, а операция сравнения как ==. Их легко спутать, при этом операция присваивания возвращает значение, поэтому присваивание на месте выражения является синтаксически корректным, а в конструкциях цикла и ветвления появление числа на месте логического значения также допустимо, так что ошибочная конструкция оказывается синтаксически правильной. Типичный пример подобной ошибки:
    if (x=0) { операторы }
    
    Здесь в условном операторе по ошибке написано присваивание вместо сравнения. В результате, вместо того, чтобы сравнить текущее значение x с нулём, программа присвоит x нулевое значение, а потом интерпретирует его как значение условия в операторе if. Так как нуль соответствует логическому значению «ложь», блок операторов в условной конструкции не выполнится никогда. Ошибки такого рода трудно выявлять, но во многих современных компиляторах предлагается диагностика некоторых подобных конструкций.
  • Операции присваивания (=), инкрементации (++), декрементации (--) и другие возвращают значение. В сочетании с обилием операций это позволяет, хотя и не обязывает, создавать трудночитаемые выражения. Наличие этих операций в C было вызвано желанием получить инструмент ручной оптимизации кода[источник не указан 827 дней], но в настоящее время оптимизирующие компиляторы обычно генерируют оптимальный код и на традиционных выражениях. С другой стороны, один из основных принципов языков C и C++ — позволять программисту писать в любом стиле, а не навязывать «хороший» стиль.
  • Некоторые преобразования типов неинтуитивны[источник не указан 827 дней]. В частности, операция над беззнаковым и знаковым числами выдаёт беззнаковый результат.
  • Многие конструкции С++ позволяют делать то же самое, что и конструкции C, также присутствующие в С++. Это иногда сбивает с толку новичков. Например, приведение типов при помощи dynamic_cast позволяет привести указатель или ссылку строго в пределах иерархии классов. Это делает код более надёжным, декларативным и позволяет находить приведения в пределах иерархии при помощи инструментов типа grep. Однако вследствие требования высокой степени совместимости с C старое приведение типов всё ещё поддерживается.

Избыточные и опасные возможности

  • C++ позволяет пропускать break в ветви оператора switch с целью последовательного выполнения нескольких ветвей. Такой же подход принят в языке Java.[14] Есть мнение, что это затрудняет понимание кода. Например, в языке C# необходимо всегда писать либо break, либо использовать goto case N для явного указания порядка выполнения.[15]
  • Макросы (#define) являются мощным, но опасным средством. Они сохранены в C++ несмотря на то, что необходимость в них, благодаря шаблонам и встроенным функциям, не так уж велика. В унаследованных стандартных C-библиотеках много потенциально опасных макросов.[16]

Отсутствие возможностей

  • Препроцессор, унаследованный от C, очень примитивен. Это приводит с одной стороны к тому, что с его помощью нельзя (или тяжело) осуществлять некоторые задачи метапрограммирования, а с другой, вследствие своей примитивности, он часто приводит к ошибкам и требует много действий по обходу потенциальных проблем. Некоторые языки программирования (например, Lisp и Nemerle) имеют намного более мощные и более безопасные системы метапрограммирования (также называемые макросами, но мало напоминающие макросы C/C++).
  • Плохая поддержка модульности (по сути, в классическом C модульность на уровне языка отсутствует, её обеспечение переложено на компоновщик). Подключение интерфейса внешнего модуля через препроцессорную вставку заголовочного файла (#include) серьёзно замедляет компиляцию при подключении большого количества модулей (потому что результирующий файл, который обрабатывается компилятором, оказывается очень велик). Эта схема без изменений скопирована в C++. Для устранения этого недостатка многие компиляторы реализуют механизм прекомпиляции заголовочных файлов.
  • Метапрограммирование на основе шаблонов C++ сложно и при этом ограничено в возможностях. Оно состоит в реализации средствами шаблонов C++ интерпретатора примитивного функционального языка программирования, выполняющегося во время компиляции. Сама по себе данная возможность весьма привлекательна, но такой код весьма трудно воспринимать и отлаживать. Менее распространённые[17] языки Lisp/Scheme, Nemerle имеют более мощные и одновременно более простые для восприятия подсистемы метапрограммирования. Кроме того, в языке D реализована сравнимая по мощности, но значительно более простая в применении подсистема шаблонного метапрограммирования.
  • Явная поддержка функционального программирования присутствует только в будущем стандарте C++0x. Данный пробел устраняется различными библиотеками (Loki, Boost), использующими средства метапрограммирования для расширения языка функциональными конструкциями (например, поддержкой лямбд/анонимных методов), но качество подобных решений значительно уступает качеству встроенных в функциональные языки решений. Такие возможности функциональных языков, как сопоставление с образцом, вообще крайне сложно эмулировать средствами метапрограммирования.
  • Некоторые считают недостатком языка C++ отсутствие встроенной системы сборки мусора. С другой стороны, средства C++ позволяют реализовать сборку мусора на уровне библиотеки.[18] Противники сборки мусора полагают, что RAII является более достойной альтернативой. С++ позволяет пользователю самому выбирать стратегию управления ресурсами.

Производительность

  • Шаблоны C++ могут приводить к порождению кода очень большого объёма.[19] Для снижения размера машинного кода можно специальным образом подготавливать исходный код.[20] Другим решением является стандартизованная ещё в 1998 году возможность экспорта шаблонов. Некоторые авторы считают, что её трудно реализовать[21][22][23] и она доступна не во всех компиляторах. Впрочем, «раздувание» машинного кода вследствие использования шаблонов часто преувеличивается, и современные компиляторы во многих случаях успешно устраняют это явление.[24]

Контраргументы

Сторонники языка считают, что при оценке критических высказываний по поводу С++ необходимо учитывать следующее:

  • Высокая совместимость с C является одной из принципиальных черт языка, так что почти все отмеченные недостатки, унаследованные от C, являются сознательной платой за преимущества, предоставляемые совместимостью (см. раздел #Философия C++).
  • Ничто не вынуждает программиста на С++ использовать «плохие» языковые средства, если он умеет использовать имеющиеся в языке и предназначенные для этой же цели «хорошие». Программист имеет в результате возможность выбора в соответствии с личными предпочтениями и уровнем знаний. Это относится и к макросам, и к шаблонам.
  • В силу широчайшего охвата различных парадигм программирования, критики сравнивают различные аспекты С++ с совершенно различными языками. Естественно, что в каждом языке можно найти некоторую отдельную возможность, отсутствующую в С++, или более удачный вариант реализации того или иного средства C++. Однако общий набор средств и возможностей С++, как правило, существенно шире, чем в любом из языков, с которым проводится сравнение. При этом некоторые возможности C++ являются уникальными или крайне редко встречаются у языков-конкурентов — например, автоматический вызов деструктора по окончании времени жизни объекта.

Сравнение с языком Java

Целью создания C++ было расширение возможностей C, наиболее распространённого языка системного программирования. Ориентированный на ту же самую область применения, C++ унаследовал множество не самых лучших, с теоретической точки зрения, особенностей C[каких?]. Перечисленные выше принципы, которых придерживался автор языка, предопределили многие недостатки C++[источник не указан 827 дней].

В области прикладного программирования альтернативой C++ стал его язык-потомок, Java. Несмотря на преемственность по отношению к C++, Java строилась на принципиально иной основе, её разработчики не были связаны требованиями совместимости с языком-предком и обеспечения максимально достижимой эффективности, благодаря чему они смогли кардинально переработать язык, отказаться от множества синтаксических средств, чтобы добиться идеологической целостности языка. Позже компания Microsoft предложила язык C#, представляющий собой ещё одну переработку C++ в том же направлении, что и Java. В дальнейшем появился язык Nemerle, в котором к средствам C# добавлены средства функционального программирования. Ещё позже появилась попытка объединения эффективности C++ с безопасностью и скоростью разработки Java и C# — был предложен язык D, который пока не получил широкого признания.

Java и C++ можно рассматривать как два языка-потомка C, разработанных из различных соображений и пошедших, вследствие этого, по разным путям. В связи с этим представляет интерес сравнение данных языков (всё, сказанное ниже про Java, можно с равным успехом отнести к языкам C# и Nemerle, поскольку в рассматриваемых деталях эти языки отличаются лишь внешне).

Синтаксис 
C++ сохраняет совместимость с C, насколько это возможно. Java сохраняет внешнее подобие C и C++, но в действительности сильно отличается от них — из языка удалено большое число синтаксических средств, объявленных необязательными. В результате программы на Java бывают более громоздки по сравнению с их аналогами на С++. С другой стороны, Java проще, что облегчает как изучение языка, так и создание трансляторов для него.
Исполнение программы 
Java-код компилируется в промежуточный код, который в дальнейшем интерпретируется или компилируется, тогда как C++ изначально ориентирован на компиляцию в машинный код заданной платформы (хотя, теоретически, ничто не мешает создавать для C++ трансляторы в промежуточный код). Это уже определяет разницу в сферах применения языков: Java вряд ли может быть использована при написании таких специфических программ, как драйверы устройств или низкоуровневые системные утилиты. Механизм исполнения Java делает программы, даже откомпилированные (в байт-код) полностью переносимыми. Стандартное окружение и среда исполнения позволяют выполнять программы на Java на любой аппаратной платформе и в любой ОС, без каких-либо изменений, усилия по портированию программ минимальны (при соблюдении рекомендаций по созданию переносимых программ — и вовсе нулевые). Ценой переносимости становится потеря эффективности — работа среды исполнения приводит к дополнительным накладным расходам.
Управление ресурсами 
C++ позволяет использовать принцип «захват ресурсов путём инициализации» (RAII), при котором ресурсы ассоциированы с объектом и автоматически освобождаются при разрушении объекта (например, std::vector и std::ifstream). Также возможен подход, когда программист, выделяя ресурсы (память под объекты, открытые файлы и т. п.), обязан явно позаботиться о своевременном их освобождении. Java работает в среде со сборкой мусора, которая автоматически отслеживает прекращение использования объектов и освобождает занимаемую ими память, если в этом есть необходимость, в некоторый неопределённый момент времени. Ручное управление предпочтительнее в системном программировании, где требуется полный контроль над ресурсами, RAII и сборка мусора удобнее в прикладном программировании, поскольку в значительной степени освобождают программиста от необходимости отслеживать момент прекращения использования ресурсов. Сборщик мусора Java требует системных ресурсов, что снижает эффективность выполнения программ, лишает программы на Java детерминированности выполнения и способен следить только за памятью. Файлы, каналы, сокеты, объекты графического интерфейса программист на Java всегда освобождает явно.
Стандартизация окружения 
В Java есть чётко определённые стандарты на ввод-вывод, графику, геометрию, диалог, доступ к базам данных и прочим типовым приложениям. C++ в этом отношении гораздо более свободен. Стандарты на графику, доступ к базам данных и т. д. являются недостатком, если программист хочет определить свой собственный стандарт.
Указатели 
C++ сохраняет возможность работы с низкоуровневыми указателями. В Java указателей нет. Использование указателей часто является причиной труднообнаруживаемых ошибок, но необходимо для низкоуровневого программирования. В принципе, C++ обладает набором средств (конструкторы и деструкторы, стандартные шаблоны, ссылки), позволяющих почти полностью исключить выделение и освобождение памяти вручную и опасные операции с указателями. Однако такое исключение требует определённой культуры программирования, в то время как в языке Java оно реализуется автоматически.
Парадигма программирования 
В отличие от С++, Java является чисто объектно-ориентированным языком, без возможности процедурного программирования. Для объявления свободных функций или глобальных переменных в Java необходимо создавать фиктивные классы, содержащие только static члены.[25] Для задания главной функции даже самой простой программы на Java необходимо поместить её в класс.[26]
Динамическая информация о типах 
в C++ RTTI ограничена возможностью сравнивать типы объектов между собой и с буквальными значениями типов. В системе Java доступна более подробная информация о типах. Эту возможность можно было бы реализовать в C++, имея полную информацию о типах во время компиляции CTTI.
Препроцессор 
C++ использует препроцессор для включения определений функций и классов, для подключения библиотек, полностью выполненных в исходном коде, а также позволяет осуществлять метапрограммирование с использованием препроцессора, которое, в частности, решает сложные проблемы высокоуровневого дублирования кода[27] Есть мнение, что этот механизм небезопасен, так как имена макросов препроцессора глобальны, а сами макросы почти никак не связаны с конструкциями языка. Это может приводить к сложным конфликтам имён. С другой точки зрения, C++ предоставляет достаточно средств (константы, шаблоны, встроенные функции) для того, чтобы практически полностью исключить использование препроцессора. Java исключила препроцессор полностью, избавившись разом от всех проблем с его использованием, потеряв при этом возможности метапрограммирования препроцессора и текстовых замен в коде средствами языка.

Отличия языков приводят к ожесточённым спорам между сторонниками двух языков о том, какой язык лучше. Споры эти во многом беспредметны, поскольку сторонники Java считают различия говорящими в пользу Java, а сторонники C++ полагают обратное. C++, в свою очередь, развивался, и ряд его недостатков устранён в последних версиях стандарта (например, появился механизм частичной спецификации шаблонов).

Далеко не все программисты являются сторонниками одного из языков[источник не указан 827 дней]. По мнению большинства[источник не указан 827 дней] программистов, Java и C++ не являются конкурентами, потому что обладают различными областями применимости. Другие считают[источник не указан 827 дней], что выбор языка для многих задач является вопросом личного вкуса.

См. также

Примечания

  1. Herbert Schildt. C++ The Complete Reference Third Edition. — Osborne McGraw-Hill, 1998. — ISBN 978-0-07-882476-0
  2. 1 2 Страуструп, 1999, 2.1. Что такое C++?, с. 57
  3. Programming Language Popularity (2009). Архивировано из первоисточника 27 мая 2012. Проверено 16 января 2009.
  4. TIOBE Programming Community Index (2009). Архивировано из первоисточника 27 мая 2012. Проверено 6 мая 2009.
  5. Стэнли Липпман, Pure C++: Hello, C++/CLI  (англ.)
  6. C++ — Standards
  7. 1 2 Страуструп, 1999, 1.4. Исторические замечания, с. 46
  8. Bjarne Stroustrup. C++ Glossary. Архивировано из первоисточника 27 мая 2012. Проверено 8 июня 2007.
  9. Страуструп Б. Дизайн и эволюция C++ = The Design and Evolution of C++. — СПб.: Питер, 2007. — 445 с. — ISBN 5-469-01217-4
  10. Страуструп, 1999, 1.6
  11. ISO/IEC 14882:1998, раздел 6.4, пункт 4: «The value of a condition that is an initialized declaration in a statement other than a switch statement is the value of the declared variable implicitly converted to bool … The value of a condition that is an expression is the value of the expression, implicitly converted to bool for statements other than switch; if that conversion is ill-formed, the program is ill-formed».
  12. Volatile в многопоточной программе
  13. STLport: Welcome!
  14. The Java Tutorials: The switch Statement
  15. MSDN: The switch statement in C#
  16. Страуструп, Бьярне. Макросы // Программирование: Принципы и практика использования C++ = Programming: Principles and Practice Using C++. — М.: ООО «И. Д. Вильямс», 2001. — С. 1065, 1066, 1133. — 1248 с. — ISBN 978-5-8459-1621-1
  17. TIOBE Programming Community Index for January 2010
  18. Boehm-Demers-Weiser garbage collector for C and C++
  19. Dave Gottner. Templates Without Code Bloat // Dr. Dobb's Journal. — январь 1995.
  20. Adrian Stone. Minimizing Code Bloat: Redundant Template Instantiation. Game Angst (22 сентября 2009). Архивировано из первоисточника 27 мая 2012. Проверено 19 января 2010.
  21. Herb Sutter. C++ Conformance Roundup // Dr. Dobb's Journal. — январь 2001.
  22. Are there any compilers that implement all of this?. comp.std.c++ frequently asked questions / The C++ language. Comeau Computing (англ.) (10 декабря 2008). Архивировано из первоисточника 27 мая 2012. Проверено 19 января 2010.
  23. vanDooren. C++ keyword of the day: export. Blogs@MSMVPs (24 сентября 2008). — «The export keyword is a bit like the Higgs boson of C++. Theoretically it exists, it is described by the standard, and noone has seen it in the wild. … There is 1 C++ compiler front-end in the world which actually supports it»  Архивировано из первоисточника 27 мая 2012. Проверено 19 января 2010.
  24. Scott Meyers. Code Bloat due to Templates. comp.lang.c++.moderated. Usenet (16 мая 2002). Проверено 19 января 2010.
  25. Class Arrays, JavaTM 2 Platform Std. Ed. v1.4.2
  26. The Java Tutorials. A Closer Look at the «Hello World!» Application
  27. David Abrahams, Aleksey Gurtovoy. C++ Template Metaprogramming 2005

Литература

  • Жилмер Дж. Дердж, Дэвид Р. Мюссер, Атул Сейни. C++ и STL: справочное руководство = STL Tutorial and Reference Guide: C++ Programming with the Standard Template Library. — 2-е издание. — М.: Вильямс, 2010. — С. 432. — (C++ in Depth). — ISBN 978-5-8459-1665-5
  • Джесс Либерти, Дэвид Хорват. Освой самостоятельно C++ за 24 часа = Sams Teach Yourself C++ in 24 Hours, Complete Starter Kit. — 4-е изд. — М.: Вильямс, 2007. — 448 с. — ISBN 0-672-32681-7
  • Стефенс Д. Р. C++. Сборник рецептов. — КУДИЦ-ПРЕСС, 2007. — 624 с. — ISBN 5-91136-030-6
  • Б. Страуструп. Язык программирования C++ = The C++ Programming Language / Пер. с англ. — 3-е изд. — СПб.; М.: Невский диалект — Бином, 1999. — 991 с. — 3000 экз. — ISBN 5-7940-0031-7 (Невский диалект), ISBN 5-7989-0127-0 (Бином), ISBN 0-201-88954-4 (англ.)
  • Страуструп Б. Язык программирования C++. Специальное издание = The C++ programming language. Special edition. — М.: Бином-Пресс, 2007. — 1104 с. — ISBN 5-7989-0223-4
  • Страуструп Б. Программирование: принципы и практика использования C++, исправленное издание = Programming: Principles and Practice Using C++. — М.: Вильямс, 2011. — С. 1248. — ISBN 978-5-8459-1705-8
  • Стивен Прата. Язык программирования C++ (C++11). Лекции и упражнения = C++ Primer Plus, 6th Edition (Developer’s Library). — 6-е изд. — М.: Вильямс, 2012. — 1248 с. — ISBN 978-5-8459-1778-2
  • Айвор Хортон. Visual C++ 2010: полный курс = Ivor Horton’s Beginning Visual C++ 2010. — М.: Диалектика, 2010. — С. 1216. — ISBN 978-5-8459-1698-3
  • Герберт Шилдт. Полный справочник по C++ = C++: The Complete Reference. — 4-е изд. — М.: Вильямс, 2011. — С. 800. — ISBN 978-5-8459-0489-8

Ссылки


Wikimedia Foundation. 2010.

Игры ⚽ Нужно решить контрольную?

Полезное



Поделиться ссылкой на выделенное

Прямая ссылка:
Нажмите правой клавишей мыши и выберите «Копировать ссылку»