Критика C++
![]() | В этой статье отсутствует обзор темы. ( сентябрь 2021 г. ) |
Хотя C++ является одним из самых распространенных языков программирования, [ 1 ] многие выдающиеся инженеры-программисты критикуют C++ (язык и его компиляторы), утверждая, что он слишком сложен. [ 2 ] и в корне ошибочен. [ 3 ] Среди критиков были: Роберт Пайк , [ 4 ] Джошуа Блох , Линус Торвальдс , [ 5 ] Дональд Кнут , Ричард Столлман и Кен Томпсон . C++ получил широкое распространение и был реализован как системный язык на протяжении большей части своего существования. Он использовался для создания многих частей очень важного программного обеспечения (такие типы программного обеспечения включают, помимо прочего: операционные системы , системы времени выполнения , интерпретаторы языков программирования , парсеры , лексеры , компиляторы и т. д.).
Медленное время компиляции
[ редактировать ]Естественным интерфейсом между исходными файлами в C и C++ являются заголовочные файлы . Каждый раз, когда файл заголовка изменяется, все исходные файлы, включающие этот файл, должны перекомпилировать свой код. Файлы заголовков работают медленно, поскольку они текстовые и контекстно-зависимые из-за препроцессора. [ 6 ] В языке C имеется лишь ограниченное количество информации в заголовочных файлах, наиболее важными из которых являются объявления структур и прототипы функций. C++ хранит свои классы в заголовочных файлах, и они предоставляют не только свои общедоступные переменные и общедоступные функции (как C с его структурами и прототипами функций), но и свои частные функции. Это приводит к ненужной перекомпиляции всех исходных файлов, которые включают файл заголовка, каждый раз, когда эти частные функции редактируются. Эта проблема усугубляется, когда классы записываются в виде шаблонов , в результате чего весь их код помещается в медленные заголовочные файлы, что характерно для большей части стандартной библиотеки C++ . Поэтому большие проекты C++ могут компилироваться относительно медленно. [ 7 ] Проблема во многом решается предварительно скомпилированными заголовками в современных компиляторах или использованием системы модулей, добавленной в C++20 ; будущие стандарты C++ планируют раскрыть функциональность стандартной библиотеки с помощью модулей. [ 8 ]
Состояние глобального формата <iostream>
[ редактировать ]С++ <iostream>
в отличие от С <stdio.h>
, опирается на состояние глобального формата. Это очень плохо сочетается с исключениями , когда функция должна прервать поток управления после ошибки, но до сброса состояния глобального формата. Одним из решений этой проблемы является использование инициализации сбора ресурсов (RAII), которая реализована в Boost. [ 9 ] библиотеки и часть стандартной библиотеки C++ .
<iostream>
использует статические конструкторы, которые вызывают накладные расходы, если они включены, даже если библиотека не используется. [ 10 ] Еще одним источником плохой производительности является неправильное использование std::endl
вместо \n
при выполнении вывода, поскольку он также вызывает .flush()
. С++ <iostream>
по умолчанию синхронизируется с <stdio.h>
что может вызвать проблемы с производительностью в приложениях с интенсивным вводом-выводом командной строки. Его отключение может повысить производительность, но вынуждает отказаться от некоторых гарантий заказа.
Ниже приведен пример, в котором исключение прерывает функцию перед std::cout
можно восстановить из шестнадцатеричного в десятичное. Номер ошибки в операторе catch будет записан в шестнадцатеричном формате, что, вероятно, не то, что нужно:
#include <iostream>
#include <vector>
int main() {
try {
std::cout << std::hex
<< 0xFFFFFFFF << '\n';
// std::bad_alloc will be thrown here:
std::vector<int> vector(0xFFFFFFFFFFFFFFFFull);
std::cout << std::dec; // Never reached
// (using scopes guards would have fixed that issue
// and made the code more expressive)
}
catch (const std::exception& e) {
std::cout << "Error number: " << 10 << '\n'; // Not in decimal
}
}
Это даже признано некоторыми членами организации по стандартизации C++. [ 11 ] что <iostream>
— это устаревший интерфейс, который со временем необходимо заменить.
Добавлен С++20 std::format
это устранило глобальное состояние форматирования и решило другие проблемы в iostreams. [ 12 ] Например, предложение catch теперь можно записать как
std::cout << std::format("Error number: {}\n", 10);
на который не влияет состояние потока. Хотя это может привести к накладным расходам из-за фактического форматирования, выполняемого во время выполнения.
Итераторы
[ редактировать ]Философия стандартной библиотеки шаблонов (STL), встроенной в стандартную библиотеку C++, заключается в использовании универсальных алгоритмов в форме шаблонов с использованием итераторов . Ранние компиляторы плохо оптимизировали небольшие объекты, такие как итераторы, что Александр Степанов охарактеризовал как «штраф за абстракцию», хотя современные компиляторы хорошо оптимизируют такие небольшие абстракции. [ 13 ] Критике также подвергся интерфейс, использующий пары итераторов для обозначения диапазонов элементов. [ 14 ] [ 15 ] Введение диапазонов в стандартную библиотеку C++20 должно решить эту проблему. [ 16 ]
Одна большая проблема заключается в том, что итераторы часто имеют дело с данными, выделенными в куче в контейнерах C++, и становятся недействительными, если данные перемещаются контейнерами независимо. Функции, изменяющие размер контейнера, часто делают недействительными все указывающие на него итераторы, создавая опасные случаи неопределенного поведения . [ 17 ] [ 18 ] Вот пример, когда итераторы в цикле for становятся недействительными из-за std::string
контейнер меняет свой размер в куче :
#include <iostream>
#include <string>
int main() {
std::string text = "One\nTwo\nThree\nFour\n";
// Let's add an '!' where we find newlines
for (auto it = text.begin(); it != text.end(); ++it) {
if (*it == '\n') {
// it =
text.insert(it, '!') + 1;
// Without updating the iterator this program has
// undefined behavior and will likely crash
}
}
std::cout << text;
}
Единый синтаксис инициализации
[ редактировать ]Синтаксис универсальной инициализации C++11 и std::initializer_list используют один и тот же синтаксис, который запускается по-разному в зависимости от внутренней работы классов. Если существует конструктор std::initializer_list, он вызывается. В противном случае обычные конструкторы вызываются с единым синтаксисом инициализации. Это может сбить с толку как новичков, так и экспертов. [ 19 ] [ 10 ]
#include <iostream>
#include <vector>
int main() {
int integer1{10}; // int
int integer2(10); // int
std::vector<int> vector1{10, 0}; // std::initializer_list
std::vector<int> vector2(10, 0); // std::size_t, int
std::cout << "Will print 10\n" << integer1 << '\n';
std::cout << "Will print 10\n" << integer2 << '\n';
std::cout << "Will print 10,0,\n";
for (const auto& item : vector1) {
std::cout << item << ',';
}
std::cout << "\nWill print 0,0,0,0,0,0,0,0,0,0,\n";
for (const auto& item : vector2) {
std::cout << item << ',';
}
}
Исключения
[ редактировать ]Были опасения, что принцип нулевых накладных расходов [ 20 ] несовместим с исключениями. [ 10 ] Большинство современных реализаций имеют нулевые издержки производительности, когда исключения включены, но не используются, но имеют накладные расходы во время обработки исключений и двоичного размера из-за необходимости развертывания таблиц. Многие компиляторы поддерживают отключение исключений языка для экономии двоичных издержек. Исключения также подвергались критике за то, что они небезопасны для обработки состояний. Эта проблема безопасности привела к изобретению идиомы RAII , [ 21 ] который оказался полезным не только для обеспечения безопасности исключений C++.
Кодирование строковых литералов в исходном коде
[ редактировать ]Строковые литералы C++, как и C++, не учитывают кодировку символов текста внутри них: они представляют собой просто последовательность байтов, а C++ string
класс следует тому же принципу. Хотя исходный код может (начиная с C++11) запрашивать кодировку для литерала, компилятор не пытается проверить, что выбранная кодировка исходного литерала «правильна» для помещаемых в него байтов, и среда выполнения не делает этого. принудительно использовать кодировку символов. Программисты, привыкшие к другим языкам, таким как Java, Python или C#, которые пытаются принудительно использовать кодировки символов, часто считают это дефектом языка.
Пример программы ниже иллюстрирует это явление.
#include <iostream>
#include <string>
// note that this code is no longer valid in C++20
int main() {
// all strings are declared with the UTF-8 prefix
// file encoding determines the encoding of å and Ö
std::string auto_enc = u8"Vår gård på Öland!";
// this text is well-formed in both ISO-8859-1 and UTF-8
std::string ascii = u8"Var gard pa Oland!";
// explicitly use the ISO-8859-1 byte-values for å and Ö
// this is invalid UTF-8
std::string iso8859_1 = u8"V\xE5r g\xE5rd p\xE5 \xD6land!";
// explicitly use the UTF-8 byte sequences for å and Ö
// this will display incorrectly in ISO-8859-1
std::string utf8 = u8"V\xC3\xA5r g\xC3\xA5rd p\xC3\xA5 \xC3\x96land!";
std::cout << "byte-count of automatically-chosen, [" << auto_enc
<< "] = " << auto_enc.length() << '\n';
std::cout << "byte-count of ASCII-only [" << ascii << "] = " << ascii.length()
<< '\n';
std::cout << "byte-count of explicit ISO-8859-1 bytes [" << iso8859_1
<< "] = " << iso8859_1.length() << '\n';
std::cout << "byte-count of explicit UTF-8 bytes [" << utf8
<< "] = " << utf8.length() << '\n';
}
Несмотря на наличие префикса C++11 'u8', означающего «строковый литерал Unicode UTF-8», вывод этой программы на самом деле зависит от кодировки текста исходного файла (или настроек компилятора - большинству компиляторов можно приказать преобразовать исходные файлы в определенную кодировку перед их компиляцией). Когда исходный файл закодирован с использованием UTF-8, а выходные данные запускаются на терминале, настроенном на обработку входных данных как UTF-8, получается следующий результат:
byte-count of automatically-chosen, [Vår gård på Öland!] = 22 byte-count of ASCII-only [Var gard pa Oland!] = 18 byte-count of explicit ISO-8859-1 bytes [Vr grd p land!] = 18 byte-count of explicit UTF-8 bytes [Vår gård på Öland!] = 22
Выходной терминал удалил недопустимые байты UTF-8 из отображения в строке примера ISO-8859. Передача вывода программы через утилиту дампа Hex покажет, что они все еще присутствуют в выводе программы, и что их удалило приложение терминала.
Однако, когда тот же исходный файл вместо этого сохраняется в формате ISO-8859-1 и перекомпилируется, вывод программы на том же терминале становится:
byte-count of automatically-chosen, [Vr grd p land!] = 18 byte-count of ASCII-only [Var gard pa Oland!] = 18 byte-count of explicit ISO-8859-1 bytes [Vr grd p land!] = 18 byte-count of explicit UTF-8 bytes [Vår gård på Öland!] = 22
Одно из предлагаемых решений — сделать исходное кодирование надежным для всех компиляторов.
См. также
[ редактировать ]Ссылки
[ редактировать ]- ^ «Опрос разработчиков Stack Overflow 2021» . Переполнение стека . Проверено 28 декабря 2021 г.
- ^ «Руководитель Google разочарован сложностью Java и C++ — Google, программное обеспечение, разработка приложений, инструменты разработки, языки и стандарты, Роб Пайк» . ИТ-директор . Проверено 28 декабря 2021 г.
- ^ «C++ (Аль Виро; Линус Торвальдс; Теодор Цо)» . yarchive.net . Проверено 28 декабря 2021 г.
- ^ «Руководитель Google разочарован сложностью Java и C++ — Google, программное обеспечение, разработка приложений, инструменты разработки, языки и стандарты, Роб Пайк» . ИТ-директор . Проверено 28 декабря 2021 г.
- ^ «C++ (Аль Виро; Линус Торвальдс; Теодор Цо)» . yarchive.net . Проверено 28 декабря 2021 г.
- ^ Уолтер Брайт. «Скорость компиляции C++» .
- ^ Роб Пайк (25 июня 2012 г.). «Меньше значит экспоненциально больше» .
Примерно в сентябре 2007 года я выполнял небольшую, но важную работу над огромной программой Google C++, с которой вы все взаимодействовали, и мои компиляции в нашем огромном распределенном кластере компиляции занимали около 45 минут.
- ^ Вилле Вотилайнен. «Смело предложить общий план для C++23» .
- ^ «Библиотека сохранения состояния потока ввода-вывода — 1.60.0» . www.boost.org .
- ^ Перейти обратно: а б с «Стандарты кодирования LLVM — документация LLVM 12» . llvm.org .
- ^ «N4412: Недостатки iostreams» . open-std.org . Проверено 3 мая 2016 г.
- ^ «P0645: Форматирование текста» . open-std.org . Проверено 20 мая 2021 г.
- ^ Александр Степанов. «Эталон Степанова» .
Итоговое число, полученное в результате теста, представляет собой среднее геометрическое коэффициентов снижения производительности отдельных тестов. Он утверждает, что представляет собой фактор, по которому вы будете наказаны вашим компилятором, если вы попытаетесь использовать функции абстракции данных C++. Я называю это число «штраф за абстракцию». Как и в случае с любым другим эталоном, такое утверждение трудно доказать; некоторые люди говорили мне, что это не типичное использование C++. Однако следует отметить тот факт, что большинство людей, возражающих против этого, несут ответственность за компиляторы C++ с непропорционально большим штрафом за абстракцию.
- ^ Андрей Александреску. «Итераторы должны уйти» (PDF) .
- ^ Андрей Александреску. «Общее программирование должно уйти» (PDF) .
- ^ «Библиотека диапазонов (C++20) — cppreference.com» . ru.cppreference.com .
- ^ Скотт Мейерс. Эффективный STL .
Учитывая все это распределение, освобождение, копирование и уничтожение. Вас не должно ошеломить, узнав, что эти шаги могут быть дорогостоящими. Естественно, вы не захотите выполнять их чаще, чем нужно. Если это не кажется вам естественным, возможно, так оно и будет, если учесть, что каждый раз, когда происходят эти шаги, все итераторы, указатели и ссылки на вектор или строку становятся недействительными. Это означает, что простое действие по вставке элемента в вектор или строку может также потребовать обновления других структур данных, которые используют итераторы, указатели или ссылки в расширяемом векторе или строке.
- ^ Анжелика Лангер. «Аннулирование итераторов STL» (PDF) .
- ^ Скотт Мейерс (7 сентября 2015 г.). «Мысли о превратностях инициализации C++» .
- ^ Бьерн Страуструп. «Основы C++» (PDF) .
- ^ Страуструп 1994 , 16.5 Управление ресурсами, стр. 388–89.
Цитируемые работы
[ редактировать ]- Страуструп, Бьярн (1994). Проектирование и эволюция C++ . Аддисон-Уэсли. ISBN 0-201-54330-3 .
Дальнейшее чтение
[ редактировать ]- Питер Сейбел (2009). Программисты за работой: размышления о ремесле программирования . Апресс. ISBN 978-1430219484 .
Внешние ссылки
[ редактировать ]- C++ FQA Lite от Йосси Крейнина
- Глава 10 «C++ — КОБОЛ 90-х» в книге The Unix Haters Group Book
- C++ в работе программистов Отрывки из книги Питера Сейбеля «Кодеры за работой»
- DConf 2014: Последнее, что нужно D Видео выступления Скотта Мейерса
- Критика C++, программирования и языковых тенденций 1990-х годов - 3-е издание , Ян Джойнер - 1996 г.