Комментарии 33
Статья звучит так, как будто именно С++ используется везде, хотя очень часто это все еще просто С
Если бы это был "C с классами", было бы значительно легче жить. Как раз к сложности синтаксиса C++ максимальное число претензий у C-программистов. Проще игнорировать классы и продолжать писать на чистом C, чем разбираться с тем обилием сущностей, которое появилось в C++ и её стандартной библиотеке. Лично я с большой неохотой фикшу что-то в OpenSource на C++, т.к. "не осилил" весь этот праздник жизни. Я не считаю язык C++ по умолчанию чем-то плохим, но лично для меня инструмент, который должен бы инкапсулировать сложность внутри своих механизмов, сам её и создаёт.
Да, вы совершенно правы, несмотря на то, что за очень редким исключением язык де-факто обратно совместим с C (про де-юре это сказать трудно, некоторый сишный код приводит к undefined behavior), у сообщества C++ сформировался специфический набор идиом и подходов, характерный только для этого языка. Так что хороший сишник вряд ли быстро станет хорошим плюсовиком, так же как и хороший плюсовик быстро не станет хорошим сишником.
С "другой стороны баррикад" могу сказать, что лично меня как раз раздражает необходимость обращаться с кодом в стиле "C с классами", или сишными библиотеками. Фундаментальные библиотеки обычно написаны именно на C, тому есть веские причины, и это одновременно проклятье и благословение. С одной стороны, нам не приходится изобретать велосипеды и отказываться от проверенных поколениями разработчиков библиотек, с другой - прикручивать к своему коду всё это довольно неудобно.
Для себя вывел такой подход к C++ чтобы упростить жизнь и не задумываться о нюансах 20 способов инициализации и 10 способах передачи аргументов:
Используем современный функциональный C++
Автоматический вывод типов везде где это возможно
Чистые функции с возвращаемым значением
Избегаем иерархий классов, наследования, и ручного выделения памяти.
Организация кода по принципу один файл - одна одноименная сущность в нем (class, namespace, function).
По умолчанию во всех параметрах используем специальные невладеющие типы-значения (например string_view x), если их недостает, то T const & (например string const &x).
Однако, в случае когда мы проектируем структуру данных или класс, то он непременно должен владеть своими полями, поэтому для их инициализации, в сетерах и конструкторах используем современную передачу по значению (T) с последующим перемещением.
Вполне разумно. Хочу уточнить только, что функциональное программирование, чистые функции и композиция вместо наследования -- это, в целом, весьма популярный подход к архитектуре ПО, применимый далеко не только к C++.
Дело в том, что в C++ долгое время был принят "эффективный подход", когда вместо возврата большого обьекта его записывали по мутабельной ссылке, например:
// Старый стиль C++
void collectApples(vector<Apple> &to) { ... }
Хотя уже лет пятнадцать как можно писать
// Современный стиль C++
vector<Apple> collectApples() { ... }
То есть, можно возвращать большие объекты как значения, даже без std::move, компилятор всегда оптимизирует это, и никакого копирования не происходит.
Но привычки сильны и целая куча людей до сих пор пилит как в 90-ых научились.
Поэтому и подчеркнул важность функционального подхода, препятствием для него является именно стереотип о неэффективности.
Не имею ничего против функционального подхода (я очень даже за), но, несмотря на то, что в простом случае оба варианта и реализованы примерно одинаково, вариант с аргументом чуть универсальнее: может записать данные в существующий массив, выделенный заранее. Могу ошибаться, но сомневаюсь, что RVO сможет понять, что мы хотим писать в существующий объект.
Попробовал даже забенчмаркать, результат с возвратом по значению довольно в два раза дольше :/
Так что лично я действую по ситуации. Вариант с возвратом выглядит чище и красивее, но иногда, при переиспользовании массива, лучше олдскульный вариант.
Немного подправил бенчмарк. Функциональный вариант, без попыток сложить все в один вектор, быстрее в две тысячи раз. Но может это компилятор заоптимизировал до вырожденного случая.
Выходит да, вы правы. Но я бы предпочел по умолчанию писать функционально, и прибегать к переиспользпользованию объектов только там где, ну очень, нужны оптимизации.
Извините, но вы оба считаете что-то не то...
Это первый вариант (оптимизированный):
std::vector<int> result(NumOfValues);
for (много раз) {
result.resize(NumOfValues);
std::iota(out.begin(), out.end(), 1);
}
Это второй вариант (оптимизированный):
for (много раз) {
std::vector<int> out(NumOfValues);
std::iota(out.begin(), out.end(), 1);
}
Вызов resize
не влияет совсем, так как размер вектора уже достаточный. Но во втором коде лишняя аллокация и деаллокация памяти, из-за чего второй вариант "типа" медленнее. Я поменял одну строку, результат эквивалентный: https://quick-bench.com/q/fSU3z97n18YhNGBgHmojrtW0Y0U
В своём бенчмарке я хотел показать, что передача по ссылке более эффективна, чем RVO, при условии, что где-то уже существует заранее выделенный контейнер (например, это статический thread_local объект).
Если же объект создаётся всегда новый - никакой разницы, конечно же, не будет.
упс, прошу прощения, в первый раз поторопился с ответом, не вглядевшись в код бенчмарка. Подправил ответ. Надеюсь, теперь получилось по существу >_<
Вот возьмем мы "Си с классами", и окажется, что там без темплейтов не выразимы такие полезные вещи, как коллекции/алгоритмы. Что, опять void* гонять, как в Си?
Потом выясняем, что неплохо было бы как-то упростить работу с памятью (умные указатели), а что-то позволяет сильно оптимизировать (move семантика), а можно и синтаксического сахара добавить... И пошло-поехало.
Я сишник, мне C++ сложен своими деталями и нюансами (здесь так, это сяк, а вот тут UB), но верхнеуровнево фичи языка я нахожу полезными.
Вполне понятно, почему и зачем появились темплейты и различные методы работы с памятью, я тоже считаю эти приобретения полезными для более высокоуровневого языка. Посыл моего предыдущего сообщения касался скорее сложности синтаксиса, а не набора фич как такового. Я, в принципе, не очень горю желанием разводить холивары на тему языка, на котором практически не пишу, каждый кодит как он хочет. Просто, по моему скромному мнению, синтаксис имеет значение. Например возьмём из доки пример с async и чуток его упростим:
X x;
auto a = std::async(&X::foo, &x, 42, "Hello");
a.wait();
Во многих других высокоуровневых языках такая же конструкция будет записана в виде:
await x.foo(42, "Hello")
Функционально разницы между этими двумя кусками практически никакой, синтаксически же -- пропасть. В связи с этим можно порассуждать об управлении памятью, скорости исполнения, легаси и прочих истоках языка, но это не отменяет факта, что я, как программист, слабо знакомый с C++, считаю первый вариант более уродливым, нежели второй, а потому менее склонен обращаться к такому языку, если у меня есть выбор.
К сожалению, стандартных типов для корутин ввести(как и добавить их поддержку в std::future) ещё не успели, если повезёт, появятся в С++23, если нет — придётся городить свои или пользоваться библиотекой Folly или чем-то подобным.
IMHO, тут принципиальная разница.
a.wait()
Заблокирует текущий тред выполнения, ожидая результата.
await x.foo()
Вернёт выполнение, это же корутина, и текущий тред сможет переключится на выполнение другой задачи в очереди, реализуя тем самым "green thread".
Интересно, почему статья ушла в минус
Потому что пора переходить на Хаскель (-:
/s
на ржавого
Полумеры!
Кстати, почему Golang c 13 на 18 место упал в рейтинге?https://www.tiobe.com/tiobe-index/
Все побежали программировать на Python? Нанометры рулят, кого волнует рантайм перформанс?
Мое мнение, потому что статья вообще не понятно о чем. Что-то кто-то сказал. По 3 раза повторы. Вода. Реферат.
Потому что цель статьи по большей части реклама своих курсов
C++ вокруг больше чем нужно, но в основном в прикладном программировании.
И в чем смысл переводить статью годовалой давности? Так то C++20 уже принят и полным ходом разработка C++23
Язык программирования C++: Как он незаметно стал основой всего, и его ближайшее будущее