C++
C++ (читается си-плюс-плюс[3][4]) — компилируемый, статически типизированный язык программирования общего назначения. Является мультипарадигменным языком программирования: поддерживаются такие парадигмы, как процедурное программирование, объектно-ориентированное программирование, обобщённое программирование, функциональное программирование. Язык имеет богатую стандартную библиотеку, которая включает в себя распространённые контейнеры и алгоритмы, ввод-вывод, регулярные выражения, поддержку многопоточности и другие возможности. C++ сочетает свойства как высокоуровневых, так и низкоуровневых языков[5][6]. В сравнении с его предшественником — языком C — наибольшее внимание уделено поддержке объектно-ориентированного и обобщённого программирования[6], а также переменных. C++ широко используется для разработки программного обеспечения, являясь одним из самых популярных языков программирования[мнения 1][мнения 2]. Область его применения включает создание операционных систем, разнообразных прикладных программ, драйверов устройств, приложений для встраиваемых систем, высокопроизводительных серверов, а также компьютерных игр. Существует множество реализаций языка C++, как бесплатных, так и коммерческих и для различных платформ. Например, на платформе x86 это GCC, Clang, Visual C++, Intel C++ Compiler, Embarcadero (Borland) C++ Builder и другие. C++ оказал огромное влияние на другие языки программирования, в первую очередь на Java и C#. Синтаксис C++ унаследован от языка Си. Изначально одним из принципов разработки было сохранение совместимости с Си. Тем не менее, C++ не является в строгом смысле надмножеством Си: множество программ, которые могут одинаково успешно транслироваться как компиляторами Си, так и компиляторами C++, довольно велико, но не включает все возможные программы на Си. История
СозданиеЯзык возник в начале 1980-х годов, когда сотрудник фирмы Bell Labs Бьёрн Страуструп придумал ряд усовершенствований к языку Си под собственные нужды[8]. Когда в конце 1970-х годов Страуструп начал работать в Bell Labs над задачами теории очередей (в приложении к моделированию телефонных вызовов), он обнаружил, что попытки применения существовавших в то время языков моделирования оказались неэффективными, а применение высокоэффективных машинных языков слишком сложно из-за их ограниченной выразительности. Так, язык Симула имеет такие возможности, которые были бы очень полезны для разработки большого программного обеспечения, но работает слишком медленно, а язык BCPL достаточно быстр, но слишком близок к языкам низкого уровня и не подходит для разработки большого программного обеспечения. Вспомнив опыт своей диссертации, Страуструп решил дополнить язык C (преемник BCPL) возможностями, имевшимися в языке Симула. Язык Си, будучи базовым языком системы Unix, на которой работали компьютеры Bell, является быстрым, многофункциональным и переносимым. Страуструп добавил к нему возможность работы с классами и объектами. В результате практические задачи моделирования оказались доступными для решения как с точки зрения времени разработки (благодаря использованию cимулаподобных классов), так и с точки зрения времени вычислений (благодаря быстродействию C). В первую очередь в C были добавлены классы (с инкапсуляцией), наследование классов, строгая проверка типов, inline-функции и аргументы по умолчанию. Ранние версии языка, первоначально именовавшегося «C with classes» («Си с классами»), стали доступны с 1980 года. Разрабатывая «Си с классами», Страуструп написал программу cfront — транслятор, перерабатывающий исходный код своего языка в исходный код простого Си. Это позволило работать над новым языком и использовать его на практике, применяя уже имевшуюся в unix-инфраструктуру для разработки на Си. Новый язык неожиданно для автора приобрёл большую популярность среди коллег и вскоре Страуструп уже не мог лично поддерживать его, отвечая на тысячи вопросов. К 1983 году в язык были добавлены новые возможности, такие как виртуальные функции, перегрузка функций и операторов, ссылки, константы, пользовательский контроль над управлением свободной памятью, улучшенная проверка типов и новый стиль комментариев ( До начала официальной стандартизации язык развивался в основном силами Страуструпа в ответ на запросы программистского сообщества. Функцию стандартных описаний языка выполняли написанные Страуструпом печатные работы по C++ (описание языка, справочное руководство и так далее). Лишь в 1998 году был ратифицирован международный стандарт языка C++: ISO/IEC 14882:1998 «Standard for the C++ Programming Language»; после принятия технических исправлений к стандарту в 2003 году — следующая версия этого стандарта — ISO/IEC 14882:2003[9]. Развитие и стандартизация языкаВ 1985 году вышло первое издание «Языка программирования C++», обеспечивающее первое описание этого языка, что было чрезвычайно важно из-за отсутствия официального стандарта. В 1989 году состоялся выход C++ версии 2.0. Его новые возможности включали множественное наследование, абстрактные классы, статические функции-члены, функции-константы и защищённые члены. В 1990 году вышло «Комментированное справочное руководство по C++», положенное впоследствии в основу стандарта. Последние обновления включали шаблоны, исключения, пространства имён, новые способы приведения типов и булевский тип. В качестве основы для хранения и доступа к обобщённым алгоритмам была выбрана Стандартная библиотека шаблонов (STL), разработанная Александром Степановым и Менг Ли[англ.]. Стандартная библиотека C++ также развивалась вместе с ним. Первым добавлением к стандартной библиотеке C++ стали потоки ввода-вывода, обеспечивающие средства для замены традиционных функций C
C++ продолжает развиваться, чтобы отвечать современным требованиям. Одна из групп, разрабатывающих язык C++ и направляющих комитету по стандартизации C++ предложения по его улучшению — это Boost, которая занимается, в том числе, совершенствованием возможностей языка путём добавления в него особенностей метапрограммирования. Никто не обладает правами на язык C++, он является свободным. Однако сам документ стандарта языка (за исключением черновиков) не доступен бесплатно[11]. В рамках процесса стандартизации ISO выпускает несколько видов изданий. В частности, технические доклады и технические характеристики публикуются, когда «видно будущее, но нет немедленной возможности соглашения для публикации международного стандарта». До 2011 года было опубликовано три технических отчёта по C++: TR 19768: 2007 (также известный как C++, Технический отчёт 1) для расширений библиотеки в основном интегрирован в C++11, TR 29124: 2010 для специальных математических функций, и TR 24733: 2011 для десятичной арифметики с плавающей точкой. Техническая спецификация DTS 18822:. 2014 (по файловой системе) была утверждена в начале 2015 года, и остальные технические характеристики находятся в стадии разработки и ожидают одобрения[12]. В марте 2016 года в России была создана рабочая группа РГ21 C++. Группа была организована для сбора предложений к стандарту C++, отправки их в комитет и защиты на общих собраниях Международной организации по стандартизации (ISO)[13]. История названияИмя языка, полученное в итоге, происходит от оператора унарного постфиксного инкремента C Философия C++В книге «Дизайн и эволюция C++»[14] Бьёрн Страуструп описывает принципы, которых он придерживался при проектировании C++. Эти принципы объясняют, почему C++ именно такой, какой он есть. Некоторые из них:
Обзор языкаСтандарт C++ состоит из двух основных частей: описание ядра языка и описание стандартной библиотеки. Первое время язык развивался вне формальных рамок, спонтанно, по мере встававших перед ним задач. Развитию языка сопутствовало развитие кросс-компилятора cfront. Новшества в языке отражались в изменении номера версии кросс-компилятора. Эти номера версий кросс-компилятора распространялись и на сам язык, но применительно к настоящему времени речь о версиях языка C++ не ведут. Лишь в 1998 году язык стал стандартизированным.
namespace foo {
const int x = 5; // константа
void t() {} // функция
namespace bar { // пространство имён в пространстве имён
float b = 2.2;
}
}
int main() {
const int y = foo::x;
y; // 5
foo::t(); // вызов функции из пространства имён "foo"
foo::bar::b; // 2.2
return 0;
}
Специальным случаем является безымянное пространство имён. Все имена, описанные в нём, доступны только в текущей единице трансляции и имеют локальное связывание. Пространство имён
ТипыВ C++ доступны следующие встроенные типы. Типы C++ практически полностью повторяют типы данных в C:
Операции сравнения возвращают тип Язык ввёл понятие ссылок, а со стандарта C++11 — rvalue-ссылки и передаваемые ссылки (англ. forwarding reference; см. Ссылка (C++)). C++ добавляет к C объектно-ориентированные возможности. Он вводит классы, которые обеспечивают три самых важных свойства ООП: инкапсуляцию, наследование и полиморфизм. В стандарте C++ под классом (class) подразумевается пользовательский тип, объявленный с использованием одного из ключевых слов В теле определения класса можно указать как объявления функций, так и их определение. В последнем случае функция является встраиваемой ( C++ поддерживает множественное наследование. Базовые классы (классы-предки) указываются в заголовке описания класса, возможно, со спецификаторами доступа. Наследование от каждого класса может быть публичным, защищённым или закрытым:
По умолчанию базовый класс наследуется как private. В результате наследования класс-потомок получает все поля классов-предков и все их методы; можно сказать, что каждый экземпляр класса-потомка содержит подэкземпляр каждого из классов-предков. Если один класс-предок наследуется несколько раз (это возможно, если он является предком нескольких базовых классов создаваемого класса), то экземпляры класса-потомка будет включать столько же подэкземпляров данного класса-предка. Чтобы избежать такого эффекта, если он нежелателен, C++ поддерживает концепцию виртуального наследования. При наследовании базовый класс может объявляться виртуальным; на все виртуальные вхождения класса-предка в дерево наследования класса-потомка в потомке создаётся только один подэкземпляр.
ПолиморфизмC++ поддерживает динамический полиморфизм и параметрический полиморфизм. Параметрический полиморфизм представлен:
Динамический полиморфизм реализуется с помощью виртуальных методов и иерархии наследования. Полиморфным в C++ является тип, имеющий хотя бы один виртуальный метод. Пример иерархии: class Figure {
public:
virtual void draw() = 0; // чистый виртуальный метод
virtual ~figure(); // при наличии хотя бы одного виртуального метода деструктор следует сделать виртуальным
};
class Square: public Figure {
public:
void draw() override;
};
class Circle: public Figure {
public:
void draw() override;
};
Здесь класс Figure является абстрактным (и, даже, интерфейсным), так как метод draw не определён. Объекты данного класса нельзя создать, зато можно использовать ссылки или указатели с типом Figure. Выбор реализации метода Draw будет производиться во время выполнения исходя из реального типа объекта. Инкапсуляция в C++ реализуется через указание уровня доступа к членам класса: они бывают публичными (открытыми,
Проверка доступа происходит во время компиляции, попытка обращения к недоступному члену класса вызовет ошибку компиляции. ДрузьяФункции-друзья — это функции, не являющиеся функциями-членами и тем не менее имеющие доступ к защищённым и закрытым членам класса. Они должны быть объявлены в теле класса как class Matrix {
friend Matrix multiply(Matrix m1, Matrix m2);
};
Здесь функция Дружественным может быть объявлен как весь класс, так и функция-член класса. Четыре важных ограничения, накладываемых на отношения дружественности в C++:
В общем виде это правило можно сформулировать следующим образом: «Отношение дружественности существует только между теми классами (классом и функцией), для которых оно явно объявлено в коде, и действует только в том направлении, в котором оно объявлено». Специальные функцииКласс по умолчанию может иметь шесть специальных функций: конструктор по умолчанию, конструктор копирования, конструктор перемещения, деструктор, оператор присваивания копированием, оператор присваивания перемещением. Также можно явно определить их все (см. Правило трёх). class Array {
protected:
std::size_t len = 0; // инициализация поля
double* val {nullptr};
public:
Array() = default; // компилятор создаст конструктор по умолчанию сам
explicit Array(size_t len):
len{len}, val{new double[len]} {}
Array(const Array& a) = delete; // конструктор копирования явно удалён
Array(Array&& a); // конструктор перемещения
~Array() {
delete[] val;
}
Array& operator=(const Array& rhs); // оператор присваивания копированием
Array& operator=(Array&& rhs); // оператор присваивания перемещением
[[nodiscard]]
double& operator[](size_t i) noexcept {
return val[i];
}
[[nodiscard]]
const double& operator[](size_t i) const noexcept {
return val[i];
}
};
Конструктор вызывается для инициализации объекта (соответствующего типа) при его создании, а деструктор — для уничтожения объекта. Класс может иметь несколько конструкторов, но деструктор может иметь только один. Конструкторы в C++ не могут быть объявлены виртуальными, а деструкторы — могут, и обычно объявляются для всех полиморфных типов, чтобы гарантировать правильное уничтожение доступного по ссылке или указателю объекта независимо от того, какого типа ссылка или указатель. При наличии хотя бы у одного из базовых классов виртуального деструктора, деструктор класса потомка автоматически становится виртуальным. ШаблоныШаблоны позволяют порождать функции и классы, параметризованные определённым типом или значением. Например, предыдущий класс мог бы реализовывать массив для любого типа данных: template <typename T>
class Array {
protected:
std::size_t len{0}; // инициализация поля
T* val{nullptr};
public:
// ...
[[nodiscard]]
T& operator[](size_t i) noexcept {
return val[i];
}
};
Стандартная библиотекаОбщая структураСтандартная библиотека C++ включает в себя набор средств, которые должны быть доступны для любой реализации языка, чтобы обеспечить программистам удобное пользование языковыми средствами и создать базу для разработки как прикладных приложений самого широкого спектра, так и специализированных библиотек. Стандартная библиотека C++ состоит из нескольких ключевых компонентов:
Стандартная библиотека C++ включает в себя часть стандартной библиотеки Си. Стандарт C++ содержит нормативную ссылку на стандарт C от 1990 года и не определяет самостоятельно те функции стандартной библиотеки, которые заимствуются из стандартной библиотеки Си. Однако в C++ заголовочные файлы Си адаптированы: вместо Доступ к возможностям стандартной библиотеки C++ обеспечивается с помощью включения в программу (посредством директивы С развитием стандартов (C++11, C++14, C++17, C++20) библиотека расширяется, предлагая более безопасные и выразительные средства. Например, в C++11 появились умные указатели для автоматического управления памятью, а в C++17 — структуры для работы с файловой системой. Каждое обновление стремится упростить разработку, сохраняя обратную совместимость и эффективность. СоставСтандартная библиотека включает в себя следующие разделы:
Контейнеры, строки, алгоритмы, итераторы и основные утилиты, за исключением заимствований из библиотеки C, собирательно называются STL (Standard Template Library — стандартная шаблонная библиотека). Изначально эта библиотека была отдельным продуктом и её аббревиатура расшифровывалась иначе, но потом она вошла в стандартную библиотеку C++ в качестве неотъемлемого элемента. В названии отражено то, что для реализации средств общего вида (контейнеров, строк, алгоритмов) использованы механизмы обобщённого программирования (шаблоны C++ — template). В работах Страуструпа подробно описываются причины, по которым был сделан именно такой выбор. Основными из них являются бо́льшая универсальность выбранного решения (шаблонные контейнеры, в отличие от объектных, могут легко использоваться для не объектных типов и не требуют наличия общего предка у типов элементов) и его техническая эффективность (как правило, операции шаблонного контейнера не требуют вызовов виртуальных функций и могут легко встраиваться (inline), что в итоге даёт выигрыш в производительности). Начиная со стандарта C++11 добавились следующие возможности:
РеализацииSTL до включения в стандарт C++ была сторонней разработкой, вначале — фирмы HP, а затем — SGI. Стандарт языка не называет её «STL», так как эта библиотека стала неотъемлемой частью языка, однако многие люди до сих пор используют это название, чтобы отличать её от остальной части стандартной библиотеки (потоки ввода-вывода (iostream), подраздел C и другие). Проект под названием STLport[16], основанный на SGI STL, осуществляет постоянное обновление STL, IOstream и строковых классов. Некоторые другие проекты также занимаются разработкой частных применений стандартной библиотеки. Отличия от Си![]() Совместимость с СиВыбор именно C в качестве базы для создания нового языка программирования объясняется тем, что язык C:
Несмотря на ряд известных недостатков языка Си, Страуструп пошёл на его использование в качестве основы, так как «в Си есть свои проблемы, но их имел бы и разработанный с нуля язык, а проблемы C нам известны». Кроме того, это позволило быстро получить прототип компилятора (cfront), который лишь выполнял трансляцию добавленных синтаксических элементов в оригинальный язык Си. По мере разработки C++ в него были включены другие средства, которые перекрывали возможности конструкций Си, в связи с чем неоднократно поднимался вопрос об отказе от совместимости языков путём удаления устаревших конструкций. Тем не менее, совместимость была сохранена из следующих соображений:
Новые возможностиНовые возможности C++ включают объявления в виде выражений, преобразования типов в виде функций, операторы В C++ появились комментарии в виде двойной косой черты ( Некоторые особенности C++ позднее были перенесены в C, например, ключевые слова C++ не включает в себя CНесмотря на то, что большая часть кода C будет справедлива и для C++, C++ не является надмножеством C и не включает его в себя. Существует и такой верный для C код, который неверен для C++. Это отличает его от Objective C, ещё одного усовершенствования C для ООП, как раз являющегося надмножеством C. Существуют и другие различия. Например, C++ не разрешает вызывать функцию Более того, код, верный для обоих языков, может давать разные результаты в зависимости от того, компилятором какого языка он оттранслирован. Например, на большинстве платформ следующая программа печатает «С», если компилируется компилятором C, и «C++» — если компилятором C++. Так происходит из-за того, что символьные константы в C (например, #include <stdio.h>
int main() {
printf("%s\n", (sizeof('a') == sizeof(char)) ? "C++" : "C");
return 0;
}
Средства C, которых рекомендуется избегатьПо замечанию Страуструпа, «чем лучше вы знаете C, тем труднее вам будет избежать программирования на C++ в стиле C, теряя при этом потенциальные преимущества C++». В связи с этим он даёт следующий набор рекомендаций для программистов на C, чтобы в полной мере воспользоваться преимуществами C++:
Дальнейшее развитиеТекущий стандарт языка ISO/IEC 14882:2020 был опубликован в декабре 2020 года. Неофициально его обозначают как C++20. Следующая версия стандарта, запланированная на 2024 год, имеет неофициальное обозначение C++23. Общие направления развития C++По мнению автора языка Бьёрна Страуструпа[20][21][22], говоря о дальнейшем развитии и перспективах языка, можно выделить следующее:
Стандарт C++11: дополнения в ядре языка
Примеры программПример № 1Это пример программы Hello, world!, которая выводит сообщение в консоль, используя стандартную библиотеку, и завершается. import std;
int main() {
std::println("Hello, world!");
return 0;
}
Пример № 2Современный C++ позволяет решать простым способом и более сложные задачи. Этот пример демонстрирует, кроме всего прочего, использование контейнеров стандартной библиотеки шаблонов (STL). import std;
int main() {
// Объявляем ассоциативный контейнер со строковыми ключами и данными в виде векторов строк.
// map эквивалентен TreeMap, vector эквивалентен ArrayList
std::map<std::string, std::vector<std::string>> items;
// Добавим в этот ассоциативный контейнер пару человек и дадим им несколько предметов.
items["Anya"].push_back("scarf");
items["Dmitry"].push_back("tickets");
items["Anya"].push_back("puppy");
// Переберём все объекты в контейнере
for (const std::pair<std::string, std::vector<std::string>>& person : items) {
// person - это пара двух объектов: person.first - это его имя,
// person.second - это список его предметов (вектор строк)
std::println("{} is carrying {} items", person.first, person.second.size());
}
}
В этом примере для простоты импортируются все имена из пространства имён std. В настоящей же программе так делать не рекомендуется, так как можно столкнуться с коллизией имён. Язык позволяет импортировать отдельные объекты: import std;
int main() {
using std::vector;
vector<int> my_vector;
}
В C++ (как и в C), если выполнение программы доходит до конца функции Сравнение с альтернативными языкамиИзвестно несколько исследований, в которых была сделана попытка более или менее объективно сравнить несколько языков программирования, одним из которых является C++. В частности:
C++ и АдаЯзык Ада близок к C++ по набору возможностей и по сферам применения: это компилируемый структурный язык с Симула-подобным объектно-ориентированным дополнением (та же модель «Алгол с классами», что и в C++), статической типизацией, средствами обобщённого программирования, предназначенный для разработки крупных и сложных программных систем. В то же время он принципиально отличается по идеологии: в отличие от C++, Ада строилась на основе предварительно тщательно проработанных условий производителей сложного ПО с повышенными требованиями к надёжности, что наложило отпечаток на синтаксис и семантику языка. Прямых сравнений эффективности кодирования на Аде и C++ немного. В упомянутой выше статье[23] решение модельной задачи на Аде привело к получению кода примерно на 30 % меньшего по объёму (в строках), чем на C++. Сравнение свойств самих языков приводится во многих источниках, например, в статье Джима Роджерса на AdaHome[29] содержится перечисление более 50 пунктов различий свойств этих языков, большая часть которых — в пользу Ады (больше возможностей, более гибкое поведение, меньше вероятность ошибок). Хотя многие утверждения сторонников Ады спорны, а часть из них явно устарела, в целом можно заключить:
В статье Стефена Цейгера из Rational Software Corporation[30], утверждается, что в целом разработка на Аде обходится на 60 % дешевле, и приводит к получению кода, имеющего в 9 раз меньше дефектов, чем на Си. Хотя эти результаты не могут быть прямо перенесены на C++, но всё же представляют интерес с учётом того, что многие недостатки C++ унаследованы от Си. C++ и JavaJava не может считаться в полной мере заменой C++, она создана как безопасный язык с низким порогом вхождения для разработки прикладных пользовательских приложений с высокими показателями портируемости[31] и принципиально непригодна для некоторых типов приложений, которые разрабатываются на C++. Однако в пределах своей области Java составляет вполне реальную конкуренцию C++. В качестве преимуществ Java обычно называют:
В то же время использование сборщика мусора и виртуальной машины создают труднопреодолимые ограничения. Программы на Java, как правило, медленнее, требуют значительно больше памяти, к тому же виртуальная машина изолирует программу от операционной системы, делая невозможным низкоуровневое программирование. Эмпирическое исследование[25] не обнаружило существенной разницы в скорости разработки на C++ и на Java. Исследование[27] также показало, что представление о существенной разнице в скорости программ на этих языках не всегда верно: в двух из трёх тестов скорость работы приложений на Java и C++ оказалась сравнима. В то же время Java лаконичнее — разница в объёме кода составила порядка 10-15 %. C++ и CОригинальный Си продолжает развиваться, на нём разрабатываются многие масштабные проекты: он является основным языком разработки операционных систем, на нём написаны игровые движки многих динамических игр и большое число прикладных приложений. Ряд специалистов утверждает, что замена Си на C++ не повышает эффективности разработки, но приводит к ненужному усложнению проекта, снижению надёжности и увеличению затрат на сопровождение. В частности:
Нет убедительных данных о преимуществе C++ перед Си ни по производительности программистов, ни по свойствам программ. Хотя есть исследования[32], утверждающие, что программисты на Си тратят около 30-40 % общего времени разработки (не считая отладки) на управление памятью, при сопоставлении общей производительности разработчиков[23] Си и C++ оказываются близки. В низкоуровневом программировании значительная часть новых возможностей C++ оказывается неприменимой из-за увеличения накладных расходов: виртуальные функции требуют динамического вычисления реального адреса (RVA), шаблоны приводят к раздуванию кода и ухудшению возможностей оптимизации, библиотека времени исполнения (RTL) очень велика, а отказ от неё лишает большинства возможностей C++ (хотя бы из-за недоступности операций
C++ и функциональные и скриптовые языкиВ одном эксперименте[23] скриптовые и функциональные языки, в частности, Haskell, показали 2-3 кратный выигрыш во времени программирования и объёме кода по сравнению с программами на C++. С другой стороны, программы на C++ оказались во столько же раз быстрее. Авторы признают, что полученные ими данные не составляют репрезентативной выборки и воздерживаются от категоричных выводов. В другом эксперименте[34] строгие функциональные языки (Standard ML, OCaml) показали общее ускорение разработки в 10 раз (в основном за счёт раннего выявления ошибок) при примерно равных показателях быстродействия (использовалось множество компиляторов в нескольких режимах). В исследовании Лутца Прехельта[25] по результатам обработки около 80 решений, написанных добровольцами, получены, в частности, следующие выводы:
КритикаО критике C++ в целомЧаще всего критики не противопоставляют C++ какой-либо другой конкретный язык, а утверждают, что отказ от использования единственного языка, имеющего многочисленные недостатки, в пользу декомпозиции проекта на подзадачи, решаемые на различных, наиболее подходящих для них, языках, делает разработку существенно менее трудоёмкой при одновременном повышении показателей качества программирования[35][36]. По этой же причине критикуется сохранение совместимости с Си: если часть задачи требует низкоуровневых возможностей, разумнее выделить эту часть в отдельную подсистему и написать её на Си. В свою очередь, сторонники C++ заявляют, что устранение технических и организационных проблем межъязыкового взаимодействия за счёт использования одного универсального языка вместо нескольких специализированных важнее, чем потери от несовершенства этого универсального языка, то есть сама широта набора возможностей C++ является оправданием недостатков каждой отдельной возможности; в том числе недостатки, унаследованные от Си, оправданы преимуществами совместимости (см. выше). Таким образом, одни и те же свойства C++ — объём, сложность, эклектичность и отсутствие конкретной целевой ниши применения — рассматривается сторонниками как «главное достоинство», а критиками — как «главный недостаток». Критика отдельных элементов и концепцийКонтроль за поведениемИдеология языка смешивает «контроль за поведением» с «контролем за эффективностью»: принцип «не платишь за то, что не используешь»предметно-ориентированного языка способен выполнить заведомо более эффективно, приводит лишь к росту объёма кода, повышению трудоёмкости программирования и снижению показателей понимаемости и тестируемости кода. Таким образом, принцип «не платить за то, что не используется» в действительности не даёт желаемых выгод в эффективности, но негативно сказывается на качестве. предполагает, что обеспечение полного контроля программиста за всеми аспектами исполнения программы на довольно низком уровне является необходимым и достаточным условием достижения высокой эффективности кода. В действительности для сколько-нибудь крупных программ это неверно: возложение на программиста низкоуровневой оптимизации, которую качественный компиляторКомпонентное и объектно-ориентированное программированиеПо мнению Алана Кэя, объектная модель «Алгол с классами», использованная в C++, уступает модели «всё — объект»[37], используемой в Objective-C, по общем объёму возможностей, показателям повторного использования кода, понимаемости, модифицируемости и тестируемости. Модель наследования C++ сложна, трудна в реализации и при этом провоцирует создание сложных иерархий с неестественными отношениями между классами (например, наследование вместо вложения). Результатом становится создание сильно зацепленных классов с нечётко разделённым функционалом. Например, в [38] приводится учебно-рекомендательный пример реализации класса «список» как подкласса от класса «элемент списка», который, в свою очередь, содержит функции доступа к другим элементам списка. Такое отношение типов является абсурдом с точки зрения математики и невоспроизводимо на более строгих языках. Идеология некоторых библиотек требует ручного приведения типов вверх и вниз по иерархии классов ( Как отмечает Ян Джойнер[39], C++ ошибочно отождествляет инкапсуляцию (то есть помещение данных внутрь объектов и отделение реализации от интерфейса) и сокрытие реализации. Это усложняет доступ к данным класса и требует реализовывать его интерфейс практически исключительно через функции доступа (что, в свою очередь, увеличивает объём кода и усложняет его). Совпадение типов в C++ определяется на уровне идентификаторов, а не сигнатур. Это делает невозможной подстановку компонентов, основанную на совпадении интерфейсов, из-за чего включение в систему новой функциональности, реализованной на уровне библиотек, требует ручной модификации уже имеющегося кода[40]. Как отмечает Линус Торвальдс[33], в C++ «код кажется абстрактным лишь до тех пор, пока не возникает необходимость его изменить». Критика C++ с позиций ООП приведена в работе[39]. МетапрограммированиеПорождающее метапрограммирование C++ основано на шаблонах и препроцессоре, оно трудоёмко и ограничено по возможностям. Система шаблонов C++ фактически является вариантом примитивного функционального языка программирования, исполняемого на этапе компиляции. Этот язык почти не пересекается с самим C++, из-за чего потенциал роста сложности абстракций оказывается ограниченным. Программы, использующие шаблоны C++, имеют крайне низкие показатели понимаемости и тестируемости, а само разворачивание шаблонов порождает неэффективный код, так как язык шаблонов не предоставляет никаких средств для оптимизации (см. также раздел #Вычислительная эффективность). Встраиваемые предметно-специфичные языки, реализуемые таким образом, всё равно требуют знания самого C++, что не обеспечивает полноценного разделения труда. Таким образом, возможности C++ по расширению возможностей самого C++ весьма ограничены[41][42]. КроссплатформенностьДля написания портируемого кода на C++ требуется огромное мастерство и опыт, и «небрежные» коды на C++ с высокой вероятностью могут оказаться непортируемыми[43]. По мнению Линуса Торвальдса, для обеспечения на C++ портируемости, аналогичной Си, программист должен ограничиться возможностями C++, унаследованными от Си[33]. Стандарт содержит множество элементов, определённых как «implementation-defined» (например, размер указателей на методы классов в различных компиляторах варьируется в диапазоне от 4 до 20 байт[44]), что ухудшает портируемость программ с их использованием. Директивный характер стандартизации языка, неполная обратная совместимость и противоречивость требований разных версий стандарта приводят к проблемам в переносе программ между различными компиляторами и даже версиями одних и тех же компиляторов. Отсутствие возможностей
Избыточные и опасные возможностиВстроенные средства обхода ограниченийЯзык содержит средства, позволяющие программисту нарушать заданную в конкретном случае дисциплину программирования. Например, модификатор Неконтролируемая макроподстановкаСредства макроподстановки Си ( Проблемы перегрузкиПринятые в C++ принципы перегрузки функций и операторов приводят к значительному дублированию кода. Перегрузка операторов, исходно предназначенная для введения так называемого «синтаксического сахара», в C++ поощряет бесконтрольное изменение поведения элементарных операций для различных типов. Это резко повышает риск ошибок, тем более что вводить новый синтаксис и изменять существующий (например, создавать новые операторы или менять приоритеты или ассоциативность) нельзя, хотя синтаксис стандартных операторов C++ адекватен семантике далеко не всех типов, которые может потребоваться ввести в программу. Отдельные проблемы создаёт возможность лёгкой перегрузки операторов Вычислительная эффективностьРезультирующий объём исполнимого кодаИспользование шаблонов C++ представляет собой параметрический полиморфизм на уровне исходного кода, но при трансляции он превращается в ситуативный (ad hoc) полиморфизм (то есть перегрузку функций), что приводит к существенному увеличению объёма машинного кода в сравнении с языками, имеющими истинно полиморфную систему типов (потомками ML). Для снижения размера машинного кода пытаются автоматически обрабатывать исходный код до этапа раскрутки шаблонов[46][47]. Другим решением могла бы быть стандартизованная ещё в 1998 году возможность экспорта шаблонов, но она доступна далеко не во всех компиляторах, так как её трудно реализовать[48][49][мнения 4] и для импорта библиотек шаблонов C++ в языки с существенно отличной от C++ семантикой она всё равно была бы бесполезна. Сторонники C++ оспаривают масштабы раздувания кода как преувеличенные[50], игнорируя даже тот факт, что в Си параметрический полиморфизм транслируется непосредственно, то есть без дублирования тел функций вообще. При этом сторонники C++ считают, что параметрический полиморфизм в Си опасен — то есть более опасен, чем переход от Си к C++ (противники C++ утверждают обратное — см. выше). Потенциал оптимизацииИз-за слабой системы типов и изобилия побочных эффектов становится крайне затруднительным эквивалентное преобразование программ, а значит и встраивание в компилятор многих оптимизирующих алгоритмов, таких как автоматическое распараллеливание программ, удаление общих подвыражений, λ-подъём, вызовы процедур с передачей продолжений, суперкомпиляция и др. В результате реальная эффективность программ на C++ ограничивается имеющейся квалификацией программистов и вложенными в конкретный проект усилиями, и «небрежная» реализация может существенно уступать по эффективности «небрежным» реализациям на языках более высокого уровня, что подтверждается сравнительными испытаниями языков[34]. Это является существенным препятствием против применения C++ в индустрии data mining. Эффективное управление памятьюОбязанность по эффективному управлению памятью ложится на плечи разработчика и зависит от навыков разработчика. Для автоматического управления памятью в C++ традиционно используются так называемые «умные указатели», ручное же управление памятью снижает эффективность самих программистов.сборки мусора, таких, как статический вывод регионов, не применимы для C++-программ (точнее, это требует реализации поверх языка C++ интерпретатора нового языка, сильно отличающегося от C++ как большинством объективных свойств, так и общей идеологией) по причине необходимости прямого доступа к абстрактному синтаксическому дереву. Многочисленные реализацииРезультативностьСоотнесение факторов результативности с затратами на разработку, а также общую культивируемую в сообществе программистов дисциплину и культуру программирования важно принимать во внимание заказчикам, выбирающим язык C++ (и, соответственно, предпочитающим этот язык разработчикам) для реализации задуманных ими проектов, а также людям, начинающим изучать программирование, особенно с намерением программировать для собственных нужд. Качество и культура программированияПринцип C++ «не навязывать „хороший“ стиль программирования» противоречит промышленному подходу к программированию, в котором ведущую роль играют качество программного обеспечения и возможность сопровождения кода не только автором, и для которого предпочтительны языки, сводящие к минимуму влияние человеческого фактора, то есть как раз «навязывающие „хороший“ стиль программирования», хотя такие языки и могут иметь более высокий порог вхождения. Существует мнение, что предпочтение использования C++ (при возможности выбора альтернативных языков) отрицательно характеризует профессиональные качества программиста. В частности, Линус Торвальдс говорит, что использует положительное мнение кандидатов о C++ в качестве критерия отсева[мнения 3]:
Исправление исправногоНепрерывная эволюция языка побуждает (а порой вынуждает) программистов раз за разом изменять уже отлаженный код — это не только удорожает разработку, но и несёт риск внедрения в отлаженный код новых ошибок. В частности, хотя изначально обратная совместимость с Си была одним из базовых принципов C++, с 1999 года Си перестал быть подмножеством C++, так что отлаженный код на Си уже не может использоваться в проекте на C++ без изменений. Сложность ради самой сложностиC++ определяется его апологетами как «мощнейший» именно потому, что он изобилует опасными взаимно-противоречивыми возможностями. По мнению Эрика Реймонда, это делает язык сам по себе почвой для личного самоутверждения программистов, превращения процесса разработки в самоцель:
СаботажОтмечены случаи, когда нерадивые программисты, пользуясь сильной контекстной зависимостью C++ и отсутствием возможности отслеживания макроопределений компилятором, тормозили разработку проекта, написав одну-две лишних, корректных с точки зрения компилятора, строки кода, но внедрив за их счёт труднообнаружимую спонтанно проявляющуюся ошибку. Например: #define if(a) if(rand())
#define j i
В языках с доказанной корректностью, даже с развитыми макросредствами, нанести урон подобным образом невозможно. Ненадёжность продуктаНеоправданное обилие побочных эффектов в сочетании с отсутствием контроля со стороны системы времени исполнения языка и слабой системой типов делает программы на C++ подверженными непредсказуемым фатальным сбоям (общеизвестные падения с сообщениями типа «Access violation», «Pure virtual function call» или «Программа выполнила недопустимую операцию и будет закрыта»), что исключает применение C++ при высоких требованиях к отказоустойчивости. Кроме того, это увеличивает длительность самого процесса разработки[34]. Менеджмент проектовПеречисленные выше факторы делают сложность менеджмента проектов на C++ одной из самых высоких в индустрии разработки ПО.
Влияние и альтернативы![]() Единственным прямым потомком C++ является язык D, задуманный как переработка C++ для устранения наиболее очевидных его проблем. Авторы отказались от совместимости с Си, сохранив синтаксис и многие базовые принципы C++ и введя в язык возможности, характерные для новых языков. В D нет препроцессора, заголовочных файлов, множественного наследования, но есть система модулей, интерфейсы, ассоциативные массивы, поддержка unicode в строках, сборка мусора (при сохранении возможности ручного управления памятью) встроенная многопоточность, вывод типов, явное объявление чистых функций и неизменяемых значений. Использование D весьма ограничено, считать его реальным конкурентом C++ нельзя. Старейшим конкурентом C++ в задачах низкого уровня является Objective-C, также построенный по принципу объединения Си с объектной моделью, только объектная модель унаследована от Smalltalk. Objective-C, как и его потомок Swift, широко используется для разработки ПО под macOS и iOS. Одной из первых альтернатив C++ в прикладном программировании стал язык Java. Его часто ошибочно считают прямым потомком C++; в действительности семантика Java унаследована от языка Модула-2, и основы семантики C++ в Java не прослеживаются. Учитывая это, а также генеалогию языков (Модула-2 является потомком Симулы, как и C++, но им не является Си), Java правильнее называть «троюродным племянником» C++, нежели «наследником». То же можно сказать о языке C#, хоть процент родственности с C++ у него несколько выше, чем у Java. Попыткой совмещения безопасности и скорости разработки, характерных для Java и C#, с возможностями C++ явился диалект Managed C++ (впоследствии — C++/CLI). Он разработан Microsoft в основном для переноса существующих проектов на C++ под платформу Microsoft.NET. Программы выполняются под управлением CLR и могут использовать весь массив библиотек .NET, но при этом накладывается ряд ограничений на использование возможностей C++, что фактически сводит C++ к C#. Данный диалект не получил широкого признания и используется в основном лишь для связывания библиотек, написанных на чистом C++, с C#-приложениями. Альтернативный путь развития языка Си — совмещение его не с объектно-ориентированным, а с аппликативным программированием, то есть улучшение абстракции, строгости и модульности низкоуровневых программ посредством обеспечения предсказуемости поведения и ссылочной прозрачности. Примерами работ в этом русле служат языки BitC, Cyclone и Limbo. Хотя есть и успешные попытки применения ФП в задачах реального времени без интеграции со средствами Си[52][53][54], всё же на данный момент (2013 г.) в низкоуровневой разработке применение в той или иной мере средств Си имеет лучшее соотношение трудоёмкости с результативностью. Много усилий было приложено разработчиками Python и Lua для обеспечения использования этих языков программистами на C++, так что из всех языков, достаточно тесно связанных с ФП, именно они чаще всего отмечаются в совместном использовании с C++ в одном проекте. Наиболее значимыми точками соприкосновения C++ с ФП можно считать привязки разработанных на C++ библиотек wxWidgets и Qt с характерной для C++ идеологией к языкам Lisp, Haskell и Python (в большинстве случаев привязки к функциональным языкам делают для библиотек, написанных на Си или на других функциональных языках). Ещё одним языком, рассматриваемым как конкурент C++, стал Nemerle, являющийся результатом попытки совместить модель типизации Хиндли-Милнера и макроподмножество Common Lisp с языком C#[55]. В том же русле находится созданный Microsoft язык F# — диалект ML, адаптированный для среды .NET. Попыткой создать промышленную замену Си и C++ стал разработанный в корпорации Google в 2009 году язык программирования Go. Авторы языка прямо указывают, что мотивом для его создания были недостатки процесса разработки, вызванные особенностями языков Си и C++[56]. Go — компактный, несложный по структуре императивный язык с Си-подобным синтаксисом, без препроцессора, со статической типизацией, строгим контролем типов, системой пакетов, автоматическим управлением памятью, некоторыми функциональными чертами, экономно построенной ООП-подсистемой без поддержки наследования реализации, но с интерфейсами и утиной типизацией, встроенной многопоточностью, основанной на сопрограммах и каналах (в духе Occam). Язык позиционируется как альтернатива C++, то есть, в первую очередь, средство групповой разработки высокоэффективных вычислительных систем большой сложности, в том числе распределённых, допускающее, при необходимости, низкоуровневое программирование. В одной нише с Си и C++ находится разработанный в 2010 году и поддерживаемый корпорацией Mozilla язык Rust, ориентированный на безопасное управление памятью без использования сборщика мусора. В частности, о планах частичной замены Си и C++ на Rust объявила в 2019 компания Microsoft[57]. В 2022 году компания Google представила новый экспериментальный язык программирования Carbon, созданный с целью стать преемником C++. В отличие от Rust, язык должен предоставлять двунаправленную совместимость с C++ и лучшую читаемость. Примечания
Мнения
Пояснения
Литература
Ссылки
|