Pull to refresh

Comments 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 сможет понять, что мы хотим писать в существующий объект.

Попробовал даже забенчмаркать, результат с возвратом по значению довольно в два раза дольше :/

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

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

Нет, такого быть, конечно, не может — тут явно сработала оптимизация, компилятор ведь видит, что переменная не используется. Добавление в обоих случаях benchmark::DoNotOptimize возвращает пример на круги своя

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

Извините, но вы оба считаете что-то не то...

Это первый вариант (оптимизированный):

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 объект).

Если же объект создаётся всегда новый - никакой разницы, конечно же, не будет.

упс, прошу прощения, в первый раз поторопился с ответом, не вглядевшись в код бенчмарка. Подправил ответ. Надеюсь, теперь получилось по существу >_<

UFO just landed and posted this here

Вот возьмем мы "Си с классами", и окажется, что там без темплейтов не выразимы такие полезные вещи, как коллекции/алгоритмы. Что, опять void* гонять, как в Си?

Потом выясняем, что неплохо было бы как-то упростить работу с памятью (умные указатели), а что-то позволяет сильно оптимизировать (move семантика), а можно и синтаксического сахара добавить... И пошло-поехало.

Я сишник, мне C++ сложен своими деталями и нюансами (здесь так, это сяк, а вот тут UB), но верхнеуровнево фичи языка я нахожу полезными.

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

X x;
auto a = std::async(&X::foo, &x, 42, "Hello");
a.wait();

Во многих других высокоуровневых языках такая же конструкция будет записана в виде:

await x.foo(42, "Hello")

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

С++20 спешит на помощь, и ещё усложняет ядро языка, вводя бесстековые корутины, так что теперь писать очень похожий код тоже можно.

К сожалению, стандартных типов для корутин ввести(как и добавить их поддержку в std::future) ещё не успели, если повезёт, появятся в С++23, если нет — придётся городить свои или пользоваться библиотекой Folly или чем-то подобным.

Спасибо, интересно. Быстро просмотрел картиночки в статье, весьма обнадёживает, буду следить за развитием корутин в плюсах.

IMHO, тут принципиальная разница.

a.wait()

Заблокирует текущий тред выполнения, ожидая результата.

await x.foo()

Вернёт выполнение, это же корутина, и текущий тред сможет переключится на выполнение другой задачи в очереди, реализуя тем самым "green thread".

Сильно зависит от реализации. Для javascript, например, не принципиально.

Интересно, почему статья ушла в минус

Потому что пора переходить на Хаскель (-:
/s

Кстати, почему Golang c 13 на 18 место упал в рейтинге?https://www.tiobe.com/tiobe-index/

Все побежали программировать на Python? Нанометры рулят, кого волнует рантайм перформанс?

Мое мнение, потому что статья вообще не понятно о чем. Что-то кто-то сказал. По 3 раза повторы. Вода. Реферат.

Единственное, что нового узнал - что Страуструп партнёр в Морган Стенли.

Научат ругаться матом, там много русских, у меня был офер от Нью Йоркского Морган Стенли, собеседовали все русские.

Потому что цель статьи по большей части реклама своих курсов

UFO just landed and posted this here

C++ вокруг больше чем нужно, но в основном в прикладном программировании.

И в чем смысл переводить статью годовалой давности? Так то C++20 уже принят и полным ходом разработка C++23

И c каждым новым стандартом всё надеяться, что std::future станет пригодной для практического использования, чтобы корутины заработали "из-коробки", и без Фейсбучных Folly. К слову, std::future появился ещё в C++11, но был мало пригоден.

Sign up to leave a comment.