Привет, Хабр! Меня зовут Георгий Осипов. Я работаю в МГУ и компании Яндекс, а также в команде курса «Разработчик С++» Яндекс Практикума. В этой статье я поделюсь своими мыслями о том, почему немолодой язык С++ до сих пор не теряет актуальности.
Кажется, что первое доказательство — новость 2022 года, когда компания Google анонсировала новый язык Carbon. Он должен стать альтернативой C++. Первая версия Carbon выйдет только через 2-3 года, но уже сейчас понятно — если C++ языку ищут замену, значит, её нет.
Разберёмся, что же делает язык с 40-летней историей таким популярным и почему сегодня он только укрепляет позиции: в 2022 году C++ занял первое место среди быстрорастущих языков по версии TIOBE.
C++ и его стандарты
C++ проделал немалый путь. Родившись надстройкой над более простым языком C, он пережил несколько крупных обновлений, которые изменили его до неузнаваемости. Эти обновления сделали C++ современным языком, учитывающим новейшие тенденции программирования.
Новый Стандарт языка выходит каждые три года. Особенность в том, как именно принимаются изменения. Каждое нововведение проходит через обсуждения и голосования в международном комитете. В итоге в стандарт попадают только тщательно выверенные изменения.
Следующее крупное обновление запланировано уже на конец текущего года. Можно сказать, что C++ действительно отставал от некоторых современных языков в плане возможностей, но верно нагоняет их. Многие претензии, которые высказывали к C++, потеряли актуальность.
Рассмотрим некоторые претензии, которые часто предъявляются к C++
Претензия 1: C++ имеет слабую стандартную библиотеку
Отчасти эта претензия правомерна. Но ситуация улучшается.
Чтобы показать это, обратимся к другому популярному языку — Python. Рассмотрим одну из его замечательных возможностей — генератор списка (англ. list comprehension). Он позволяет одним выражением выбрать из списка все четные элементы и поделить их на два. Делается это так:
# смысл — положить в новый список x // 2 (половина x)
# для всех x из списка list, если x делится на 2
[x // 2 for x in list if x % 2 == 0]
Ещё несколько лет назад в C++ ничего подобного не было. Но сейчас можно использовать std::ranges:
namespace view = std::views;
auto even = [](int i) { return i % 2 == 0; };
auto half = [](int i) { return i / 2; };
auto range = view::all(list) |
view::filter(even) |
view::transform(half);
Немного сложнее, но смысл передаётся так же хорошо. Эта возможность была добавлена в стандартную библиотеку в 2020 году.
Как правило, Python не рассматривают в качестве конкурента C++, эти языки используются для разных целей. Но пример показывает, как растёт C++, впитывая лучшее из разных языков. Также в стандартной библиотеке появились средства для синхронизации потоков, работы с регулярными выражениями, календарём и часами, файловой системой, многопоточными алгоритмами.
Одна из самых ожидаемых возможностей C++ — работа с сетью. Сетевые приложения в C++ можно написать, только используя сторонние библиотеки. Комитет по стандартизации упорно работает, но пока не удаётся преодолеть все проблемы, чтобы построить идеальный сетевой фреймворк.
Претензия 2: в C++ много избыточных копирований
Объекты в программировании часто передаются, сохраняются, возвращаются.
Как сохраняет объект C++: копирует объект.
Как сохраняют объекты другие языки: сохраняют не сам объект, а ссылку на объект.
Для больших объектов операция копирования может быть очень затратной.
Такое поведение в C++ имеет свои причины. Главные из которых — использование стека и принципиальное отсутствие сборщика мусора. В C++ есть целый ряд техник, способных решить эту проблему. Это специальные оптимизации, ссылки, умные указатели, и самая интересная — перемещение.
Перемещение — это лёгкая операция, появившаяся в C++ в 2011 году, которая часто может заменить копирование. Перемещение применимо в случаях, когда нужна копия объекта, а оригинал больше не понадобится. Оказывается, это подавляющее большинство случаев.
class Schoolmates {
public:
Schoolmates(std::vector<Students> students)
// перемещаем вектор, вместо того, чтобы копировать его
: students_(std::move(students)) {}
private:
std::vector<Students> students_;
};
Перемещение в сочетании с умными указателями позволяет решить проблему копирований даже для объектов, не поддерживающих перемещение, делая претензию к C++ неактуальной.
Претензия 3: в C++ отсутствует сборщик мусора, приходится вручную управлять памятью
Претензия состоит из двух частей:
- В C++ отсутствует сборщик мусора — это правда.
- Приходится вручную управлять памятью — это неправда.
Современный программист на C++ имеет множество инструментов, позволяющих контролировать память автоматически. Среди них контейнеры, автоматические переменные и умные указатели. Они, конечно, имеют некоторые недостатки, но недостатки потенциального сборщика мусора сильно бы перевесили. Например, сборщик мусора иногда хочет «остановить мир» — заблокировать выполнение сразу всех потоков программы, чтобы безопасно собрать неиспользуемые объекты. Со сборщиком мусора была бы невозможна популярная идиома RAII — благодаря ей автоматически и в нужный момент выполняется освобождение ресурсов.
Например, рассмотрим логирующий класс для записи информации в файл на Python:
class Logger:
def __init__(self, file):
self.file_obj = open(file, "w")
def log(self, message):
current_time = datetime.datetime.now()
formatted_timestamp = current_time.strftime("%Y-%m-%d %H:%M:%S")
self.file_obj.write(f"{formatted_timestamp}: {message}\n")
def close(self):
self.file_obj.close()
А теперь рассмотрим похожий класс на C++:
class Logger {
public:
Logger(std::filesystem::path file_name) : file_obj(file_name) {
}
void log(std::string_view message) {
auto current_time = std::chrono::system_clock::now();
file_obj << std::format("{:%Y-%m-%d %H:%M:%S} {}\n", current_time, message);
}
private:
std::ofstream file_obj;
};
Главное отличие в методе close — в C++ он не нужен. В Python вы обязаны самостоятельно позаботиться о закрытии файла. Иначе он будет занимать системный идентификатор даже после того, как лог перестал использоваться, и до тех пор, пока сборщик мусора не решит удалить этот объект. А это может произойти относительно нескоро.
В C++ закрытие произойдёт автоматически, потому что момент удаления объекта строго определён. Это достигается благодаря отсутствию сборщика мусора.
Претензия 4: в C++ ужасный ввод-вывод
Действительно, в Python вы можете написать так:
print(f"x = {x}, y = {y}, x + y = {x + y}");
В C++ ничего подобного нет. Но если вы не в курсе последних новостей, то, вероятно, не знаете, что 38-летие язык отмечает с добавлением стандартной функции print, имеющей замечательные возможности:
std::print("x = {}, y = {}, x + y = {}", x, y, x + y);
Шаблон будет разобран на этапе компиляции. Ещё одно приятное дополнение — эта функция корректно работает с кодировкой UTF-8, а значит, консольные приложения наконец-то смогут выводить текст на русском или любом другом языке.
Претензия 5: в C++ вы потратите часы на поиск, подключение и сборку нужных зависимостей
Вероятно, это одна из самых серьёзных и обоснованных претензий. Нельзя написать что-то подобное команде pip install opencv-python и затем свободно использовать пакет opencv в любом проекте, просто импортировав его. Традиционный способ установки пакетов в C++:
- найти репозиторий,
- подобрать нужные настройки и конфигурировать пакет,
- собрать библиотеку,
- сделать библиотеку видимой в нужном проекте.
Такой способ не просто сложен. Главный недостаток — его неудобно автоматизировать. Придётся повторять все эти шаги при обновлении библиотеки.
Но и тут время не стоит на месте. В C++ появляются менеджеры пакетов. Например, conan. Он позволяет автоматизировать эти шаги. Но сам по себе требует определённой сноровки и в удобстве пока уступает pip. Однако даже в этой области ситуация постепенно улучшается.
Претензия 6: C++ сложен в изучении. Даже не беритесь за него, если у вас нет диплома в области математики или программирования
И снова претензия из двух частей:
- C++ сложен в изучении — это правда.
- Доступен только дипломированным специалистам — это неправда.
Опыт Яндекс Практикума показывает, что стать C++-разработчиком может каждый. Но стоит признать: порог входа в язык достаточно высок. C++ растёт и усложняется, и у этого процесса есть две стороны. Язык становится больше, и его труднее учить. С другой стороны, он становится выразительнее, многие основные операции описываются более наглядно и понятно.
Однако даже в сложности языка есть преимущество. Освоив C++ вам будет проще перейти на другой язык.
Вывод: претензии к С++ теряют актуальность, потому что язык всё время обновляется.
Сильные стороны С++:
Рассмотрим несколько особенностей C++, выделяющих его на фоне большинства других языков.
Нулевой оверхед
C++ славится высокой производительностью. Проверим, так ли он хорош. В качестве конкурента рассмотрим популярный язык Java. Но вместо того, чтобы сравнивать C++ и Java друг с другом, сравним эти два языка с самими собой.
Для примера возьмём популярную конструкцию — цикл по диапазону. Он позволяет обработать массив из множества элементов. Такой вид циклов есть во всех современных языках. Не стали исключением C++ и Java.
Рассмотрим цикл, который суммирует элементы массива. В C++ и Java он записывается одинаково:
// C++ #1
// Java #1
for(int i : array) {
sum += i;
}
Если отказаться от синтаксического сахара, то цикл можно переписать, используя более примитивные конструкции:
// C++ #2
for (int j = 0; j < array.size(); j++) {
sum += array[j];
}
// Java #2
for (int j = 0; j < array.size(); j++) {
sum += array.get(j);
}
Такой вариант сложнее писать и проще допустить ошибку. Но что с производительностью? Сравним производительность этих вариантов в каждом языке. Для Java возьмём следующий пример. Запустив его, увидим подобную картину:
Цикл по диапазону — 46 ms
Простой счётчик — 7 ms
Простой счётчик работает в 6 раз быстрее. Это говорит о том, что в Java за удобство нужно платить. Программисты в таких случаях говорят, что цикл по диапазону в Java имеет оверхед.
Теперь рассмотрим C++. Запустим бенчмарк на специальном сервисе. В нём мы добавили ещё два варианта и получили такой результат:
Все варианты цикла работают одинаково быстро!
Вот описание версий, которые мы сравнили:
- ForEach — цикл по диапазону. Аналог цикла, работающего медленно в Java.
- WithSize — простой цикл со счётчиком.
- WithIterators — сложный цикл, в котором вместо счётчика используются итераторы. Это то, как работает цикл по диапазону, но без синтаксического сахара.
- RawMemory — цикл, в котором используется прямой доступ к памяти. Ничего лишнего, он должен быть самым быстрым.
C++ смог свести весь оверхед к нулю и уравнять производительность самой сложной и самой простой версий. Удивительно, что сложные операции создания и перемещения итераторов ничего не стоят.
Секрет в мощном оптимизаторе. C++ выполняет огромное количество вычислений на этапе компиляции. При этом он разворачивает функции, убирает лишние переменные и вычисления.
В результате сложные и элегантные языковые конструкции при компиляции превращаются в простой и эффективный машинный код. В этом сила C++.
Шаблонное программирование
Сильные стороны C++ — вычисления времени компиляции и шаблонное программирование. Чем больше вычислит компилятор, тем меньше придётся вычислять у пользователя, тем быстрее будет работать программа.
Шаблонное программирование позволяет использовать общий код — шаблон — который может настраиваться на разные параметры. Это не только удобно, но и крайне эффективно: будет генерироваться очень быстрый машинный код, учитывающий специфику конкретного случая.
Один и тот же шаблон может сгенерировать несколько вариантов кода, оптимально работающих в разных ситуациях
Настоящие чудеса начинаются, когда мы используем функциональный объект как параметр шаблона. Рассмотрим пример. Два варианта одного и того же цикла на Python:
import time
# запускаем обычный цикл
million = range(1000000)
target = []
start_time = time.time()
for x in million:
if x % 2 == 0:
target.append(x // 2)
elapsed_time = time.time() - start_time
print(f"Loop time: {elapsed_time:.6f} seconds")
# используем list comprehension с функциями
million = range(1000000)
start_time = time.time()
even = lambda x: x % 2 == 0
half = lambda x: x // 2
target = [half(x) for x in million if even(x)]
elapsed_time = time.time() - start_time
print(f"Comprehension time: {elapsed_time:.6f} seconds")
В первом варианте мы используем обычный цикл. Во втором list comprehension в сочетании с лямбда-функциями. Python-программист, скорее всего, сразу скажет: разумеется, второй вариант будет работать дольше, ведь вызов функции — дорогая операция! И окажется прав. Запустим этот код и увидим подобный результат:
Loop time: 0.119847 seconds
Comprehension time: 0.179655 seconds
C++ не исключение. В нём тоже вызов функции требует накладных расходов. Но попробуем запустить бенчмарк, аналогичный примеру из Python’а. Удивительно, но версия, миллион раз вызывающая функции, не уступает варианту без них. Дело в том, что C++ полностью исключил вызовы функций при оптимизации. И это стало возможно именно благодаря шаблонной магии. Компилятор выполнял настройку автоматически выведенных типов в таком выражении:
view::filter(even) |
view::transform(half)
Каждая из этих функций возвращает сложный обобщённый объект из библиотеки ranges. Затем эти объекты комбинируются операцией “|”. При этом компилятор знает, какие три действия выполняются, и даже то, что именно делают вызываемые лямбда-функции. Благодаря этому стала возможной комплексная оптимизация. Таким образом, из трёх отдельных операций и двух лямбда-функций компилятор чудесным образом построил код, вообще не имеющий вызовов функций.
Заключение
Мы не преследовали цель показать, что C++ лучше Python или C++ лучше Java. Каждый из языков имеет свою нишу и своё назначение. C++ пока что не превзойдён во многих отраслях, особенно требующих высокопроизводительных вычислений. У него появляются конкуренты: такие языки, как Go, Rust, тоже способны показывать высокую производительность. Но C++ растёт высокими темпами и осваивает новые рубежи. Например, компания Яндекс активно развивает фреймворк userver и переводит на C++ свои сервисы. Всё это позволяет сделать вывод: C++ хоть и старый язык, но вовсе не устаревший.
Комментарии (306)
benjik
05.09.2023 12:46+17П.5, если вдруг не повезло с conan, то вот вам гайд https://www.reddit.com/r/cpp/comments/ix9n1u/why_is_it_such_an_abysmal_pain_to_use_libraries/?utm_source=share&utm_medium=web2x&context=3
eao197
05.09.2023 12:46+1П.5, если вдруг не повезло с conan, то вот вам гайд
То перед следованием гайду еще хорошо бы попробовать vcpkg ;) Вполне может повести.
Polarisru
05.09.2023 12:46+39namespace view = std::views; auto even = [](int i) { return i % 2 == 0; }; auto half = [](int i) { return i / 2; }; auto range = view::all(list) | view::filter(even) | view::transform(half);
Немного сложнее, но смысл передаётся так же хорошо.
Вы издеваетесь??
eao197
05.09.2023 12:46+15Так у нас, у C++ников, собственное представление о прекрасном!
А вы взяли и сразу с козырей зашли... Хорошо же сидели, ну в самом-то деле ;)Ktator
05.09.2023 12:46+43Так у нас, у C++ников, собственное представление о прекрасном!
Именно так!
MountainGoat
05.09.2023 12:46+7Мне ещё нравится "QTextStream::allocate() can not be used as a function", что совершенно очевидно означает, что у вас в проекте две разные зависимости тянут две разные версии библиотеки CGAL.
anz
05.09.2023 12:46+2самое ужасное, ни один разработчик IDE так и не додумался фолдить куски этой тарабарщины и хоть как-то подсвечивать синтаксис... Наверняка еще и эвристик можно накрутить, чтобы хоть как-то на человеческий язык переводить. Ах ну да, я забыл что плюсовики должны перманентно страдать
AnimeSlave
05.09.2023 12:46Есть питоновские скрипты для вывода ошибок, но тут уже не понятно почему не добавляют «адекватности», то ли потому, что питон, то ли потому, что мазохизм
Kelbon
05.09.2023 12:46+3Этот код понятнее чем код питона
P.S. views::all тут лишнее
Boilerplate
05.09.2023 12:46+14С# LINQ:
collection.Where(i => i % 2 == 0).Select(i => i / 2)
если не нужны ленивые вычисления, то добавим
collection.Where(i => i % 2 == 0).Select(i => i / 2).ToList();
После такой работы со множествами, хочется плакать в других языках.
andreymal
05.09.2023 12:46+4Rust:
let result: Vec<_> = collection .into_iter() .filter(|i| i % 2 == 0) .map(|i| i / 2) .collect();
Kelbon
05.09.2023 12:46-4в расте нет реальных итераторов, это всё крайне ограниченные примеры работающие только для интов
andreymal
05.09.2023 12:46Можно пример того, что по-вашему не работает?
Kelbon
05.09.2023 12:46+1сортировка на чём то кроме span, да и вообще в расте +- всё завязано либо на input итераторы, либо на span (слайс по растовому), все операции определены только для них, и то дублируются в разных контейнерах(коллекциях), что делает невыразимым громадное количество вещей, таких как поиск в мапе и возвращение не option, а итератора
И это прямое следствие того как устроены в расте дженерики, язык не способен выразить то что от него требуется(а то что выражено - крайне криво и на костылях, вплоть до того что в стандартной библиотеке раста используются фичи раста, которых нет в других частях собственно языка)
P.S. посмотрите на количество реализаций Sum, фактически на каждый тип макросом/вручную раскрыта реализация, это не один шаблон функции как в С++, а вот такой вот ужас
andreymal
05.09.2023 12:46+1Что-то я не понял проблемы. Слайсы универсальны для любых контейнеров и их можно сортировать. Итераторы универсальны для любых контейнеров и их можно возвращать при условии, что объект, на который ссылается итератор, будет продолжать где-то храниться (или вы хотите владеющие итераторы?). Поиск по мапе тоже прекрасно работает.
Можно конкретный пример того, что по-вашему невозможно или неприемлемо сложно сделать в Rust?
Kelbon
05.09.2023 12:46Я привёл 2 конкретных примера,
1. сортировка того что не является слайсом(например вью на Vec которое reversed или deque из С++, которого в расте нет, но убогость коллекций в расте это отдельная проблема, хоть это и вытекает из невыразительности языка)возвращение итератора на объект из мапы, чтобы второй раз не искать его
auto it = map.find(key); if (it != map.end()) use(*it); VS // 2 поиска в мапе if (map.contains(key)) use(map.find(key));
Это реальный итератор, то что в расте - не итератор, он не способен указывать на какой-то объект,
andreymal
05.09.2023 12:46+1Кажется, я понял - вам не нравится отсутствие так называемых "random access iterators"? Если так, то хорошо (хотя по личному опыту я прекрасно обхожусь без них)
andreymal
05.09.2023 12:46+2Хотя, немного погуглив, я обнаружил, что
std::iter::RandomAccessIterator
раньше существовал, но был удалён, потому что оказался никому не нужен.В связи с чем назревает вопрос — а действительно ли он нужен?
1.1. Действительно ли есть необходимость сортировать что-то, что не является слайсом? В реальном проекте я просто сделаю что-то вроде
v.sort_by(|a, b| b.cmp(a))
, и я ещё не встречал ситуаций, когда этого оказалось бы недостаточно.1.2. Действительно ли есть необходимость сортировать deque? Я иногда использовал deque для создания, ну, собственно очереди, и мне никогда не приходило в голову её сортировать, потому что зачем?
2. Зачем из мапы возвращать итератор на объект, когда можно возвращать сам объект (или ссылку на него)?
if let Some(it) = map.get(&key) { // Один поиск, в "it" ссылка на объект use_it(it) }
Есть ли задачи, которые жизненно необходимо решать именно через random access iterators и никак иначе?
Kelbon
05.09.2023 12:46+4Зачем из мапы возвращать итератор на объект, когда можно возвращать сам объект (или ссылку на него)?
чтобы не искать его второй раз, когда захочется вставить значение или сделать ещё миллиард операций которые может захотется сделать с мапой, например итерация от этого места до конца
Действительно ли есть необходимость сортировать deque?
именно так и отвечают последователи раста - а нам и не надо. Т.е. не надо работать никогда ни с чем, кроме как с последовательной памятью, отлично
Действительно ли есть необходимость сортировать что-то, что не является слайсом?
спросите тех кто делает крейты для сортировки таких последовательностей. В расте это делается "просто" - делают вектор индексов, его сортируют, потом расставляют по индексам получившимся элементы. Это очевидно имеет ограничения и невероятный оверхед
И главное: в С++ можно написать один шаблон функции, который будет просто работать и который можно дальше специализировать. Например в С++17 добавили новый вид ренжей - сделали для них более эффективную реализацию не меняя интерфейс или просто реализация захотела более эффективную сортировку интов - добавили не меняя интерфейс.
В расте такое невозможно из-за того как дженерики работают, поэтому там есть только слайс и операции на нём, т.к. не смотря на то что на компиляции всё известно, компилятор раста не способен ничего сказать о пришедшем типе, например проверить что он удовлетворяет требованию Х (в С++ можно проверить практически что угодно и на этой основе решить как поступать)
andreymal
05.09.2023 12:46+2чтобы не искать его второй раз, когда захочется вставить значение
А зачем, если его можно заменить через ту же самую ссылку
it
?итерация от этого места до конца
А вот это уже интереснее, тут я действительно могу представить какую-нибудь задачу
не надо работать никогда ни с чем, кроме как с последовательной памятью
Ну, простите, покажите когда надо?
делают вектор индексов, его сортируют, потом расставляют по индексам получившимся элементы
Ещё раз простите, но, блин, зачем? Чаще всего коллекция, которую нужно отсортировать, изначально и является вектором. Можно пример коллекции, которая должна НЕ являться вектором и её жизненно необходимо сортировать для решения задачи?
новый вид ренжей [...] В расте такое невозможно из-за того как дженерики работают
Это было бы возможно, если бы ренж реализовали как трейт. Почему так не сделали — не знаю (возможно, потому что, опять простите меня, не нужно?), но если сильно захотеть, то принципиальных ограничений вроде бы не вижу
Впрочем, в Rust уже существует какой-то
std::ops::RangeBounds
, но почему-то не особо применяется — возможно, потому для полноценной работы требуется включать найтли-фичуassociated_type_bounds
, но раз она в принципе существует, то можно понадеяться, что в будущих версиях ренж допилятпроверить что он удовлетворяет требованию Х
Именно для этого в Rust и существуют трейты. Я чего-то не понимаю?
Kelbon
05.09.2023 12:46+5А зачем, если его можно заменить через ту же самую ссылку
it
?ссылка теряет информацию о позиции элемента в контейнере
Ну, простите, покажите когда надо?
практически любой контейнер кроме вектора это не последовательная память, т.е. постоянно надо
принципиальных ограничений вроде бы не вижу
а они есть
Всё в трейтах не выразишь, потому что они кривые и невыразительные
void func(auto x) { if constexpr (fooable<x>) { x.foo(); } else { x.bar(); } }
Вот это уже невыразимо, в расте нужно написать требование на Х в сигнатуре, при этом || в системе трейтов раста не существует
andreymal
05.09.2023 12:46+1ссылка теряет информацию о позиции элемента в контейнере
И что? Пусть теряет, мне не жалко. На протяжении всей этой ветки вы не завершаете свои мысли и не поясняете, какие реальные задачи это мешает решить, из-за чего мне очень тяжело вас понимать.
практически любой контейнер кроме вектора это не последовательная память, т.е. постоянно надо
Когда «постоянно» надо? Вы опять не завершили свою мысль. Из других контейнеров помимо вектора и deque я могу вспомнить разве что BTreeMap и «печально известный» linked list, но первый отсортирован по определению и позволяет итероваться по себе (хотя и не на уровне random access iterator), а второй вообще непонятно зачем нужен
Вот это уже невыразимо
Понял (вот тут держите от меня плюс)
Kelbon
05.09.2023 12:46+3И что? Пусть теряет, мне не жалко.
весь смысл в том чтобы информацию не терять и дважды/трижды не искать. Именно поэтому лист в расте такой бесполезный - на нём нельзя сделать ни одной операции листа.
Вставка в середину? Ищи за O(N)
Убрать из середины? Ищи за O(N)Потому что нет понятия итератор, чтобы можно было взять этот самый итератор и по нему вставить до / после, удалить элемент и тд
Из других контейнеров помимо вектора и deque
контейнеры бывают не только в стандартной библиотеке
И контейнеры лишь частные случаи ренжей, я уже приводил пример
void foo(auto rng) { std::ranges::sort(rng); } foo(std::vector<int>{}); // работает, contiguous range foo(std::vector<int>{} | reverse); // работает, но уже рандом аксес
В расте второе уже сломается, а таких ситуаций - бесконечное множество
andreymal
05.09.2023 12:46+1контейнеры бывают не только в стандартной библиотеке
Это понятно, но вопрос никуда не делся — зачем? Вот эти все примеры с контейнерами и итераторами, отсутствие которых в Rust вас не радует, мне не были нужны ни в одной из тех задач, которые мне приходилось решать. Наверное, у меня просто не возникало таких задач, в которых это было бы необходимо, но из-за того, что вы опять не завершили свою мысль и продолжаете увиливать от описания реальных задач, а не выдуманных синтетических примеров, я продолжаю вас не понимать
Chaos_Optima
05.09.2023 12:46+2Отвечу за него, например есть такая область как геймдев и компьютерная графика, где кроме стандартных векторов, есть дохрена других контейнеров по которым нужно итерироватся, например octree, BVH, sparse vectors и куча куча других, по которым нужно эффективно итерироватся. Не говоря уже о куче разных графов, типа графа сцены, графа материалов, графа поведения, графа смешивания анимаций и т.д а раст + графы == боль.
andreymal
05.09.2023 12:46Эх, всё-таки стоит когда-нибудь написать свой игровой движок для общего развития
domix32
05.09.2023 12:46impl Iter for Octree { fn next() -> Option<Self> {} }
Ничто не мешает вам сделать так. Ровно то же самое что придётся сделать и в плюсах, разве что только на самой структуре. Для графов есть Box/RefCell. Тот же Bevy вполне успешно работает с графом сцены и прочими. Самое забавное, что для того чтобы иметь нормальную производительность для больших игр вам придётся приводить все эти графы к некоторому линейному виду и не сильно принципиально на каком языке вы это делаете. Без совсем сырых указателей конечно больно, но Rust именно для этого и был придуман.
DarthVictor
05.09.2023 12:46А если перед использованием этой информации из итератора в контейнер что-нибудь положат?
ZirakZigil
05.09.2023 12:46Зависит от контейнера. Для каких-то (как map) ничего страшного не произойдёт, для других (как vector) отвалятся итераторы после места вставки, для третьих (как deque) отвалятся все.
domix32
05.09.2023 12:46Грубо говоря compile time выражения включая трейты пока ещё в процессе, это верно, но оно и в плюсах не сказать чтобы давно появилось. Можно конечно попробовать такое на макросах провернуть, но в среднем композиция была выбрана чтобы избежать подобной ереси как выше.
domix32
05.09.2023 12:46Всё ещё не вижу проблемы. `.entry` делает ровно это же
map .entry(key) .and_modify(use)
вью на Vec которое reversed или deque из С++
vec.into_iter().rev().map(use)
Или я чего-то не понимаю?
Kelbon
05.09.2023 12:46entry это костыль и дополнительная сущность, которая пытается покрыть как раз описанные проблемы, но как с entry найти элемент и потом от него до конца проитерироваться, например? Или сделать любую другую операцию, которую не предусмотрели те кто писал entry?
Итератор это хорошая абстракция, которая позволяет делать невероятно много, не добавляя лишних сущностей(таких как entry)
vec.into_iter().rev().map(use)
Или я чего-то не понимаю?
Как это теперь отсортировать? Это же не слайс
domix32
05.09.2023 12:46-2Без превращения в линейный кусок памяти - никак. То есть
.collect::<Vec<_>>().sort_by(sorter).
Ровно также будет вести себя сортировка и в плюсах.
KanuTaH
05.09.2023 12:46+2Ровно также будет вести себя сортировка и в плюсах.
В смысле? Тот же
std::sort()
в плюсах работает со стандартной парой итераторов, указывающих на начало и конец сортируемого диапазона, и ему пофигу как то, что он сортирует, расположено в памяти. Как и практически всем стандартным алгоритмам из std - они работают с итераторами.
domix32
05.09.2023 12:46Строго говоря в Rust стандартные сортировки сейчас гвоздями прибиты к вектору/слайсу. Также как и по всей стандартной либе есть несколько вариантов map/flat_map и прочие проблемы в том же духе. Ничто не мешает написать собственную функцию
`sort(impl Iterator)`,
и сортировать как хочется. Но утверждать при этом, что итераторы у раста какие-то ненастоящие как минимум странно. Ну, а отсутвие генерик сортировщиков и прочих обобщённых алгоритмов в стандартной библиотеке компенсируется экосистемой крейтов.
andreymal
05.09.2023 12:46+1Даже если переписать эту сигнатуру в более «реалистичном» виде
fn sort<T: PartialOrd>(iter: impl Iterator<Item = T>)
я что-то не очень представляю, как с её помощью написать сортировку, кроме как скопировать/склонировать все элементы в новый Vec. А если элементы не позволяют себя клонировать, то не представляю вообще хоть как-нибудь
domix32
05.09.2023 12:46Вот, например, топологическую сортировку делают. Для перемещения можно использовать std::mem::swap или std::mem::replace
andreymal
05.09.2023 12:46По ссылке я не вижу итератора и вижу копирование чего-то в свежевыделенный вектор.
swap требует передать в неё две мутабельных ссылки - как мне одновременно взять два мутабельных элемента из одного итератора и не получить отлуп в стиле "already borrowed as mutable"?
Kelbon
05.09.2023 12:46+2а вот и нет, в С++ сортировка будет работать, потому что она не требует последовательной памяти
Другие примеры подобных ренжей это
iota(n, m) и все её модификации-последовательность чисел от n до m, которая в памяти не лежит и соответственно слайс там не взять
stride - например 1 3 5 7, последовательность с пропусками, тоже память не последовательная. В случае reverse кстати память поледовательная, но не выполняется требование что ++ итератора даст следующий элемент(он даст предыдущий)
Или у вас есть 2 вектора A и B, которые ведут себя как один вектор struct { A, B } (это может быть flat map или zip(rng1, rng2 к примеру)
Ну и ещё таких примеров масса, со всеми ними раст работать откажется
domix32
05.09.2023 12:46+1iota(n, m) ->
(n..m)
, с баундчеком n < m.stride -> iter::step_by
reverse -> iter::rev
flat map или zip(rng1, rng2 к примеру)
Есть и zip и map/flat_map, да ещё и в парарллель. Причём буквально с релиза в 2015. В то время как простой
string_view
появился в 17 стандарте, а прочие view только в 21, да и те не все. Кстати не без влияния ржавчины.Так что вас ограничивает только отсутвие функций в стандартной библиотеке, принимающих итераторы на вход - сортеров, миксеров, траверсеров и прочих алгоритмов. Для этого люди пишут отдельные крейты, да.
Kelbon
05.09.2023 12:46А причём тут это вообще? Да, в раст прямо в язык затащили n..m, ограничив его только для интов.
В С++ эта штука работает для чего угодно, у чего есть оператор ++ (например те же итераторы) и реализовано на уровне библиотеки, а не языка
И речь была про другое - что это всё random access ренжи, но на них нельзя взять слайс и соответственно сортировать(в расте)
И нет, это не фиксится на уровне библиотек, во первых потому что это должно быть в стандарте(чтобы все знали что есть такое и реализовывали интерфейс для этих итераторов), во вторых потому что в расте это неосуществимо из-за того как там работает unsafe и ссылки
DarkEld3r
05.09.2023 12:46n..m, ограничив его только для интов.
Это не совсем так. В найтли доступен трейт
Step
.
Kelbon
05.09.2023 12:46+1Слайсы универсальны для любых контейнеров
буквально для одного контейнера они "универсальны", они не могут представить ничего кроме последовательной памяти(массива)
andreymal
05.09.2023 12:46-2фактически на каждый тип макросом/вручную раскрыта реализация
Макросом — это не вручную, никакого ужаса тут нет. Вот тут уже держите меня минус за дезинформацию
Expurple
05.09.2023 12:46+5Судя по этому треду,
BidirectionalIterator
в расте выразим, но только по immutably borrowed элементам. Почему его нет в стандартной библиотеке, я не знаю. А по мутабельным элементам нельзя by design, в соответствии с правилами borrow checker'а:The reason is simply aliasing vs mutability.
Iterator
is designed so that you can retain references to the elements that it yielded, so that if you can go back and forth you would be able to have multiple references to the same element (aka: aliasing). Now, imagine that the references in question are mutable: you now have multiple mutable references to the same element, BOOM. So, since yielding mutable references is desirable, theIterator
trait has given up aliasing.Насчёт реализаций
Sum
. В плюсах нетstd::sum()
, так что предолагаю, что выSum::sum()
сравниваете сstd::accumulate()
. Это не совсем корректно, потому что последняя принимает начальное значение и ней не нужно ничего знать про 0. На расте для такого тоже тривиально пишется единственная реализация на трейтах:use std::iter::Iterator; use std::ops::Add; pub fn accumulate<T, I>(it: I, init: T) -> T where T: Add<Output=T>, I: Iterator<Item=T> { it.fold(init, Add::add) }
Посмотрел исходники
sum()
, по сути там три реализации (вручную написанных макроса): для интов, для флоатов и дляSimd
. Отличаются они в основном тем, как задаётся изначальное значение:$zero
,0.0
илиSimd::splat(0 as $type)
. В расте нет неявных приведений типов как в плюсах, так что нельзя везде написать просто0
. Честно, не знаю, почему они не завезли в стандартную библиотеку что-то типа num::traits::Zero и не сделали на трейтах вместо макросов. Не вижу для этого особых преград со стороны языка.Kelbon
05.09.2023 12:46Ужас в том, что для компилятора каждая написанная макросом реализация - это хуже чем написанная вручную, её ещё раскрывать надо
Т.е. в С++ будет шаблон, который инстанцируется ТОЛЬКО если в коде пригодился шаблон для этих типов
А в расте компилятор даже если вы не используете эти SIMD<i32, 64 8 43 53> всё равно будет генерировать код, замедлять компиляцию и тд(а компиляция и без этого в расте медленнее чем в С++ сильно)
В общем получается, что в С++ сложность компиляции O(N) где N это количество вашего кода, а в расте O(M) где M это количество кода на расте в мире(т.к. для каждого типа всё раскрывается каждый раз)Expurple
05.09.2023 12:46+3Генерировать код эти реализации не будут, потому что они всё ещё обобщённые по Iterator. Пользователь их будет инстанцировать только для конкретных используемых пар Iterator и T.
Вы правы, что даже эти обобщённые реализации требуют время компилятора (на раскрытие макросов и проверку типов). Но есть важный нюанс: это актуально, только если вы сами собираете стандартную библиотеку раста. Обычно она уже собранная. Так что на пользователе это никак не отражается. Наоборот, за него уже сделана вся работа по тайпчекингу тела шаблона. А тело плюсового шаблона будет тайпчекаться для каждого инстанцирования, и при большом количестве инстанцирований это будет медленнее. Тайпчекнутые шаблоны рулят. Тело шаблона должно быть корректным для любого инстанцирования. Надо сразу прописывать нужные constraints в сигнатуре шаблона. Substitution failure is an error! В расте ошибка инстанцирования это обычная ошибка типов в месте инстанцирования, а не монструозная непонятная простыня куда-то внутрь шаблона. В плюсах только в 20 стандарте с концептами взялись за эту проблему
Kelbon
05.09.2023 12:46Это миф, инстанцировать раст дженерик не легче чем С++ шаблон, там также нужно всё проверить(и даже больше), никакая часть работы не переносится в некий "тайпчекинг"
Доказательство этому элементарное - у этих функций разный генерируемый ассемблер, т.е. для каждого набора аргументов таки нужно сделать разную функцию/тип
Expurple
05.09.2023 12:46+2Про кодогенерацию я ничего не говорил. Там работы одинаково много в обоих языках. Но про "всё проверить" вы объективно неправду говорите сейчас. Проверяется только, соответствуют ли входные типы констрейнтам и соотносятся ли у этих типов лайфтаймы как надо. За счёт проверки лайфтаймов работы на этом этапе больше чем в плюсах, тут вы правы. Но типы и лайфтаймы в теле шаблона и вызываемых им шаблонах повторно не проверяются. Это абсолютно точно. Если не согласны, аргументируйте с хоть одним примером ошибки инстанцирования в расте, которая происходит в теле шаблона.
Kelbon
05.09.2023 12:46Но типы и лайфтаймы в теле шаблона и вызываемых им шаблонах повторно не проверяются
они и в С++ не проверяются дважды, просто когда что то ломается это ошибка компиляции
Expurple
05.09.2023 12:46Объясните пожалуйста, как по-вашему компилятор может во время инстанцирования выявить и кинуть ошибку типов, не выполняя собственно проверку типов?
Kelbon
05.09.2023 12:46+1Я докажу вам что компилятор раста ничего заранее не проверяет приведя контрпример:
struct A<T>(T); pub fn foo<T>(x:T) { if false { return } foo(A(x)); } pub fn main() { foo(5); // если закомментировать, то код компилируется }
Комментируете foo(5) - код компилируется.
То есть только на этапе подстановки реально проверяется что происходит и код "тайпчекается"
Что и требовалось доказать
Более того, если сделать if true { return; }, то код ВНЕЗАПНО тоже компилируется, что говорит о том что компилятор просто проигнорировал что идёт дальше, а это либо баг компилятора, либо очередная невероятная глупость разработчиков языка
Expurple
05.09.2023 12:46+1Я докажу вам что компилятор раста ничего заранее не проверяет
Вы доказали только то, что он не проверяет возможность получить бесконечный рекурсивный тип. Это интересный кейс, и да, я пересмотрел своё прошлое мнение, что в расте не бывает ошибок внутри инстанцирования. Мой обновлённый тейк: в расте не бывает ошибок внутри инстанцирования, связанных с несоответствием лайфтайму или констрейнту (а это большинство ошибок с дженериками).
только на этапе подстановки реально проверяется что происходит и код "тайпчекается"
Код просто подставляется, не тайпчекаясь. Ошибка, которую выдаёт ваш пример, это не ошибка проверки типов, а ошибка рекурсии при выполнении "тупой" безусловной подстановки. Хотя разумеется можно сказать, что к ней приводит дыра в системе типов или их проверке при объявлении шаблона. Возможно, потом починят.
Kelbon
05.09.2023 12:46+1В С++ есть 2 стадии подстановки:
1. читаем шаблон, проверяем синтаксис и не dependent вещи, "связываем" то что не dependent сразукогда подставлены типы, происходит просто подтановка, никакой дополнительной "проверки" там не надо, ошибка выходит сама по себе, если подстановка невалидна, в расте это также. Т.е. невозможно подставлять не делая проверку, это неотделимые вещи
В расте абсолютно то же самое, только есть ограничения глупые от языка, такие что например нельзя сделать foo<I + 1>, потому что а хрен знает почему, спросите разработчиков языка
Раст делает всё возможное, чтобы не существовало dependent типов, типы выражений и типы значений вынесены на уровень грамматики (например *x это всегда lvalue в расте)
А если нет dependent типов, то и проверять собственно нечего, но это не говорит о том что в расте хорошо, это говорит о том что раст забирает возможности писать код
Expurple
05.09.2023 12:46Объясните пожалуйста, как по-вашему компилятор может во время инстанцирования выявить и кинуть ошибку типов, не выполняя собственно проверку типов?
RubberDuck315
05.09.2023 12:46Пусть меня поправят более опытные люди, но в питоне это будет выглядеть как-то так:
r = (i / 2 for i in list if i % 2 == 0)
По-моему, всё выглядит понятно. Ну или я уж слишком сильно привык к питону..
domix32
05.09.2023 12:46как минимум код ругнётся на то что list - это такое ключевое слово.Ну и в вашем случае будет генератор, который станет бесполезен после использования. Если цепочка трансформаций вырастает, то кода будет заметно больше. Можно ли склонировать генератор, чтобы использовать его в нескольких независимых вещах это отдельный вопрос.
RubberDuck315
05.09.2023 12:46Да знаю я, что list - это ключевое слово, но сил хватило только заменить range на r. А насчёт генератора, насколько я понимаю исходный пример на C++, там результат
auto range
- это тоже что-то вроде генератора, а не список или вектор.А вообще, каюсь, я написал тот комментарий, ещё не читав саму статью. Сообственно, в статье есть код на Питоне, очень похожий на мой:
# смысл — положить в новый список x // 2 (половина x) # для всех x из списка list, если x делится на 2 [x // 2 for x in list if x % 2 == 0]
Только деление на 2 чуть другое и на выходе - список, а не генератор.
Breathe_the_pressure
05.09.2023 12:46+1Не хочу устраивать холивары, но если язык не устаревает, может синтаксис как-то поправить на попроще? Ну чтобы избежать таких крокодилов "
std::chrono::system_clock::now()" например.
Kelbon
05.09.2023 12:46+11Это не часть синтаксиса, это неймспейсы и типы, назвать их можно как угодно, если захочется
namespace time = std::chrono; using clock = time::system_clock; clock::now();
danilshvalov
05.09.2023 12:46+6Легко!
using sclock = std::chrono::system_clock; sclock::now();
bak
05.09.2023 12:46+5А я не хочу называть "как угодно" я хочу просто напистать
import time
print(time.time())
А не вспоминать каждый раз какой лютейший треш напридумывали обкуренные создатели chrono.
eao197
05.09.2023 12:46+4А не вспоминать каждый раз какой лютейший треш напридумывали обкуренные создатели chrono.
Пространства имен существеннейшим образом облегчают жизнь в больших проектах. И там глубина вложенности, да и сами названия пространств имен, будут гораздо круче, чем в случае std::chrono. C++ные пространства имен позволяют с этим жить не просто нормально, более чем хорошо.
Так что претензии к синтаксису C++ есть, но вот с конкретным примером вы знатно промахнулись.
bak
05.09.2023 12:46-6А нечего тянуть в глобальный скоуп всё подряд по дефолту. Ещё одно не удобное плюсовое решение.
PS. Неймспейсы это костыль который вставили вместо того чтобы сделать по нормальному.
eao197
05.09.2023 12:46А нечего тянуть в глобальный скоуп всё подряд по дефолту.
И что же такое "всё подряд" в C++ в глобальный скоуп тянется по дефолту?
PS. Неймспейсы это костыль который вставили вместо того чтобы сделать по нормальному.
По-нормальному это как модули в Python-е?
bak
05.09.2023 12:46+4Всё подряд - означает что все определения из h файла будут подтянуты в глобальный скоуп по дефолту.
По нормальному - как в питоне или в расте, когда нужно явно указывать что именно тащится из модуля (или обращаться через модуль).
eao197
05.09.2023 12:46+2Всё подряд - означает что все определения из h файла будут подтянуты в глобальный скоуп по дефолту.
Так ведь благодаря наличию пространств имен в глобальный скоуп ничего не подтягивается. Условный
import time
в C++ реализуется двумя строчками вместо одной:#include <my_time> using namespace my_time;
При этом как раз синтаксис здесь вообще ни при чем. В выражении
std::chrono::system_clock::now()
элементами синтаксиса являются разве что::
и это не сильно хуже, чем какой-нибудьorg.chrono.system_clock.now()
.bak
05.09.2023 12:46-5Так ведь благодаря наличию пространств имен в глобальный скоуп ничего не подтягивается.
У вас проблемы с причинно-следственной связью. Из за того что в глобальный скоуп всё подтягивается приходится использовать неймспейсы чтобы этого избежать.
Условный
import time
в C++ реализуется двумя строчками вместо однойМне нужен конкретный time а не условный которого в C++ нету. Как нету и целой кучи других нормальных вещей которые на плюсах сделать можно немного включив мозги и подумав о том кто будет этим пользоваться. Вместо этого разработчики стандарта выдают решения которые напоминают наркоманский бред.
При этом как раз синтаксис здесь вообще ни при чем.
Я про синтакис ничего и не писал, в данном конкретном случае виноват не синтаксис а в первую очередь ущербный модуль chrono (и во вторую очередь организация файлов / модулей / кода).
eao197
05.09.2023 12:46+1Из за того что в глобальный скоуп всё подтягивается приходится использовать неймспейсы чтобы этого избежать.
Вы, похоже, не знаете как работает include. Это простая текстовая подстановка в место, в котором include задействовали. Поэтому ни про какой глобальный скоуп include не знает. Например, пусть у нас есть файл a.ipp вида:
int a; struct demo { int b; };
и файл main.cpp, в котором мы делаем include этого a.ipp:
// Содержимое появится в глобальном скоупе. #include "a.ipp" void f() { // Содержимое появится в скоупе функции f. #include "a.ipp" } namespace my { // Содержимое появится в скоупе пространства имен my. #include "a.ipp" class outer { // Содержимое появится в скоупе класса outer. #include "a.ipp" }; } ...
Да, пространства имен как раз сделаны для того, чтобы не захламлять гобальный скоуп. Но с момента их появления (а это более 30 лет назад), про захламление глобального скоупа (или автоматического подтягивания чего-либо в глобальный скоуп) говорить уже не приходится.
Мне нужен конкретный time а не условный которого в C++ нету.
Вообще-то есть. Вам просто не нравится то, что он лежит не прямо в std::
И, если я вас правильно понял, если бы в Python-е в модуле time лежал бы класс Clocks, внутри которого бы лежал класс System, внутри которого уже был бы метод time, и вызов приходилось бы делать какClocks.System.time()
, то у вас были бы такие же претензии и к Python-у. Не так ли?Я про синтакис ничего и не писал
Ой, да ладно, кому вы рассказываете:
но если язык не устаревает, может синтаксис как-то поправить на попроще? Ну чтобы избежать таких крокодилов "std::chrono::system_clock::now()"
bak
05.09.2023 12:46+1Ой, да ладно, кому вы рассказываете
Может вы для начала проверите кто автор этой цитаты?
Clocks.System.time()
Так ещё нормально. Мои претензии вот к такому:
Std.Chrono.DurationCast(Std.Chrono.Seconds)(Std.Chrono.SystemClock.Now().TimeSinceEpoch())
eao197
05.09.2023 12:46Может вы для начала проверите кто автор этой цитаты?
А зачем, если вы поддержали предыдущего оратора?
AnimeSlave
05.09.2023 12:46+1А нечего тянуть в глобальный скоуп всё подряд по дефолту. Ещё одно не удобное плюсовое решение.
Это наследство языка C. Вы явно не программист на C++, раз пишете такое. В те времена, когда эти языки появились не было тех проблем, что сейчас пытаются решить в других языках. Тут как бы стоит вообще сделать ремарку, что если бы не именованные пространства C++ был бы ещё хуже C в плане удобства написания больших проектов. Именованные пространства как раз спасают. А в связке с псевдонимами, еще и упрощают написание кода
Ritan
05.09.2023 12:46+3А теперь обратитесь в питоне к таймеру высокого разрешения. Не подсматривая в документацию
bak
05.09.2023 12:46В питоне это
time.time_ns()
- в документацию я всё же посмотрел, но я одинаково не помню что в плюсах что в питоне. Для сравнения в плюсах это:typedef std::chrono::high_resolution_clock Clock;
auto t1 = Clock::now();
danilshvalov
05.09.2023 12:46+2Так тоже можно, если очень хочется. Создаем где-нибудь в проекте файл
time.hpp
:#pragma once #include <chrono> inline int64_t time() { return std::chrono::system_clock::now().time_since_epoch().count(); }
И уже в нужном месте делаем почти точно также, как и в Python:
#include <iostream> #include "time.hpp" int main() { std::cout << time() << std::endl; }
Правда в C++ так не очень принято, да и пространства имен все-таки отличная вещь.
bak
05.09.2023 12:46-6Серьезно?
std::chrono::system_clock::now().time_since_epoch().count()
6 уровенй вложения с неочевидными названиями vs 2 супер очевидных в питоне. И так во всем в плюсах.
PsihXMak
05.09.2023 12:46+3По моему, это наоборот классно, что одной строчкой сразу видно, откуда что тянется. И по названиям можно предположить, как будет вести себя функция в разных ситуациях.
А в питоне ты вызываешь time и лезешь в документацию проверять, подойдёт ли оно для твоего кейса.
bak
05.09.2023 12:46И что значит count() в данном случае? Вот я не понимаю без документации. Это секунды? Тики процессора? И чем это лучше чем условный time? И на тему документации. Вот я нажал с ctrl на time и открыл описание:
Return the current time in seconds since the Epoch.
Теперь для плюсов. Нажал на count(), и что я вижу?
_LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR rep count() const {return __rep_;}
Ну просто офигеть как всё понятно. rep count, есть даже _LIBCPP_CONSTEXPR пометка не ужели не ясно. Даже тип понятно какой - rep! Каждый день типом rep пользуюсь, ну что не ясно то.
Для time_since_epoch к слову абсолютно тоже самое.
Kelbon
05.09.2023 12:46Так вы смотрите в какой-то рандомной реализации, вместо того чтобы смотреть собственно в документацию
bak
05.09.2023 12:46+3Я в IDE смотрю. Перехожу по функции и читаю документацию. В питоне к системным функциям ко всем есть нормальный docstring, а в плюсах вместо этого кишки реализации из которых ниче не понять.
Kelbon
05.09.2023 12:46это не документация
всё там понятно, если знать С++
в разных реализациях питона там одинаковый docstring?))
Kelbon
05.09.2023 12:46+1функция буквально делает return X;
этот X там на соседней строчке как единственное поле структуры, что тут может быть непонятно?
alextretyak
05.09.2023 12:46-1Кстати,
std::chrono::system_clock::now().time_since_epoch().count()
— очень хороший пример «продуманности» библиотеки chrono.что тут может быть непонятно?
Непонятно «что значит count() в данном случае?» микросекунды? наносекунды?
вместо того чтобы смотреть собственно в документацию
Покажите, пожалуйста, как найти в документации С++ ответ на этот вопрос? И где эта документация C++ вообще находится?
Серьёзно, я не прикалываюсь. Во времена Microsoft Visual C++ 6.0 в качестве таковой я пользовался установленным с диска MSDN. Сейчас пользуюсь в основном cppreference.com. Но ни то, ни другое официальной документацией C++ не является.Но вернёмся к нашему примеру.
В «документации» к методу count сказано:
Return value
The number of ticks for this duration.Ок, количество «тиков».
А теперь попробуйте без компиляции какого-либо C++-кода [а используя только «документацию»] ответить на вопрос: а сколько это будет в секундах?
Причём просьба ответить для различных компиляторов C++ (GCC и MSVC [а количество тиков в секунде у них отличается]) и без использования Google. И ещё прошу подробно описать по шагам процесс поиска ответа на такой простой вопрос. [В Python ответ находится в первом же предложении в документации к функциямtime.time()
иtime.time_ns()
, в документации, которая устанавливается вместе с Python и которую не нужно искать в Интернете.]
Kelbon
05.09.2023 12:46+5Каждый разработчик на С++ знает такие сайты как cppreference, где подробно всё описано, более продвинутые знают где почитать стандарт
Непонятно «что значит count() в данном случае?» микросекунды? наносекунды?
если duration это секунды, то количество секунд, если микросекунды, то количество микросекунд, выглядит логично
А теперь попробуйте без компиляции какого-либо C++-кода [а используя только «документацию»] ответить на вопрос: а сколько это будет в секундах?
а зачем мне это, если .count для других целей используется? Если мне нужно будет узнать сколько это в секундах, я напишу duration_cast<seconds> или что-то в этом духе
ImagineTables
05.09.2023 12:46Так а кто не даёт?
#include <time.h> printf("%ld", ::time(NULL));
bak
05.09.2023 12:46-3Угу, вот только проблема, printf и time это функции языка С а не C++, который кстати говоря создали аж в 1972 году. А модуль chrono в C++ впихнули в 2011-м. Почему одним хватило мозгов сделать нормальный print и нормальный time (спорно - другие функции там тоже дичь), а другим нет?
eao197
05.09.2023 12:46+4Почему одним хватило мозгов сделать нормальный print
Это Си-шный-то printf сделан нормально? O_o
bak
05.09.2023 12:46+1У сишного printf-а есть проблемы, но с точки зрения разработчика мне нужен нормальный print с шаблонами и форматированием, а не вот это вот извращение:
<< " " << " value = " << " value
.eao197
05.09.2023 12:46+3одним хватило мозгов сделать нормальный print
У сишного printf-а есть проблемыИ как это все укладывается в одной голове, хз...
bak
05.09.2023 12:46Наличие проблем в printf означало что надо его немного доработать и всё. А вместо этого разрабы запилили наркоманию со стримами, которые теперь жутко тормозят и считаются плохим паттерном кстати говоря.
eao197
05.09.2023 12:46+1Наличие проблем в printf означало что надо его немного доработать и всё.
Для того, чтобы его "немного" доработать в язык пришлось добавить variadic templates, на что понадобилось где-то 25 лет.
ImagineTables
05.09.2023 12:46А что и как можно улучшить в сишном printf? Если у нас нет виртуальной машины и базового класса Object и нельзя сделать {0}? Спрашиваю не флейма ради, а саморазвития для.
bak
05.09.2023 12:46+21) На шаблонах можно сделать чтобы основные типы определяло само а в пользовательских определяло и использовало кастомную функцию (при её наличии).
2) Сделать аналог f-строк питонячьих (синтаксический сахар на этапе компиляции), чтоб писать типо
printf(f"Result is {obj.method()}");
ImagineTables
05.09.2023 12:46-
И вместо вывода адреса в заданном формате по переданному указателю будет unresolved method to_string()? А главное, настройки форматирования данного типа, очевидно, переедут из декларативной строкозаготовки в императивный вызов.
-
Зачем вообще нужно форматирование строк? Два самых частых юзкейса — вывод пользователю (например, сообщение про не найденный файл) и подготовка DSL (например, запрос на SQL). Я считаю, и в том, и в другом случае надо принципиально держать код отдельно от шаблона строки (в первом случае по соображениям локализуемости, во втором — по соображениям безопасности, таким как sanitizing). Из того, что во многих современных языках (например, ES) появилась эта сомнительная фича, не следует, что такое форматирование лучше сишного.
-
eao197
05.09.2023 12:46+2А что и как можно улучшить в сишном printf?
Посмотрите на то, что сделано в fmtlib и std::print (C++23).
ImagineTables
05.09.2023 12:46-2Не знаю, кто и за что минусует, а мой point очень прост. Язык безнадёжно испорчен «обкуренными создателями chrono». Но совершенно необязательно идти у них на поводу. Как вариант можно вернуться на уровень C, и в этом нет ничего плохого. Ещё можно взять какую-нибудь другую библиотеку для работы со временем (правда, я не знаю такие — может, в POCO что-нибудь найдётся?). Ну и, наконец, если не нужна кросс-платформенность, можно работать с примитивами операционной системы (в силу специфики моих проектов, я делал именно так).
placidity_master
05.09.2023 12:46+13но уже сейчас понятно — если C++ языку ищут замену, значит, её нет
"если я ищу мороженное, вкуснее того что я обычно ем, значит в мире не существует, И НЕ БУДЕТ существовать мороженного вкуснее того что я знаю".
Rust ?....
Для больших объектов операция копирования может быть очень затратной. Такое поведение в C++ имеет свои причины. Главные из которых — использование стека и принципиальное отсутствие сборщика мусора
Как связан GB и процесс копирования ?
std::print("x = {}, y = {}, x + y = {}", x, y, x + y);
интересно, кто у кого подсмотрел, С++ у Rust или наоборот.
C++ славится высокой производительностью. Проверим, так ли он хорош. В качестве конкурента рассмотрим популярный язык Java
ну это совсем позорище, ктож в здравом уме сравнивает компилируемый язык с JIT ? компил. и интерпретируемый ?
А кто лучше, вертолёт или трактор ? трактор или бугати широн ?Kelbon
05.09.2023 12:46+4ну это совсем позорище, ктож в здравом уме сравнивает компилируемый язык с JIT ? компил. и интерпретируемый ?
это демонстрация идеи, что не нужно писать какой то плохой код, чтобы получить перфоманс, т.к. компилятор способен анализировать код(в джаве - не способен)
интересно, кто у кого подсмотрел, С++ у Rust или наоборот.
Да, print оказывается изобрёл раст, в С и питоне и куче других языков этого никогда не было
placidity_master
05.09.2023 12:46+3Да, print оказывается изобрёл раст,
там про фигурные скобки, которые "всеядные", а не как раньше %d
JKot
05.09.2023 12:46+1Всегда считал это питовским синтаксисом, так что думаю правильно сказать что раст появился как минимум на 2 десятилетия позже, чем {} для форматирования.
xjossy Автор
05.09.2023 12:46+2Хороший вопрос, как связаны копирование и сборщик мусора. Объясняю. Часто копировать приходится, потому что нужно передать владение объектом. В модели со сборщиком мусора объектом владеет глобальный пул и передавать владение не нужно. В модели без сборщика мусора - нужно) Отсюда копирования.
GigaCore
05.09.2023 12:46С JIT как раз можно производить profile guided рекомпиляцию, причем С++ код тоже можно джитить ( смотреть llvm )
placidity_master
05.09.2023 12:46Я не эксперт, но просто интересно:
разве LLVM не позволяет проводить оптимизации кода ?
Rust не на этом ли слое случаем делает оптимизации ?domix32
05.09.2023 12:46У раста несколько слоёв кодогенерации и на каждом есть некоторое количество оптимизаций, включая в том числе и этап оптимизации LLVM IR.
domix32
05.09.2023 12:46интересно, кто у кого подсмотрел, С++ у Rust или наоборот.
Емнип питон первый завёз их в виде f-string. Раст это дело облагородил по своему и тоже добавил. Где-то между двумя этими событиями появилась плюсовая библиотека fmt, которая завезла такое форматирование в плюсы. Ну и не так давно похожий синтаксис попал и в стандарт js. За остальные языки не скажу.
lamerok
05.09.2023 12:46В примере с суммой элементов, такой результат, наверное, только при включённой оптимизации, но тогда странно почему Компилятор вообще этот цикл не выкинул, только если сумма куда то выводится, хотя в примере с Java она никуда не выводится
xjossy Автор
05.09.2023 12:46Да, в бенчмарках я заботился о том, чтобы оптимизатор не выоптимизировал то, что измеряется)
В Java не выводится, но разница во времени означает, что цикл выполнялся
lgorSL
05.09.2023 12:46Код на java неэквивалентен коду на плюсах. Либо надо использовать int[] в java, либо в С++ итерироваться по std::vector<shared_pointer<int>>.
lgorSL
05.09.2023 12:46+4Если в бенчмарке заменить ArrayList<Integer> на int[1000000] и прогнать код раз пять, то производительность оказывается вполне на уровне С++ и без разницы на способ итерирования по циклу - на моём железе порядка 240микросекунд на миллион итераций.
k-morozov
05.09.2023 12:46+3Вы написали про проблему с зависимостями, но conan даже близко не стоит рядом с pip, Да и системы сборки оставляют желать лучшего.
Модули. Появились в 20-м стандарте, до сих пор не работают. Еще пару лет ждем пока появятся в том же cmake, потом еще лет 5 пока нужные библиотеки перепишут с использованием модулей (а может и не перепишут).
Kelbon
05.09.2023 12:46переписать библиотеку на модули это дело минутное,
1. добавляешь export перед namespace библиотеки(или перед кокретными функциями или типами из библиотеки, которые хочешь сделать частью модуля)
2. создаёшь файл и сверху пишешьexport module module_name; #define LIBNAME_EXPORT export #include ... // все хедера библиотеки
Ну и всё, модуль готов
rmrfchik
05.09.2023 12:46+7Опять Яндекс Практикум? Сначала Go, теперь вот C++.
Глаз цепляется за каждое утверждение. Некоторые утверждения просто непонятны. Например
array.get(j)
Это из какой явы код?Как бенчмаркали Java?
GC не обязан останавливать мир.
GC не мешает RTTI.
Вообще, странный выбор соперников Python и Java.
danilshvalov
05.09.2023 12:46Про
array.get(j)
. Это, насколько я помню, из AbstractList:public abstract E get(int index)
Returns the element at the specified position in this list.
Автор использует ArrayList, в котором этот метод также есть.
OMR_Kiruha
05.09.2023 12:46+2https://habr.com/ru/companies/yandex_praktikum/articles/758744/#:~:text=популярная идиома RTII
"популярная идиома RTII" - опечатка, RAII - Resource Acquisition Is Initialization
xjossy Автор
05.09.2023 12:46Спасибо, да, опечатка)
rmrfchik
05.09.2023 12:46GC не мешает RAII.
Вот пример RAII из scheme:
(with-output-to-file some-file
(lambda () (printf "hello world")))
Для чистки ресурсов используются "таможенники" (custodians).
DarkEld3r
05.09.2023 12:46+1Это не RAII — аналог такого и в C#/джаве есть (using/try with resources).
rmrfchik
05.09.2023 12:46В яве и c# можно забыть сделать t-w-r, в scheme нет.
DarkEld3r
05.09.2023 12:46Да ладно? Как язык помешает мне вместо
with-output-to-file
использоватьopen-output-file
?rmrfchik
05.09.2023 12:46Никак. Это другой механизм. Так же, как и RAII не запрещает создавать не-RAII объекты. Считай, что порты, получаемые
with-output-to-file
иopen-output-file
это разные объекты.Джавский-же OutputStream можно использовать двояко.
Chaos_Optima
05.09.2023 12:46+3Со сборщиком мусора была бы невозможна популярная идиома RTII
Может всё-таки RAII? RTII эт что-то новенькое, даже не гуглиться, может конечно имелось в виду RTTI но по контексту не подходит совсем.
Beholder
05.09.2023 12:46+4За бенчмарк Java - сразу двойка. Для этого есть специальные инструменты, учитывающие "прогрев" HotSpot, а не вот эта вот самодеятельность с таймерами. size() в цикле тоже вызывать не надо. И вообще есть улучшенный for и streams. Ну, в целом тут C++ наверняка и выйдет вперёд, но не с таким отрывом.
По поводу сетевой библиотеки - ну украли бы уже классы сокетов из Java и не мучались.
Kelbon
05.09.2023 12:46-2а в С++ size() в цикле вызывать можно и проблемы нет, что собственно этот бенчмарк и должен продемонстрировать
А классы сокетов - зачем? Все эти сокеты и протоколы уже есть в библиотеках, а вот стандартного из коробки нет - потому что нетривиально сделать хорошую реализацию подходящую всем
voldemar_d
05.09.2023 12:46+1Если так рассуждать, то и std::filesystem нетривиально сделать, но ведь сделали же.
xjossy Автор
05.09.2023 12:46-4А мы не сравнивали C++ и Java) Мы сравнивали оверхэд в этих языках. Если сохранить size(), то оверхед цикла по диапазону может только вырасти сильнее. И да, мы сравнивали непрогретый цикл на Java с непрогретым на Java. Так что результат на мой взгляд репрезентативен
aGGre55or
05.09.2023 12:46+2Ох уж этот Яндекс. Корпорация IUMO выпускает новый процессор @ssshot. Давайте попробуем угадать что будет в SDK? Rust? Go? М.б. Carbon? Нет, там будет LCC для C, C++ и Fortran. При любом раскладе поставлю на то что C++ там будет.
MountainGoat
05.09.2023 12:46+3И не просто С++, а С++98 с частичной добавкой С++11 и уникальным поведением в некоторых граничных случаях.
bfDeveloper
05.09.2023 12:46+7Я каждый раз удивляюсь, когда говорят, что в C++ слабая стандартная библиотека. Да, там нет встроенных сокетов, json, http или 3D движка, но зачем, если для этого есть сторонние библиотеки? Зато есть stl, который даст фору станадртным библиотекам любых популярных языков (не уверен про Rust разве что). Каждый раз, когда я пишу на других языках, я удивляюсь тому насколько мне не хватает stl. И это касается и разнообразия контейнеров, и алгоритмов, и деталей вроде возможности итерироваться по ключам и значениям map одновременно. Stl не совершенен, но в большинстве других языков всё ещё гораздо хуже.
P.S. Ranges деалют stl не только мощной, но и выразительной, за что им большое спасибо.
nagayev
05.09.2023 12:46зачем, если для этого есть сторонние библиотеки
На работе надо было работать с json. В одной популярной библиотеки код почему-то не работал, пришлось использовать QJson. Была бы стандартная ситуации не работает не было бы.
Kelbon
05.09.2023 12:46+1"код почему-то не работал" будет всегда, потому что всегда можно написать неработающий код
voldemar_d
05.09.2023 12:46+1Был случай, когда пришлось принимать данные из сторонних приложений в формате JSON.
Штуки 3 библиотеки пробовал, были разные проблемы.
Оказалось проще написать свой (быдло)код, чем разбираться, что с библиотеками не так. Возможно, данные были кривые, но повлиять на других разработчиков возможности не было, да и времени тоже. Надо, чтобы работало.
JKot
05.09.2023 12:46Для тех, кому как и мне не хватает даже стандартных контейнеров slt, есть eastl.
voldemar_d
05.09.2023 12:46А что там за особенные контейнеры, если не секрет? Есть описание? Репозиторий с исходниками я видел, но какого-то описания там не нашел (может, плохо искал).
JKot
05.09.2023 12:46+1https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2271.html
EASTL additional functionality (not found in std STL or TR1)
Из того чем пользуюсь постоянно. std::map и std::unordered_map в большинстве случаев ужасное зло.
fixed_vector
fixed_string
fixed_map
fixed_hash_mapvector_set
vector_multiset
vector_map
vector_multimapring_buffer
voldemar_d
05.09.2023 12:46+1Спасибо. В каком смысле и в каких задачах std::map - ужасное зло? Нужно какое-то экстремальное быстродействие?
humbug
05.09.2023 12:46-2Автор клоун, статья - рекламный высер.
KanuTaH
05.09.2023 12:46О, а вот и одно очень дружелюбное коммьюнити подтянулось... мгновенный переход на личности с первого комментария.
AnimeSlave
05.09.2023 12:46+1Переход на личности - это не хорошо, но статья на самом деле плохая. Передергивание фактов, умалчивание проблем языка. Нужно признавать ошибки и стараться их решать, помогать их решать. А когда C++ рекламируют, тем что он незаменим, это же глупость. Нет незаменимых. Это же не автор статьи решает, это решает руководитель проекта, владелец бизнеса и тд.
Я сам разработчик на C++. Я знаю язык достаточно чтобы утверждать, что язык устарел. Но при этом в нём ещё есть порох. Ситуация с C++ это «суперпозиция». С одной стороны в самом языке всё есть для написания хорошего кода, с другой у языка есть комитет и стандартная библиотека. Если разрабатывать высокоуровневый код для прикладных программ, то стандартная библиотека очень помогает. Спускаешься на «нижний» уровень, стандартная библиотека начинает мешать. А без стандартной библиотеки C++ практически превращается в C, но с более удобным синтаксисом, но с менее очевидным поведением компилятора. То есть C++ есть за что ругать. И за это его и ругают. Нужно просто, как я уже писал, это не скрывать, а демонстрировать пути решения. А большинство рекламных статей наоборот всё переиначивают. Будто С++ был создан сверхлюдьми. Он был создан людьми. Именно поэтому у него есть проблемы. Как у других языков. Как у Rust, неоднократно упоминаемого в сравнении с C++. Rust повезло тем, что он разрабатывался с оглядкой на проблемы C++, но даже это не спасло его от проблем
MiraclePtr
05.09.2023 12:46+2без стандартной библиотеки C++ практически превращается в C, но с более удобным синтаксисом
более лучшей типобезопасностью и возможностью кучу всего генерить в compile-time.
Kelbon
05.09.2023 12:46без стандартной библиотеки C++ практически превращается в C, но с более удобным синтаксисо
вот именно что нет, он остаётся С++
А то как устроена стандартная библиотека стало возможно потому что С++ такой какой он есть, на С(и на Java и на C# и на растеЕ) просто невозможно написать такие абстракции, которые написаны в С++ в stl
AnimeSlave
05.09.2023 12:46вот именно что нет, он остаётся С++
Я не буду дискутировать, чем C++ остаётся. Важно, что он теряет без стандартной библиотеки
Leetc0deMonkey
05.09.2023 12:46+7Видишь рекламу %LANGUAGE% - жди что скоро набежит стадо полуграмотных выпускников курсов. Вот и до C++ добрались. Да уж, засрали программирование...
squaremirrow
05.09.2023 12:46+3[x // 2 for x in list if x % 2 == 0]
Автор ни разу не писал на Python? Иначе зачем использовать встроенную функцию в качестве названия переменной? Или это ChatGPT вам такой пример выдал?
rukhi7
05.09.2023 12:46+1Цель языка Python и большинства подобных - оторваться от уровня реального железа,
одна из основных целей языка С/С++ - НЕ потерять связь с железом при этом по возможности ее скрывать.
Чтобы это понять нужно попробовать ответить на вопросы:
В каком еще языке можно писать ассемблерные вставки? Как быть с кодом в котором приходится использовать ассемблерные вставки при переходе на какой-то вновь изобретенный язык? А какие еще языки так же просто разрешают работу с регистрами?
Почему низкоуровневый код вообще на С пишется в основном?
В общем возникает вопрос:
Какой смысл сравнивать синтаксис языков созданных для разных целей?
Но, хотя, наверно, это прикольно, и наверно даже вполне полезно, если ты понимаешь что разница предопределена целеполаганием.
SuperTEHb
05.09.2023 12:46+1А вот кстати микроконтроллеры те же. Ассемблер? 8-битники часто, АРМы чуть реже, но тоже случается. Си? Регулярно и везде. Плюсы? Тоже нередко бывает. Лично под СТМку одну на плюсах писал. И да, не могу не согласиться, что
одна из основных целей языка С/С++ - НЕ потерять связь с железом
OpenA
05.09.2023 12:46+2Если коротко подытожить, то ответ на вопрос почему плюсы не устаревают примерно таков:
Потому что на них тоже вполне можно говнокодить!
Особенно ужаснул пример с вектором который куда то мувается при создании класса.
Что это почему это... я ничего не понимаю.
Есть же с 11х плюсов как минимум синтаксис для приема аргументов в виде ссылок:
Schoolmates(std::vector<Students> &students) {}
То что в языках типа джаваскрипта все объекты по дефолту передаются в виде ссылок то это не потому что там сборщик мусора есть, а в плюсах нет. А потому что в плюсах есть ссылки и поинтеры, а в джаваскриптах их нет. В php например ссылки есть и функции/методу можно передать в виде ссылки не только массив / объект, но и простую переменную по которой функция будет записывать/читать значения на стеке выше. Не смотря на то что это скриптовый язык со сборщиком мусора. Вообще сборщик мусора это только про долгоживущие объекты т.е. созданные через new (или malloc ) в Си или плюсах каждый такой объект надо руками удалять, тогда как в джаве их сборщик мусора сам удаляет если они больше ниоткуда не видны. А обычные стековые объекты самоуничтожаются при выходе вместе с куском стека в котором бы.
Kelbon
05.09.2023 12:46+2Есть же с 11х плюсов как минимум синтаксис для приема аргументов в виде ссылок:
ссылки появились... немного раньше чем С++11
И в примере всё было правильно, а у вас какая то дичь написана с бесполезной ссылкой
в Си или плюсах каждый такой объект надо руками удалять
не надо, прочитайте статью
OpenA
05.09.2023 12:46-1Я и написал что минимум, ранние плюсы просто не застал.
не надо, прочитайте статью
В статье не дано никаких разъяснений. Открыл документацию по плюсам:
std::string str = "Salut"; std::vector<std::string> v; v.push_back(str); std::cout << "After copy, str is " << std::quoted(str) << '\n'; v.push_back(std::move(str)); std::cout << "After move, str is " << std::quoted(str) << '\n'; std::cout << "The contents of the vector are { " << std::quoted(v[0]) << ", " << std::quoted(v[1]) << " }\n"; /* After copy, str is "Salut" After move, str is "" The contents of the vector are { "Salut", "Salut" }*/
То есть оно просто вместо копирования меняет данные на стеке, полезно если структуру или вектор заполняли только для того что бы передать объекту и забыть про них, но это точно никакого отношения к долгоживущим объектам и их очищению не имеет.
Кроме того в примере из статьи при вызове конструктора данные сначала копируются в его контекст (так как как я и написал не проставлена ссылка), а потом героически переносятся в объект.KanuTaH
05.09.2023 12:46+4Кроме того в примере из статьи при вызове конструктора данные сначала копируются в его контекст (так как как я и написал не проставлена ссылка), а потом героически переносятся в объект.
Но ведь это не так.
std::vector<Students> foo() { [...] } std::vector<Students> v; Schoolmates s1(foo()); // Нет ни одного копирования Schoolmates s2(std::move(v)); // Нет ни одного копирования Schoolmates s3(v); // Одно копирование, которое было бы в любом случае
В статье не дано никаких разъяснений.
Статья предполагает хотя бы базовое знакомство с языком, а у вас оно отсутствует.
MiraclePtr
05.09.2023 12:46+1Кроме того в примере из статьи при вызове конструктора данные сначала копируются в его контекст (так как как я и написал не проставлена ссылка), а потом героически переносятся в объект.
Ну да, если у вас аргумент lvalue, то он скопируется, а потом сохранится в поле в объекте - будет такое копирование как и при передаче по ссылке. А вот если у вас rvalue, то любой приличный компилятор это оптимизирует и обойдется вообще без копирования. Со ссылкой у вас такое не получится.
xjossy Автор
05.09.2023 12:46+1и вызове конструктора данные сначала копируются в его контекст, а потом героически переносятся в объект.
Да, это действительно очень непривычно для тех, кто не знаком с мув-семантикой, и кажется, что происходит так как вы написали. Но в реальности, всё лучше: данные копируются или муваются в его контекст. И вот второй случай как раз благоприятный для нас. А если данные не смогли мувнуться в конструктор - что ж, значит копирования не избежать. Тогда в принципе без разницы, в какой момент сделать это копирование: при переносе в контекст конструктора, или внутри конструктора.
move-семантика не такая самоочевидная штука, и нужно неплохо вникнуть в неё, чтобы понять, что передача по значению - единственный верный способ в современном C++.
ZirakZigil
05.09.2023 12:46+1move-семантика не такая самоочевидная штука, и нужно неплохо вникнуть в неё, чтобы понять, что передача по значению - единственный верный способ в современном C++.
Я бы, всё же, не обобщал. В гугловском протобафе, вот, вставку строк сделали через получение std::string по значению. В результате, если работаешь через интерфейсы, то за всей их виртуальщиной компилятор ничего оптимизировать не может, и получится мув при передаче, и ещё один мув при переносе в поле-член. Вот такая вот экономия на лишней перегрузке получилась: мелочь, а таки платим за то, что не используем.
MiraclePtr
05.09.2023 12:46+3Особенно ужаснул пример с вектором который куда то мувается при создании класса.
Что это почему это... я ничего не понимаю.
Есть же с 11х плюсов как минимум синтаксис для приема аргументов в виде ссылок:
Schoolmates(std::vector<Students> &students) {}
В чем ужас? Пример с мувом как раз очень правильный. Обратите внимание, данные в итоге сохраняются как поле класса. В итоге:
когда у вас аргумент передается по значению и ему делается move, как у автора, то в случае аргумента lvalue у него будет 1 копирование, в случае rvalue 0 копирований;
когда у вас аргумент передается по ссылке и без move, как вы предлагаете, у вас в обоих случаях всегда будет как минимум 1 копирование;
короче говоря, первый вариант в ряде случаев более оптимальный с точки зрения производительности и использования ресурсов.
Move-семантика появилась ещё в C++11, почитайте на досуге.
Leetc0deMonkey
05.09.2023 12:46-3Всё это лишнее, компилятор по стандарту имеет право убирать лишние конструкторы копии при инициализации (copy elision). Я бы вообще
const&
сделал как правило хорошего тона при передаче объектов.Kelbon
05.09.2023 12:46+1нет, ничего компилятор здесь не уберёт и не может, RVO/NRVO контексты в появляются как раз при передаче по значению
const & как раз создаст лишнее копирование, потому что чтобы передать значение придётся его создать, т.е. foo(bar()) даже если возвращается из bar() вектор по значению будет лишний раз скопировано внутри foo, т.к. вы в foo принимаете константную ссылку
Leetc0deMonkey
05.09.2023 12:46-1Проверяйте.
#include <iostream>
struct C {
C() {}
C(C const&) { std::cout << "Copy"; }
};C f() {
return C();
}class Schoolmates {
public:
Schoolmates(C const& students)
: students_(students) {}private:
C students_;
};int main() {
Schoolmates S{f()};
}MiraclePtr
05.09.2023 12:46+1Так проверили: GCC 13.2 и Clang 16.0, оба даже с опцией -O3 вызывают именно конструктор копирования. Не работает ваша теория.
Потому что, как уже сказали выше, copy epsilon работает только для передачи по значению, но не для передачи по константной ссылке, как предлагаете вы.Leetc0deMonkey
05.09.2023 12:46-1Он и должен быть один. Он и в исходном примере один. Что с std::move, что без него. Так зачем тогда эти пляски с std::move и какой вред от
const&
?MiraclePtr
05.09.2023 12:46+1Он и должен быть один.
Нет, не должен, и в исходном примере его нет. В случае, если аргумент rvalue, то копирований вообще не должно быть. Как раз благодаря copy epsilon. А ваша константная ссылка все портит, и вы получаете дополнительное копирование там, где оно не нужно.
Leetc0deMonkey
05.09.2023 12:46-1Расставим точки над i. Если вы планируете принимать rvalue, то у вас должен быть перегруженный конструктор для правосторонних ссылок. В идеале - шаблон с универсальной ссылкой для таких аргументов и форвардингом. А так, у вас какая-то эрзац-оптимизация для prvalue, а xvalue (
Schoolmates(std::move(students));
) вызовет два конструктора перемещения.MiraclePtr
05.09.2023 12:46Для prvalue даже лишнего перемещения не будет, компилятор соптимизирует, а для xvalue в двойном перемещении обычно нет ничего фатального, т.к. move-операция обычно очень дешевая (для строк и большинства стандартных контейнеров это элементарный swap пары полей).
Поэтому добавлять везде где попало дополнительный конструктор для правосторонних ссылок или наворачивать на каждом углу шаблоны с std::forward, чтобы избежать двойной мув - это именно что микрооптимизация и экономия на спичках, а вот делать pass-by-value + move - это уже имеет гораздо больше смысла, и при этом не требует писать больше кода.MiraclePtr
05.09.2023 12:46Тогда еще лучше.
В случае с константными ссылками, как предлагает комментатор выше, у вас всегда будет минимум 4 копирования - в ряде случаев 4 лишних копирования. Которые будут гораздо тяжелее, чем 8 мувов.
А если делать отдельные конструкторы для правосторонних ссылок, как тоже он советует, и варианты с вызывающей стороны могут быть разные (какие-то аргументы lvalue, какие-то rvalue, в других случаях наоборот), то можно дойти до того, что придется объявлять аж до 16 перегрузок.
Ну либо надо будет наворачивать шаблонный контруктор для передачи аргументов со всей вытекающей шаблонной грустью.
Leetc0deMonkey
05.09.2023 12:46Да ну что там грустного с шаблонами? Зато в декларации чётко понятно что конструктор может принимать. А это выглядит как хак и костыль какой-то с отсутствием выгод.
MiraclePtr
05.09.2023 12:46В случае ошибок взрывают компилятор адовыми сообщениями на пять экранов, в которых не всегда легко разобраться без бутылки (когда там уже концепты в стандарт завезут нормально?)
Инстанциируются при каждом инклуде, замедляя компиляцию;
Если вы делаете такой интерфейс в библиотеке - то сгенеренный код будет дублироваться в каждом бинаре или другой и библиотеке, которые этот интерфейс используют, ещё сильнее раздувая их.
Leetc0deMonkey
05.09.2023 12:46-1Надуманно. C++ без шаблонов даром не нужен. Благо в STL их и так напихано, что +- уже погоды не сделают. Писать надо общепринятый понятный код и не страдать графоманией.
MiraclePtr
05.09.2023 12:46+1Делать шаблонным конструктор каждого класса, который что-либо принимает и сохраняет себе в поле - вот это как раз уже крайность, переусложнение и графомания, согласен, не надо так :)
Leetc0deMonkey
05.09.2023 12:46-2Это общепринятый подход, а не графомания. Хаки и костыли приемлемы только в специфических кейсах. Например когда в проекте шаблоны принципиально не применяются по каким-то причинам и это вне контроля разработчика. Применять хаки и вычурные механизмы в мейнстриме - вот графомания и демонстранция своих психологических проблем "я не такой как все, я умнее всех".
MiraclePtr
05.09.2023 12:46У вас извращённое понятие хаков и костылей. Pass-by-value + std:: move - это именно что общепринятый паттерн современного C++, можно даже сказать best practise, который повсеместно используется в куче проектов. Интуитивно понятный любому, кто хоть чуть-чуть знает основы современного C++, не требующий написания дополнительного кода, практически не уступающий эффективности шаблонному подходу, но при этом гораздо проще читаемый и не раздувающий код.
Впрочем, можете не продолжать, уже после второго вашего сообщения, стало ясно, что вы просто жирненько набрасываете без особой цели. Тоньше надо, тоньше.
middle
05.09.2023 12:46+7Разбудите меня через 0x100 лет и спросите, что делают в Яндексе. И я отвечу -- пишут статьи о том, что альтернатив C++ нет.
AnimeSlave
05.09.2023 12:46-3но уже сейчас понятно — если C++ языку ищут замену, значит, её нет.
Полнейшая чушь. Из-за большой кодовой базы переход на другие языки условному бизнесу не выгодны. На время перехода нужно держать две команды разработчиков, одна на C++, другая - на том языке, на который идёт переход. Неясны перспективы перехода, там может быть махровое легаси с огромным техническим долгом, которое работает за счёт хард-код хаков, и требуется фактически только переписывать с нуля. И еще и документация нужна, которой на изи может и не быть. А так как работает, зачем трогать?
C++ сейчас конкурирует с теми языками, что изначально разрабатывались для замены языка C++. Rust, Go, Odin, Jai, Nim, D, еще Carbon светится на горизонте. Только к 20-у стандарту коммитет стал осознавать, что если они ещё 10 лет промаринуются в своём болоте, то они начнут терять членов, а с ними и пользователей языка. Потому что некоторые альтернативы и вправду очень не дурно выглядят на фоне C++
LAutour
05.09.2023 12:46+1А где проблема с поддержкой честных модулей? Формально она как бы недавно появилась,но практически все SKD и библиотеки, которые приходится использовать, никто под модули переделывать не торопится. А отсюда долгое время компиляции и костыльные решения для ее осуществления.
domix32
05.09.2023 12:46Потому что поддержка на самом деле так и не появилась. Клочки модулей есть по разным компиляторам, но полноценной поддежки пока нет ни у кого, насколько мне известно. Под какие-то из этих имплементаций в CMake внедряется поддержка оных, но пока что всё довольно плохо с ними. Потому никто и не спешит
CrashLogger
05.09.2023 12:46+6Главная проблема С++ - что в него постоянно добавляют что-то новое. В итоге язык превратился в свалку разных подходов и парадигм, слабо совместимых друг с другом. И код, написанный на С++ разными людьми, часто выглядит как код на разных языках. На мой взгляд, следовало бы заморозить его в 98 году, а все новое делать уже в рамках других, специализированных языков, и не тащить груз совместимости.
eton65
05.09.2023 12:46а все новое делать уже в рамках других, специализированных языков
Так это и так мог делать кто угодно. А комитет как раз заинтересован в совместимости (не смотря ни на что).
Jianke
05.09.2023 12:46все новое делать уже в рамках других, специализированных языков
Проблема в том, что во всё новое вставляют неотключаемый Сборщик Мусора, и это новое начинает тормозить существенно уступая по скорости С++.
8street
05.09.2023 12:46а все новое делать уже в рамках других, специализированных языков
Ага и назвать его C++++, хотя погодите-ка...
AnimeSlave
05.09.2023 12:46-2Всё это позволяет сделать вывод: C++ хоть и старый язык, но вовсе не устаревший.
И здесь я тоже сделаю ремарку. C++ таки устарел. Но из-за огромной существующей кодовой базы его тащат. И будут тащить пока это выгодно условному бизнесу. Нововведений в сам язык не так много. Большая часть, так называемых, нововведений языка - это очередное раздувание стандартной библиотеки. При чём часть вещей в стандартной библиотеке - это костыли для избавления от выстрелов в ногу от добавленных ранее вещей. Я до сих пор кекаю с std::move
eugenk
05.09.2023 12:46+4Вот ребят не знаю я... Сложное у меня к плюсам отношение. Мой основной опыт работы - электроника, встроенные системы и т.п. Там очень важна "прозрачность" языка. Т.е. чтобы посмотрев на код, было более не менее понятно, во что это превратит компилятор. Одно дело если программа работает на столе у бухгалтера. И совсем другое дело, если она управляет коробкой скоростей автомобиля. Полезет она за каким-нибудь умным указателем в интернет, и что тогда ??? Поэтому пишу я практически на чистом С. Очень осторожно используя свойства ++, там где это реально оправдано и безопасно. Во всяком случае никогда не использую шаблоны, перезагрузку операторов и т.п. И мне вполне хватает. Мне вообще немного непонятна мотивация развития современного С++. Исходно это системный язык не слишком высокого уровня. Главное требование к такому языку - "прозрачность" кода, а не абстракции. Для абстракций есть языки куда более приятные. Прошу прощения если кого-то обидел, я не ради флейма, а просто высказаться. Во всяком случае спасибо автору за отличную статью.
Anton-V-K
05.09.2023 12:46Кстати, C++ уже и до Windows-драйверов добрался, правда, там почти для всего надо изобретать свои велосипеды. См., например, Developing Kernel Drivers with Modern C++ - Pavel Yosifovich
CrashLogger
05.09.2023 12:46Он туда добрался уже лет 20 как. Я на своей первой работе в 2006 году ковырял виндовые драйвера на плюсах.
eugenk
05.09.2023 12:46Не, ну это как бы уже давно. И я ещё успел драйвера под винду на плюсах немного пописать. Я немного о другом. О жестком реальном времени и довольно ограниченных ресурсах. Например запихнуть серьёзную задачку в STM32 причем не самый крупный. Вот там действительно приходится внимательно следить за тем что делается и как оно делается. И тут плюсы скорее вредят, чем помогают. Кстати и Rust у меня по этой же причине не пошел. Непонятный он какой-то... Хотя вроде затачивался тоже в том числе под bare metall. Впрочем может у меня просто руки кривые и голова туповата.
voldemar_d
05.09.2023 12:46Может, это не совсем "тру" программирование железок, но в последних версиях Arduino IDE подвезли фичи C++20. Там и шаблоны есть, и лямбды, и много чего еще.
eugenk
05.09.2023 12:46+1Да нет, почему не совсем тру ! Самое что ни на есть тру, пожалуй даже трулялистее многого другого. Ибо дуньки как правило мелкие (те же AtMega) и за тем во что превращается код, там надо реально следить. А подвезли - очень просто. ЧТОБ БЫЛО. Мода, ничего не поделаешь. Более того, куча народа этим будет пользоваться ! Особенно начинающие. Пока с накоплением опыта не убедятся, что лучше этого не делать. Я сейчас читаю книжку Anton's OpenGL 4 Tutorials https://antongerdelan.net/opengl/ (кстати хочу перевести её на русский и бесплатно выложить для всех желающих, автор мне разрешил). Казалось бы совершенно другая область. И таких суровых ограничений там нет. Тем не менее автор про себя говорит что тоже пишет в моем стиле и всем его рекомендует. Увы, программирование последние лет 30 развивалось во многом под действием хайпа и самой злокачественной рекламы. То всё должно быть объектно-ориентированным, то с паттернами носятся как с писаной торбой. Старички вроде меня ещё помнят "венгерскую нотацию", про которую слава Творцу сейчас забыли. Сейчас (опять таки слава Творцу) положение начинает потихоньку выправляться. Разум возвращается.
eao197
05.09.2023 12:46+2Я сейчас читаю книжку Anton's OpenGL 4 Tutorials https://antongerdelan.net/opengl/ (кстати хочу перевести её на русский и бесплатно выложить для всех желающих, автор мне разрешил). Казалось бы совершенно другая область. И таких суровых ограничений там нет. Тем не менее автор про себя говорит что тоже пишет в моем стиле и всем его рекомендует.
Т.е. вы рекомендуете в C++ писать вот в таком стиле:
int main() { // start GL context and O/S window using the GLFW helper library if (!glfwInit()) { fprintf(stderr, "ERROR: could not start GLFW3\n"); return 1; } ... GLFWwindow* window = glfwCreateWindow(640, 480, "Hello Triangle", NULL, NULL); if (!window) { fprintf(stderr, "ERROR: could not open window with GLFW3\n"); glfwTerminate(); return 1; }
Реально? (Речь прежде всего о рукопашном вызове glfwTerminate).
eugenk
05.09.2023 12:46-1Смотря где и когда. Если Вы в этом деле новичок (как я), то так и только так, и никак иначе. Всё нужно вызывать собственными ручками и видеть это в коде. Тогда рано или поздно (скорее рано) придет понимание того как это всё работает. Почему мне кстати не нравятся книги Алекса Борескова. Если Вы уже достигли какого-то опыта и понимания, разумеется всё это будет завернуто в некие обертки (как это делает Боресков). Впрочем ни на чем не настаиваю. Я сейчас активно осваиваю современный OpenGL (занимался этим последний раз лет 20 назад, сейчас он совсем другой), и мне просто так легче учиться. У других могут быть другие предпочтения.
eao197
05.09.2023 12:46+1Если Вы в этом деле новичок (как я), то так и только так, и никак иначе.
Простите, а зачем вам вообще C++? Ну т.е. если вы сходу отказываетесь от его преимуществ.
Байки про суровое реальное время можно не рассказывать.
eugenk
05.09.2023 12:46Например очень удобная штука классы, виртуальные функции и т.п. Я не против ++. Я исключительно за разумное и оправданное применение этих возможностей. Не слишком затемняющее код.
eao197
05.09.2023 12:46+1Например очень удобная штука классы
Особенно их деструкторы. Но вы шлете их лесом, потому что сперва нужно вызывать функции вроде glfwTerminate вручную.
виртуальные функции
Не слишком затемняющее код.Ну да, ну да. А шаблоны они вот прям нипанятнашта с кодом делают. На фоне косвенных виртуальных вызовов. Ага.
Я исключительно за разумное и оправданное применение этих возможностей.
Звучит как за все хорошее против всего плохого.
eugenk
05.09.2023 12:46Звучит как за все хорошее против всего плохого..
Да, вобщем-то где-то так оно и есть... Ну не за всё же плохое мне быть против всего хорошего ! Можно сказать, что скорость байка ограничивается в первую очередь видимостью на дороге. Способны Вы видеть, во что разворачиваются конструкции языка - пользуйтесь ради бога ! Нет - лучше воздержитесь. Особенно если программа управляет чем-то реальным и небезопасным.
eao197
05.09.2023 12:46+1Способны Вы видеть, во что разворачиваются конструкции языка - пользуйтесь ради бога ! Нет - лучше воздержитесь.
Именно поэтому вы:
Во всяком случае никогда не использую шаблоны, перезагрузку операторов и т.п.
Это все генерирует гораздо худший (со всех точек зрения) код, чем виртуальные вызовы. Конечно же.
eugenk
05.09.2023 12:46-2А кто Вам сказал что я ВСЕГДА отвергаю шаблоны и ВСЕГДА пользуюсь виртуальными вызовами ??? Неприятность с шаблонами в том, что их сложность быстро нарастает, по мере того, как человек что называется входит во вкус. И скоро сам становится не в состоянии контролировать свой код. Особенно когда вернется к нему года через три после написания. Ещё раз, я целиком и полностью за любые языковые фичи. Но там где во-первых они оправданы, и во-вторых не приводят к потере контроля. Если Вы гуру в С++, наверно можете творить всё что душе угодно. Мои знания (а особенно самоуверенность !) куда скромнее. Поэтому я предпочитаю осторожность и консерватизм.
eao197
05.09.2023 12:46+3Ну послушайте, я же ничего не выдумываю, я вам привожу ваши же слова из вашего же комментария. Вот они в более развернутом контексте:
Поэтому пишу я практически на чистом С. Очень осторожно используя свойства ++, там где это реально оправдано и безопасно. Во всяком случае никогда не использую шаблоны, перезагрузку операторов и т.п. И мне вполне хватает.
Тут же русским языком по белому написано "никогда не использую". Как по мне, так "никогда не использую" тождественно "ВСЕГДА отвергаю".
Хотя, судя по другим вашим комментариям, вы в одном месте говорите одно, а в другом -- другое:
Мне надо было реализовать это на FPGA, для чего конвейеризовать алгоритм. Я написал это с использованием шаблонов и перезагрузки операторов.
И вот непонятно, чему в ваших простынях текста верить: тому, что вы "никогда не используете шаблоны" или тому, что вы на шаблонах написали быстрое преобразование Фурье по алгоритму Кули-Тьюки.
eugenk
05.09.2023 12:46-3И вот непонятно, чему в ваших простынях текста верить: тому, что вы "никогда не используете шаблоны" или тому, что вы на шаблонах написали быстрое преобразование Фурье по алгоритму Кули-Тьюки.
Чего непонятного-то ??? Я пользуюсь только тем, что в данной конкретной задаче ОПРАВДАНО и УМЕСТНО. Принцип KISS слыхали ??? Keep It Simple, Stupid ! Вот это именно оно и есть.
eao197
05.09.2023 12:46+2Чего непонятного-то ???
Непонятно как можно говорить, что вы "никогда не используете шаблоны", если вы их таки используете там, где это уместно.
Возможно, вы какой-то специальный смысл вкладываете в слово "никогда".
eugenk
05.09.2023 12:46-3Я где-то утверждал, что я вообще не знаю что такое шаблоны и как они работают ??? Я где-то утверждал, что не пользуюсь (хотя и с осторожностью) средствами которые в задаче вполне уместны ??? Не знаю какой смысл я вкладываю в слово "никогда", но Вы мне кажется пришли сюда исключительно чтобы почесать своё ЧСВ овер 80-го левела. Извините коллега, но мне такие разговоры не интересны. С Вашего позволения хочу пожелать Вам хорошего вечера (или незабываемой ночи) и откланяться.
eao197
05.09.2023 12:46+3Я где-то утверждал, что не пользуюсь (хотя и с осторожностью) средствами которые в задаче вполне уместны ???
В общем-то да. Цитаты я уже дважды приводил, повторять не буду. Из вашего стартового сообщения практически однозначно следует, что вы просто никогда не используете шаблоны и перегрузку операторов поскольку это делает код непредсказуемым в ваших условиях.
Плохо здесь то, что неопытные разработчики прочитают мнение "гуру с десятилетиями опыта в рил-тайме" и вынесут для себя "что в рил-тайме ни шаблоны, ни перегрузка операторов не используются". Что, мягко говоря, будет не совсем правильным выводом.
eugenk
05.09.2023 12:46-1Дорогой коллега, повторяю в тысячу первый раз. Для тех кто даже не в танке, а в бронепоезде. Применяется абсолютно всё, что разработчик знает и умеет (если конечно оно не противоречит стандартам кодирования принятым в команде). Самоограничение (если мы говорим о realtime) только одно. Четкое понимание, во что примерно это превращается компилятором. Понимаешь во что превращаются твои шаблоны - ради бога ! Не понимаешь - лучше реши задачу как-то по-другому. Это единственное что я хотел сказать. В десктопной разработке это ограничение существенно мягче. Там можно позволить себе больше.
eao197
05.09.2023 12:46+1Так а что заставило вас написать что вы никогда не используете шаблоны и перегрузку операторов? Вы не понимаете во что это разворачивается? Вы увидели, во что разворачивается и убедились, что получается ужас-ужас и что лучше все делать в стиле старого-доброго Си?
PS. Вопрос вызван тем, что мне сложно представить, чтобы какой-нибудь условный std::min/std::max, будучи шаблоном, приводил к каким-то фатальным последствиям для написанного для real-time кода.
eugenk
05.09.2023 12:46-3Знаете, у меня сложилось впечатление, что Вы либо очень крутой гуру в С++, либо у Вас очень мало практического опыта в разработке. Расскажу одну поучительную историю. В СССР при найме водителей-дальнобойщиков был такой довольно жесткий тест. Человеку показывали глубокую яму, с битым стеклом. Ставили в 5 метрах от края, завязывали глаза и предлагали идти вперёд. Тех кто отказывался идти вообще, не брали. Тех кого приходилось ловить на краю ямы, тоже. Брали тех, кто спокойно проходил 3 метра, но потом его трактором не сдвинешь. Подумайте, как это соотносится с использованием тонких мест С++ в практической разработке.
eao197
05.09.2023 12:46+4Знаете, у меня сложилось впечатление, что Вы либо очень крутой гуру в С++, либо у Вас очень мало практического опыта в разработке.
Вы не угадали ни там, ни там.
Но я (да и полагаю, не только я) был бы признателен, если бы вы оперировали фактами, а не аналогиями. Типа: использовал такой-то шаблон с таким-то компилятором, убедился, что генерируется туева хуча одинакового кода в разных единицах трансляции, которые затем не убираются линкером и это оказалось недопустимо, переписал на вызовы Си-шных функций и количество кода пришло в норму.
eugenk
05.09.2023 12:46-2Практически это было у меня году по-моему в 12-м, в одном проекте под iOS с std::vector, когда приложение просто падало. Что там было конкретно, глубоко копать не стал. Просто реализовал свои динамические массивы, после чего всё нормально заработало.
eao197
05.09.2023 12:46+4Что там было конкретно, глубоко копать не стал.
Пожалуй, это ключевое. Остается только сказать "вопросов больше не имею".
voldemar_d
05.09.2023 12:46+2Почему бы при программировании железок не использовать шаблоны (например, чтобы не писать повторящийся код для разных типов данных), auto и какие-нибудь инициализации вроде ={}? Среди прочего, это уменьшает вероятность ошибок в каких-то случаях. Лямбды, например, сужают область видимости - позволяют что-то внутри посчитать и результат вычисления выдать в неизменяемую переменную, которая объявлена как const.
Я как-то сделал учебный проект на Arduino с наследованием классов и виртуальными функциями. Подменяешь один класс-потомок на другой, и на экранчике светодиоды начинают другую анимацию показывать. И добавить еще одну анимацию очень просто - добавляешь еще один класс-потомок с другим алгоритмом, из базового класса при этом один и тот же код в цикле вызывает одну и ту же функцию.
Я не знаю, о каком хайпе идет речь - если есть удобный инструмент, позволяющий решить задачу, почему бы им не воспользоваться?
eugenk
05.09.2023 12:46-1Целиком и полностью согласен. Моя первая программа, совершенно сознательно написанная на С++, была вычислением быстрого преобразования Фурье по алгоритму Кули-Тьюки. Мне надо было реализовать это на FPGA, для чего конвейеризовать алгоритм. Я написал это с использованием шаблонов и перезагрузки операторов. А потом просто заменил комплексные числа, на некий класс, сбрасывающий в лог граф вычислений. Откуда конвейерная схема для FPGA получилась уже достаточно прозрачно. Да, это можно было бы написать и на С. Но куда менее удобно. Однако такие задачи у меня были не слишком часто. Куда чаще жесткое реальное время и весьма ограниченные ресурсы. Отсюда и мой стиль... Ещё раз, я не против удобных инструментов. Но только там, где их применение оправдано.
AllexIn
05.09.2023 12:46Как же мне не нравится std::move...
Мы буквально не можем использовать переменную, которая после передачи в функцию может перестать быть инициализированной.
То есть у нас код, в котором переменная которая нормально работает(и мы, может быть, даже в начале проверили что она ввлидная) в один прекрасный момент достаточно прозрачно и незаметно становится невалидной.
Простите, но я лучше буду пользоваться указателями.Kelbon
05.09.2023 12:46после передачи в функцию может перестать быть инициализированной.
не может
незаметно становится невалидной.
не становится, ну либо вы сами такой кривой тип написали(не соответствующий требованиям мува)
Expurple
05.09.2023 12:46+3Ну не невалидной, а in unspecified state. Какая разница. Пользоваться без переинициализации ей больше нельзя, но компилятор об этом ничего не скажет. Да, в собственном классе можно написать какой угодно specified мув, но это не отменяет того, что стандартные контейнеры из STL ведут себя так, как я написал выше. И что их использование, что написание своих мувов это один сплошной footgun
Kelbon
05.09.2023 12:46пользоваться можно, это буквально тоже самое, что просто изменить вектор
vector x;
change(x);
по вашему это уже "footgun", ведь 'x' изменился
AllexIn
05.09.2023 12:46-2Это не буквально тоже самое. Как много вы видели конструкторов объектов, которые изменяют состояния входящих объектов? Ожидаете ли вы такого поведения от конструктора? Ожидаете ли вы в принципе, что конструктор что-то меняет во внешних переменных?
Часто ли вы используете функции, которые меняют состояние переменных непредсказуемо? То есть вы даже посмотрев на код функции не можете сказать, в каком состоянии будет переменная.
И вся эта мув семантика без проблем заменяется умными указателями. С абсолютно понятным и предсказуемым поведением. Причем при работе с указателями любой класс будет себя вести одинаково. И вот этого "вы сами написали кривой класс" не имеет значения, потому что класс в перемещении указателя не фигурирует.
Мув семантика - это костыль, которые прикрутили, потому что народ боится указателей. Но указатели есть и будут важной частью плюсов. Не любишь указатели - просто выбери другой язык. Не надо из-за этого костыли прикручивать.Expurple
05.09.2023 12:46И вся эта мув семантика без проблем заменяется умными указателями. С абсолютно понятным и предсказуемым поведением.
В целом согласнен, но с указателями в какой-то момент всё упирается в производительность, а мы же про С++ говорим.
Мув семантика - это костыль, которые прикрутили, потому что народ боится указателей.
Не согласен. Мув семантику прикрутили как оптимизацию. Чтобы можно было создать скажем вектор на стеке, избежать лишней аллокации и double indirection при его использовании, но при этом потом всё равно суметь его мувнуть. Или чтобы внутри вектора можно было эффективно свапать произвольные T, лежащие по значению, а не только скажем указатели и стандартные классы (для которых гипотетически могли написать те же мув костыли, но не публикуя их как часть стандарта).
Надо было просто делать как в расте. Чтобы компилятор знал про moved out состояние и запрещал его использовать. Ну и move by default в расте это очень круто, но в плюсах этого конечно не будет, потому что совместимость
AllexIn
05.09.2023 12:46Был бы на уровне компиляции запрет на использование объекта после move - у меня и вопросов бы не было. А так добавили еще один инструмент, который делает код плохо поддерживаемым.
Какую проблему вы видите с указателями? Мув почти никогда не беспатный. Да, он дешевый, ну так и копирование shared указателя дешевое.
Expurple
05.09.2023 12:46+1Ну я же написал: лишние аллокации и indirection. Для каких-то кодбаз кстати даже копирование shared_ptr слишком дорогое, потому что оно атомарное, а это не всем нужно. Поправьте, если я неправ, но разделения аналогичного Rc/Arc в плюсы ещё не завезли.
Cheater
05.09.2023 12:46копирование shared указателя дешевое
С инкрементом refcount и гарантиями атомарности, которые даёт shared_ptr, - уже не дёшево
Kelbon
05.09.2023 12:46+1Мув из раста работает только для локальных переменных и то частично
struct X { A a; B b; }; X value; auto y = std::move(value.a); // как это сделать с деструктивным мувом? vector<X> vec = ...; auto x = std::move(vec[5]); // тоже никак
Expurple
05.09.2023 12:46Насколько я знаю, эту ситуацию постепенно улучшают. В первом случае
b
всё ещё можно использовать. Иvalue
тоже можно будет, если переинициализироватьa
в той же функции. Либо, в случае с такой POD структурой, можно вообще её деструктурировать в отдельные переменные и потом собрать новую.С вектором вероятно сложнее. Но если мы заранее знаем новое значение для мувнутого элемента, то тут достаточно
std::mem::swap()
с локальной переменной. Или даже если не знаем, но для элемента реализованDefault
.Я сейчас в дороге с телефона, так что неудобно в подтверждение писать код или искать ссылки, извините.
Kelbon
05.09.2023 12:46mem swap - нет, потому что не все типы можно переместить копируя байты. Ах да, в расте же это отдельная громадная проблема
Expurple
05.09.2023 12:46Вы имеете в виду объекты с ссылками внутрь самих себя? Если я правильно помню, то в расте сами эти объекты невозможны, потому что алиасинг. Или у вас на уме был какой-то другой пример? Можно подробности?
Kelbon
05.09.2023 12:46да, селф референс объекты, среди которых даже обыкновенная строка(в хорошей реализации)
Kelbon
05.09.2023 12:46-1Тут нечего сказать, вы абсолютно некомпетентны. Умные указатели вместо мув семантики это надо выдумать
AllexIn
05.09.2023 12:46Про некомпетентность рассуждает человек, который не знал что стандартные типы после мува по стандарту имеют Undefined State.
Expurple
05.09.2023 12:46+2Поправочка, valid but unspecified state ???? Люблю плюсы, столько разных слов на un
AllexIn
05.09.2023 12:46Использование переменной в Undefined State ведет к Undefined Behaviour.
Это и к вопросу "это тоже самое что change(x)" - после change(x) - состояние x вполне определено и не меняется от реализации компилятора. после move - переменную нельзя использовать ни для чего кроме повторной инициализации.Expurple
05.09.2023 12:46+1Простите мою педантичность, но нет, в общем случае не ведёт. Компилятор не сделает что угодно (как при UB), он сделает именно то, что написано в реализации конкретной стандартной библиотеки для этого контейнера. Если в реализации написано занулить мувнутый вектор, значит с этой библиотекой читать его size абсолютно безопасно и всегда вернётся 0. UB может произойти только косвенно, например если попытаться прочитать элемент, не проверив size. Но это не зависит от того, вектор мувнутый или просто пустой. Просто надо проверять состояние объекта после мува. Не обязательно делать unconditional переинициализацию. Но с духом поста я согласен, unspecified мутация это хуже, чем просто мутация.
Kelbon
05.09.2023 12:46+1я прекрасно знаю что у них за стейт, он VALID, т.е. все методы продолжают работать как обычно
Expurple
05.09.2023 12:46+3по вашему это уже "footgun", ведь 'x' изменился
Как любитель функционального стиля, я скажу да ???? Но если серьёзно, то да, тут вы правы, по сути это обычная clobbering мутация (в случае стандартных контейнеров). Но блин, введение мув семантики всё равно же потребовало изменений на уровне языка: как минимум синтаксис для rvalue references (&&). Кто им мешал на уровне языка сделать ещё и проверку как в расте, чтобы нельзя было использовать clobbered объекты? Хотя бы раз, хотя бы от единственного блин footgun защитить программиста? Хотя бы спустя 30 лет развития языка?
AnimeSlave
05.09.2023 12:46Кто им мешал на уровне языка сделать ещё и проверку как в расте
Отсутствие видимой конкуренции от Rust на момент введения move-семантики. Хоть Rust и 2006 года, мир его увидел в 2015 году. А к тому времени комитет решил проблему по своему через стандартную библиотеку
Expurple
05.09.2023 12:46В 2015 вышел стабильный Rust 1.0. Судя по статье на вики, как минимум в 2012 был релиз 0.2. Мне лень копать, когда точно раст опубликовали. Ну и в целом про конкуренцию немного странный аргумент. У самого раста же тоже не было конкуренции с растом) И affine types уже были на тот момент исследованы в других экспериментальных языках, это не изобретение раста.
через стандартную библиотеку
В том-то и дело, что нет. Я же написал про rvalue references
AnimeSlave
05.09.2023 12:46Мне лень копать
А копать не нужно. Это просто знать надо, что move-семантика появилась в С++11 (2011 год). Но это лишь стандарт, а сама идея появилась раньше. И на момент появления идеи не было прямых примеров для такого, чтобы язык смог в себя это забрать. Потому что тогда это считалось нормой для низкоуровневых языков
lgorSL
05.09.2023 12:46Статья изобилирует манипулятивными приёмами и неверными утверждениями.
1. Код на питоне в одну строчку, код на С++ - 6. Это не немножко сложнее, это фиаско.
2. Да, в С++ много избыточных копирований И перемещений.
Главное отличие в методе close — в C++ он не нужен. В Python вы обязаны самостоятельно позаботиться о закрытии файла. ... В C++ закрытие произойдёт автоматически, потому что момент удаления объекта строго определён. Это достигается благодаря отсутствию сборщика мусора.
В большинтсве случаев не обязаны:
with open(name) as f: ... # тут файл будет автоматически закрыт
Не благодаря отсутствию сборщика мусора, а благодаря тому что в С++ есть такая фича. В Си, например, автоматического закрытия не сделать.
Сравнение в java некорректное. Неизвестно, в какой момент включится jit компиляция и изменит производительность. Мерять точно надо не так
Дело в том, что C++ полностью исключил вызовы функций при оптимизации.
Добро пожаловать в статически компилируемые языки. И в языках с jit такое тоже возможно.
Kelbon
05.09.2023 12:46Код на питоне в одну строчку, код на С++ - 6. Это не немножко сложнее, это фиаско.
код измеряется не в строчках, а в понятности для чтения, в С++ тоже это можно написать в одну, просто так будет менее удобно читать
В питоне код менее читаемый, хрен пойми что это вообще значит x for x in x
lgorSL
05.09.2023 12:46-1list.filter{ it % 2 == 0 }.map{ it / 2 }
Вот это - простой, короткий и читаемый код на Kotlin. Код на Python - тоже простой и читаемый для тех, кто знаком с синтаксисом Python.
Код на С++ ни в каком виде не удобно читать из-за тяжеловесных объявлений лямбда-функций и вложенных неймспейсов.
namespace view = std::views; auto even = [](int i) { return i % 2 == 0; }; auto half = [](int i) { return i / 2; }; auto range = view::all(list) | view::filter(even) | view::transform(half); // покороче, но не сильно лучше auto range = view::all(list) | view::filter([](int i) { return i % 2 == 0; }) | view::transform([](int i) { return i / 2; });
AnimeSlave
05.09.2023 12:46-1Вся статья манипуляция ради рекламы курсов. И самое обидное, что до такого опускается Яндекс
Endeavour
05.09.2023 12:46+2Очередная демонстрация того, что Java бенчмарки лучше писать используя JMH.
Benchmark (size) Mode Cnt Score Error Units MyBenchmark.index 10 avgt 5 4,431 ? 0,029 ns/op MyBenchmark.index 1000 avgt 5 252,146 ? 0,791 ns/op MyBenchmark.index 100000 avgt 5 26894,154 ? 900,024 ns/op MyBenchmark.iter 10 avgt 5 5,582 ? 0,016 ns/op MyBenchmark.iter 1000 avgt 5 248,616 ? 1,010 ns/op MyBenchmark.iter 100000 avgt 5 27130,305 ? 1030,116 ns/op
Hidden text
@State(Scope.Benchmark) @Warmup(iterations = 5) @Fork(1) @Measurement(iterations = 5) @BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.NANOSECONDS) public class MyBenchmark { @Param({ "10","1000","100000" }) int size; List<Integer> ii; @Setup public void setup() { ii = new ArrayList<>(IntStream .range(0, size) .mapToObj(i -> i) .toList()); } @Benchmark public long iter() { long s = 0; for (int i : ii) { s += i; } return s; } @Benchmark public long index() { long s = 0; for (int j = 0; j < ii.size(); ++j) { s += ii.get(j); } return s; } }
swordgna
05.09.2023 12:46+2В результате сложные и элегантные языковые конструкции при компиляции превращаются в простой и эффективный машинный код. В этом сила C++.
Вот эта мысль, по моему мнению, является самой важной в этой статье.
Не теряя удобства, получаем Результат.Другая важная мысль - это почтенный возраст! Привычка - дело сильное. Многие программисты, уверенно шпарящие в C++, даже и не собираются переходить на что-то новое.
denim
05.09.2023 12:46-1Я кончено в Яндексе не работаю и в команде «разработчик с++» не состою, но так вот случайно получилось что большую часть своей профессиональной жизни пишу на си крест крест, уан лав, все дела. Помню как с замиранием сердца включал заветный std=c++11 в gcc4.8, разбирался с мув семантикой и писал первую лямбду в продакшн коде. К сожалению, с++ остается инструментом в котором есть тысяча и один способ выстрелить себе в ногу и для того чтобы начинать новый проект на нем нужны очень, очень веские причины. С каждым новым стандартом порог входа становится все выше, для понимания почему комитет сделал так а не иначе нужно знать контекст в котором это решение принималось. После с++ переход на любой современный язык программирования это как пересесть с жигули на мерседес, от простоты и понимания что все может быть гораздо проще текут слезы. Но есть и хорошие новости - кода напилено столько что работы хватит всем и на долго
DeepFakescovery
05.09.2023 12:46-3C++ мёртв для тех, кто хоть раз попробовал написать что-то на современных популярных языках.
C++ жив потому что инертную кодовую базу еще долго придётся тащить, и за это будут платить.
bfDeveloper
05.09.2023 12:46+4Утверждение неимоверно сильное и поэтому ложное. Я писал на всём "современном": Go, C#, даже чуть на TypeScript и Rust, а на D не только писал, но и преподавал. Особенно сильно у меня пригорело с C#, потому что я думал, что язык продвинутее и современнее старого C++, а по большинству моментов выяснилось обратное. Мы с коллегой даже начинали подборку "WTF это появилось сейчас, а не 15 лет назад", когда супер базовые (с нашей точки зрения) функции и классы появлялись только в самых последних версиях стандарта.
Есть не мало областей, в которых C++ практически на передовой дизайна языков. Да, своеобразно, да сложно, да тонна наследия и обратной совместимости, но после LINQ std::range выглядит как манна небесная.
Есть множество областей, где действительно можно поругать C++ за отсталость: отсутствие пакетного менеджера (CMake FetchContent для меня решает проблему, но не идеально), слабые гарантии безопасности с повсеместным UB, и так далее. Но почему-то я чаще вижу, что его ругают необосновано те, кто не осилил даже синтаксис.
iboltaev
05.09.2023 12:461) Претензия #1: слабая стандартная библиотека? нет генераторов?
std::generate + insert iterators кажется еще в c++98 были, это раз. STL, имхо, одна из лучших библиотек коллекций. Ну а list comprehensions, std::ranges... просто взгляните на Scala, и поймете, что и то, и другое - одинаково убоги2) Претензия #2: много избыточных копирований?
из плюсов убрали указатели, а я не заметил? rvalue reference, там, move semantics уже 12 лет как существуют
3) Претензия #3: нет GC?
нет GC - есть RAII. GC не собирает сокеты/файлы, RAII - можно ненароком сделать цикл из shared_ptr. И то, и другое - одинаково неудобно. Имхо, в Scala есть GC, и со ScalaARM, или bracket и тд как-то живем. С точки зрения производительности RAII лучше, не надо stop-the-world и накладных расходов на него (в java они большие)4) Претензия #4: плохой ввод/вывод?
мм, меня как-то std::istream/ostream всегда устраивали, не сказал бы, что где-то еще он прям сильно лучше5) Претензия #5: нет пакетов
раньше обходились пакетным менеджером системы, но да, по сравнению с java - отстой6) Претензия #6: сложность в изучении
Книги Майерса "55 граблей C++", "Еще 35 граблей C++", "50 граблей STL" не дадут соврать) Но справедливости ради, у java тоже есть подобный труд от Д. Блоха, только потоньше.Шаблонное программирование - читаем Александреску "
Как я поел грибовсовременное проектирование на C++" и просвещаемся, тем более книге 20 лет
x2v0
05.09.2023 12:46Про будущее C++
https://www.youtube.com/watch?v=ELeZAKCN4tY
IMHO, оно лучезарное.C++ фарева! :)
x2v0
С нетерпением жду выхода C++2 от Herb Sutter
https://github.com/hsutter/cppfront
который сделает C++ в десятки раз проще и безопаснее.
sgusev
Зашел в репозиторий, и сразу же на втором скрине вижу в десятки раз более простой и безопасный код:
Вот он, прорыв! Наконец-то кто-то, давно пишущий на плюсах, знающий язык вдоль и поперёк, решил избавить миллионы программистов от их ежедневных сложностей, переставив возвращаемый тип функции в конец, добавив двоеточий и стрелочек, ну чтобы всё было как у нормальных математиков в лучших академических кругах, и убрав необходимость писать фигурные скобки для однострочных функций. Ну теперь-то заживём!
В отличной статье Разработчик с мозгом груга есть замечательный момент:
Никому не нужно указывать тип функции в конце её определения, слушай меня мистер большой мозг Герб Саттер! Это не проблема языка, которую надо исправлять, миллионы людей выучили этот синтаксис и привыкли к нему. Может быть в комнате, полной теоретиков программирования, указывание типа перед сигнатурой функции и звучит как анекдот, но я никогда в своей жизни не видел проблем ни с чтением, ни с работой такого кода.
Просто дайте мне, блин, обычные плюсы, ровно с тем же самым синтаксисом, но с
кучей дополнительного синтаксического сахара, позволяющего автоматизировать написание типового кода
сильно расширенной стандартной библиотекой, учитывающей современные и используемые стандарты
стандартным менеджером пакетов
удалёнными из языка совсем устаревшими конструкциями (да, я вижу, что они удалили
union
и адресную арифметику, что тоже дискуссионно)вменяемо оформленными ключевыми словами, а не безумными
[[nodiscard]]
и самое главное - с возможностью писать как можно меньше кода, получая при этом самое очевидно ожидаемое поведение, которое не надо потом часами дебажить в отладчике
Иногда складывается впечатление, что люди, которые создают очередного убийцу плюсов, вообще не особо пишут на плюсах, либо пишут что-то академическое, либо очень специфичные вещи.
Kelbon
cppfront это конечно мертворожденное творение и явно нигде в итоге не будет использовано, но всё что вы перечислили это не то что нужно С++, даже наоборот, это всё категорически нельзя делать
sgusev
Расскажите, пожалуйста, почему в C++ категорически нельзя делать стандартный менеджер пакетов, расширенную стандартную библиотеку и вменяемо оформленные ключевые слова?
eao197
Сначала было бы хорошо сделать стандартную систему сборки. Исторически этим никто не занимался, поэтому расцвело сто цветов, из которых до наших дней дошло несколько широко известных (CMake, meson) и некоторое количество неизвестных (в том числе и закрытых, которые за пределы родивших их компаний никогда не выйдут).
Стандартным менеджером пакетов так же никто изначально не озадачился (поскольку тогда и понятия-то такого и не было), поэтому расцвело сто цветов... И тем, кто дожил до сегодняшнего дня (в первую очередь vcpkg и conan) приходится адаптироваться к тому, что есть и CMake, и meson, и autotools, и...
В общем, тот факт, что к моменту, когда в ИТ массово осознали, что требуется стандартный менеджер пакетов, в мире C++ (и чистого Си) уже было такое многообразие, справится с которым не так-то просто.
Главным образом принцип развития языка: работа через комитет и предложения. И большинству тех, кто продвигает C++, за эту деятельность ничего не платят.
Но еще и тот факт, что C++ применяется в слишком разнообразных условиях. Можно вспомнить хотя бы наличие запрета на исключения во многих проектах. Расширять stdlib в таких условиях гораздо сложнее, чем в каких-нибудь Ruby или Python.
Человеческая природа: вкусы у всех слишком разные. Поэтому чтобы не выбрала некая группа людей в результате тяжелых и длительных обсуждений, всегда найдется другая группа, которой результат принципиально не понравится. Вне зависимости от самого результата.
Kelbon
Стандартный менеджер пакетов == монополия, монополия == плохо. Есть другие пути, которые и развиваются успешно
"расширенная" стандартная библиотека это непонятно что, видимо json и прочий мусор в std, это там не нужно, потому что устаревает каждый день и всё равно все будут использовать сторонние библиотеки по разным причинам
По ключевым словам вообще непонятно в чём претензия, [[nodiscard]] это вообще аттрибут, а не ключевое слово, аттрибут кстати может быть и [[ewruriweire3432432]], потому что это мать его аттрибут, там произвольное пишется
MiraclePtr
Стандартный - это не обязательно "допустимый к использованию только этот, и никакой другой".
Менеджер пакетов - это только лишь инструмент. Никто не запрещает использовать его со сторонними или вообще своими личными репозиториями пакетов.
JSON, который вы привели для примера а) сегодня используется вообще на каждом углу, вплоть до embedded-систем б) не устаревает уже с пару десятков лет, и проживет еще столько же.
То же самое про кучу других вещей, которых не хватает в стандартной библиотеке. Вам не нужно, тысячам других программистов - очень даже нужно. Особенно учитывая, что you don't pay for what you don't use, если лично вам не нужно - просто не используйте, оно не раздует вам код и добавит каких-либо неудобств, зато сэкономт время тысяч разработчиков по всему миру и сделает их код более предсказуемым и понятным.
eao197
Поскольку в C++ реализаций stdlib больше одной, то раздувание stdlib значительно увеличивает объем работы для мейнтейнеров этих самых реализаций. А это будет вести к тому, что в стандарте C++xx добавили что-то в stdlib, а до пользователей это доберется только спустя N лет, где N будет постепенно увеличиваться.
Так что нет, в мире C++ это не такой однозначный и убойный аргумент, как может показаться на первый взгляд.
MiraclePtr
Тут вполне работает классический подход, когда новые фичи, в процессе обсуждения их комитете для включения в новые стандарты реализуются и обкатываются в каком-нибудь Boost'е, а потом, когда все решено и утверждено, разработчики stdlib'ов могут взять и использовать уже готовые наработки.
Да это уже происходит именно так, даже не для "свистелок и перделок", а для базовых вещей из новых стандартов. Так что хуже уже не будет.
eao197
Или не работают как в случае с Executors TS и Networking TS.
Будет. Я вот, после долгих лет ожидания нормальной реализации C++98 в большинстве компиляторов, а потом ожидания нормальной реализации C++11 в большинстве компиляторов уже успел порадоваться тому, что было в C++14 и C++17. Но тут пришел C++20 и старые недобрые времена вернулись.
Поэтому лично я был бы только за то, чтобы в стандарт включали лишь фичи, которые уже готовы к релизу в большой тройке компиляторов. И если при этом stdlib остается прежних размеров, то ничего страшного. Компенсировать это можно развитием vcpkg и conan. А уж если бы вместо CMake что-то вменяемое стало стандартом де-факто для системы сборки, так ваааще. Но такого чуда, боюсь, я при жизни не увижу :(
Kelbon
json используют активно всего пару лет, он ещё и меняется (json5 всякий), через ещё пару лет его забудут, а С++ (просто напомню) уже >30 лет
то есть вместо того чтобы конкурировали собственно менеджеры вы разрешаете только делить всё на какие то большие репозитории с пакетиками, т.е. монополия на собственно сборщик этого всего остаётся
AnimeSlave
Вот ты и попался
eao197
В требованиях добавить json в stdlib самое интересное -- это отсутствие требований добавить туда еще и XML. Видимо, требователи просто слишком молоды, чтобы застать период тотального доминирования XML-я. И ведь XML до сих пор используется.
MiraclePtr
Во многих других языках XML-парсеры тоже есть в стандартных библиотеках, и ничего :) В стандарте C++ до недавних пор вообще была поддержка триграфов, на случай редактирования кода в терминалах где на клавиатуре нет символов {} и []. Спустя тридцать лет ее наконец-то удалили. Времена меняются, язык меняется вместе с ними.
На самом деле я соглашусь, что не обязательно тащить в стдлибу все подряд (JSON был просто упомянут как один из примеров очень часто используемого функционала) - как уже выше отметили, достаточно просто хорошего менеджера пакетов. Но товарищ выше сказал, что менеджеры пакетов тоже не нужны, поэтому we are trapped.
eao197
ТоварищЪ выше слишком экстремален даже для C++ного мира :)
Но в его суждениях есть здравое зерно: единый стандартный менеджер пакетов, если он будет централизованным и будет находиться под контролем какой-то одной организации (типа того, что сейчас мы видим в лице vcpkg и conan), не есть хорошо. Иногда включение обновлений для уже принятых в vcpkg/conan библиотек может занимать недели, буквально. Да, есть всякие способы кастомизировать vcpkg/conan под себя и не зависеть от глобального репозитория. Но это все-таки для других целей предназначено.
Плюс к тому, остается вопрос о том, что будет с тем же самым conan-ом, если спонсирующей его организации данное направление бизнеса покажется невыгодным. Повторится история biicode, из которого conan и вырос. А оно нам надо?
Вот если бы был придуман стандартный формат оформления пакетов и в состав компиляторов входил бы стандартный инструмент, который бы позволял вытянуть и собрать нужные зависимости... Вот это было бы да, круто.
Но, вроде как, в этом направлении работы не ведутся, либо про них особо не говорят :(
MiraclePtr
Ну я именно это и имел в виду. Менеджер пакетов - это именно общепринятый инструмент для скачивания и интеграции зависимостей (в стандартном формате), он должен быть опенсорсным, чтобы не зависеть намертво от какой-либо компании.
Репозитории пакетов, откуда он их будет качать, требуют определенных ресурсов и вложений, и поэтому да, они скорее всего будут поддерживаться какими-либо компаниями. Но на этот случай обычно есть возможность подключить свои кастомные репы, либо указать прямую ссылку куда-нибудь на github где лежит пакет. Как, например, это делается в Linux-дистрибутивах, когда какие-либо пакеты не принимают в mainline-репы или принимают слишком поздно.
Kelbon
суть в том, чтобы не было никакого "общего репозитория пакетов"
Kelbon
Сейчас коммитет как раз работает над стандартом описания для этих самых пакетных менеджров, чтобы и смейк и прочие базели научились читать некое "общее" представление и всё это многоообразие сборщиков будет иметь "общий язык"
eao197
Ну раз коммитет работает, то следует ожидать чего-то настолько же всратого, как система модулей из C++20 :(
Проще вряд ли получится.
AnimeSlave
Система модулей, кстати, не самое всратое из нововведений языка. Оно хотя бы работает. То, что оно не было сразу встроено в компиляторы, это жуткий косяк. И для критики, можно было бы, конечно, сделать что-то типа псевдонима, как в некоторых языках при подгрузке модуля, потому что синтаксически модули это просто другой вид include
MiraclePtr
Если вы по каким-то причинам не видите чего-то рядом с собой, это не значит, что это не используют другие . Сам JSON существует ещё с начала 2000-х годов, и активно и повсеместно используется уже с десяток лет, а то и больше, в том числе и в C++ мире.
json5 существует уже тоже с десяток лет, вот только он как раз не получил широкого распространения - все по-прежнему используют старый классический JSON, который ещё много лет никуда не денется.
domix32
Единственный источник пакетов - это монополия. А стандартный менеджер пакетов это очень даже отличный план. Сравните с прочими языками - python, js, ruby, go, да даже D .
eao197
А какой в вашей предметной области "типовой код"?
sgusev
Круды.
Ладно, конкретно с этим пунктом я погорячился - не в том смысле, что этого не надо делать, а в том, что я это упомянул, да ещё и в самом начале. Естественно, это не первостепенная проблема.
eao197
Круды не самая развитая область для C++. А вам для крудов чего-то не хватает именно в языке или в stdlib?
antonkrechetov
По-моему, вы не поняли, о чём этот отрывок из статьи про гругов.
sgusev
Когда я читал этот отрывок, я понял его так: сидят очень умные люди (я без сарказма), и рассуждают, как же нам вот так вот по всем математическим и логическим правилам организовать архитектуру языка, чтобы охватить все крайние случаи, и обобщить всё на свете. Потом придумывают свои идеальные с академической точки зрения решения, и выдают их в язык. Но в большинстве случаев программистам это не нужно, им не нужно даже задумываться о таких глубоких материях - дайте мне обычный метод, который берёт понятную структуру данных и выдаёт понятный результат, я вызову этот метод, получу результат и пойду дальше писать код, а не спотыкаться на каждой строчке, думая, а как вот тут с точки зрения теории программирования правильнее обработать данные. Просветите же меня, в чём я неправ, и о чём тот отрывок на самом деле.
В данном случае есть язык C++, у него есть проблемы, очередной очень умный человек пошёл эти проблемы решать, и в первом же своём идеальном примере показывает, что он там нарешал. А в примере возвращаемый функцией тип данных идёт не перед названием функции, а после - так, как делают все современные языки программирования, потому что так, несомненно, правильнее с точки зрения каких-нибудь там Теорий Построения Компиляторов или чего-нибудь такого же заумного. Но это никогда не было проблемой языка с точки зрения программиста, это не нужно было исправлять и тратить на это силы. Потратьте силы на более важные вещи, а
int main
пусть остаётсяint main
, а неmain: () -> int
.gatoazul
Это правильнее не с точки зрения Теорий, а чисто практически. Проще построить тип функции вроде "возвращающей массив ссылок на функции, возвращающие целое".
AnimeSlave
Ха-ха. Тогда вам стоит поменять язык. В C++ либо очевидное поведение, либо меньше кода за счёт "сахара".
Суть в том, что Саттер хочет того же что и вы. И именно поэтому начал разработку C++2
domix32
я конечно понимаю, что у многих до сих пор остался Сишный фетиш на спиральные определения, но не очень понимаю чего он так вас подрывает.
Человек сделал препроцессор, который избавляет от многих болезненных точек и добавляет больше безопасности, при этом не отламывая совместимость с плюсовым кодом. Ну то есть буквально
Ну, а про
Саттер - это человек который слишком любит плюсы, чтобы действительно писать убийцу плюсов. Фактически это Core Guidelines воплощённое в суперсете синтаксиса плюсов. Да ещё и экспериментальная, в первую очередь.
firehacker
Вот бы еще люди прозрели до осознания того, что отсутствие менеджера пакетов это не баг, а фича.
domix32
Так а что мешает использовать уже сейчас?