Pull to refresh

Comments 404

Логика у человека простая: языки, на первый взгляд, довольно близкие и почти обратно-совместимые, базовый синтаксис одинаков, про ООП кандидат что-то слышал, и значит, основная база уже есть и он сможет легко освоить C++ за 21 день в процессе работы, поэтому можно наплести про "с C++ тоже работал", начать писать на "Си с классами" и все получится.

Мне кажется, что такая логика может встречаться только у совсем уж новичков и я сомневаюсь, что хоть кто-то из более-менее опытных программистов на C может так думать или не знать про кардинальные различия между данными языками. Из перечисленного вами списка, многое может не использоваться намеренно, а не потому, что об этом не знают.

В общем очень спорные утверждения.

UFO just landed and posted this here

Как "программист на С" (в том числе) я подтверждаю. Для меня "современный С++" - тьма непроглядная, в которую мне вникать и вникать. Rust, кстати, в этом смысле проще.

Приходилось "поддерживать код" на новых С++ - писал именно как указанно в статье (ну кроме new/malloc - но только и-за того что неправильно смешивать парадигмы управления памятью в одной кодовой базе)

100% согласен. Поэтому для меня, как для Си-шника, Java, как ООП-продолжение, намного приятней, чем современный C++.

Подтверждаю. Я даже как начинающая студентка после относительного освоения C, плюсы были сложнее в освоении, чем понять концепции Rust и начать писать на нем. Хотя и понимаю, что работу найти пока будет сложнее.

На месте директора я бы сразу уволил такого менеджера в 24 часа, с пометкой "Больше его к техническим специалистам не допускать". И мне все равно, что всего за один дурацкий заголовок.

UFO just landed and posted this here

Я скорее энтузиаст, и предпочитаю использовать максимум API нежели готовые framework-и. Да, я художник, а не биоробот и с такими работаю максимум подрядом. Потому что не работаю с помешанными на перфекционизме диктаторами. Как я уже сказал, без зазрения совести такого менеджера нельзя допускать к разработке, даже если он карьерист и уже Team Lead. Либо ваш диструктивный подход скорее вредит разработчикам, чем помогает. И о вас уже есть целая статья.

Вот когда станете в ходе работы над собой более конструктивным, научитесь созидать, а не разрушать, то тогда можете написать, предложите интересный сложный проект, может и поработаю с вами. Я уже к тому времени проект выложу, над которым работаю сейчас.

UFO just landed and posted this here

>.... когда станете в ходе работы над собой более конструктивным

imho, все просто, это статья написана не для вас (и не для меня), честно говоря не совсем понимаю для кого она, но это не повод для волнений, опыт всегда прав, но у разных людей в разное время :)

Действительно, полыхает, но от того, что половина списка — карго-культ:


Используете простые указатели на функции вместо std::function;

Это очень разные инструменты для очень разных случаев. Советовать слепо применять аллоцирующий полиморфный враппер независимо от того, нужен ли он — это шикарно само по себе, даже если забыть о том, что половина случаев с указателями на функции — это интероп с C и std::function туда не пролезет при всем желании.


Используете простые enum вместо enum class;

Если у вас enum'ами задаются конкретные константы, которые часто надо куда-то передавать в числовом виде (тот же интероп), то от enum class больше попаболи, чем пользы.


Для функций, не кидающих исключения, не используете noexcept.

А вы можете доказать, что не кидающих? И что все, используемое в этих функциях, тоже некидающее? И используемое используемым all the way down? noexcept уместен в специальных функциях и критических местах, в остальных — опять же, больше попаболи, чем пользы.


Для конструкторов забываете explicit.

Для всех подряд?


Для деструкторов забываете virtual

Для всех подряд?


Если вам нужно вернуть из функции несколько разных значений (например, результат работы и/или код ошибки)

+ 21. Если вы используете коды ошибок вместо исключений.

UFO just landed and posted this here
Статья есть лишь краткое перечисление пунктов, а не огромный талмуд описывающий подробно все возможные случаи и границы применения.

Вы пишете "Как различить C и C++-разработчиков" и приводите безапелляционный список "делай так, а не так". А потом дети и сишники начитаются и начнут лепить explicit и virtual куда ни попадя, потому что "это С++, так в интернете написано".

UFO just landed and posted this here

Если Вы приходите в проект, то необходимо применять приемы, уже принятые ранее в проекте. Иначе Ваш код будет выглядеть кривой заплаткой на корпусе корабля.

UFO just landed and posted this here

Я хотел показать, что поиск кристально-чистого С++ - cника сродни поиска сферического коня. Многие коллеги в своем роде "мутанты" между этими двумя языками. И мне кажется намного важнее в случае рассмотрения кандидатур знание/умение применить перечисленные приемы, чем слепое безапелляционное следование им "я_типа_тру_С++". То есть важно смотреть не то, что по умолчанию применил человек при решении теста, а его способность решить поставленную задачу "с применением таких-то вещей".

UFO just landed and posted this here

Ну возможно я что-то и упустил из Вашей статьи, начитавшись комментариев. ))

Советовать слепо применять аллоцирующий полиморфный враппер независимо от того, нужен ли он — это шикарно само по себе

std::function аллоцирует только при наличии контекста (например, захвата из лямбд). Полным аналогом void set_callback(std::function<int(int)>&& cb) в чистом си будет void set_callback(int(cb*)(int), void* ctx, void(*ctx_deleter)(void*)); где ctx в общем случае тоже надо аллоцировать, ну и не забыть удалить. Так что совет "используйте std::function вместо коллбеков когда можете" более чем валидный.

UFO just landed and posted this here
Бывают каллбеки с контекстом, бывают без контекста.
Если он там есть — естественно, std::function проще, лучше и понятнее, чем любой ручной колхоз.

Однако, оверхед там есть и он далеко не нулевой, даже с оптимизациями. И noexcept, как автор рекомендует в 12 пункте, уже не прицепишь.

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

Однако, оверхед там есть и он далеко не нулевой, даже с оптимизациями. И noexcept, как автор рекомендует в 12 пункте, уже не прицепишь.

Присмотритесь. В примере bar() отлично оптимизируется в noop, пара доп. инструкций в foo() и call std::__throw_bad_function_call() это проверка функции против null (то, чего сишная версия не делает, а если делать, то оптимизируется и там и там) а typeinfo сгенерился для всяких стектрейсов и убирается с --no-rtti. По сути весь оверхед сводится к заполнению std::function при инициализации, да и это важно только если ваш метод вызывает коллбек никуда его не сохраняя

Если против прыжка на NULL меня страхует MMU, то я не хочу тратить на проверку на NULL отдельные инструкции. Си позволяет их не тратить; а если хочется тратить, то позволяет и тратить.

если в конкретном случае вы замерили перф и оказалось что да, лишний if в std::function тормозит, то конечно же, делайте на указателях. Вот только под общий случай такой кейс вообще даже близко не подходит. Ну и от себя добавлю, что на практике я куда чаще сталкивался с недостаточно гибким интерфейсом (и костылями как его следствием), чем с тормозами в хорошо предиктящемся if

Вынужден вас расстроить: по крайней мере часть компиляторов вставляет эту проверку принудительно,.им им пофигу, что адрес 0x0 находится в секции с флагом x

UFO just landed and posted this here
А есть объяснение, почему такую проверку не добавят в clang-tidy?
clang.llvm.org/extra/clang-tidy/checks/bugprone-exception-escape.html ругнётся, если увидит явное кидание исключения (т.е. функции должны быть в header), но вызов `noexcept(false)` функции — не ошибка.
Казалось бы, проверить noexcept() всех вызываемых — легче, чем анализировать их код.
Ну пометь `// nolint`, статический анализ — штука с ложными срабатываниями, но это не значит, что надо выключать все проверки.

Если у вас enum'ами задаются конкретные константы, которые часто надо куда-то передавать в числовом виде (тот же интероп), то от enum class больше попаболи, чем пользы.

Можно ли этот тезис раскрыть более подробно?

Тезис уже достаточно подробно раскрыт в других комментариях, но ок.

Проблема простых enum'ов в отсутствии скопа и неявных преобразованиях, позволяющих смешивать разные enum'ы.
Комитет в своей бесконечной мудрости ловко решил обе проблемы одним выстрелом, добавив enum class.
Однако, это две сильно разные проблемы: отсутствие скопа — это (почти) всегда плохо, а вот неявное преобразование — плохо не всегда, однако его оторвали совсем и нельзя даже opt-in.

Традиционно enum используется для определения констант, как окультуренный аналог #define. Скоп здесь весьма уместен, дабы не пихать префиксы прямо в имена констант, и с легкостью внедряется банальным поиском и заменой. А вот с отстуствием неявного приведения типов все гораздо хуже: чтобы не писать static_cast на каждый чих, тип этого enum class надо протаскивать везде, где он используется, что в любом проекте сложнее helloworld порождает вопросы «а стоит ли оно того?». Проще обычный enum в struct или namespace положить.

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

Для понимания проблемы попробуйте переделать вот этот достаточно простой и идиоматичный код на enum class, желательно, не меняя слишком много (в реальном проекте миллион строк) и сохраняя идиоматичность (с кодом работает более одного человека):

enum flags
{
	flag1 = 0x1,
	flag2 = 0x2,
	flag3 = 0x4,
};

int main()
{
	auto state = 0;
	if (some_condition)
		state = get_some_external_state();
	cout << "state is " << hex << state;

	if (state & flags::flag1)
	{
		// something	
	}

	if (state & flags::flag2)
	{
		// something else
	}

	set_some_external_state(state | flags::flag3);
}

 а вот неявное преобразование — плохо не всегда

Это плохо всегда.

Традиционно enum используется для определения констант, как окультуренный аналог #define.

Традиционно где? В чистом Си?

Если мы говорим именно про C++, то в C++ использование унаследованных из Си enum-ов бесполезно чуть меньше, чем полностью (особенно после появления C++11 с enum class).

А дабы проблем, которую вы проиллюстрировали, не было, в C++ (еще со времен добавления в него namespaces) можно было делать так:

namespace flags {
  typedef unsigned short type;
  const type flag1 = 0x1;
  const type flag2 = 0x2;
  const type flag3 = 0x3;
}

Получаем и скоуп, и фиксированный тип, который скрывается за "перечислением". И имеем те самые неявные преобразования, которые выходцам из Си так нравятся.

Самый простой способ сделать нормальные флаги в с++ - std::bitset. Можно сделать тонкую обертку для enum class, переопределив operator[]. А все эти битовые трюки с флагами-степенями двоек были не от хорошей жизни придуманы.

Тем не менее, когда с таким приходится работать, то почему бы и нет. Тем более, что встречаются флаги, в которых должно быть выставлено сразу несколько битов, типа 0x82 или 0x71. Ну и до C++11 в заголовочных файлах константы через std::bitset было не задать, разве что вместо констант писать inline-функции, которые возвращали бы экземпляры std::bitset.

Тем более, что встречаются флаги, в которых должно быть выставлено сразу несколько битов, типа 0x82 или 0x71

так у bitset'а есть конструктор от числа, можно в обертку добавить конструктор от initializer_list<my_enum>, вариантов уйма

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

Опять же, если речь заходит о старом коде, значительная часть которого написана во времена C++98, если не раньше, то там таких продвинутых фич не будет.

Тип то фиксированный, но общий и вполне интегральный.

Его наличие никак не помешает написать, например,

tCounter.store ( std::memory_order_release );

вместо

tCounter.store ( 0, std::memory_order_release );

Это бы простейший пример того, как в старых плюсах (старых -- это времен 1993-1994 годов, когда стандарта еще не было, а вот namespaces уже в компиляторах стали появляться) получить все тоже самое, что дает C-шный enum, но с парой дополнительных плюшек.

Чтобы обеспечить большую безопасность по типам в тех же старых плюсах (но уже когда там поддержка шаблонов появилась) можно было бы использовать шаблоны. Типа:

namespace type_safety {
  template<class Scalar, class Tag>
  class scalar_value {
  	...
  public:
    explicit scalar_value(Scalar v) ... {}
    ...
    Scalar raw() const {...}
    ...
    // Полный фарш включая перегрузку различных операторов.
 };
}

namespace flags {
  struct flags_tag {};
  typedef type_safety::scalar_value<unsigned short, flags_tag> type;
  ...
}

Правда, у этого подхода были существенные недостатки.

Во-первых, все эти шаблоны негативно сказывались на скорости компиляции, а некоторые компиляторы при обилии шаблонов в коде могли тупо падать.

Во-вторых, нельзя было просто так в заголовочном файле размещать инициализированные константы для flags::type, т.к. можно было получить ошибку линковки.

Но там, где безопасность по типам была нужна, на такое вполне себе шли.

В старых плюсах у C-шного enum-а была большая область применения вот в таком контексте:

struct demo {
  enum { value = 1 };
  ...
};

без необходимости бодаться со static-полями и их инициализацией.

Но в современном C++ для всего этого уже есть чисто C++ные решения. Так что какую пользу от С-шного enum-а можно получить, например, в C++17 -- это лично для меня большой вопрос.

Мне кажется, плюсовики - это такая секта.

Они готовы не только спросить о принципах ООП, но и уточнят в каком текстовом редакторе вы обычно работаете. Потому что не все из них "правильные".


Ну а навыком кодить на бумажке должен обладать каждый уважающий себя программист.

UFO just landed and posted this here

>12. Используете простые enum вместо enum class;

К сожалению, enum class - неполноценная замена enum: в том же QT enum используются для хранения флагов и констант из них/проверок на их наличие, и этого нельзя достичь с использованием enum class без повсеместного ручного приведения типов. Хуже всего, что нет нормального удобоваримого способа достать из него значение (без приведения типов или третьестопных малоизвестных шаблонов).

>15 Если вам нужно вернуть из функции несколько разных значений (например, результат работы и/или код ошибки), то одно из них вы возвращаете через return, а другое - по указателю или по неконстантной ссылки, вместо использования std::optional, std::pair/std::tuple (особенно хорошо в паре со structured binding) или просто возврата struct;

Есть ещё альтернативный вариант - сделать для этой функции класс, передавать параметры к ней как конструктор/использовать setter'ы, забирать результат через getter'ы. Очень удобно, когда хочется вернуть много отладочной информации и при этом не замусоривать синтаксис основного применения.

UFO just landed and posted this here

И это будет из пушки по воробьям. Синтаксический сахар должен уменьшать объем написанного кода, а не увеличивать его.

То есть, как у вас в конце статьи и написано: надо смотреть по конкретной ситуации.

UFO just landed and posted this here

Можно перегрузить &, |, &&, ||, ^, &=, |=у этого enum'а и работать с ними как раньше, или даже навернуть темплейт сверху для более универсального решения.

Можно, но зачем?

А я скажу, что после этого код превратится в легаси и в чем-то буду прав.

Если завтра к вам придет обычный разработчик, а не фанат плюсовой магии, как вы ему объясните, зачем, это было сделано?

UFO just landed and posted this here

Не, вы меня не поняли.

Обычный разработчик (не фанат плюсоизмов), на большинстве языков из топ10 не сможет или не будет это делать. Он просто напишет функцию is_read_only().

Как вы этому человеку объясните зачем вы это делаете:

Можно перегрузить &, |, &&, ||, ^, &=, |=у этого enum'а и работать с ними как раньше, или даже навернуть темплейт сверху для более универсального решения.

UFO just landed and posted this here

А код с бизнес-логикой писать хоть один из этих людей будет?

  • Один отстреливает себе ноги.

  • Другой расширяет язык, чтобы он в неочевидном месте неочевидным способом семантически был похож на другой язык

  • Третий разбирается в причинах трехэтажных ошибок шаблонов из-за того что он написал bit | flag для типа которому оператор | не перегружен его коллегой сверху

  • Последний спорит в комментах на хабре

UFO just landed and posted this here

Нет, нельзя, скрпы эмбеддеров это запрещают!

Шутка. Я бы конечно все просто писал на расте. Я пробовал и под МК без MMU писать и под арм64, все отлично, удобно и комфортно.

Просто есть отдельная часть людей, которые считают что под МК надо писать только на Си, а под армы с MMU - на С++. Причины и доводы, как обычно, те же самые, но вешать ярлыки не будем.

Просто почему-то этих людей совершенно не смущают известные и успешные проекты, которые были сделаны не на С/С++.

А как бы мы жили без Java Card - вообще не понятно.

Ещё бы был у это Раста синтаксис адекватный а не вырвиглазный.

В общем С++ лучше, к тому же с активным развитием современных стандартов вряд ли Раст особо сможет потеснить ++, т.к. и там приемлемо безопасно и адеквантый синтаксис схожий с Java и C# - а это большой плюс при выборе яп.

Например когда Джавистам или Шарпистам потребуется высокая производительность - вряд ли они выберут Rust, но выберут С++ т.к. им его будет значительно проще освоить.

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

Ещё бы был у это Раста синтаксис адекватный а не вырвиглазный.

А что конкретно, по вашему, не так с синтаксисом Rust?

UFO just landed and posted this here

Скорее, непривычный в мире языков с C-подобным синтаксисом.

Я же спросил конкретно. А то и я могу сказать, что у C++ вырвиглазный синтаксис.

я написал своё мнение, т.к. лично для меня чем дальше язык по синтаксису от Си++ тем он уродливее (именно от ++, а не от родоначальника синтаксиса – Си). В т.ч. мне не нравятся и Питон например. Но нравятся Джава, Шарп и Делфи (хоть он и не из Си семейства, но за счет многословности – а многословность неплохо описывает сам язык без документации, читать Делфийский код можно без предварительного изучения я.п, что не скажешь о Расте или Питоне например).

А вот Си мне не нравится например, причем сильно. Потому что на больших проектах (например Gimp) или там OpenVPN хорошо видно что языку не хватает того что есть в ++, в следствии чего например усложненные интерфейсы по сравнению с ++ (по кол-ву входящих параметров), обилие похожих друг на друга интерфейсов, отличающихся префиком/постфиксом (т.к. нет шаблонов и перегрузок - то часто на каждый тип входящих параметров или возвращаемых значений делают отдельный интерфейс).

UFO just landed and posted this here
Можно перегрузить &, |, &&, ||, ^, &=, |=у этого enum'а и работать с ними как раньше

Можно, но «как раньше» не будет.
Если вы, например, перегружаете & для некоторого enum class, моделирующего битовые флаги, то из оператора вы, вероятно, тоже возвращаете этот же тип, а не какой-то голый int, иначе ради чего это все. Однако, enum class неявно не конвертится ни в bool, ни в int и добавить к нему это нельзя, поэтому вместо

if (Value & my_flags::foo)

придется городить

if ((Value & my_flags::foo) != my_flags::zero_flag)

или вообще

if (flags::is_set(Value, my_flags::foo))

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

enum class — это «хотели как лучше, а получилось как всегда».

Вообще не особо понятно, зачем автор предлагает из монструозного и сложного С++ костылями в одном месте сделать простой и лаконичный С, если до этого целую статью объяснял какие сишники смешные, что не хотят менять свой работающий подход кодирования определенных вещей (совместимый по стилю и семантике с другими популярными языками) на очередной новый стиль С++420.

UFO just landed and posted this here

Можно перегрузить "!" 😂

enum class E { Zero, A };

bool operator!(E e) {
    return e!=E::Zero;
}

int main() {
    E e = E::A;
    if (!!e) {
        return 1;
    }
}

В Qt есть Q_FLAGS/Q_DECLARE_FLAGS для генерации типобезопасной обертки над enum class описывающим флаги.

UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here

Бьярне (Bjarne); Bjørn -- тоже существующее имя, но другое.

UFO just landed and posted this here

интересно, через сколько в комментарии к статье придут любители Rust?

Явная провокация. Ну вот к чему это было? Не зря в комментариях в основном критическое восприятие статьи.

UFO just landed and posted this here

C: gettimeofday(), clock_gettime() . Уверен, что и другие способы найдутся.

Обе эти функции не из стандартной библиотеки C, на Windows, например, они не существуют.

Но по факту, time уже почти ни кто не использует. Кроме уж совсем простых случаев. Если есть возможность, используют clock_gettime, т.к. только он возвращает MONOTONIC. Если реализация POSIX уж слишком древняя, мирятся с gettimeofday и возможным дрифтом часов, но зато хоть миллисекунды получаю

На Windows наверняка тоже есть способы. Легкий Гуглинг показывает GetSystemTime, GetSystemTimeAsFiletime и GetSytemTimeAsPreciseFileTime. И для монотонного времени QueryPerformanceCounter и QueryPerformanceFrequency .

В общем, начинает казаться, что уж лучше пусть будет несколько функций в стандартной библиотеке, чем несколько в нестандартной.

Кстати, POSIX - это стандарт, или не стандарт?
Формально - стандарт. А по факту :-(

А по теме: мне давно кажется, что в индексе TIOBE на четвёртом месте находится не C++, как там написано, а совершенно другой язык — C/C++. Допустим, мне понадобился SQL-движок с максимально шустрыми кастомными агрегирующими функциями. Я беру SQLite. При этом, в обвязочном коде используются и классы, и шаблоны, и ещё кой-чего, и в целом получается, что модуль написан на C++. Но на самом деле меня интересовал способ воспользоваться чисто сишной библиотекой, а эту архитектурную астронавтику из chrono оставьте себе, спасибо.

Ещё мне кажется, C++-ные пуристы со своими наездами не понимают, до какой степени их язык паразитирует на сишном фундаменте. Убери его — и язык из топа быстро скатится на дно рейтинга. Рядом есть другие языки и у них стандартные библиотеки не такие упоротые.

Я настолько еретик, что пишу в резюме C/C++/C#

У меня есть пара проектов, где в C# unsafe ставится целиком на классы, причём почти на все. Вот там C/C++/C# вполне валидная категория )))

А я наоборот. Пытаюсь засунуть привычные мне реализации ООП C# в язык Си. В основном, это работа со строками. C# unsafe я тоже вполне применял в одном проекте.

Используете C-style массивы вместо std::array;

А это как? Ведь передача `int a[]`, `int a[20]` и даже `int a[static 20]` это всего

лишь передача указателя, как можно передачу указателя перепутать с передачей самого массива?

Используете функции из stdio.h вместо ... потоков ввода-вывода

Так потоки ввода/вывода в C++ были всегда тормознее Cшных вариантов. А умение clang/gcc проверять строчку форматирования во время компиляции сводило преимущество в типизации к нулю. Что-то изменилось?

UFO just landed and posted this here

3) умеет в итераторы, то есть совместим с range-based for и с алгоритмами из <algoritm>

Обычный массив разве не умеет и не совместим?

int t[20];
for (auto &item : t) std::cin >> item;
std::cout << *std::max_element(std::begin(t), std::end(t));
UFO just landed and posted this here
А это как? Ведь передача int a[], int a[20] и даже int a[static 20] это всего лишь передача указателя, как можно передачу указателя перепутать с передачей самого массива?

Не совсем. Можно делать так:


template <class T, size_t N>
void Test(const std::array<T, N>& arr);

template <class T, size_t N>
void Test2(const T (&arr)[N]);

Так потоки ввода/вывода в C++ были всегда тормознее Cшных вариантов.

Дело даже не в тормозах, а в такой неприятной штуке, как глобальное состояние, которое приводит к широкому спектру ошибок:
https://stackoverflow.com/questions/2273330/restore-the-state-of-stdcout-after-manipulating-it


Ну и достаточно уникальная реализация потоков в C++. Вызов printf хорош своей универсальностью: во многих языках он работает одинаково. Альтернатива printf — позиционные аргументы (типа print "i = {i}, j = {j}"). А вот аналога C++ потоков нигде нет.

UFO just landed and posted this here

опытный плюсовик-затейник, который будет активно внедрять best practices

Переход на Java, Python или Javascript?

UFO just landed and posted this here

Ну если память не экономим и скорость не оптимизируем (или оптимизируем?), то можно хоть PHP, нет?

Скрепы плюсовиков-эмбеддеров это категорически запрещают

C# вот отлично работает в embedded. Пробовал на плате с 4ядерным ARM процессором и 2 Гб оперативы. Нужно было сделать MVP, который потом подрядчик не сильно улучшил переписав на C++. Работать стало конечно быстрее, но конечный потребитель разницы не заметил.

UFO just landed and posted this here

а когда у вас 500 мегагерц и 256 мегабайт, то шикануть уже не получиться

У меня когда-то был компьютер с amd k6-2/500 и 128 мегабайтами памяти. Так вот там был сайт на пхп с mysql, еще кучка сервисов, которые обслуживали около сотни человек.

Современный ПХП еще в несколько раз быстрее той пятой версии, что я тогда использовал.

Как-то вы совсем недооцениваете возможности железа.

Только за продшие 20 лет не только интерпретаторы и компиляторы стали эффективнее, но и прикладной софт разбух до неимоверных масштабов.

Вы сегодня на 256 метрах ОЗУ вряд ли классический LAMP стек с какой-либо существенной полезной нагрузкой сможете развернуть.

2 ГИГА ОЗУ в Эмбеддед.

Чтобы я так жил.

Да вы там Ноду, прости господи, ДжиЭс можете использовать.

А у меня 32 КиБ ОЗУ и лапки.

Хм, в моём мире Embedded - это встраиваемые решения. Типа банкомата, бортового компьютера и прочее, на них может работать линукс. 32 кб ОЗУ - это микроконтроллер

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

Вот вы смеётесь, а существует и довольно популярен python для микроконтроллеров: MicroPython

На нём довольно удобно писать всякое для квест-комнат.

>опытный плюсовик-затейник

как-то не пришлось таких встречать, приходилось видеть:

либо опытный плюсовик,

либо плюсовик-затейник,

либо опытный затейник,

но это конечно не значит, что такое животное не существует, просто пока не обнаружено :)

как обычно, imho

UFO just landed and posted this here

Скорей как начало анекдота. Возможно даже про поручика, да.

Понятный и надежный код, Embedded Linux, C++ - вы серьезно это все поместили в одно предложение? Особенно применительно к микроконтроллерам и штукам класса зарядка для электромобиля? Где гораздо уместнее смотрелось бы RTOS+MISRA C хотя бы...

UFO just landed and posted this here

Если в самой зарядке RTOS тогда вопросов нет, коммуникационный узел на Linux как раз понятно, что будет оптимальным. Я просто попробовал представить себе Linux, управляющий 30 кВт зарядкой, особенно когда что-то пошло не так.

Наконец из вашего текущего комментария стала понятна простая вещь. Вы пишете под Linux. В область Embedded разработки входит еще и огромный пласт разработчиков под мк и их вы не убедите использовать C++ в своих проектах, ибо под всеми контейнерами уже не поймешь сколько у тебя памяти и на что ушло. Напишите в шапке или вначале статьи что вы имеете ввиду только Linux и половина хейта к вам отвалится автоматом. Под камень с MMU всем очевидно что нужно пользоваться всеми прелестями готовых плюсовых контейнеров.

UFO just landed and posted this here

Да, вы правы

Я сам каким-то чудом упустил этот момент. Попробуйте жирным выделить (и убрать Embedded), но видимо это и правда не поможет

"Умом я понимаю что лапа Шарика на плече, но ср@ть оставиться уже не могу."

(с) анекдот.

Я однажды заглядывал внутрь с++ фреймворка Mbed для STM32, когда оказалось, что изменение GPIO пина с Input на Output занимает на нем 600+ тактов... Всего-то несколько слоев классов, да.

Ни разу именно эту штуку не видел и говорить предметно не могу, но рискну предположить, что не в классах и количестве их слоёв дело, скорее — в том, какие именно абстракции используются и сможет ли компилятор их оптимизировать или нет.

Идиоматичные обёртки на C++ могут быть вполне себе zero-cost, на хабре есть несколько статей на тему, вот одна из них.

С другой стороны, (не знаю, как сейчас, но как минимум несколько лет назад) STMовский HAL был весьма тормозящей либой, даром что сишной и даже написанной производителем железок.

Вот это пояснение что, где и как используется очень не хватает в статье. Без этого пояснения статья выглядит как минимум тонким троллингом и слегка провокацией.

UFO just landed and posted this here

>Понятный и надежный код, Embedded Linux, C++ - вы серьезно это все поместили в одно предложение

встречается, но не часто, редко бывает оправдано, и дело не в количестве протоколов, которые к C++ прямого отношения как правило не имеют, а скорее в сложности взаимодействия большого количества процессов в реальном времени, типа система из сотни устройств координирует работу, приходилось участвовать, удовольствие ниже среднего,

как обычно imho

ps

>представить себе Linux, управляющий 30 кВт зарядкой, особенно когда что-то пошло не так.

как насчет spacex в случае когда что-то пошло не так?

Ещё и с аппаратным Watchdog'ом... ;))))))))))))))))))))))

Хм... Оказывается, я "матерый плюсовик". С большинством из пунктов я согласен, но с некоторыми конкретно хотел бы поспорить.

Реально я "олдскульный плюсовик". На "чистом С" почти не писал, только в студенчестве, в самом конце 80х. Основной опыт - середина 90х - конец 2009, подавляющее время работы писал на MSVC++, в то время с библиотекой MFC. Сейчас много нового, смотрю, но, изучать уже не буду... Вобще от программирования отошел...

Похоже комментаторы выше рассматривали 20 пунктов как совет к действию, это же просто шаблон для определения си с классами. Поэтому можно согласиться с каждым пунктом, но при этом и использовать исключения из каждого. Статья полезная, но уж слишком много провокаций.

Ощущение что это очень тонкий троллинг. И возможно даже опасный для юных умов что не начали толком баловаться с ++ но уже подумывают об этом.

UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
В Quake3 от C++ только классы но хуже он не стал. Уже сто раз видел проЭкты с невероятными мета гуру C++ которые через пол-года убегают оставляя после себя «чукча писатель не читатель».

Мне кажется, Q3 это одновременно очень плохой и очень хороший пример.

Очень плохой он потому, что при мысли об исходниках Q3 на ум в первую очередь приходит Fast inverse square root — ведь именно там же он впервые широко засветился. Такие вещи Кармак и компания делали явно не от хорошей жизни — компьютеры в те времена были намного слабее нынешних. И, следовательно, даже тот копеечный оверхед, который даёт, например, стандартная библиотека, мог играть для них принципиальную роль. Даже сильно позже, намного ближе к нашим дням, я обращал внимание, что писатели движков косо поглядывают на нововведения именно из-за привычки считать такты. Что поделать — эти люди сами выбрали мученическую стезю )) Но для нас, для большинства, эта разница не должна играть большую роль, а значит оглядываться на игропром, тем более такой старый, не очень продуктивно. Жизнь заставит — и на ассемблере будешь программировать!

А хорошим его делает… скажем вежливо, напоминание, что ЯП, вообще-то — инструмент для создания практически полезных программ. Таких как Quake 3, да. Или ядро Линукса. Кто может — тот делает, а кто не может — учит нас «совершенному коду» и «модерновому сиплюсплюсу». Я помню, как в своё время прочитал широко известную в узких кругах книгу Джеффа Элджера. Такую, с чёрненькой обложкой. Вы должны помнить — умные указатели, гомоморфные иерархии, вот это всё. Конечно, ходил под впечатлением: какой вумный дядька! Такое тройное сальто с переворотом и поцелуем себя в задницу в верхней точке при прыжке через горящий обруч — это ж не каждый сделает! А сейчас я поискал 'jeff alger' и ничего толком не нашёл. Вики про него не знает. Что он вообще сделал-то? Книжку написал?

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

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

Вот так! Теперь вы знаете, почему горячо рекомендуемый в статье std::string ничего не умеет, а алгоритмы навешиваются на него как амбарный замок. Александра Александровича нимало не смущает тот факт, что типичный программист вообще редко пишет алгоритмы (в том смысле, который вкладывает он в это слово) — типичный программист ими в основном пользуется. И что старый, недобрый, пропахший нафталином CString действительно облегчал программисту жизнь при переходе от char*: он, во-первых, худо-бедно, реализовывал абстракцию «строка». Можно было не думать, Юникод там внутри или ещё что-то. (Да, макросы _UNICODE — это, наверно, не очень хорошо. Но это решение! А наличие ДВУХ базовых типов строк — это просто отказ от всяких попыток решить проблему). Во-вторых, он умел весь джентльменский набор по работе с текстом — поиски с обоих концов, замены, капитализации и прочее — прямо внутри класса. Может быть, кто-то думает, что мы таскаем классы просто по факту наличия? Нет. Класс должен приносить пользу. И, в-третьих, что немаловажно, создатели подумали о совместимости с Си. То есть, о том факте, что как на сиплюсплюсе не пиши, а рано или поздно придётся вызывать WinAPI (или API другой ОС). И заложили в него operator LPCTSTR(). Вызов `::CreateWindow(strClassName, strWindowName...` однозначно считывается как `:: СоздатьОкно(оконныйКласс, заголовокОкна...` Напротив, степановское детище заставляет писать так: `::CreateWindow(strClassName.c_str(), strWindowName.c_str()...`. Что в переводе на русский означает: `:: СоздатьОкно(контейнерСодержащийОконныйКласс.извлекаем_строку(), контейнерСодержащийЗаголовокОкна.извлекаем_строку()...`. Конечно, это сделано в интересах трудящихся. Нас же хлебом не корми, дай лишний метод вызвать руками, чтобы напомнить себе, что мы тут не в бирюльки играем, а пишем на взрослом языке. Народ и начал разбегаться, как только стало куда — Java, C#, you name it. Осталась кучка самых стойких, из которых половина просто привязана к сишным API, и воленс-неволенс вынуждена досмотреть этот цирк до конца — так и до тех доковырялись. Плохо, дескать, летаем. Низэнько.

Вы собираете свои emb проекты на ядре linux, написанного на С. Т.е. могу предположить, что как и в большинстве подобных проектов это уже 80% кода. Туда еще можно положить немаленький u-boot или его альтернативу. А потом заявляете, что мы вот, без обид образно говоря ), по CAN гоняем 10 кодограмм и десяток алгоритмов бизнес-логики крайне мемори и типо безопасно т.к. не контрибьютим ни строчки "warning: use of old-style". Так у вас де-факто 90% кода уже в опасности ). Вам надо срочно переписывать ядро! По крайней мере загрузчик уж точно можете написать, он же проще пареной репы.

UFO just landed and posted this here

Поэтому в проекте разрабатывают под Embedded Linux и в качестве основного языка используют C++

Есть Cortex M4. 144 мгц и 160 к ОЗУ на борту. Необходимо поддерживать GSM модуль, FM микросхему. С FM Постоянно мониторить звук с эфира на предмет выловить кодовую посылку DTMF. В это же время обслужить микросхему Ethernet. При необходимости обслужить флеш память. По двум, трем каналам АЦП нужно мониторить звук на предмет поступления кодовой посылки DTMF. По команде транслировать звук из выбранного канала на УНЧ, или на выбранный выходной канал. Звук для обеспечения приемлего качества должен быть хотябы 24к семплов в сек.. Также необходимо мониторить солнечную панель и заряд аккумуляторов. И все это работает в режиме реального времени 24 часа в сутки. То о каком Ebedded Linux Здесь может быть речь? Только RTOS. Динамическая память не получается, так как некогда собирать мусор в памяти.

UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here

Как различить C и C++-разработчиков по их коду? Элементарно. У одного код написан на С, у другого на С++. С уважением, Капитан Очевидность.

UFO just landed and posted this here

Мы о каком C говорим ?

C99 или C11 или C2x ?

Количество "пары моментов" не так уж и мало.

UFO just landed and posted this here

по сей день нередко можно встретить Си-программу которую удастся скомпилировать C++-компилятором вообще без проблем или с самыми минимальными модификациям

Тут вы можете сильно ошибаться. Что разрешено в С, часто включается в С++ по умолчанию в msvc или через gnu-extensions, но валидным С++ кодом не является, отсюда получается то, что в Сях ок, в плюсах - UB. Оно скомпилируется да, но работоспособность ничем не гарантирована.

Вчера как раз размышлял на подобную тему, и даже придумал термин — «ассемблерное мышление». Т.е. изучаем элементарные операции, а дальше их комбинируя создаем всё что нужно. На курсах по асму нам объяснили ввод, вывод, арифметику. Преподаватель говорит — «Ну вот теперь вы можете написать Тетрис или Ксоникс». ИМХО, проблема изучения языков относительно высокого уровня (как С++), что когда знаешь Си, непонятно зачем нужны все эти плюш-плюшки. Ну впрочем я пишу в основном что-то алгоритмическое — из файла данные взяли, посчитали, запихнули обратно в файл. Видимо надо начать писать что-то более сложное, чтобы возникла необходимость и я увидел как что-то легко и изящно делается на ++ и долго и муторно на чистом Си.
Ну впрочем я пишу в основном что-то алгоритмическое — из файла данные взяли, посчитали, запихнули обратно в файл

Забавное описание. Я вот тем же самым занимаюсь, разве что в «боевой» конфигурации обычно чтение идёт потоком с камер, и результаты передаются какой-нибудь управляющей системе по какому-нибудь специальному интерфейсу. Но этот ввод-вывод совсем незначительную часть кода занимает.

Могу ответить вам на этот вопрос. Необходимость плюсов по сравнению с pure С начинаешь понимать когда проект становиться больше определенного размера, когда его нужно поддерживать и развивать.

Я преписывал код С на плюсы несколько раз именно из за того что при малейшей модификации вылазило куча проблем. Классический пример это освобождение памяти, когда на плюсах, при условии выполнения нескольких правил, можно вообще не задумываться об освобождении памяти при выходе из блока. А вот в С надо обязательно отследить все динамические переменные и их освободить. А когда внутри кода несколько ветвлений то освобождение надо делать в нескольких местах либо усложнять код чтобы все сводилось в одну точку.

либо усложнять код чтобы все сводилось в одну точку.

А goto в нужную точку в хвосте функции для освобождения не помогает, как в ядре Linux часто делают? Или ресурсы часто не так линейно вкладываются?

UFO just landed and posted this here
* даже в том же Rust я обнаруживаю, что очень часто fn (Rust-аналог сырого указателя на функцию) предпочтительнее Fn (Rust-аналог std::function), и я говорю не об интеропе.

Вы что-то путаете, причём капитально. fn — это всегда функциональный указатель и всегда размером с usize. А вот Fn{, Mut, Once} — это трейты, и их использование совершенно не обязательно подразумевает аллокацию памяти. Вдобавок, каждая конкретная функция в Rust имеет свой собственный тип нулевого размера, и использование обобщённых типов с Fn*-ограничениями позволяет сохранить нулевой размер в памяти, в отличие от функциональных указателей:


struct FnWrapper<F: Fn()>(F);

fn f() {}

fn main() {
    use std::mem::size_of_val;
    assert_eq!(size_of_val(&FnWrapper(f)), 0);
    assert_ne!(size_of_val(&FnWrapper(f as fn())), 0);
}
UFO just landed and posted this here

Зачем такой спор в комментариях? Элементарно - пишите на C - вы молодец, оставьте его себе и пользуйтесь на здоровье.

Пишете на C++ - вы тоже молодец, но будет добры, соглашайтесь со всеми правилами языка! Никаких old-style кастов, никакого ручного управления памятью, используйте всё что уже есть в языке грамотно и с умом. И чёрт побери, не забывайте о кросс-платформенности! Код должен быть переносимым ради ваших будущих коллег!

Я это прочитал и подумал: боже, как хорошо, что я в отношении С/С++ остановился на стандарте в районе 1994 года (то бишь после templates но до exceptions), и вообще что сейчас проектирую хардвер на Verilog-е, а не пишу на том ужасе, в который превратился C++.

(При том что я был в прошлом автором продаваемого компилятора с C/C++)

UFO just landed and posted this here

Весьма странная статья. И странные комментарии. Мягкие. Не по мощам елей. На мой взгляд, статья граничит с признанием автора в профнепригодности. Кто то тут способен спутать процедурного и объектного программера? Цирк ёёёё... Не, я конечно понимаю, круг ваших задач ограничен рабочим окружением, но все таки... Ограниченность задач играет с вами дурную шутку дружище. Сложность низкая, объемы кода видимо не высокие, всегда можно прошивку с нуля переписать и не нужно париться с кодом доставшимся в наследство))). А вы возьмите и создайте два варианта даже простенькой игры с GUI на С и на С++ а потом передайте своему коллеге с приколом внести в программу новую фишку. И после этого уже наверное можно вудет с вами обсуждать похожести языков. И все таки настолько не интересоваться программированием, что бы не выйти за рамки рабочей рутины, и не попытаться сделать что то еще, не попытаться получить другой опыт? Я сочувствую той организации где работает автор. Чем похожи С и С++? Кроме синтаксиса и сопоставимого машинного кода на выходе компилятора - они похожи мало чем - буквами. Это два разных инструмента. Они созданы для работы в разных парадигмах и для решения разных задач. И философии программистов их использующих - не сопоставимы ни в кривь ни в кось: почти все что хорошо для С, есть ужас в С++ и наоборот. И эти философии у людей закладываются годами. И именно с инерцией изменения философии человеческой связана сложность освоения новой парадигмы, а не с новым языком. Новый синтаксис ты можешь пробежать глазами и начать использовать за весьма короткое время, но создавать продукт промышленного качества... Оооо - Нет. Как показывает практика, подавляющее большинство работничков не достигает уровня понимания достаточного для самостоятельной работы в принципе. Они так и остаются на уровне кодеров, которым лиды нарезают задачи и контролируют каждый их шаг. Увы.

UFO just landed and posted this here

Возможно я просто чего то не понял. Без обид. Я тоже лох порядочный и не претендую на что то великое. Но. Ты возьми дезассемблированный код, или посмотри машинные коды в PE. что ты там увидишь? Объекты? Процедуры? Не будет их там. Там будут пушенья параметров в стэк и условные/безусловные переходы к адресам. Поэтому, все костыли в виде языков программирования это для нас, для людей. Еще раз повторюсь. У нас разночтение возникло из за разных объемов и наследия. Ничего удивительного нет, что С++ похож на С. С++ выродился, мучительно и тяжело выломался из Си под воздействием обстоятельств, а именно - под давлением возрастающей сложности создаваемых систем. Когда у тебя проект объемом в пол года - год работы коллектива, и ты не имеешь права на ошибку, приходят совсем другие требования. Просто я с Си начинал развлекаться еще в девяностых, потом был С++, потом С#. И в моей голове объектный и процедурный стили никак не смешиваются даже теоретически, как масло и вода, несмотря на обманчивую схожесть синтаксиса и наличие функций/процедур. Я уже давно не смотрю на свои поделки глазами кодера. Как правило, я сначала достаточно долго хожу вокруг новой поделки ковыряяся в носу (I pick in a deep in my big nose), и поковырявшись исторгаю из себя зачатки жизнеспособной архитектуры, обычно это DDD. И тут я никак не могу допустить, что можно коим то образом я могу недопонять где у меня будет линейное решение, а где нет.

"Пушенья и переходы по адресам" это всё же не про парадигму, а про компилятор. Ну и про известную идиому (применимую ко всем подобным языкам) о том, что преждевременная оптимизация - зло. Да, всё превращается в машинные коды конкретного процессора. Но изначально всё идёт из выразительности языка и других плюшек. И хорошо, когда для перехода от объектов к процедурам _не нужно_ менять язык, а можно просто писать в другой парадигме на том же языке.

А вот, например, использование COM (неважно, на С или С++) - в дизассемблере вполне видимо поверх всех "пушений и переходов" (а если есть ещё и отладочные символы - то это вообще очень сильно помогает в reverse engineering). Так что в этом смысле в PE вполне можно увидеть и процедуры, и объекты.

Мне кажется, что на С объёмные проекты вполне удаются. Например ядро Линукс.

Ну или Windows на худой конец.

Кто то тут способен спутать процедурного и объектного программера?

вы наверно не слышали про то, как на плюсах пишут в процедурном стиле или как на си пишут в объектно-ориентированном (пример)? Очевидно что использовать ООП-парадигму в плюсах сильно удобнее чем в си, но и любую другую тоже. В общем, давайте поскромнее со всеми вашими профнепригодностью, ограниченностью, уровнем кодера...

Вот в этом и проблема. Для использования ООП парадигмы надо таки иметь поддержку языка иначе будет очень больно.

Я считаю что использование ООП в pure C это ересь и то что например нагородили в Gnome это ужас. Такое получилось только потому что чистые С-программисты решили доказать миру что на С вполне пишется большая GUI-система. Все что там нагромождено в ООП-стиле - на порядки удобнее и проще и главное безопаснее делать в С++.

Кто то тут способен спутать процедурного и объектного программера?

...

Они созданы для работы в разных парадигмах и для решения разных задач

Так вы не языки сравниваете, а парадигмы. Можно вполне писать на плюсах в процедурном стиле, и это таки будут плюсы, а не си. Он (С++) мультипарадигменный, всё стерпит. Если про С ещё можно сказать, что там сложно в ООП, то вот говорить, что на С++ нельзя в процедуры или даже ФП - уже звучит странно.

Умножаемые комментаторы, а вот такой вопрос:
есть чистый сишный проект, надо сделать что-то где без классов(т.е. без ++) никак от слова совсем.

Насколько "глубоким" проникновение ++ в тело Си проекта допустимо?

Насколько будет правильно писать "до упора" на Си но когда уже никак - то ++, но по самой минималке насколько это оправдано?

А либы не спасут? Основной проект на С, всякие расширения "где без классов никак" - в проект DLL/LIB. Мне кажется более правильным при необходимости до упора следует применять принцип совмещать, но не смешивать ( принцип Бонда ).

При желании, вы можете вынести необходимую функциональность в отдельную либу, написанную на с++, как предложил @dvserg, но здесь начинаются вопросы по ограничениям на линковку? Сможете ли вы линковать свой си-проект со стандартными либами с++ (статически или динамически), если понадобится?

Кроме этого может существовать проблема использования исключений. Будете ли вы их использовать или использовать классы стандартной библиотеки? На некоторых платформах, например, есть ограничения на использование исключений. Если у вас подобный случай, то вам нужно думать, как обрабатывать ошибки классов стандартной библиотеки.

В ядре линукса есть классы и объекты. На чистом С, да.


Может вам не так уж и нужен С++?

Я когда попробовал на с++ пособеседоваться вроде честно поменял все printf на cout <<, и даже насоздавал каких-то классов, но меня всё равно спалили и сказали, что пишу на си. :(

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

std::string не подойдет когда вы не можете работать с динамической памятью (хотя, и тут можно придумать что-нибудь интересное с кастомными аллокаторами);

Очень много чего не подойдет, когда вы не можете работать с динамической памятью. Легче перечислить, что подойдет.

Изначально я думал, что много хейта от того что задели Embedded программистов, как меня. Почитав больше комментариев я вижу что это не так. Задели программистов старой школы си.

Мир шагнул дальше. То что делается на си, за неделю, а раньше и на плюсах делалось за неделю, сейчас на плюсах делается за день. Это прямо не говорится и для Автора очевидно он то знает плюсы, но не очевидно для старой школы. Я то как раз сам прифигел при переходе на новые плюсы (не относится к Qt), и пока не проникнешься - не поймешь.

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

Статья хорошая, хоть и при первом прочтении у меня и вызвала негативные эмоции, ибо я тот самый Си-программист и мне обидно признавать, что я сделаю приложение под Linux за больший срок чем даже джун но на плюсах. К счастью, я их и не пишу, а плаваю как раз там где и нужен Си.

Еще раз лучи поддержки Автору

UFO just landed and posted this here

Как мне кажется, программистов на Си сильно больше чем на плюсах, тем более чем хороших программистов на плюсах. И преимуществ у Си много, но они постепенно пропадают. Из реальных областей которые у нас, остались это: малоресурсные мк, низкое потребление или драйвера. Сейчас внутри копеечного мк esp32 памяти столько, что ее даже толком и не считают. За 10 баксов можно взять готовый одноплатник на Linux с более-менее отлаженными драйверами и впихнуть в него даже arm-контейнеры.

Время разработки проектов сильно сократилось за последние 10 лет. Помню пару лет назад читал статью на хабре где посыл был примерно такой:

"Друг попросил сделать освещение по хлопку. Я еб...лся с выбором мк, кое как уместил туда свой код так чтобы работало и выставил другу счет за разработку а он носом повертел. Больше не друг."

При том что уже тогда любой школьник мог просто взять arduino набор за 1000 рублей и все сделать за один вечер скачав библиотеку из интернета, автор той стать упорно все делал с нуля. Уже тогда было понятно что целый класс, невероятно полезных в свое время, людей превратился в набор староверов, мешающих прогрессу и даже теряющих друзей в угоду своей вере. И этот класс страдает от того что их, таких полезных людей, не берут на вакансии С++, хотя казалось бы, каких-то 10 лет назад вообще проблем никаких не было. Страдает, от того что сам понимает что мир шагнул вперед, а они остались на месте. Вы лишний раз напомнили нам об этом и получили вагон минусов.

Вот примерно так думаю я

у Arduino (особенно у дешевых типа Uno, LilyPad) ресурсов тоже как бы не сильно дофига.

Другое дело, что наколхозить на них относительно рабочее решение вполне реально за 1 выходной.

Мир шагнул дальше. То что делается на си, за неделю, а раньше и на плюсах делалось за неделю, сейчас на плюсах делается за день.

Только если говорить об абстрактных задачах, типа: "на голом языке получи из файла строки, отсортируй их и сохрани в другой файл", потому как в плюсах есть готовые структуры в std.

Если говорить о реальных проектах и о программистах с опытом и багажом библиотек, время будет сопоставимо, а может на C даже меньше, потому как проект собирается за 10 секунд, а не за минуту, потому как при отладке больше всего времени в плюсах уходит на сборку...

В больших проектах большую часть времени занимает не кодирование, а придумывание алгоритмов, отладка и покрытие тестами.

Я долго разрабатывал на C и у меня накопился некислый багаж как сторонних библиотек для решения задач, так и собственных решений под все нужды проектов в моей сфере (системы управления рекламой). И из этого конструктора я собираю решение не медленнее, чем на плюсах, на котором пишу проекты последние лет 5.

Кстати пробовал golang, прекрасный язык, красивый, куча стандартных и сторонних библиотек, но не получилось на нём разрабатывать быстрее, чем на C/C++.

В моем комментарии я не старался охватить все аспекты и позволил себе использовать утрирование. Понятное дело что не каждый проект на плюсах можно сделать быстрее в 5 раз, или даже просто быстрее.

Условия, которые вы составили в своем сообщении можно перефразировать так: "У нас есть набор готовых проверенных решений и библиотек на Си, нам нужно сделать бизнес-логику и запустить в прод. На каком языке писать будем?"

Конечно, в таком идеальном мире ответ очевиден. Представьте что вы пошли в новую область, в которой еще нет библиотек, в которой нужно выдать отлаженное и покрытое тестами решение как можно быстрее, так оно еще должно быть эффективным по производительности. В этом случае "абстрактные задачи" типа "на голом языке и std" обретают реальное воплощение и все чаще встречаются в жизни.

Современные языки, в отличие от Си стараются включать в себя огромный набор стандартных библиотек и это позволяет разным людям работать в одной и той же кодовой базе, а так же быстро разбираться в чужих проектах. Вряд ли всем известно как работать с вашими библиотеками или готовыми решениями. Вы умрете и что делать бизнесу? Новичек облажается с вашими решениями и библиотеками из-за закона дырявых абстракций, но если бы вы пользовались преимущественно std, то вероятность провала была бы сильно ниже.

Я ни в коем случае не намекаю на то что ваш подход не верен. Вы нашли свою нишу, ваш подход в ней эффективен и правилен, бизнес выбирает вас для решения своих задач. Так же как и я, пишу себе код под 8 кБайт оперативки и доволен Си. Автор же говорит о других областях, в которых подход Си устарел

Конечно, в таком идеальном мире ответ очевиден. Представьте что вы пошли в новую область, в которой еще нет библиотек, в которой нужно выдать отлаженное и покрытое тестами решение как можно быстрее, так оно еще должно быть эффективным по производительности. В этом случае "абстрактные задачи" типа "на голом языке и std" обретают реальное воплощение и все чаще встречаются в жизни.

Если не брать новичков и собеседования, обычно программист работает в какой-то "своей" сфере: разработка для микроконтроллеров, gamedev, мобильные приложения и т.п. и работодатель покупает не знание языка и умение решать олимпиадные задачи, а опыт работы в конкретной сфере, а с опытом программист "обрастает" инструментами для решения задач в этой сфере.

Современные языки, в отличие от Си стараются включать в себя огромный набор стандартных библиотек и это позволяет разным людям работать в одной и той же кодовой базе, а так же быстро разбираться в чужих проектах. Вряд ли всем известно как работать с вашими библиотеками или готовыми решениями. Вы умрете и что делать бизнесу? Новичек облажается с вашими решениями и библиотеками из-за закона дырявых абстракций, но если бы вы пользовались преимущественно std, то вероятность провала была бы сильно ниже.

Как показывает мой опыт, для новичков в сфере, к которым вы аппелируете выше ("абстрактные задачи" типа "на голом языке и std" обретают реальное воплощение) фреймоврки значительно затормаживают вхождение в проект, потому как они весьма наворочены и требуют отдельного изучения фреймворка и его осбенностей. Одного языка знать недостаточно.

С другой стороны, хорошо написанный проект, например nginx, написанный на голом C, с собственной реализацией подавляюшего большинства алгоритмов (в том числе деревьев, hash-таблиц и т.п.), читается легко и непринуждённо... На понимание nginx на С мне потребовалось сильно меньше времени, чем на понимание проекта на PHP на Symfony.

Новичек облажается с вашими решениями и библиотеками из-за закона дырявых абстракций, но если бы вы пользовались преимущественно std, то вероятность провала была бы сильно ниже.

В реальном проекте новочёк облажается на уровне архитектуры, а не на уровне использования тех или иных библиотек. По библиотекам есть документация.

Я ни в коем случае не намекаю на то что ваш подход не верен. Вы нашли свою нишу, ваш подход в ней эффективен и правилен, бизнес выбирает вас для решения своих задач. Так же как и я, пишу себе код под 8 кБайт оперативки и доволен Си. Автор же говорит о других областях, в которых подход Си устарел

Я ничего не писал о своём подходе. Автор жалуется на то, что в плюсовые проекты приходят сишники и разрушают его приплюснутый "храм". Но дело тут не в языке, си или плюсах, дело тут в культуре человека, если он не уважает "чужое", он будет одинакого "говнокодить" как на си, так и на плюсах. Меня вот бесит, когда кто-то приходит в проект и начинает писать в своём стиле, а не в стиле, который устоялся в проекте...

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

UFO just landed and posted this here

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

Проекты бывают разные.

UFO just landed and posted this here

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

Если в одном, слабо нагруженном месте я использую std::unordered_map, потому как высокая производительность тут не нужна, то в другом, высоконагруженном месте, я использую JudyArray, потому как он даёт (или давал на момент разработки) более чем двухкратный выигрыш по производительности.

Скажите, пожалуйста, JudyArray -- это стандарт или осебятина от "кулибина"? Если CoCo Hash на моих данных работае лучше контейнеров из std и я его использую -- это стандарт или отсебятина?

Если мне, на каждый запрос надо генерировать uuid и стандартная библиотека это делает это медленно и я пишу собственную, это лютая остебятена и такой код

    uuid_t uuid;
    uuid_generate_random(uuid);
    char cuuid[37];
    uuid_unparse_lower(uuid, cuuid);

исключительно лучше понимается проффесионалом, чем такой

    rapiduuid::Value uuid;
    rapiduuid::Parser parser;
    parser.generate(uuid);
    std::string s = parser.toString(uuid);

Если только у вас там не совсем что-то оооочень специфическое (например, высокочастотный трейдинг, где все на lock-free построено и хардкорные оптимизации), в чем я сильно сомневаюсь, да и это всё-таки в любом случае редкое явление.

Зря сомневаетесь, не трейдин, но тоже весьма нагружено и по-возможности блокировки не используются.

Проекты разные бывают и нельзя все под одну гребёнку...

Я дорабатывал проект на Java, который писал проффесионал, у которого были все возможные сертификаты по Java, но тупило местали люто.

Управлял проектами на "стандартных" Django, Symfony, где тоже было всё стандартно, но трудно для понимания и оптимизации другими программистами, с хорошим опытом в этих фреймворках.

Были проекты от "кулибиных" с отсебятиной, но читались они как хороший роман. Да вон nginx сплошь отсебятина от "Кулибина".

Не использование стандартов и знание паттернов делает проект хорошим, работающим, понятным.

исключительно лучше понимается проффесионалом, чем такой

Можно задать пару-тройку вопросов, которые сходу возникли у (вроде как профессионала) при первом взгляде на этот фрагмент?

char cuuid[37]; // Почему именно 37?
                // Что будет, если написать 36?
                // Нижеследующий вызов как-то проверяет
                // размерность cuuid? Если нет, то почему?
uuid_unparse_lower(uuid, cuuid); // Что значит "unparse"?
                // Почему не "format_to_string_lower"?
                // Откуда видно, что результат unparse (что
                // бы это ни значило) помещается в cuuid?

Вроде как профессионал может ввести в гугле "uuid_unparse_lower" и понять по первому результату, что это "стандартная" библиотека для генерации uuid в linux и вопрос надо адресовывать авторам библиотеки, а не мне. Не к тому докопались ))

Ну и опять таки, если зайти по этой первой ссылке, то в первом же предложении секции DESCRIPTION есть ответ на ваш вопрос

The uuid_unparse() function converts the supplied UUID uu from the binary representation into a 36-byte string (plus trailing '\0') of the form 1b4e28ba-2fa1-11d2-883f-0016d3cca427 and stores this value in the character string pointed to by out

Вы утверждали, что у профессионала вопросов по коду не возникнут. Вот, возникли. По части из них вы отправляете искать ответы в Интернете, на часть из них не ответили вообще.

Так что приведенный вами код вызывает кучу вопросов, на которые нужно a) тщательно собирать информацию и b) скрупулезно следить за тем, что пишешь, ибо если по недосмотру объявишь cuuid[36], а не cuuid[37], то можно и ногу отстрелить.

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

class uuid_as_cstr {
  std::array<char, 37> value_{0};
public:
  uuid_as_cstr() = default;
  [[nodiscard]] const char * c_str() const noexcept {
    return value_.data();
  }
  [[nodiscard]] char * data() noexcept {
    return value_.data();
  }
};
[[nodiscard]] uuid_as_cstr
uuid_to_c_str_lowercase(const uuid_t & uuid) {
  uuid_as_cstr result;
  uuid_unparse_lower(uuid, result.data());
  return result;
}
...
const auto cuuid = uuid_to_c_str_lowercase(uuid);

И вы не будете иметь проблем ни с размерностями, ни с тем, что можете случайно начать использовать cuuid до присвоения ему значения.

Вы задаёте вопросы по стандартной библиотеке для решения задачи генерации случайного uuid, которая была приведена в качестве примера, в ответ на утверждение, что стандартное читается проще. Мне странно, что вы от меня ждёте ответов на вопросы, которые должны быть адресованы авторам библиотеки, а не мне. Но ваша реакция говорит о том, что моя "отсебятина" читается лучше, чем стандарт...

Вместо использования 4 строк стандартного кода вы предлагаете написать обёртку вокруг стандартной библиотеки, это должно как-то упростить понимание кода???

Если вы хотели сказать, что в вашем случае uuid получатся одной строкой, а не 4-мя, то в у меня он тоже обёрнут в функцию:

std::string
Utils::makeUUID()
{
    uuid_t uuid;
    uuid_generate_random(uuid);
    char cuuid[37];
    uuid_unparse_lower(uuid, cuuid);
    return std::string(cuuid);
}

Вы задаёте вопросы по стандартной библиотеке для решения задачи генерации случайного uuid

Я задаю вопросы по примеру кода, который вы продемонстрировали. Причем продемонстрировали именно как пример, к которому вопросы не возникнут.

Мы здесь даже не будем углубляться в выяснение того, в каком именно стандарте определена uuip_unparse и почему она считается "стандартной библиотекой".

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

Вместо использования 4 строк стандартного кода вы предлагаете написать обёртку вокруг стандартной библиотеки, это должно как-то упростить понимание кода???

Это может выглядеть как парадокс, но тем не менее именно так и происходит зачастую.

Если вы хотели сказать, что в вашем случае uuid получатся одной строкой, а не 4-мя, то в у меня он тоже обёрнут в функцию:

К вашей функции, помимо всех уже заданных вопросов (про размерность cuuid и последствия ошибок с этой размерностью), можно задать и еще один вопрос: а точно ли мы всегда готовы платить за динамическую аллокацию строки из 37-ми символов?

Я задаю вопросы по примеру кода, который вы продемонстрировали. Причем продемонстрировали именно как пример, к которому вопросы не возникнут.

Этот код демонстрировался как "стандартный", а не как тот, к которому не возникнут вопросы.

Но зато мы можем легко продемонстрировать то, что происходит в реальной жизни: я прихожу в проект, который раньше писался условным Васей Пупкиным, и в первый раз вижу код, вроде показанного вами.

Вы его, скорее всего, не увидите, вы увидете что-то типа

id_ = Utils::makeUUID();

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

Это может выглядеть как парадокс, но тем не менее именно так и происходит зачастую.

Ну каждый верит во что хочет, да.

К вашей функции, помимо всех уже заданных вопросов (про размерность cuuid и последствия ошибок с этой размерностью), можно задать и еще один вопрос: а точно ли мы всегда готовы платить за динамическую аллокацию строки из 37-ми символов?

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

Для того места, где это действительно важно, как я писал выше, используется собственная функция генерации случайного uuid, написанная с использованием intrinsic-ов и работающая на 2 порядка быстрее стандартной.

Этот код демонстрировался как "стандартный", а не как тот, к которому не возникнут вопросы.

Обратимся к первоисточнику:

Если мне, на каждый запрос надо генерировать uuid и стандартная библиотека это делает это медленно и я пишу собственную, это лютая остебятена и такой код

...

исключительно лучше понимается проффесионалом, чем такой

Отсюда стороннему наблюдателю (а именно мне) видно, что приведенный вами код демонстрировался именно как тот, который лучше понимается (т.е. к которому вопросов меньше).

Вы его, скорее всего, не увидите, вы увидете что-то типа

Да, я увижу здесь что-то типа C++ и это, знаете ли, как-то вступает в диссонанс с вашим первоначальным комментарием, в котором вы поете хвалебную оду Си и накопленным вами велосипедам.

и у вас вряд ли возникнут вопросы, что тут происходит и есть ли проблемы в реализации

Давайте все-таки обсуждать именно тот пример, который вы привели как "исключительно лучше понимается". Если же вы сами убедились, что пример не показателен, то приведите другой пример, который мог бы проиллюстрировать вашу мысль лучше.

Любимый момент:

Использует char*-строки и функции <string.h> вместо std::string и std::string_view. (единственное исключение - строковые константы через constexpr).

А это хорошо или плохо? String упрощает взаимодействие со строками, но char* и char str[] дает возможность строго определить занимаемое место. Работаю в основном с программированием микроконтроллеров, и неопределенность, которую вносит string, недопустима, и его стараюсь избегать, используя там, где действительно нужно.

Например, при определении структуры, которая будет передаваться и использоваться в очередях FreeRTOS, string уже не прокатит: недетерминированное поведение строки при передаче через очередь

UFO just landed and posted this here

А какая польза от static_cast<int>(number) по сравнению с (int)number?

Использование static_cast - это проверка допустимости преобразования во время компиляции.

Это понятно. Но какая польза от static_cast<int>(number) по сравнению с (int)number в случае, когда number — это простое выражение с очевидным типом? Очевидный вред от static_cast — это многословность.


Одно дело, когда мы работаем с шаблонами, и тогда это всё оправдано. И совсем другое — это что-то вроде:


auto N = (int)ceil(p / 2.0)

Так там же написано "number". Если бы "непонятно что" или "указатель", то другое дело.

Евангелисты C++ утверждают, что надо использовать static_cast всегда и везде, даже в очевидных случаях.


При этом, если почитать посты от PVS Studio с разбором кода проектов, то окажется, что ошибки, связанные с неправильным приведением типов, когда мог бы помочь static_cast, встречаются чуть более, чем никогда.

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

Я вот во втором лагере. Данный пример вижу так:

Если вы не используете венгерскую нотацию в том или ином виде, то там будет не number, а какое-нибудь другое название.

Например, есть ли ошибка в следующем коде, если он компилируется и не падает?

long long timeout_min =
    ((long long)read_config("timeout_sec") + 59) / 60;

Ответ: а фиг его знает.

Если там возвращается double (потому что в JSON только вещественные числа) — то, наверное, тут хотели округлить секунды вверх до минут. А вот если там случайно возвращается сишная строка, то вы только что взяли в качестве количества секунд значение указателя. И никаких предупреждений компилятора не получили даже на самом высоком уровне предупреждений.

Разумеется, если протестируете, что таймаут у вас корректно работает, вы сразу обнаружите ошибку. Если протестируете. Или хотя бы выведете на экран.

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

с полным отключением статического анализа типов, как в Python

Я сейчас пишу на Пайтоне со включенным статическим анализатором. Боже, как это прекрасно. Я уже забыл что такое когда ты запускаешь программу и она тут же сразу работает.

UFO just landed and posted this here

Совершенно недостаточно.

Я тут пытался написать своего рода "шаблонную" функцию которая принимает тип и потом возвращает значение этого типа. Так и не смог донести до питоновского typing что же я хочу. Т.е. функцию я то написал, но заставить питон выводить правильный тип функции не смог.

Но все равно, type annotations - это большой шаг вперед. Я вообще не профессиональный python разработчик, так иногда пишу тулзы разной степени сложности. И даже в мелких скриптах type annotations сильно облегчают жизнь.

Кажется, должны помочь type variables/generics? Например:

from typing import TypeVar

T = TypeVar('T')
def foo(a: T) -> T:
    return a

x: int = foo(20)   # ok
y: str = foo('x')  # ok
z: str = foo(20)   # incompatible types in assignment

Это не совсем то что я хотел:

def get_typed_value(obj: MySpecialObj, T: type) -> Optional[T]:
    # Perform some checks on "obj"..
    # ..
    # And then
    if isinstance(obj.value, T):
        return obj.value
    return None

Так, конечно же вообще не работает. А хотелось бы.

Работает вот такой вариант:

def get_typed_value(obj: MySpecialObj, typename: type) -> Optional[T]:

Но тайпчекер не может вывести правильный тип для вызова

`get_typed_value(o, int)` например.

Вообще я хотел пойти дальше и сделать пачку хелперов вот таким образом:

get_int_value = partial(get_typed_value, typename=int)
get_boolean_value = partial(get_typed_value, typename=bool)

и т.д.
Но опять же, тайпчекер не может вывести правильный тип Callable[[MySpecialObj], int] для таких функций (и это понятно, потому что на самом деле partial возвращает не Callable).

Короче, такие приколы возможны в каком-нибудь Хаскеле, но явно не в питоне. А жаль.

В чистом Haskell, мне кажется, нельзя, там типы нельзя передавать в качестве аргументов функций. Или получаем зависимые типы (dependent types) и какую-нибудь Agda.

UFO just landed and posted this here

Ну да, я пожалуй неправильно выразился. В Haskell можно использовать какое-то подобие run-time polymorphism, но оно мягко говоря не идиоматично, насколько я понимаю. Поэтому никаких isinsrance() . И та проблема, которую я решал (чуть-чуть более продвинутая десериализация YAML) решается совсем по-другому. Похожий кусок кода там просто в принципе не возник бы.

Чтобы понять, зачем нужен static_cast, достаточно понять, какие проблемы он решает.
В соседнем комментарии привели пример:
long long timeout_min =
    ((long long)read_config("timeout_sec") + 59) / 60;

Код компилируется, но в нем ошибка, если read_config возвращает, например, указатель.
Что делать? Можно исправить код и счастливо пойти дальше собирать те же грабли. А можно что-то придумать, чтобы избежать таких ошибок в дальнейшем.
Что придумать? Например, не разрешать приводить что угодно к чему угодно. Для этого придумали семейство операций: static_cast, dynamic_cast, reinterpret_cast, const_cast. Каждая из них разрешает только некоторые виды приведений типов.
В приведенном примере кода static_cast не разрешит преобразование указателя к целочисленному типу и приведет к ошибке компиляции.
Но вообще говоря, это костыли, борьба с симптомами болезни, а не с самой болезнью. Приведений типов в коде лучше, по возможности, избегать. ЕМНИП поэтому для операций приведения выбрали такие уродливые длинные названия — чтобы мотивировать разработчиков от этих операций избавляться, переписав код (но конечно ни в коем случае не возвращаться обратно к сишному приведению!). Если же по каким-то причинам приведение типа действительно необходимо, то опять хорошо — уродливый синтаксис поможет не забывать об этом опасном месте в коде. Не лишним будет и написать объяснительную в комментарии, что это за место такое, и почему красиво сделать не получилось.

Почему нигде нет варианта "пишу на Си с классами, потому что в проекте огромная команда, и такой код проще всего развивать и поддерживать"?

Посмотрите в код Open Source проектов - сплошь и рядом Си с классами. Написали на махровых плюсах - половина контрибьюторов минус.

У меня 3 вопроса:

1 Чем #define хуже const при объявлении константы? Насколько я помню он же фактически заменяет одну последовательность символов другой на каком-то из начальных этапов компиляции.

2 А можно поточнее, о каком разделе эмбедеред идёт речь? Точнее на кой нам плюсы даже в 32-битном контроллере для стиралки при том, что на нём можно даже простенькие гуи для сенсорного дисплея писать?

3 И совсем уж нубовский вопрос: какое такое сакральное преимущество даёт C++ системном программировании что бы это нельзя было сделать на си? Ядра ж в конце концов на нём пишут. Речь не идёт о ситуации когда вам проще поставить какой нибудь линукс в свой контроллер и писать уже обычные программы.

UFO just landed and posted this here

> И совсем уж нубовский вопрос: какое такое сакральное преимущество даёт C++ системном программировании что бы это нельзя было сделать на си?

Ни один язык не даёт сакральных преимуществ над другим. Даже если (что не так в случае Си и C++) является строгим надмножеством другого; не всегда больше возможностей — это хорошо.

А конкретная команды в конкретном проекте делает выбор, исходя из ситуации. Язык, тулчейн, стиль кода, архитектура...

Сакральное преимущество очень простое: Автоматический вызов деструкторов.

Автоматический вызов деструкторов может приводить к нежелательным эффектам. Например, есть следующие стандартные аргументы (мне, впрочем, автоматический вызов всё равно нравится больше альтернатив):

  1. За } теперь может скрываться сколь угодно сложная операция, работающее произвольное время и потребляющая произвольное количество ресурсов. Обычно, конечно, небольшое, но может и взорваться. Это же, кстати, стандартный аргумент против перегрузки операторов (с которым тоже можно вести холивары).

  2. В частности, если мы пишем какое-нибудь владеющее дерево/список, то при наивной реализации (через unique_ptr и сгенерированные деструкторы) для его уничтожения требуется стек размера с глубину этого дерева. Можно случайно переполнить стек и даже не догадаться: рекурсии-то мы нигде явно не писали, она скрыта.

  3. Деструкторы провоцируют писать логику для закрытия/сброса буферов именно в деструкторах. Создаётся ложное ощущение, что это безопасно, хотя на самом деле ошибки при закрытии (диск отвалился => не удалось файл записать) тут либо полностью игнорируются, либо приводят к падению приложения. Если же, например, есть явный вызов close(), который надо делать абсолютно всегда (либо под угрозой назальных демонов, либо чего-нибудь попроще), то за его наличием следить проще и привычнее.

Верно, я уже наобжигался с RAII, когда писал многопоточный код. Программа уходит в дедлок. А почему? А потому что логика скрыта в деструкторах, отлаживать которые — дело неблагодарное. С тех пор на апологетов RAII смотрю с большим недовольствием, ресурсы чищу вручную, а деструкторы рассматриваю как обработку аварийных сценариев.

думаю что описанная вами проблема является следствием не RAII, а просто обычного недоучтенного юзкейса. То же самое в коде с ручным управлением происходит постоянно. Плюс, корректно освобождать ресурсы, особенно когда могут возникать исключения, очень сложно.

Так разговор шёл именно об "сакральном" отличии С от С++. Почти всё из С++ можно реализовать средствами языка С, но вот это -- к сожалению нет.

И всё, что вы описали, согласитесь, может произойти и при ручном удалении.

Преимущество деструкторов в другом -- на каждый return или "}" не нужно вычислять что ты уже освободил а что ещё нет.

Особенно, если нужно добавить что то в уже существующий код, с множеством управляемых этим кодом ресурсов.

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

Ровно этим и хуже (помимо указанного выше). По крайней мере, пока не привыкнуть обходить его острые углы. Вы хотите константу объявить, а не замену символов произвести. Классический пример из олимпиадного программирования:

#define MAXN (int)1e6 + 1
int data[2 * MAXN];  // int data[2 * (int)1e6 + 1];

Хотели объявить массив размера два миллиона и два элемента, а получили массив размера два миллиона и один элемент.

Чинится легко — добавляем вокруг каждой такой макроконстанты круглые скобки.

А дальше выбираем в своём проекте: ничего с этим не делать, забанить #define для констант, разрешить и форсить наличие круглых скобок (только где обязательны или вообще везде — отдельный вопрос) на code review/автоматически/не форсить вообще и разгребать редкие последствия.

Конечно, если много раз писал #define, то скобки наверняка на автомате ставишь. Вот и холивар: кому-то это вообще риском не кажется, просто "надо уметь писать", а кому-то риском кажется. Потом скатываемся в стандартный спор на тему "нужны ли code style", "какой code style использовать"...

Пожалуй malloc/free - самый явный C-прогер детектор ИМХО)

Есть несколько возражений:

1) Код соответствует стандарту С++, если он компилируется компилятором С++. Шах и мат. Между прочим Бьярне ровно так и говорит - идите нахер, пуристы.

2) В общем легко отличить программиста на С++ от программиста С даже без этих пунктов. Если есть код, который можно поглядеть (и доказуемо, что его автор перед вами), то там очевидно все будет. Дальше уже могут быть тонкости, например "этот автор как-то плохо разбирается в поведении string_view".

3) Кажется лучше взять хорошего программиста на С и переучить его на нужный диалект С++, чем упарываться в пуризм.

UFO just landed and posted this here

в проект уже взяли несколько "хороших программистов на C", и теперь нужно укрепить команду как раз теми, кто будет их переучивать в процессе работы и следить, чтобы они дел не наворотили :)

Мда, действительно))) Аргумент снимается.

Код соответствует стандарту С++, если он компилируется компилятором С++.

Каким из компиляторов? Они, бывает, отличаются и добавляют свои собственные расширения. А ещё поведение отличается между ОС, потому что помимо компилятора есть стандартная библиотека и протекающие абстракции.

Ill-Formed No Diagnostics Required куда делся?

Код соответствует стандарту С++, если он компилируется компилятором С++

А что, в стандарте С++ убрали undefined behavior?

Узнал массу интересного про С++. Спасибо, очень полезно. Видимо, меня уже поздно переучивать и я буду и дальше работать в С++, главным образом, Си-шными методами.

свои пять коп: приплюснутый компайлер считает себя умнее программера, а сишный компайлер считает программера умнее себя. to whom how. ну или ССЗБ.

UFO just landed and posted this here
  • std::string не подойдет когда вы не можете работать с динамической памятью (хотя, и тут можно придумать что-нибудь интересное с кастомными аллокаторами);

Так можно просто взять std::pmr::string из стандартной библиотеки. Или это как раз и подразумевается под "кастомными аллокаторами"?

UFO just landed and posted this here

9/19 "за", так и знал, что я не плюсовик! :)

А ещё забыли:
"Использует #ifndef MY_SOMETHING #define MY_SOMETHING #endif вместо #pragma once",
"Использует битовые поля вместо std::bitset."
"Использует сишные библиотеки на прямую без уровня абстракции над ней"
"В заголовочных файлах куча инклудов, которые можно было в принципе там и не писать (incomplete class)"

А что, #pragma once уже часть стандарта?
Ах да, ещё модули есть.

Увы, имею опыт выстреливания в ногу и с #ifndef HEADER_H..., и с #pragma once.

Использует ручное управление памятью с new и delete, вместо RAII и умных указателей;

Я конечно извиняюсь, но new&delete с самого начала были именно что с++ фишки

Для деструкторов забывает virtual :)

А у вас постоянно встречается ромбовидное наследование, что ли? Это же фу-фу

Я конечно извиняюсь, но new&delete с самого начала были именно что с++ фишки

Однако, в современных плюсах их использование есть моветон, а сишникам наверняка проще привыкнуть писать new/delete вместо malloc/free, чем освоить концепцию RAII.


А у вас постоянно встречается ромбовидное наследование, что ли? Это же фу-фу

Причём здесь ромбовидное наследование? Отсутствие виртуального деструктора в любом интерфейсном (абстрактном) классе — это выстрел себе в ногу.

Однако, в современных плюсах их использование есть моветон, а сишникам наверняка проще привыкнуть писать new/delete вместо malloc/free, чем освоить концепцию RAII.

new/delete - это чисто с++ концепции (вызов конструктора/деструктора при аллокации/очистке памяти), к С не имеют никакого отношения

Причём здесь ромбовидное наследование? Отсутствие виртуального деструктора в любом интерфейсном (абстрактном) классе — это выстрел себе в ногу.

[вопрос снимается, ниже ответили]

UFO just landed and posted this here

Поэтому использование new и delete часто является одним из признаков того, что человек пришел из языка, где нет умных указателей и подобных оберток и не привык к ним, либо вообще не подозревает о их существовании.

я уже давно живу в мире, где есть garbage collectors. Могу много рассказать про преимущества ручного управления памятью в частных случаях

Имхо, не-пользование unique_ptr это скорее про незнание отдельных аспектов стандартной библиотеки, а не про понимание принципов ооп

без virtual при delete вызовется только деструктор парента, но не производного, и вы можете получить утечку ресурсов.

ясно

ужас какой, как хорошо, что я на плюсах уже почти и не пишу; забыл уже все эти заморочки

UFO just landed and posted this here

К принципам ООП, впрочем, умные указатели отношения имеют мало (разве что в плане самих понятий "конструктор" и "деструктор", на которые они полагаются). Умные указатели можно встретить даже там где вообще нет ООП, например в проектах на чисто процедурном Си

тогда посыл статьи не совсем понятен
она тогда всего лишь о том, что новонанятый прогер просто должен отлично знать современную std

Но это ж несерьезно. Умения программиста они не совсем о знании фреймворков

UFO just landed and posted this here
я уже давно живу в мире, где есть garbage collectors. Могу много рассказать про преимущества ручного управления памятью в частных случаях

GC и smart pointers — очень разные концепции, и последние покрывают большинство сценариев, где GC уступает ручному управлению.


Имхо, не-пользование unique_ptr это скорее про незнание отдельных аспектов стандартной библиотеки, а не про понимание принципов ооп

Это действительно не имеет никакого отношения к ООП. Это про RAII — гораздо более важную и центральную особенность C++. Не понимать и не использовать RAII == не знать плюсы.


без virtual при delete вызовется только деструктор парента, но не производного, и вы можете получить утечку ресурсов.

Здесь на самом деле всё ещё хуже, такой delete вообще является UB, и, например, в моей практике приводил к memory corruption, после чего любая аллокация могла с некоторым шансом вызвать дедлок.

GC и smart pointers — очень разные концепции, и последние покрывают большинство сценариев, где GC уступает ручному управлению.

идею-то одна - автоматическое управление памятью

Да и в тех сценариях, где нужно ручное управление, умные указатели будут такими же (если не более) неуклюжими, как и gc
Будем откровенны, смарт поинтеры - это все таки костыль в сравнении в нормальным gc (вот который в дотнете, например)

Это действительно не имеет никакого отношения к ООП. Это про RAII — гораздо более важную и центральную особенность C++. Не понимать и не использовать RAII == не знать плюсы.

всегда думал, что плюсы это больше про ООП, а не про способы управления ресурцами

UFO just landed and posted this here

Есть много случаев, когда GC неуместен вообще, например когда у нас real-time или ограничены ресурсы.

насколько мне известно, в таких случаях будет использоваться скорее с, а не плюсы:))

Просто из-за того, что многие GC явно делают stop-the-world, а если даже и не делают, то накладные расходы на его работу все равно явно ненулевые.

конечно

как и на все фишечки std. Это стандартный трейдоф удобство вс перформанс; но чаще-то всего железо позволяет

помню, один товарищ (игродел) рассказывал, что они даже виртуальные функции стараются не использовать в борьбе за fps - переход по vtable ест пару тактов:) Но это давно еще было

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

он адски сложный. Сколько там стандарт уже, полторы тыщи страниц? Честно, не вижу особого смысла его ни вспоминать, ни учить.

При этом, взять хотя бы вот это обсуждение - тут 80% дискуссии о том, как правильно использовать фишки языка. Не как правильно определить бизнес логику/архитектуру/алгоритмы, а как правилно использовать язык. Такая вещь в себе этот ваш с++
Причем, мне сейчас за этот камент еще и минусов насуют, очень нетерпимая коммьюнити:))

UFO just landed and posted this here

Я уже с десяток лет с подобным работаю. Именно на плюсах. И таких проектов все больше и больше.

а на Go или там Rust народ не хочет переходить? Не помню, какой там из них в нативный код компилится

А вот на C++ по ряду причин такое сделать получится легко, и как показывает практика, очень многие пытаются так делать. Об этом и статья.

это понятно, я все еще помню жаркие дискуссии с vs c++ в начале этак 2000х. "Веревка достаточной длины, чтоб выстрелить себе в ногу", а как же:)
Но это, имхо, не достоинство прикладного языка

UFO just landed and posted this here

Пора, пора!
Еще kotlin native есть, к слову
У меня был как-то проект, полуembedded (десктоп клиент и управляющая программа на железе), так там на железе натурально комп с winnt стоял, мы туда даже студию вкорячили, для отладки:)) Там с go наверное не было бы проблем, но его тогда и не было еще

При этом, взять хотя бы вот это обсуждение — тут 80% дискуссии о том, как правильно использовать фишки языка. Не как правильно определить бизнес логику/архитектуру/алгоритмы, а как правилно использовать язык.

А что ещё вы ожидали от комментариев к статье, поднимающей вопрос идеоматичного кода на C++?

так статья, по сути, не об умении программировать, а о знании некоторых специфических (пусть и широко распространенных) особенностей фреймворка (std)

Мне такое странно, фреймворк и фреймворк, нормальный прогер основные положения выучит за пару месяцев, это не рокет саенс. А тут прям такое внимание

Статья о сравнении концепций, которые принято применять в C и C++. Да, часть из них реализуется с помощью абстракций, которые не входят в сам язык, но 1) от программиста на C++ обычно ожидается хотя бы поверхностное знакомство с STL 2) другие фреймворки в большинстве своём всё равно либо опираются на STL, либо предлагают аналогичные абстракции — использовать сишные строки или вручную управлять памятью вы наверняка не будете, независимо от фреймворка.

от программиста на C++ обычно ожидается хотя бы поверхностное знакомство с STL

по статье это не заметно:)

UFO just landed and posted this here

идею-то одна - автоматическое управление памятью

в случае с умными указателями - не только памятью, а объектами в общем, пояснение ниже

Да и в тех сценариях, где нужно ручное управление, умные указатели будут такими же (если не более) неуклюжими, как и gc

Будем откровенны, смарт поинтеры - это все таки костыль в сравнении в нормальным gc (вот который в дотнете, например)

давайте для начала вспомним что в сравнении с GC умные указатели не вносят оверхеда, то есть уже применимы в большем числе юзкейсов. Во-вторых, сам по себе GC не дает абсолютно ничего в сравнении с умными указателями, кроме некоторого снижения когнитивной нагрузки, т.е. недостаточная выразительность языка компенсируется за счет доп. расходов рантайма.

А ручное управление памятью в языке с GC это костыль для бекдора в другом костыле...

всегда думал, что плюсы это больше про ООП, а не про способы управления ресурцами

а что такое ООП по вашему? Из трех "краугольных камней" ООП основополагающей является именно инкапсуляция. Мы объединяем логику, данные и ресурсы, в некоторую единую абстракцию - объект. RAII позволяет делать герметичные объекты, которые при уничтожении освобождают все ресурсы. Иначе могут получаться "протекающие" абстракции - объект уничтожен, а ассоциированный с ним ресурс - нет. Что собственно и происходит в языках с GC.

ужас какой, как хорошо, что я на плюсах уже почти и не пишу; забыл уже все эти заморочки

учитывая степень вашего понимания базовых концепций программирования хорошо конечно, что вы не пишете на с++...

в случае с умными указателями - не только памятью, а объектами в общем, пояснение ниже

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

Во-вторых, сам по себе GC не дает абсолютно ничего в сравнении с умными указателями, кроме некоторого снижения когнитивной нагрузки

он дает чудовищное снижение этой самой нагрузки

Попишите, например, год на сишарпе или там жаве, и потом опять на плюсах - разницу сразу прочувствуете.

т.е. недостаточная выразительность языка компенсируется за счет доп. расходов рантайма.

это не к языку, это к CLR/virtual machine

Что касается рантайма - разница там не сказать чтоб значительная, к тому же выделение памяти в managed heap сильно быстрее, чем в нативной, в силу отсутствия дефрагментации; да и современные gc нонеча работают уже совсем не так как давеча

а что такое ООП по вашему? Из трех "краугольных камней" ООП основополагающей является именно инкапсуляция. Мы объединяем логику, данные и ресурсы, в некоторую единую абстракцию - объект

одно из самых странных обьяснений ООП что я слышал, если честно

противоречит хотя бы тому же single responsibility

Иначе могут получаться "протекающие" абстракции - объект уничтожен, а ассоциированный с ним ресурс - нет. Что собственно и происходит в языках с GC.

не, не происходит:) Ну разве что с кастомными плохо спроектированными обьектами, использующими unmanaged resources

учитывая степень вашего понимания базовых концепций программирования хорошо конечно, что вы не пишете на с++...

каким образом понимание программирования на с++ связано с пониманием программирования вообще? Имхо, тут скорее наоборот, знание с++ требует столько умственных усилий, что на собственно программирование их и не остается

UFO just landed and posted this here

В том же Шарпе это будет не "когда они не нужны", а "когда они не нужны и до них наконец-то доберется сборщик мусора". Поэтому в языке пришлось изобретать костыли с using.

ну что вы, давным давно уже не так

запустится он, когда у него перестанет хватать памяти под новые обьекты, при этом, все обьекты он проверять на удаление не будет. А пока ее хватает, какие проблемы
Была тут вроде переводная статья про gc как эмулятор бесконечной памяти

что до using - это не про чистку памяти, это про гарантированный вызов некоей закрывающей функции, например, чтоб файл закрыть, или там курсор по базе
IDisposable никоим образом не гарантирует освобождение ресурсов, это просто интерфейс
такой аналог try/finally

UFO just landed and posted this here

ну да, там поколения еще есть, если я правильно помню.

там много чего есть:)

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

а если мне закрыть надо, а ресурс освобождать нет?:) Они ж довольно примитивные, unique_ptr эти. Никто ж не строит графов обьектов и не проверяет ссылки на них; вызвал кто reset, и все. А если на указатель внутрях еще один unique ссылается?

Ну и вроде все равно надо явно вызывать что-то типа unique_ptr.reset(nullptr), нет?

а если мне закрыть надо, а ресурс освобождать нет?

То вероятно это должны быть два разных уровня абстракции.


Никто ж не строит графов обьектов и не проверяет ссылки на них; вызвал кто reset, и все. А если на указатель внутрях еще один unique ссылается?

unique_ptr называется unique не просто так, два unique_ptr не могут ссылаться на один объект. Для таких случаев существует shared_ptr, который ведёт счётчик ссылок и удаляет объект только вместе с последней ссылкой.


Ну и вроде все равно надо явно вызывать что-то типа unique_ptr.reset(nullptr), нет?

Нет, обычно полагаются на вызов деструктора при выходе из скоупа. Вызов unique_ptr::reset() практически полностью аналогичен delete и существует для тех ситуаций, когда вам всё-таки необходимо ручное управление ресурсами.

unique_ptr называется unique не просто так, два unique_ptr не могут ссылаться на один объект.

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

Нет, обычно полагаются на вызов деструктора при выходе из скоупа

Понятно

Тогда мы снова возвращаемся к ситуации, когда мне (в случае необходимости немедленного освобождения памяти) нужно писать ручное удаление, которое еще и черти к чему приведет

О чем я и говорю, очень примитивная обертка, не гарантирующая, по сути, ни от чего

UFO just landed and posted this here

Прямым текстом сказано вообще-то:

дык а как это обеспечивается на уровне реализации этого UP? что мне мешает сделать указатель на обьект при помощи new и запихнуть его в 2 разных UP? Тока не говорите, что здравый смысл - это а) как повезет б) в сложных codebases вручную все проконтролировать невозможно

Объявили объект внутри блока {} - он удалится когда закончится этот блок

я, может, неправильно все делаю, но что-то вот в таком раскладе деструктор у меня не вызывается

class AutoCheck
{
public:
	AutoCheck(int i)
	{
		inner = i;
	}
	
	~AutoCheck()
	{
		inner = 0;
	}

private:
	int inner;
};
int main()
{
    auto test = new AutoCheck(10);
	  {
    	auto p = std::make_unique<AutoCheck*>(test);
    	auto p1 = std::make_unique<AutoCheck*>(test);
    }
}
UFO just landed and posted this here

похоже, мы всю дорогу о разном говорили.
unique_ptr тут вообще сбоку, говорить надо было про auto_ptr

UFO just landed and posted this here

угу, вижу

Суть моих филиппик очень простая - сам по себе язык и окружение не гарантируют ни от чего; как и 20 лет назад, надо самому за всем внимательно следить.

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

У него просто нет копирующего конструктора / присваивания, только перемещающие.
Выстрелить себе в ногу можно разве что хитро извернувшись в стиле unique_ptr{other_ptr.get()}.


Тогда мы снова возвращаемся к ситуации, когда мне (в случае необходимости немедленного освобождения памяти) нужно писать ручное удаление, которое еще и черти к чему приведет

Скоуп — любой блок кода в фигурных скобках, если ресурс должен быть занят только при выполнении нескольких строчек кода, которые не хочется выносить в функцию, то эти строки просто оборачиваются в фигурные скобки.
Обычная практика в плюсах для блокировки мутекса, например.

У него просто нет копирующего конструктора

auto test = new int(10);
auto p = std::make_unique<int*>(test);
auto p1 = std::make_unique<int*>(test);

а так?

Тока не говорите, что здравый смысл - это а) как повезет б) в сложных codebases вручную все проконтролировать невозможно

привести искуственный пример самострела можно в любом языке. И точно так же невозможно вручную проконтроллировать отсутствие его в достаточно большой кодобазе. Ну и не просто так рекомендуется использовать make_unique.

Что до здравого смысла - он ведь не дает вам например выпрыгнуть из окна, верно?

а так?

вы видите тут вызов копирующего конструктора?

привести искуственный пример самострела можно в любом языке. И точно так же невозможно вручную проконтроллировать отсутствие его в достаточно большой кодобазе. Ну и не просто так рекомендуется использовать make_unique.

я о том, что в силу особенностей языка использование unique_ptr на спасет ни от чего, выстрелить в ногу можно точно так же, как и 20 лет назад
просто тогда б-м прошаренный народ писал свои обертки и использовал всякие creator patterns
а вот наличие gc от такого вполне так себе гарантируeт

UFO just landed and posted this here

Использование unique_ptr защищает об большинства ошибок по невнимательности, но не защитит, если вы упорно намеренно и осознанно пытаетесь выстрелить себе в голову.

или использую его неправильно, от чего язык не защищает никак

Я, собственно, об этом в основном. Т.е., язык практически не изменился, просто оброс некими обертками, кои можно использовать правильно, можно неправильно, а можно и вообще не использовать.
Это мне напоминает одну контору в конце 90х, в которой было запрещено использовать арифметические операции с указателями - чтоб, зчачить, не попортить чего

А что вы этим кодом хотели показать? Что у типа int * есть конструктор копирования? Ну так не используйте указатели, и будет вам счастье.


Нормальный человек же напишет:


auto test = std::make_unique<int>(10);

После чего закономерно словит ошибку компиляции.

Конечно же словит. Нельзя привести std::unique_ptr<int> к int*.


Можно написать так, выстрелив в ногу через .get():


auto test = std::make_unique<int>(10);
auto p = std::make_unique<int*>(test.get());
auto p1 = std::make_unique<int*>(test.get());

А можно написать так:


auto test = std::make_unique<int>(10);
auto p = std::make_unique<std::unique_ptr<int>>(std::move(test));
auto p1 = std::make_unique<std::unique_ptr<int>>(std::move(test));

Никаких проблем, кстати, со вторым кодом не будет. Но это просто дичь.

Конечно же словит. Нельзя привести std::unique_ptr к int*.

я просто не понял, что вы имели в виду

Стоило бы сначала прочитать документацию make_unique и unique_ptr, ваш код эквивалентен


int* test = new int(10);
int** p = new int*(test);
int** p1 = new int*(test);
delete p1;
delete p;

Скорее всего вы имели в виду


auto test = new int(10);
std::unique_ptr<int> p(test);
std::unique_ptr<int> p1(test);

А я могу привести ещё один пример:


struct RecVector : std::vector<RecVector> {
  using std::vector<RecVector>::vector;
};

И сделав


RecVector a;
a.push_back(a);

Вы получите утечку памяти.


Знаете, чем этот пример отличается от вашего?
Тем, что я готов поверить в то, что что-то подобное, хоть и маловероятно, но может случайно возникнуть в большом и сложном проекте.


А само наличие new совершенно очевидно указывает на проблемное место, и ни один плюсовик, знакомый с хорошими практиками, такую ошибку никогда не допустит, и через кодревью не пропустит.


В C++ есть множество способов выстрелить себе в ногу, но не надо приплетать к ним случаи, возможные только в искуственных примерах.

А само наличие new совершенно очевидно указывает на проблемное место, и ни один плюсовик, знакомый с хорошими практиками, такую ошибку никогда не допустит, и через кодревью не пропустит.

ну т.е. в языке есть задокументированная в стандарте конструкция, пользоваться которой нельзя:) Понятно:)

Скорее всего вы имели в виду

да я уже посмотрел внутрь make_unique, вижу, что он указатель на переданное value создает
А как быть с созданием картинок, например, в цикле? Создал-обработал-автоудалил? Мне штож, в стеке их создавать?

В C++ есть множество способов выстрелить себе в ногу, но не надо приплетать к ним случаи, возможные только в искуственных примерах.

как-то, говорю ж, с 98 года не сильно все изменилось

UFO just landed and posted this here

Можно, если вам это действительно нужно и вы понимаете, что и зачем вы делаете.

если судить по исходной статье и обсуждению, такого знающего просто на работу не возьмут:))

А в чем сложность? Сделали make_unique внутри цикла, он автоматически удалит созданную картинку при завершении итерации.

вопрос, что мне передавать в пареметр make_unique? Картинку (например, в полгига), созданную на стеке?

Изменилось все настолько сильно, что я бы сказал, что C++98 и C++17 - это вообще почти что два разных языка

эт не язык изменился, это стандартная библиотека изменилась

вопрос, что мне передавать в пареметр make_unique? Картинку (например, в полгига), созданную на стеке?

Может, наконец, прочитаете документацию?
make_unique переадресует аргументы в конструктор типа, его суть ровно в том, что вам не нужно сначала создавать объект, а потом помещать в unique_ptr, как приходилось делать до c++14.


эт не язык изменился, это стандартная библиотека изменилась

Обсуждаемые нововведения были бы малополезны без появившегося в c++11 move semantics.

UFO just landed and posted this here

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

файловые дескрипторы, сокеты, мьютексы в конце концов. RAII дает куда больше чем простое освобождение памяти.

он дает чудовищное снижение этой самой нагрузки

Попишите, например, год на сишарпе или там жаве, и потом опять на плюсах - разницу сразу прочувствуете.

вы сильно недооцениваете моё понимание плюсов и сильно переоцениваете моё понимание джавы/шарпа.

это не к языку, это к CLR/virtual machine

по факту они скорее нераздельны

Что касается рантайма - разница там не сказать чтоб значительная, к тому же выделение памяти в managed heap сильно быстрее, чем в нативной, в силу отсутствия дефрагментации; да и современные gc нонеча работают уже совсем не так как давеча

Единственный вариант когда managed heap теоретически может быть быстрее - если специфика нагрузки хорошо ложится на конкретный аллокатор. Опять же, такие сценарии в плюсах покрываются переопределением аллокатора. В противном случае GC всегда будет медленнее, а идеальный GC по сути сводится к автоматическому управлению. Подробнее можете ознакомиться в этом докладе

одно из самых странных обьяснений ООП что я слышал, если честно

противоречит хотя бы тому же single responsibility

Во-первых, вы смешиваете категории. Парадигма ООП основана на инкапсуляции, а принцип единственной ответственности - всего лишь паттерн ООП. Во-вторых, здесь нет никакого противоречия. Простой пример: класс File обычно будет инкапсулировать файловый дескриптор (ресурс), данные (путь до файла, состояние, режим открытия), ну и соответственно логику работы с ним (методы открыть/закрыть/читать/писать). При этом SRP не нарушается, ведь File всё еще выполняет ровно одну задачу.

каким образом понимание программирования на с++ связано с пониманием программирования вообще? Имхо, тут скорее наоборот, знание с++ требует столько умственных усилий, что на собственно программирование их и не остается

я критикую ваше понимание ООП а не с++. А еще вы опять смешиваете категории, на этот раз "я не справился с с++" и "с++ не нужен"

запустится он, когда у него перестанет хватать памяти под новые обьекты, при этом, все обьекты он проверять на удаление не будет. А пока ее хватает, какие проблемы

действительно, какие проблемы выжрать 300 гигов памяти на серваке, а потом потратить десяток секунд на её очистку, пока все остальные приложения ждут...

что до using - это не про чистку памяти, это про гарантированный вызов некоей закрывающей функции, например, чтоб файл закрыть, или там курсор по базе
IDisposable никоим образом не гарантирует освобождение ресурсов, это просто интерфейс

собственно в этом и проблема. Вы разве никогда не сталкивались с проблемами типа заканчивающихся файловых дескрипторов?

файловые дескрипторы, сокеты, мьютексы в конце концов. RAII дает куда больше чем простое освобождение памяти.

да обычный менеджмент ресурсов. Мы еще в 2000х писали auto critical sections, ничего тут особенного нет, раз открыл - закрой; а если еще и будешь делать это через спец-обьект на стеке, так и вообще молодец

вы сильно недооцениваете моё понимание плюсов и сильно переоцениваете моё понимание джавы/шарпа.

ну, вот у меня был такой опыт. Очень освежающий!! Когда через пару дней активной разработки мы вдруг сообразили - мама дорогая, это ж не дотнет, надо ж самим обьекты прибирать! И началось..

Единственный вариант когда managed heap теоретически может быть быстрее - если специфика нагрузки хорошо ложится на конкретный аллокатор.

? Managed heap - это непрерывный кусок памяти, уже заранее выделенный вирт. машиной, какие там спец аллокаторы. Для небольших обьектов никакой там магии нет

Подробнее можете ознакомиться в этом докладе

в докладе говорится, что гц добавляет оверхед - с этим-то я вообще не спорил; я говорил, что а) на современном железе на него чаще всего пофиг, б) помещение обьекта в управляемую память - быстрее

ну и докладчик не очень знает про современные стратегии сборки мусора:)

Парадигма ООП основана на инкапсуляции

нууууу... Об этом до сих пор спорят, на чем же она основана. А один из основателей так и вообще сказал, что он имел в виду скорее event driven, а не вот ЭТО

действительно, какие проблемы выжрать 300 гигов памяти на серваке, а потом потратить десяток секунд на её очистку, пока все остальные приложения ждут...

ну так если вы настроили вирт. машину так, что ей можно 300 гиг памяти, кто ж виноват

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

Иными словами, пофиг, кто сколько сожрал памяти, если ее хватает

собственно в этом и проблема. Вы разве никогда не сталкивались с проблемами типа заканчивающихся файловых дескрипторов

нет, конечно, потому что я гарантированно закрываю открытые файлы

IDisposable - это просто интерфейс, позволяющий имплементировавшему его классу быть использованным в конструкции using. Как имплементацию напишете, так и будет - ровно как и в случае с деструктором вашего файлового обьекта

да обычный менеджмент ресурсов. Мы еще в 2000х писали auto critical sections, ничего тут особенного нет, раз открыл - закрой; а если еще и будешь делать это через спец-обьект на стеке, так и вообще молодец

это и называется "протекающие абстракции"

? Managed heap - это непрерывный кусок памяти, уже заранее выделенный вирт. машиной, какие там спец аллокаторы. Для небольших обьектов никакой там магии нет

это в теории там просто непрерывный последовательно заполняющийся кусок памяти. На практике это конечно же не работает (фрагментация уводит потребление памяти в космос) и приходится реализовывать полноценные аллокаторы, распределяющие память внутри этой managed heap.

б) помещение обьекта в управляемую память - быстрее

по сути это получается полноценная деаллокация. Грубо говоря, любой аллокатор, который используется внутри managed heap, можно просто подключить к плюсам и, если это выгодно, получить профит, безо всяких затрат на GC. Просто обычно это не делают потому, что это не выгодно.

ну и докладчик не очень знает про современные стратегии сборки мусора:)

а это не важно. Сколько ни оптимизируй/усложняй стратегию сборки мусора, отрицательной или даже нулевой её стоимость не станет.

нууууу... Об этом до сих пор спорят, на чем же она основана. А один из основателей так и вообще сказал, что он имел в виду скорее event driven, а не вот ЭТО

ООП по версии Алана Кая конечно больше похож на то, что нынче называют "акторами", но инкапсуляция в его парадигме всё так же фундаментально необходима

на современном железе на него чаще всего пофиг ... железо нынче дешевое, а программисты дорогие ... пофиг, кто сколько сожрал памяти, если ее хватает

Во-первых, вы предполагаете что разработка на языках с GC быстрее чем на тех же плюсах. Начиная с с++11 это как минимум не очевидно и не безусловно. Во-вторых, не всегда можно пренебречь стоимостью железа и не всегда есть его стократный запас. При сравнимой стоимости разработки ценник в $1m за облачную инфраструктуру покажется куда привлекательнее $5m.

это в теории там просто непрерывный последовательно заполняющийся кусок памяти. На практике это конечно же не работает (фрагментация уводит потребление памяти в космос) и приходится реализовывать полноценные аллокаторы, распределяющие память внутри этой managed heap.

да какая теория? При каждом коллекте gc дефрагментирует managed heap. Это гарантированно всегда непрерывный кусок памяти
Вот чесслово, почитайте доку уже
https://docs.microsoft.com/en-us/dotnet/standard/garbage-collection/fundamentals

а это не важно. Сколько ни оптимизируй/усложняй стратегию сборки мусора, отрицательной или даже нулевой её стоимость не станет.

не станет, но дьявол кроется в деталях

Во-первых, вы предполагаете что разработка на языках с GC быстрее чем на тех же плюсах. Начиная с с++11 это как минимум не очевидно и не безусловно

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

При сравнимой стоимости разработки ценник в $1m за облачную инфраструктуру покажется куда привлекательнее $5m.

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

ООП по версии Алана Кая конечно больше похож на то, что нынче называют "акторами", но инкапсуляция в его парадигме всё так же фундаментально необходима

да все равно unique_ptr под это дело не очень попадает
инкапсуляция - сокрытие данных, которые определяют состояние обьекта, а указатель внутри up него не определяет, это просто обертка

да какая теория? При каждом коллекте gc дефрагментирует managed heap. Это гарантированно всегда непрерывный кусок памяти

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

не станет, но дьявол кроется в деталях

какой блин "дьявол" то? У вас есть дополнительные расходы на механизм, который своим существованием не может сэкономить ресурсы даже теоретически.

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

то, что язык сложнее, не значит, что писать на нём тоже сложнее. Взять например питон - на дистанции в 1-10 kloc писать на нём проще чем на чем угодно другом; писать на питоне проект на 100+ kloc - самоубийство.

вряд ли будет такая разница, цифры-то с потолка

ну оверхед джавы/шарпа относительно плюсов по CPU порядка 2-4 раз плюс стоимость stop the world, по памяти больше. Моя оценка не то чтобы консервативна, но в реальных пределах. По порядкам цифры тоже вполне реальные.

При этом, найти толкового дева на плюсах сложнее и затратнее, чем, скажем, на дотнете или жаве

вы посмотрите средние ЗП плюсовиков и джавистов, удивитесь

да все равно unique_ptr под это дело не очень попадает
инкапсуляция - сокрытие данных, которые определяют состояние обьекта

его задача - быть тонкой владеющей оберткой для объекта, размещенного в куче. unique_ptr инкапсулирует сырой указатель T* _ptr, не давая его менять кроме как через методы unique_ptr. По мне так тут всё совершенно логично и корректно. Нелогично было бы если бы приходилось для каждого объекта unique_ptr<T> писать финализатор, который вызовет ~T(), а память освобождалась хитрым и тормозным неявным способом, останавливающим все потоки исполнения

ну оверхед джавы/шарпа относительно плюсов по CPU порядка 2-4 раз

Я недавно делал замеры скорости работы алгоритмов обработки изображений на C# и C++: C++ оказывался в среднем всего на 20% быстрее, чем C#. Разница в разы была, наверное, лет 10 назад.

Недавно столкнулся стем что на Java LU-разложение быстрее чем на С++, на несколько процентов. Исходные коды идентичны.

Правда использовался GraalVM vs Visual C++.

UFO just landed and posted this here

В том то и дело что использовался CLang и максимально возможные оптимизации, + AVX.

Честно говоря я был немного в шоке от GraalVM.

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

ну так в реалиях-то как раз эта сборка происходит редко, да и сбор в первой генерации быстрый. При этом не будет out of memory из-за дефрагментации памяти

какой блин "дьявол" то? У вас есть дополнительные расходы на механизм, который своим существованием не может сэкономить ресурсы даже теоретически.

теоретически - да
А на практике - очень даже. Потому что сбор мусора происходит редко

то, что язык сложнее, не значит, что писать на нём тоже сложнее.

именно это оно и значит:))

Взять например питон - на дистанции в 1-10 kloc писать на нём проще чем на чем угодно другом; писать на питоне проект на 100+ kloc - самоубийство

ну так давайте не питон возьмем, а сишарп, или там, свят-свят, жаву

вы посмотрите средние ЗП плюсовиков и джавистов, удивитесь

да зачем мне смотреть, мы весь прошлый год искали
сишарп/жава - без проблем, а сишников тока в экс-ссср и нашли, дядьку уже за 40

Нелогично было бы если бы приходилось для каждого объекта unique_ptr писать финализатор, который вызовет ~T(),

ну в с++ наверное
а в сишарпе это просто деструктор

а память освобождалась хитрым и тормозным неявным способом, останавливающим все потоки исполнения

финализация идет в отдельном потоке, о чем вы?

И еще раз замечу - вы чересчур переоцениваете необходимость своевременной чистки памяти. В частных случаях - да, в целом нет

теоретически - да
А на практике - очень даже. Потому что сбор мусора происходит редко

чем реже запускается сбор мусора, тем больше ресурсов надо почистить, тем больше времени уходит на чистку. По сути это просто некоторые N% времени потраченные на GC. И этот N >> 0

именно это оно и значит:))

попробуйте написать что-то сколь угодно сложное на таком примитивно-тривиальном ЯП как баш и вы поймете о чем я

ну так давайте не питон возьмем, а сишарп, или там, свят-свят, жаву

ну так они на порядок сложнее питона, что в очередной раз подтверждает мой аргумент

да зачем мне смотреть, мы весь прошлый год искали
сишарп/жава - без проблем, а сишников тока в экс-ссср и нашли, дядьку уже за 40

может быть потому что вы искали "сишников" а не "плюсовиков"? Или потому что вы искали джуна, а спрашивали как с сеньора? В общем, сомнительный результат сомнительного эксперимента. В то время как рынок куда более объективен.

а в сишарпе это просто деструктор

который вызывается когда конкретно?

финализация идет в отдельном потоке, о чем вы?

синхронизированным с остальными, что ведет к их замедлению. Плюс, количество "отдельных потоков" и ресурсов CPU на их исполнение в общем случае не бесконечно.

И еще раз замечу - вы чересчур переоцениваете необходимость своевременной чистки памяти. В частных случаях - да, в целом нет

вот беру я рабочий ноут с CLion, вижу потребление RAM в 2.5 Гб, тыкаю "очистить", вижу 850Мб. Система репортит потребление CLion'а в 4.5 Гб + 1.3 Гб clang code model. Естественно настроен ShenandoahGC + ExperimentalVMOptions + GuaranteedGCInterval, т.к. без этого потребление памяти CLion'ом достигало 13 гигов а при инициации чистки мусора можно было смело идти пить чай. И ладно бы это было раз в день, а не каждые 1-2 часа. Вся эта память могла бы использоваться на что-то полезное, например на браузер. Вот это частный случай, или всё-таки общий?

UFO just landed and posted this here

ну, про такие примеры не знаю, не сталкивался, потому спорить не буду

UFO just landed and posted this here

RAII не даёт ничего, кроме, по факту, «выполни вот этот код при выходе из скоупа»

Во-первых, этого более чем достаточно противопоставляя "когда-нибудь GC подчистит память, остальное будь добр руками". Во-вторых, не обязательно "при выходе из скоупа". В третьих, вы опять придумываете гипотетические проблемы.

Такие гарантии (вместе с предсказуемым потреблением ресурсов) дают только типы.

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

А потом вы захотите управлять не только файлами на диске, но и чем-нибудь mmap'ленным, например, и понятие дескриптора файла надо будет как-то выносить отдельно.

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

UFO just landed and posted this here

Мы обсуждаем производительность или корректность?

мне нравится как вы даже переспросили о чем спор, причем всего лишь во втором комментарии после того, как к нему присоединились. Сильно лучше некоторых.

Обсуждается производительность и универсальность соответствующих инструментов. Моя позиция - GC не может быть в общем случае быстрее и ограничивается лишь одним видом ресурсов - памятью, оставляя остальные на ручное удаление программистом.

Ну а когда RAII ещё срабатывает?

когда мы удаляем объект любым способом. Например, я же не выхожу из скоупа когда делаю optional<T>::operator=(nullopt), но эта операция удаляет вложенный объект при его наличии, вызывая его деструктор.

C++ приучил :(

выдумывать теоретически возможные ошибки, чтобы потом их у себя искать - весьма неэффективный подход к написанию и отладке кода.

Не понял, почему это?

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

Плюс, ФП ≠ иммутабельность. В этих наших хаскелях вполне есть локальная мутабельность ...

мутабельность в ФП это бекдор, противоречащий парадигме. Собственно, поэтому и имеет смысл её локально ограничивать.

Ничему не мешает, просто показывает, что SRP таки нарушается.

можете показать где конкретно нарушается принцип единственной ответственности? Вот хоть убей не вижу.

UFO just landed and posted this here

Естественно, потому что любая последовательность событий освобождения ресурсов в GC может быть повторена вручную

а еще любая последовательность событий поверх аллоцировал/освободил является избыточной с точки зрения управления памятью. С++ позволяет мне этим ограничиться.

Однако, вы оставляете тут за скобками вопрос потребных человеческих ресурсов. Не надо так.

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

Часто встречаете такой код для T ~ std::unique_ptr<T'> или myDbConnection?

сформулируйте вопрос иначе

Но на плюсах писать код с хоть какой-то степенью осознанности и уверенности нельзя.

define "какой-то степенью" потому что большая часть спектра возможных интерпретаций противоречит моему опыту. Вот буквально сегодня столкнулся с "invalid memory address or nil pointer dereference" в go приложении. Значит ли это что писать на go с "какой-то степенью осознанности и уверенности" тоже нельзя? Но ведь такие ошибки бывают и в c#/java, что про них? "А если нету разницы, зачем платить больше?" (с).

Ну а вот в ФП с точки зрения перформанса объекты принимаются по ссылке, а не по значению (с точки зрения семантики это неразличимо в ФП, понятное дело)

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

Там, где объект начинает отвечать и за абстракцию над fd, и/или за конкретный специальный случай обычных файлов на диске. Это разные вещи.

я волен инкапсулировать объект любым удобным мне образом. Захочу - сделаю тонкую обертку над дескриптором. Захочу - это будет представление "файла на диске". Или "файла на диске и/или в памяти". Или "файла лежащего по абстрактному URI". SRE не нарушится ни в одном из случаев, это всего лишь разные уровни абстракции.

UFO just landed and posted this here

Плюс, attention span у людей ограничен. Если мне нужно думать о плюсовых UB, то у меня остаётся меньше ресурсов на обдумывание логики.

Во-первых, это вопрос опыта разработки на конкретном языке, во-вторых, логика и реализация продумываются на разных этапах?

Часто засовываете в std::optional всякие unique_ptrы или соединения с БД, и не для ленивой инициализации, а для того, чтобы один unique_ptr потом заменить другим?

не часто, но важно ведь не это. Важно то, что деструктор сработает при любом удалении объекта, а не только при неявном выходе из скоупа (чего в общем-то можно добиться и всякими defer'ами, пусть и многословнее).

Как часто они там встречаются?

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

У вас мир C#/Java ограничивается, что ли? Должно быть, это очень грустный мир.

это самые яркие примеры языков общего назначения с GC, которые в остальном похожи на плюсы. Не с полумертвым D же сравнивать.

Вы можете как-то сразу определиться со своими аргументами? То у вас все объекты передаются по значению, то это теперь неважно.

С точки зрения ABI передача по значению в с++ это создание нового объекта и передача его в функцию по ссылке. Если для объекта гарантируются свойства иммутабельности и неотличимости копии от оригинала, то копирование можно опустить и сразу передавать объект по ссылке. Собственно, в функциональных языках оба эти свойства могут гарантироваться, поэтому и не принципиально как там называется способ передачи

SRP вообще имеет смысл-то так? А то ведь для любого описания функциональности есть уровень абстракции, где это единый юнит.

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

А во-вторых, забываете что принципы SOLID это просто свод указаний, а не жестких законов.

UFO just landed and posted this here

Ну вот моих 18 лет опыта, очевидно, недостаточно. Практика дырок в других приложениях показывает, что там тоже недостаточно.

опыт коммерческой разработки с 12 лет то?

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

проблема лайфтаймов ортогональна GC и решается без него (как в том же расте)

А зачем вы спорите с соломенными чучелами? Давайте лучше сравним количество висячих ссылок, выходов за границы и так далее.

хотите сказать в managed языках не бывает выхода за границы?

Зачем похожесть на плюсы? Императивно-нулловая философия накладывает свои ограничения.

так вся дискуссия про то, насколько полезен GC. Соответственно желательно сравнивать языки, которые максимально близки во всем, кроме наличия/отсутствия GC.

А если я его после этого сохранил как поле объекта?

вариантов уйма же, самый простой - копирование.

Я не то что забываю, я изначально во всех этих солидах не шарю, там сложно чё-т слишком.

вы постоянно находите простейшие вещи невероятно сложными... И наоборот

UFO just landed and posted this here

Ну наконец-то вы сами начали подходить к тому, о чём я говорил изначально — о типах, то бишь.

в дискуссии о полезности GC?

Хочу сказать, что в managed-языках без unsafe выход за границы отлавливается и не является UB.

Это всё еще ошибка, она всё еще допущена, код всё еще работает некорректно. Более того, для неожиданных исключений обычно отсутствует нормальная обработка. В итоге приложение приложение уходит в некоторое некорректное состояние, в котором может застрять до рестарта (недавно ловил такое). По мне так лучше краш + рестарт.

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

Нет. Имеет смысл рассматривать языки, которые без GC реализуются тяжко (функциональщину там всякую).

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

Ну ещё раз, скопируйте мне соединение с БД.

shared_ptr<DBConnection> conn(otherConn); ?

UFO just landed and posted this here

В дискуссии о методах управления ресурсами.

GC не является "методом управления ресурсами", а только лишь памятью.

Нет, код не работает некорректно.

"Код не работает корректно", whatever. Вы пытаетесь продать исключение при выходе за границу так, словно оно предотвратило ошибку. Однако надежность системы не выросла ни на йоту.

Код вообще не работает, потому что его выполнение прервалось экзепшоном.

"Прервалось экзепшном" != "не работает". Максимально вероятно код будет продолжать работать каким-то не запланированным способом. Так же как и в плюсах. Но у вас почему-то в одном языке в выходе за границу массива будет виноват программист, а в другом языке - сам ЯП. Где в вашем сознании пролегает та самая эфемерная граница, их разделяющая? Возможно это банальная предвзятость?

Очень по значению, ага.

От того, что вы указатель завернули в класс и класс передаёте по значению, вся программа в «только по значению» не превратилась.

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

UFO just landed and posted this here

Если у меня есть условный торговый бот, который рассчитывает, торговать или не торговать, то я бы предпочёл, чтобы при ошибке доступа за границы он упал, чем если бы он просто модифицировал соседний массив флоатов с весами для какой-нибудь регрессионной модели, которая после этого начнёт выдавать фигню.

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

UFO just landed and posted this here

К счастью, кроме динамических проверок есть возможность что-то про код доказывать статически (про что я тоже писал).

И тогда рассматривать пример с динамической проверкой границ и выбросом исключения становится бессмысленным.

Ну или замените торгового бота на программу, рассчитывающую дозу рентгеновского аппарата.

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

По поводу динамических проверок можно еще катастрофу Ариан-5 вспомнить, когда был переиспользован код из Ариан-4, не рассчитанный на условия взлета другой ракеты. Динамические проверки сработали, но надежности это не добавило. Потому что ошибка была на другом уровне.

И да, софт для Ариан-5 был не на C++, а на более безопасной Ада.

UFO just landed and posted this here

На что, собственно, я и ответил, что плохо оно это даёт, типы лучше.

т.е. по свойству транзитивности типы лучше GC?

Если у меня есть условный торговый бот

а если не торговый бот, а условная АЭС, то варианты 2 и 3 абсолютно иррелевантны - после катастрофы задача "не допустить сбой" провалена. АЭС конечно же редкий случай, но если проект может позволить себе failsafe, то опять же варианты 2 и 3 практически иррелевантны.

Объясните для тупых, как код будет продолжать работать после того, как гипотетический operator[] кинет здесь ошибку?

исключение долетит до ближайшего хендлера и продолжит выполнение оттуда. Например, сервер вернет 500-ку. Вот только т.к. никто не почистил состояние после возникновения ошибки, этот выход за границы повторится и для следующих запросов. Что хуже - один битый запрос, или миллионы?

Тем не менее я не хочу меряться чей вырожденный негативный сценарий хуже. Я лишь пытаюсь объяснить что возможность обработки ошибки сама по себе не помогает её предотвратить.

В плюсах оно не прерывается экзепшоном. Как это «так же»?

пишите at, будет прерываться.

Это не граница, это спектр.

где-то я это уже слышал. Правда, тот раздел психологии перестали преподавать в вузах.

Что вообще такое рефкаунт в иммутабельном случае? Как там счётчик выглядит, что вы модифицировать собрались?

рефкаунтинг всегда делают "под капотом", так, что снаружи выглядит словно инстанс не меняется. И я всё еще не понимаю чего вы от меня добиваетесь. Хотите чтобы я спроектировал функциональный язык без GC?

а если не торговый бот, а условная АЭС, то варианты 2 и 3 абсолютно иррелевантны - после катастрофы задача "не допустить сбой" провалена.

почему? Если у меня нормальная стратегия обработки ексепшенов, я при вылете оного спокойненько аварийно заглушу станцию

А если ошибка есть, но никак себя не проявила - станция подвзорвется, даже при наличии стратегии обработки ошибок - потому что обрабатывать и реагировать не на что

почему? Если у меня нормальная стратегия обработки ексепшенов, я при вылете оного спокойненько аварийно заглушу станцию

Это если вы предусмотрели "нормальную стратегию обработки" исключений конкретного вида, сигнализирующих о кейсе, который вы явно не предусмотрели. Чувствуете противоречие?

вопрос-то в том, что во втором случае пофигу, есть она у меня или нет

вопрос-то в том, что во втором случае пофигу, есть она у меня или нет

я уже дважды привел кейс где объяснял насколько это совершенно не "пофигу"

Ну давайте сначала

Допустим, в обоих случаях у нас есть внятная стратегия обработки ошибок

И возникает ситуация, описанная выше по треду

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

Т.е., есть во втором случае есть у вас обработка ошибок, нет ее - совершенно никак вам не поможет

Ну давайте сначала

хорошо, в четвертый раз...

Допустим, в обоих случаях у нас есть внятная стратегия обработки ошибок

так я и утверждаю что её нет! Вы либо предусмотрели возможность ошибки типа X (в нашем случае выхода за границы массива), добавили доп. условие и предотвратили возникновение этой ошибки. Либо вы эту ошибку не предусмотрели, и соответственно забыли добавить для неё корректную обработку. И тогда лучшее, на что вы можете надеяться - что ошибка типа X улетит в обработчик ошибки типа Y, который волшебным образом сделает что-то отдаленно логичное для конкретной ситуации. В худшем случае последствия будут печальнее проезда по памяти.

UFO just landed and posted this here

500-ка — это не то же самое, что «мы тут рассчитали какую-то ерунду и вернули её», не так ли?

я пытаюсь доказать что ошибки обоих видов могут вести к довольно-таки неприятным последствиям. Вы же пытаетесь доказать что гипотетическая программа A на языке X, содержащая ошибку W, обязательно обработает эту ошибку хуже гипотетической программы B на языке Y. Совершенно игнорируя тот факт что это зависит не от языков X vs Y, а от программ A vs B. И я уже объяснил почему язык B не поможет предотвратить ошибку W сколь угодно лучше языка A.

Давайте если продолжать спор, то вы не будете пытаться доказать что-то заведомо абсурдное.

Нет, я хочу, чтобы вы определились, понимаете ли вы, как работает чистое ФП (и не предлагали рефкаунты под капотом), или же не понимаете (и не пытались сказать, что ФП — это семантически C++, где всё pass-by-value).

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

UFO just landed and posted this here

Вы спорите с соломенным чучелом.

А "условного торгового бота" тоже я выдумал?

А то, что я пытаюсь сказать — это что диагностируемые ошибки лучше недиагностируемых и скрытых.

В общем случае да, я говорю про частный - когда задача не диагностировать ошибку, а предотвратить её возникновение. По крайней мере я понимаю "надежность ПО" именно как отсутствие непредвиденных отказов.

Это деталь реализации, при обсуждении операционной (или денотационной, неважно) семантики про подобное вспоминать не имеет смысла.

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

Вы изначально начали говорить, что в ФП за счёт иммутабельности что-то там происходит

не происходит, а гарантируется

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

почему нельзя написать то?

UFO just landed and posted this here

Нет, конечно. Но это пример, призванный иллюстрировать, чем непредсказанные во время разработки, но пойманные во время выполнения ошибки лучше непойманных.

Но так как мы таки их обсуждаем, то этот частный случай как-то тут не к месту рассматривать, ИМХО.

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

Вот вы описали все ужасы порчи памяти, а я продемонстрировал, как некорректная обработка ошибки может усугубить её последствия. Это гипотетические worst case scenario, основанные на предположениях о конкретном поведении конкретного кода. В целом можно игнорировать, верно?

Далее, вы утверждали диагностируемые ошибки строго лучше UB. С этим я не спорю. Но вы так же утверждали что это улучшает надежность системы, с чем я не согласен по тому определению надежности, которое я привел выше. Почему это частный случай и его не имеет смысл рассматривать?

Если язык позволяет вам выразить рефкаунт, то это уже не язык с одними лишь иммутабельными объектами, только и всего.

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

UFO just landed and posted this here

Однако надежность системы не выросла ни на йоту.

Тут вы не правы. Надежность определяется как тем, делает ли программа какую-то незапланированную фигню (выходит за пределы массива), так и тем, диагностируется ли эта незапланированная фигня или же нет (отсутствие диагностики как раз снижает надежность), так и тем, есть возможность среагировать на диагностику.

Как раз динамические проверки в ран-тайме с выбросом исключения, на которое можно среагировать, и дают возможность простым смертным разрабатывать надежные программы.

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

Надежность определяется как тем, делает ли программа какую-то незапланированную фигню (выходит за пределы массива), так и тем, диагностируется ли эта незапланированная фигня или же нет (отсутствие диагностики как раз снижает надежность), так и тем, есть возможность среагировать на диагностику.

ну давайте рассмотрим такой сценарий. Мы хотим предотвратить ошибки с помощью их диагностики в предварительных прогонах. Например, в тестах. Что нам мешает в плюсах запускать тесты с санитайзерами, которые распечатают нам все ошибки памяти?

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

в тех языках страдает еще и применимость. Смысл спорить какой язык, A или B, подойдет для задачи X лучше, если B для этой задачи в принципе не применим?

Что нам мешает в плюсах запускать тесты с санитайзерами, которые распечатают нам все ошибки памяти?

Тут важнее другое: а какие гарантии нам это дает?

Момент первый. Вроде как еще никому не удавалось что-то более менее серьезное протестировать на всех возможных входных данных во всех возможных ситуациях.

Момент второй. В некоторых областях накладные расходы, которые мы вынуждены нести на санитайзеры или даже на отсутствие должных оптимизаций в debug-режимах, могут вести к существенному изменению т.н. "времянки". Из-за чего под санитайзерами просто не будут возникать те приключения, которые у нас возникнут при полной скорости работы.

Смысл спорить какой язык, A или B, подойдет для задачи X лучше, если B для этой задачи в принципе не применим?

У меня сложилось впечатление, что 0xd34df00d тупо убил часть своего детства и всю свою молодость на задрачивание C++ и долгое время был вынужден использовать только C++. Даже там, где у этого могло и не быть смысла. А потом, в какой-то момент, он открыл для себя дивный чудный мир за пределами его старого узкого мирка и у него слегка снесло крышу. Ведь, как оказалось, где-то есть инструменты поудобнее. И решать на них какие-то задачи проще, чем на C++. Боль от такого открытия и заставляет 0xd34df00d снова и снова сравнивать C++ с другими языками.

У меня сложилось впечатление, что 0xd34df00d тупо убил часть своего детства и всю свою молодость на задрачивание C++ и долгое время был вынужден использовать только C++. Даже там, где у этого могло и не быть смысла. А потом, в какой-то момент, он открыл для себя дивный чудный мир за пределами его старого узкого мирка и у него слегка снесло крышу.

и в итоге зачастую сравнивает с++98/03 с языками, появившимися после с++11/14. Я почти уверен что никто из здесь присутствующих не хотел бы разрабатывать на тех же джаве/шарпе версий 2000-ного. То есть аргументы вроде как правильные, но они рисуют картину, которая от реальной разработки на современных плюсах отличается как небо и земля.

Я почти уверен что никто из здесь присутствующих не хотел бы разрабатывать на тех же джаве/шарпе версий 2000-ного

я после 8 лет на плюсах начал писать еще на c# 1.1
Радовался как дитя

А когда 2й вышел, так и вообще стало казаться, что больше уже ничего не надо

А когда вышел 3й, стало ну совершено понятно, что с++ это забытое прошлое:)

я после 8 лет на плюсах начал писать еще на c# 1.1
Радовался как дитя

то есть вы сейчас расписались в той же самой ошибке, за которую я критикую 0xd34df00d, и которая явно является базой вашей предвзятости.

так как вы можете судить об ошибках, если ни на чем, кроме плюсов не пишете?

так как вы можете судить об ошибках, если ни на чем, кроме плюсов не пишете?

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

так я последний раз пару лет назад писал, как раз с использованием разного - unique_ptr и прочего

Мне не понравилось, слишком много надо держать в голове в сравнении с другими языками, типа с#, жава, котлин, питон.

А у вас, судя по всему, подобного опыта нет. Как там было, "узкий специалист подобен флюсу - полнота его одностороння"(ц)

По крайней мере я понимаю "надежность ПО" именно как отсутствие непредвиденных отказов.

ыы

вы, наверное, не очень давно работаете, извините

непредвиденные отказы есть всегда, как только программа попадает в б-м массовые руки пользователей

Мне не понравилось, слишком много надо держать в голове в сравнении с другими языками, типа с#, жава, котлин, питон.

на жаве/котлине не писал, а вот на питоне довелось, и там надо держать в голове больше, чем в плюсах. Как минимум - типы переменных/аргументов. Да, вроде как не беспокоишься о лайфтаймах, но почти любая опечатка ловится только в рантайме. Спасибо, я лучше на плюсах буду писать, нервы дороже.

вы, наверное, не очень давно работаете, извините

что-то около 6-7 лет, наверно не очень давно, да. За выслугу лет еще не награждали.

непредвиденные отказы есть всегда, как только программа попадает в б-м массовые руки пользователей

а если я бекенд разрабатываю, то кто есть пользователь? А если ошибки бывают, но в большинстве чисто логические, которые были бы допущены и в других языках?

на жаве/котлине не писал, а вот на питоне довелось, и там надо держать в голове больше, чем в плюсах. Как минимум - типы переменных/аргументов.

Зачем?

а если я бекенд разрабатываю, то кто есть пользователь?

как только у вас появляется хотя бы пару тысяч пользователей, все становится интересно. Потому что то, что они могут учудить, ни одному QA в голову не придет
Так что ошибки/баги есть всегда; рассуждения о том, что надо писать без них - идеалистические

UFO just landed and posted this here

То что кроме плюсов есть языки более дружелюбные к программисту и позволяющие меньше напрягать мозг?

Меньше напрягать мозг на особенности языка. И эти высвободившиеся мощности можно вполне пустить на бизнес-логику. Это, на мой взгляд, эффективнее - все таки программы на работе мы пишем не для себя, a для пользователей, а им пофиг на язык

То что плюсы с годами и новыми стандартами не становятся лучше и приятнее? Я с ними работаю каждый день и вижу своими глазами обратное. Очень даже становятся.

из того, что я вижу в обсуждении - они становятся все сложнее и с б0льшим UB

То что все остальные должны срочно перестать писать на плюсах?

Так оно само так случится, очень уж высокий порог входа и дорогая поддержка

Графические движки пишутся на плюсах

Например, Unity:))

Например, Unity:))

А на чём он по-вашему написан?

и на плюсах, и на с#

но вся разработка с ним - на c#

Если с++ так чудесен, почему бы не сделать его с++ only, как тот же unreal?

Вам говорят


Графические движки пишутся на плюсах

А вы в ответ приводите контрпример в виде… движка, написанного на плюсах!


То, что скрипты игровой логики к нему пишутся на C# совершенно неважно, так как это уже другая область, в которой вообще вроде бы Lua доминирует.

А вы в ответ приводите контрпример в виде… движка, написанного на плюсах!

Ок, ок, ошибся

Core у него на плюсах, все остальное - на сишарпе

То, что скрипты игровой логики к нему пишутся на C# совершенно неважно

скрипты пишутся на lua:)
А игра, т.е. взаимодействие логики отображения и взаимодействия - на с#; движок просто рисует меши, грубо говоря

UFO just landed and posted this here

в большинстве упомянутых мною сфер плюсы по-прежнему лидируют и даже понемногу проникают в смежные.

Ну вы посмотрите на тренд за последние лет 10-15

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

Вы теоретизируете о чем-то что где-то услышали

Я всего лишь читаю данную дискуссию

Вы, должно быть, хотели блеснуть остроумием, но я вас расстрою - движок Unity написан на C++, шарп там используется только для вытаскивания апишек движка наружу.

только?:) Т.е., то, что основной интерфейс для работы с юнити это с#, это только?

Ну вы посмотрите на тренд за последние лет 10-15

Использование плюсов неуклонно снижается.

так уж прям неуклонно снижается? По данным stackoverflow доля с++ опять же заметно выросла за последние лет пять.

я потому и предлагаю смотреть на 10-15 летние тренды. То, что сейчас поднялся - ну ок, но до уровня его массовости в начале 2000х, например, куда как далеко

ну и в вашей ссылке речь о С :)

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

я потому и предлагаю смотреть на 10-15 летние тренды. То, что сейчас поднялся - ну ок, но до уровня его массовости в начале 2000х, например, куда как далеко

а потом мощности компьютеров стали позволять писать даже на языках с GC

ну и в вашей ссылке речь о С :)

вчитайтесь

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

Во-первых, с++ сильно производительнее большинства из этой массы современных языков. Экономить ресурсы на высокоуровневом ЯП куда сложнее, например, память в джаве или cpu в питоне. Во-вторых, я же никогда не пытаюсь доказать что с++ - самый безопасный и самый выразительный язык. Мой посыл как раз в том, что с годами он стал сильно удобнее и дал больше средств для обеспечения надежности. Настолько, что размен производительности на выразительность/безопасность становится куда менее очевидным. Большая часть ошибок, с которыми лично мне доводится иметь дело, чисто логические, которые я или мои коллеги допустили бы в любых ЯП. Что до меньшей части - ну буду я не дебажить краши, а оптимизировать боттлнеки, в общем случае это не быстрее.

а потом мощности компьютеров стали позволять писать даже на языках с GC

Да, как вариант; я сразу сказал, что труд погромиста сильно дороже железа

Во-первых, с++ сильно производительнее большинства из этой массы современных языков.

по размеру памяти - возможно. Чисто как числодробилка - где как, и совсем не "сильно" производительнее; да и скорость выделения в managed/unmanaged heap мы уже обсуждали

Мой посыл как раз в том, что с годами он стал сильно удобнее и дал больше средств для обеспечения надежности

другие-то тоже на месте не стояли:)

вопщем, с моего имха я б не замыкался в рамках одного языка

Да, как вариант; я сразу сказал, что труд погромиста сильно дороже железа

в частном случае да, в общем - нет. По факту совсем не париться о производительности обычно могут позволить себе только фронтендеры (потому что за железо платит пользователь) и ML специалисты (потому что их код всего лишь вызывает тяжелую обработку на с++). Ну либо если проект маленький и бесперспективный.

по размеру памяти - возможно. Чисто как числодробилка - где как, и совсем не "сильно" производительнее;

класс приложений-"числодробилок" куда меньше класса приложений, работающих с текстом.

да и скорость выделения в managed/unmanaged heap мы уже обсуждали

и кажется вы из этого обсуждения ничего не вынесли

другие-то тоже на месте не стояли:)

ну в качестве примера аналоги auto и лямбд появились в java позже с++, хотя казалось бы...

вопщем, с моего имха я б не замыкался в рамках одного языка

да, я всё не могу выделить время поучить раст. А то даже глупо знать про UB в языке больше чем про синтаксис...

в частном случае да, в общем - нет

наоборот

В общем случае железо дешевле

По факту совсем не париться о производительности обычно могут позволить себе только фронтендеры (потому что за железо платит пользователь) и ML специалисты

ну-ну
наша контора продает, например, десктопный софт, который стоит (в полном сборе) 40000 баксов за 1 (одно) рабочее место. Даже супермощный десктопный комп будет стоить сильно меньше

Если же речь об онлайне, там еще проще, там амазоновское облако, например, которое отлично скейлится за очень небольшие деньги

и кажется вы из этого обсуждения ничего не вынесли

мне кажется, это вы должны были что-то вынести, т.к. совсем себе не представляли, как работает выделение/освобождение памяти в хотя бы дотнете

ну в качестве примера аналоги auto и лямбд появились в java позже с++, хотя казалось бы...

а в дотнете они появились 14 лет назад

да, я всё не могу выделить время поучить раст

смотрите, потом поздно будет

наоборот В общем случае железо дешевле

как у вас частное перестало быть подмножеством общего?

наша контора продает, например, десктопный софт, ...

и этот частный пример по-вашему задает общие тенденции?

Если же речь об онлайне, там еще проще, там амазоновское облако, например, которое отлично скейлится за очень небольшие деньги

как раз там линейная зависимость стоимости аренды от потребляемых ресурсов. В зависимости от дистанции и масштабов проекта эта стоимость может дорастать до весьма существенной.

мне кажется, это вы должны были что-то вынести, т.к. совсем себе не представляли, как работает выделение/освобождение памяти в хотя бы дотнете

вы просто не поняли обоснование того, как GC даже теоретически не способен экономить ресурсы по сравнению с автоматическим освобождением

как у вас частное перестало быть подмножеством общего?

потому что я не согласен с вашим утверждением. Да, собственно, это уже давно общее место, что железо дешевле софта, и соответственно, труда программистов

как раз там линейная зависимость стоимости аренды от потребляемых ресурсов. В зависимости от дистанции и масштабов проекта эта стоимость может дорастать до весьма существенной.

Может, и что? Дешевле и быстрее заплатить за железо, чем за дополнительную разработку

вы просто не поняли обоснование того, как GC даже теоретически не способен экономить ресурсы по сравнению с автоматическим освобождением

смотря что понимать под ресурсами
если говорить только о железе, то да
Если комплексно о разработке, то сильно экономит ресурсы; человеческие, в первую очередь

потому что я не согласен с вашим утверждением. Да, собственно, это уже давно общее место, что железо дешевле софта, и соответственно, труда программистов

"в среднем" - может быть, "в общем" - нет.

Может, и что? Дешевле и быстрее заплатить за железо, чем за дополнительную разработку

опять же, это "иногда" а не "всегда".

Если комплексно о разработке, то сильно экономит ресурсы; человеческие, в первую очередь

ну вот буквально сегодня я словил факап на проде потому что, как оказалось, кеширующая прокся на go не способна держать жалкие 2k rps на 4-х ядрах. Чтобы достигнуть такого уровня производительности на с++ даже задумываться не приходится. Как думаете, сколько человеческих ресурсов мне и моим коллегам бы сэкономило если бы всё просто работало?

"в среднем" - может быть, "в общем" - нет.

в среднем и чаще всего
Речь не идет о высокопроизводительном софте, которого проценты от общего. 90% софта вполне попадают под "общее"

опять же, это "иногда" а не "всегда".

да почти всегда

доп дев обойдется конторе в 70-100 тыс в год (это в нашем небогатом квебеке, например), нарастить облако будет сильно дешевле

ну вот буквально сегодня я словил факап на проде потому что, как оказалось, кеширующая прокся на go не способна держать жалкие 2k rps на 4-х ядрах.

так тут либо а) прокся просто криво написана б) либо да, затык по производительности. Я б поставил на первое, особенно если выяснится, что данные хреново параллелятся на эти самые 4 ядра

При чем тут язык?

Речь не идет о высокопроизводительном софте, которого проценты от общего. 90% софта вполне попадают под "общее"

откуда вы взяли цифру в 90%?

доп дев обойдется конторе в 70-100 тыс в год (это в нашем небогатом квебеке, например), нарастить облако будет сильно дешевле

а с чего вы взяли что доп разраб обязательно потребуется? И с чего вы взяли что он не окупится? И с чего вы взяли что дешевле? Из вашего конкретного случая или из головы?

так тут либо а) прокся просто криво написана б) либо да, затык по производительности.

криво или нет - не мне судить, я лишь знаю что прокся средней кривости на с++ должна выдержать 2к рпс без нареканий.

Я б поставил на первое, особенно если выяснится, что данные хреново параллелятся на эти самые 4 ядра

ну то есть вы предполагаете что надо было потратить больше времени на оптимизацию, да? Или просто закидать железом с горкой?

откуда вы взяли цифру в 90%?

да вакансии посмотрите на работных сайтах

а с чего вы взяли что доп разраб обязательно потребуется?

а они всегда требуются, если мы хотим чего-то большего

Если делаешь коробку, например, на доп улучшения надо кого-то нанимать, т.к. остальные уже заняты - новые фичи, апгрейды и прочее, запланированное менеджментом, и отложить их нельзя

ну то есть вы предполагаете что надо было потратить больше времени на оптимизацию, да? Или просто закидать железом с горкой?

если программа хреново написана, то да. Просто потому, что увеличение числа ядер ничего не даст

Но это от языка не зависит вообще; я-то говорил о ситуациях, когда ваше аккуратное использование памяти легко бьется покупкой доп планок (если совсем примитивно), или доп. виртуальных машин

Если делаешь коробку, например, на доп улучшения надо кого-то нанимать, т.к. остальные уже заняты - новые фичи, апгрейды и прочее, запланированное менеджментом, и отложить их нельзя

так рождается софт, который потом приходится переписывать с нуля.

если программа хреново написана, то да. Просто потому, что увеличение числа ядер ничего не даст

вы забываете что с++ куда лучше масштабируется по числу ядер чем языки с GC. Из-за самого GC, собственно

Но это от языка не зависит вообще; я-то говорил о ситуациях, когда ваше аккуратное использование памяти легко бьется покупкой доп планок (если совсем примитивно), или доп. виртуальных машин

когда нагрузка несущественная или распределяется идеально - тогда да, мб и бьется...

так рождается софт, который потом приходится переписывать с нуля.

увы, это реалии отрасли

вы забываете что с++ куда лучше масштабируется по числу ядер чем языки с GC. Из-за самого GC, собственно

эээ.. это, мягко говоря, слабо обоснованное умозаключение

когда нагрузка несущественная или распределяется идеально - тогда да, мб и бьется...

эээ.. gc чаще всего и распределяет ее б-м идеально - в непрерывном куске

увы, это реалии отрасли

разница в том, перепишут вашу софтину с нуля через два года или через десять.

эээ.. это, мягко говоря, слабо обоснованное умозаключение

Если у нас stop the world GC: чем больше потоков плодят мертвые объекты, тем дольше цикл сборки мусора, который останавливает все потоки, т.е. суммарное время простоя ядер квадратно пропорционально числу активных потоков.

А конкурентные GC вводят дополнительные синхронизации потоков, стоимость которых снова квадратно пропорциональна числу активных потоков.

И в обоих случаях стоимость GC не является пренебрежимо малой даже на малом числе потоков. Выглядит как формальное доказательство.

эээ.. gc чаще всего и распределяет ее б-м идеально - в непрерывном куске

я имел в виду распределение нагрузки между виртуальными машинами.

разница в том, перепишут вашу софтину с нуля через два года или через десять.

кто ж ее через два года переписывать будет-то:)) Бабло надо рубить, а не переписывать

Если у нас stop the world GC: чем больше потоков плодят мертвые объекты, тем дольше цикл сборки мусора, который останавливает все потоки, т.е. суммарное время простоя ядер квадратно пропорционально числу активных потоков.

это, снова-здорово, если у нас вдруг перестало хватать памяти и запустился gc. А памяти у нас много, как уже было сказано

кто ж ее через два года переписывать будет-то:)) Бабло надо рубить, а не переписывать

так у вас софтина не работает как надо, клиенты перетекают к конкурентам, поток бабла постепенно снижается, если вовремя не переписать бабло закончится. Это с точки зрения бизнеса.

С точки зрения отдельного разработчика конечно же интереснее из раза в раз писать поделия-однодневки, постепенно обогащая резюме мертвыми проектами.

это, снова-здорово, если у нас вдруг перестало хватать памяти и запустился gc. А памяти у нас много, как уже было сказано

может быть и много, но никогда не бесконечно. Рано или поздно GC запустится, и чем больше памяти надо почистить, тем дороже цикл сборки мусора.

так у вас софтина не работает как надо, клиенты перетекают к конкурентам

это если они есть, и у них не хуже

Обычно, у них такие же проблемы. Большинство софта, который на рынке давно и даже стандарт - редкостное г в плане кода, там просто чюдовищный легаси

С точки зрения отдельного разработчика конечно же интереснее из раза в раз писать поделия-однодневки

поделия очень разные бывают. Можно пилить сайты, когда вся работа сведется к перекладыванию json-ов из таблички в табличку, а может и нет. Я вот, помню, работал на одну офшорку - ни одной одинаковой задачи не было за 2 года. Может, повезло, конечно

может быть и много, но никогда не бесконечно

ну так и нормально написанное приложение не увеличивает память постоянно

Обычно, у них такие же проблемы

а если у кого-то из конкурентов это не так, можно сразу сворачиваться?

ну так и нормально написанное приложение не увеличивает память постоянно

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

Можно пытаться выделять все объекты/буферы заранее, а потом работать уже с ними. Но, опять же, не в языках с GC

UFO just landed and posted this here
UFO just landed and posted this here

Неа, не отличается. На C++20 писать в прод мне не приходилось, но на C++17 — вполне.

расскажите подробнее. Сколько, объем проекта, какого размера команда, насколько там современный код?

Или в вашей версии C++20 что-то принципиально поменялось по части гарантий со стороны языка?

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

То, что нет никаких гарантий распечатывания всех ошибок памяти.

скорее гарантируют чем нет. По крайней мере я не знаю сценарий, способный обмануть санитайзер.

Я уж не говорю о логистических проблемах сборки всех зависимостей с санитайзерами, о повышенном потреблении ресурсов

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

... о том, что некоторые ошибки проявляются только сильно спустя время, и так далее.

и в условном шарпе таких ошибок, не сразу проявляющихся, конечно же не бывает?

Почему неприменим-то?

Если хотите поспорить на тему "почему языки с GC не применимы в latency-critical системах" можете перейти в эту статью

UFO just landed and posted this here

Неужели мои аргументы так похожи на стандартное олдфаговское нытьё из C++98 или 03?

да, слово в слово. Ну или на нытьё студентов, которые хотят учиться программировать по питону вместо с++...

От 50 до 800 kLOC.

странно ловить описываемые вами проблемы (а их вы с учетом других дискуссий перечислили немало) в проектах таких масштабах с совеременными плюсами...

ХЗ, в чём современность измерять, но практически каждая фишка из современных плюсов есть

"Современность" кода это же не ачивка в стиме, важно не сколько единичных фич заиспользовано, а насколько хорошо ими покрыт код. Например "какой процент аллокаций через new/delete", "как часто аргументы передаются по указателю" и всё в таком духе.

Например?

мув семантика по сути дала адекватный способ передавать владение объектами не по сырому указателю (auto_ptr не в счет т.к. он сравнительно редко использовался и с ним свои нюансы). Ввели shared_ptr/unique_ptr, они как раз покрывают критикуемые вами кейсы. Асинхронное программирование сильно упростилось за счет лямбд, thread, mutex, и lock'ов.

Ну, да, появился operator<=>, но подсчёт хеша, дебаговый вывод, сериализацию в жсон, беготню по деревьям, вложенные optional и прочее всё равно приходится писать руками. Сравните с deriving (Show, Generic, Hashable, ToJSON, FromJSON)

Я согласен, что чем короче/проще код, тем сложнее спрятать в нём ошибку. Собственно, на этом моя аргументация и сторится - код на с++ действительно стал короче и проще. А ваша позиция - "по сути ничего не поменялось". Так тогда возникает закономерный вопрос - ваше мнение изменится если скажем в с++ появится рефлексия?

UFO just landed and posted this here

Заметьте, я всегда, во всех этих тредах в первую очередь ругаюсь на фрактальную непознаваемость языка, на уймы UB, и так далее

на UB ругаются все. Просто одни ругаются на то, что у них нет рантайм проверок на каждом шагу, а другие бесятся что компилятор делает какие-то там предположения когда они весьма вальяжно работают с памятью.

а не на то, что, условно, find_if неюзабельный, потому что там надо создавать отдельный объект (причём вне функции, function-local class не может быть аргументом шаблона до C++11, как мы все, конечно, помним) или обмазываться динозаврами из std::bind1st какими-нибудь

ну конкретно с find_if вроде либо использовали функцию, а-ля find_if(.., isspace), либо писали циклом.

Ну, один проект (личный), увы, на кутях со всеми вытекающими с наследованием от QObject и управлением памятью через голый new и иерархии

ну так new только для QObject иерархии и нужен, с передачей владения, т.е. просто так удалить не забудешь...

Все кодовые базы, с которыми я работал до 11, спокойно юзали boost::shared_ptr и не парились

то у нас торговый бот, которому лишняя микросекунда вечность, то пофиг на рефкаунтинг... Хех.

При этом это добавляет интересных вещей в язык. Например, объяснять, почему плохо делать T getFoo() { T foo; ...; return std::move(foo); } мне приходилось.

блин вы сравните последствия move вместо copy elision с одной стороны и выбором между копированием и выделением в куче с передачей по указателю с другой...

Треды, мьютексы и локи де-факто были всегда

не всегда переносимые

Folding expressions, constexpr, етц? По моим грубым прикидкам большинство программистов на C++ этим всем не пользуется, и вполне оправданно.

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

Spaceship? Опять недоделка — вместо рефлексии для описания произвольных функций выкатили это (и, кстати, тоже с весёлыми подводными камнями)

не "вместо" ведь... Просто пока рефлексия не готова, а потребность существует. А этот подводный камень не имеет отношения к spaceship, там разница именно в симметричности операторов в с++20+. Что кстати тоже сокращает код. Могли бы мб сделать явный квалификатор для этого, но их уже и так много...

Я боюсь, что рефлексии будет недостаточно по куче разных причин

ну для задач типа "сериализуй эту структуру с политикой по умолчанию" более чем хватит.

UFO just landed and posted this here

Первое есть подмножество второго, к слову.

те, кто гуляет по UB и бесится что компилятор не дает им работать с памятью как с простым массивом байт точно не являются подмножеством тех, кто хочет рантайм проверку повсюду.

Ну так то было 10-15 лет назад, я тогда ботами ещё не занимался. А в тех ботах, которыми я занимался недавно, вы и unique_ptr не найдёте.

trivial ABI для unique_ptr пробовали?

Просто как медицинский факт — до C++11 люди как-то более-менее код писали относительно успешно

как только мне доводится залазить в с++03 код, обычно одних лишь подсказок IDE достаточно чтобы насчитать с десяток лишних копий в одном файле. Просто критерий "успешности" тоже эволюционировал.

Можете назвать платформу, для которой сейчас нет бустовских/ACE/POCO/етц-тредов и мьютексов, а вот стандартные плюсовые там есть?

ну на винде нет толкового posix, а всё остальное не всегда хочется тащить в проект.

Сколько времени вам (и прочим, всё ещё читающим тред) понадобится, чтобы распарсить, что здесь происходит? Чур не запускать.

мне понадобилась пара секунд, вероятно потому, что я ожидал такой выпад. Лучше было бы написать так - корректнее и читаемее:

template <typename F, typename ...Args>
void foo(F&& f, Args&&... args) {
    (f(std::forward<Args>(args)), ...);
}

Или хитрая засада была в вычислении задом наперед?

На днях я написал такую функцию:

там весь пример как назло переусложнен. Всякие ObjectInt/ObjectMap и т.д. никто в здравом уме делать не будет, используются using или в худшем случае, если нужен distinct type, делают через наследование. Вместо std::make_tuple тут нужен std::tie. Но это всё мелочи.

Важно то, что вы смешали optional и исключения. Если писать только с исключениями, вообще без optional, код можно очень сильно сократить, а бонусом будет прокидываться текст ошибки из "задачи со звёздочкой".

И сдается мне что если писать только на optional, без исключений, то код тоже может получиться короче, точнее, длиннее, но менее объемным. Грубо говоря, struct Nothing не нужен, все её использования спокойно заменяются на return std::nullopt, а реальное экономие на исключениях у вас всего несколько строк внутри лямбд, что вы в общем-то скомпенсировали hoist'ом.

Конечно же код с optional был бы еще короче будь в с++ монадические интерфейсы, их не хватает, и с этим я согласен.

Ура, тут мы стали C++20-only, потому что до C++20 нельзя капчурить structured bindings в лямбды.

можно, через [&foo=foo]. Согласен, хак.

UFO just landed and posted this here

И если уж гулять, то хотелось бы, чтобы места, в которых код может гулять по памяти, были как-то явно выделены и изолированы

это то тут при чем? Вот я утверждаю: есть люди, которые чаще всего нынешние или бывшие сишники, которые чаще пишут под всякую микруху, и которые критикуют с++ за оптимизации на основе UB, которые видите ли ломают им код. Кажется даже здесь где-то были.

Неа, вы потеряли семантику. ... Именно.

ну то есть по итогу не потерял. Правда, не понимаю, чего я добился или чего вы хотели сказать. В каждом языке есть свои хитрые нечитаемые трюки, в с++ их наверно даже меньше чем в условном js.

Подождите. Как мне через плюсовый std::variant выразить рекурсивную структуру данных?

как ObjectInt/ObjectString помогают вам выразить рекурсивную структуру данных? Никак же, просто бойлерплейт.

Касательно всего следующего - здесь я причесал ваш сниппет согласно моим же замечаниям, плюс пара мелочей - забытые &, подчеркнул потенциальный null deref, кое где привел к стилю.

Детальнее тут

Давайте начнем с того, что в плюсах конструкция return ctx.withPayload([&ctx](auto& payload){...})выглядит странно - в объект передается лямбда, захватывающая и использующая этот объект. Обычно бы написали функцию, принимающую ctx аргументом вместе с payload'ом. И вся портянка была бы в этой функции, как-то так.

Во-первых, использование исключений сократит только внешний try/catch, не более.

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

Во-вторых, вызывающий код ничего не хочет знать про исключения, вызывающий код хочет получать std::optional<T> и проверять её

А вызывающий код так хочет потому что... ээм... вы взяли пример из реализации на хаскеле, языке, в котором принято возвращать значение типов-сумм/произведений? В с++ то таких ограничений нет. И совершенно нормально если с++ API кидает исключения. Разве что еще приветствуется в дополнение к кидающим версиям делать не кидающие.

А с исключениями у вас есть три варианта:

(три убогих варианта)

4. ловить std::exception& и брать текст ошибки из e.what()?

Да, хаскель версия всё еще короче. Но не настолько короче и не настолько читаемее (если), чтобы говорить о какой-то кардинальной разнице.

UFO just landed and posted this here

Как это не потерял? Ваш вариант применяет функцию к аргументам в прямом порядке, мой — в обратном

я не то имел в виду

Зависит от вашего опыта с шаблонным метапрограммированием, тащем-та. Для значимой части людей всё это — нечитаемые трюки.

для людей, которые не освоились с с++11 то? Ну хз, хз. Да, я знаю что fold ввели в с++17, но его можно эмулировать в с++11

Помогают не думать о том, есть ли у какого-то другого поля в variant'е неявное преобразование из инта, и как variant с этим будет работать, особенно если я завтра поменяю int на uint64_t — плюсы не настолько дружественны к механическому рефакторингу, чтобы не думать об этом наперёд

это можно делать либо type alias'ом, либо, на совсем худой конец, через enum class foo : int; - создает distinct type. Точно так же как class ObjectString : public std::string;. И без бойлерплейта

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

в теории могут - это ведь описывает даже не компилятор/стандарт, а x64 ABI.

Потеряли логгинг о ненайденном экстмарке в этом и двух следующих случаях.

Кидать исключение ради лога странно, лучше вставить логгирование перед return {}.

В каком случае вам нужно больше напрягаться, чтобы понять, что происходит?

в хаскеле, потому что я его не знаю, и потому что в нём логика задом-наперед.

Ловите все исключения, включая те, которые должны пробрасываться наверх. Недружественно к рефакторингу на будущее.

во-первых, вы выше заявили что функция должна возвращать optional и не кидать исключения. Во-вторых, я повторил поведение оригинала (ну, кроме логгинга). В-третьих, ну будет там тогда два разных catch блока с std::bad_variant_access и std::out_of_range вместо одного с std::exception, неужели это так принципиально?

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

блин вы могли сказать "вот видите, я же допустил ошибку", на что я бы ответил "а вдруг специально?", но нет же, надо в дебри... Вы очень даже можете это выразить, просто не через shared_ptr, а сделав другой класс с необходимой семантикой. Единственное destructive move в с++ всё еще нет, поэтому сделать non-null unique_ptr будет проблемно.

В хаскеле таких ограничений тоже нет, там вы тоже можете кидать исключения, это не идрис и не агда. Просто исключения не видно в типах

где конкретно я вижу тип исключения в операции 5 / 0? Как это поможет по пунктам 1, 2, 3?

Поэтому их в любом языке лучше избегать.

удачи избегать их в доброй половине ЯП, ага

"Статью написать, что ли."

Я бы почитал пост.

Собсна, чего далеко ходить — надо было написать свой циркулярный буфер, так там кода как раз на сто строк, но сидел и писал я его дня два, потому что большую часть этого времени бегал по стандарту и проверял, что и как я имею право делать с точки зрения лайфтаймов объектов для разных категорий объектов.

А попробуйте спроецировать эту задачу на другие языки программирования. Ну там C#/Java, Haskell, Rust. За счет чего бы вы получили упрощение своей работы и какую бы производительность/предсказуемость получили бы в итоге (равно как и функциональность)?

Уж не за счет ли наличия GC в C#/Java/Haskell (с boxing-ом)? И не за счет ли отсутствия стандарта как такового у Rust-а?

И не за счет ли отсутствия стандарта как такового у Rust-а?

ну раст не умеет ни в исключения, ни в нетривиальный мув, поэтому логика контейнера очень сильно упрощается. А еще в расте считается, что всё что safe и скомпилилось не содержит UB. Правда, для реализации полноценного буфера понадобится unsafe, а там гарантий практически нет...

UFO just landed and posted this here

К сожалению или к счастью panic-safety всё же требуется от любой корректной библиотеки

а компилятор этого требует? В смысле "скомпилирует ли код, не удовлетворяющий panic safety"? Так или иначе, тривиальность мува сильно упрощает задачу (а точнее, отсутствие необходимости поддерживать исключение/панику при муве).

А преимущество Rust в том, что для каждой unsafe функции в её документации есть раздел "Safety" и требования там простые и ясные. 

так речь же не об использовании unsafe функций, а о написании unsafe кода. Там с гарантиями всё намного хитрее, и стандарта, по которому можно было бы перепроверить код, попросту не существует.

но в основном всё-таки при выяснении что в Rust UB, а что нет ты удивляешься, когда узнаёшь, что что-то не является UB, а в плюсах, к сожалению, наоборот.

это всё круто, учитывая что состав UB rust зависит от llvm, поведение миддленда которого по большей части определяется стандартом плюсов...

UFO just landed and posted this here

Нет, не требует. И код, нарушающий panic safety, является sound

то есть через нарушение panic safety можно допустить UB в safe rust?

Да нет, не зависит. Но если вы можете привести конкретный пример, мне было бы интересно.

Например отсюда есть пара ссылок на доку LLVM, namely "pointer aliasing rules" и "uninitialized memory".

А вообще если судить по списку потенциальных UB transmute, кажется основной принцип UB в раст таков, что правила сильно строже, для простоты. Взять например правило что каст & в &mut это UB, в отличие от плюсов, где UB - модификация const объекта, а кастить туда-обратно можно сколько влезет. Впрочем, раст запрещает "mutating immutable data" отдельно. Или например для структуры struct S { int x, y; } c++ позволит сделать каст S* -> int* -> S* без UB (т.к. первый член S - int), в то время как в rust второй каст будет UB, ведь "it is Undefined Behavior for U to be larger than T".

UFO just landed and posted this here

Беглый гугл подсказал, что это о копировании с помощью mem::transmute_copy<T, U>, не о касте.

Где это вы такое нашли? Это неправда.

Здесь: "mem::transmute_copy<T, U> somehow manages to be even more wildly unsafe than this. It copies size_of<U> bytes out of an &T and interprets them as a U. The size check that mem::transmutehas is gone (as it may be valid to copy out a prefix), though it is Undefined Behavior for U to be larger than T".

По умолчанию компилятор такое не пропустит, если вам от этого легче

Беглый гугл подсказал, что это о копировании с помощью mem::transmute_copy<T, U>, не о касте.

Оттуда же: "Also of course you can get all of the functionality of these functions using raw pointer casts or unions, but without any of the lints or other basic sanity checks. Raw pointer casts and unions do not magically avoid the above rules."

UFO just landed and posted this here

Ну всё-таки здесь нет однозначного запрета. И на самом деле в Rust кастить указатели можно любой к любому, а правила разименования того что получилось менее строгие чем в C++.

в rust некорректный каст указателя уже является UB, и это куда более строгий запрет, чем в с++. А потом разыменовывай сколько влезет, ага.

Ну камон, то что вы цитируете про размеры типов очевидно относится не к касту, а к операции «каст + копирование».


Raw pointer casts and unions do not magically avoid the above rules.

И вот эта фраза никак этого не отменяет.

Ну камон, то что вы цитируете про размеры типов очевидно относится не к касту, а к операции «каст + копирование».

во-первых, transmute_copy это не "каст + копирование", а "копирование + каст", и весьма очевидно что UB кроется в касте, а не в копировании. Во-вторых, там буквально написано что А. transmute_copy'ровать в тип большего размера - UB, и B. это правило распространяется и на касты указателей.

И вот эта фраза никак этого не отменяет.

Она буквально и прямым текстом говорит что касты указателей никаким волшебным образом не обходят правила transmute/transmute_copy. И единственная причина почему все эти UB не перечислены в главе про указатели - она неполная (за незавершенностью модели памяти), сказано лишь что *T всегда должен указывать на valid instance of T, и что нужно придерживаться правил pointer aliasing'а и borrowing'а.

Неужели вы всерьез готовы глорить раст даже вопреки его единственной документации?

весьма очевидно что UB кроется в касте, а не в копировании

Не очень понимаю, почему для вас копирование sizeof<U> > sizeof<T> байт из переменной типа T очевидно не является UB, а каст является.


Она буквально и прямым текстом говорит что касты указателей никаким волшебным образом не обходят правила transmute/transmute_copy.

Она говорит, что сделать то же самое, но избежав UB в обозначенных случаях, у вас не выйдет, только и всего.


Согласен, что раздел о кастах выглядит не совсем полным, но в списке UB кастов тоже нет, только разыменования.
Да и в многочисленных предупреждениях о незавершённости модели памяти тоже говорится о разыменованиях, а не о самих кастах.


Неужели вы всерьез готовы глорить раст даже вопреки его единственной документации?

Я ни в коем случае не глорю раст, просто прочитав ту же самую документацию, я в ней вижу совершенно другие утверждения, чем вы.

Не очень понимаю, почему для вас копирование sizeof<U> > sizeof<T> байт из переменной типа T очевидно не является UB, а каст является.

потому что я исхожу из предположения, что указатель смотрит на valid instance U или что-то layout-compatible с U. В противном случае UB ожидаемо гарантирован и при касте, и при копировании. Но ведь документация говорит не так, она говорит что transmute_copy в тип большего размера это UB независимо от всего остального.

Согласен, что раздел о кастах выглядит не совсем полным, но в списке UB кастов тоже нет, только разыменования.

а еще в списке UB написано что он неполный, и про касты/трансмьюты там ничего нет. Могли бы хоть добавить туда индекс со ссылками на все UB, обозначенные в других разделах.

Да и в многочисленных предупреждениях о незавершённости модели памяти тоже говорится о разыменованиях, а не о самих кастах.

пока модель памяти не завершена, поведение всего, что от неё зависит, не определено, т.е. UB. Ну это так, к слову.

Я ни в коем случае не глорю раст, просто прочитав ту же самую документацию, я в ней вижу совершенно другие утверждения, чем вы.

значит вопрос в том, кто из нас смотрит в документацию и видит в ней то, что там написано, верно?

потому что я исхожу из предположения, что указатель смотрит на valid instance U или что-то layout-compatible с U. В противном случае UB ожидаемо гарантирован и при касте, и при копировании.

Она же вообще не указатель, а ссылку принимает. По сути функция идентична std::bit_cast из C++20, предназначена исключительно для type punning. И, кстати, UB у них очень похожи.


Но ведь документация говорит не так, она говорит что transmute_copy в тип большего размера это UB независимо от всего остального.

Ограничения mem::transmute, я так понимаю, на mem::transmute_copy тоже распространяются.


а еще в списке UB написано что он неполный, и про касты/трансмьюты там ничего нет.

То что UB в библиотечных функциях описываются отдельно от UB в языке на мой взгляд вполне нормально.
А вот касты — фича языка, и описания UB в ней я бы ожидал видеть и в этом списке, и в документации кастов, но их нет ни там, ни там. Я из этого могу сделать вывод, что либо UB возникает только при разыменовании, либо в документации есть ошибки.
Совершенно не понимаю, как вы делаете вывод, что документация напрямую говорит о UB при определённых кастах.

Она же вообще не указатель, а ссылку принимает. По сути функция идентична std::bit_cast из C++20, предназначена исключительно для type punning. И, кстати, UB у них очень похожи

на transmute похож, да. Но bit_cast не умеет в типы разных размеров, в отличие от transmute_copy.

А вот касты — фича языка, и описания UB в ней я бы ожидал видеть и в этом списке, и в документации кастов, но их нет ни там, ни там. Я из этого могу сделать вывод, что либо UB возникает только при разыменовании, либо в документации есть ошибки.

либо дока неполная и она еще не определяет то поведение, которое компилятор rust будет готов гарантировать. Ну а любое поведение которое еще не определено буквально является UB

Совершенно не понимаю, как вы делаете вывод, что документация напрямую говорит о UB при определённых кастах.

я говорю про конкретный UB, который прямым текстом прописан для конкретной операции и на конкретную сноску про то, как этот конкретный UB распространяется и на касты/union'ы.

на transmute похож, да.

transmute ещё и что-то хитрое с лайфтаймами делает, так что transmute_copy более похож.


Но bit_cast не умеет в типы разных размеров, в отличие от transmute_copy.

Это, насколько могу судить, единственное существенное отличие — возможность скопировать первые sizeof<U> байт при разных размерах. И именно этим обусловлено UB при sizeof<U> > sizeof<T>.


Ну а любое поведение которое еще не определено буквально является UB

Тогда надо смотреть сюда, в описание всех возможных типов кастов. Там и для pointer to pointer cast описаны ограничения


*V where V: Sized

т.е. размер целевого типа должен быть известен при компиляции


or T and V are compatible unsized types, e.g., both slices, both the same trait object.

И ниже очередная сноска на memory model:


Warning: This interacts with the Rust memory model, which is still under development. A pointer obtained from this cast may suffer additional restrictions even if it is bitwise equal to a valid pointer. Dereferencing such a pointer may be undefined behavior if aliasing rules are not followed.

И снова предупреждают только о разыменовании.


я говорю про конкретный UB, который прямым текстом прописан для конкретной операции

Операции, которая не ограничивается кастом, а в плюсах и вовсе реализовывалась бы без (явных) кастов


template<typename U, typename T>
U transmute_copy(const T &t) {
  U u;
  std::memcpy(&u, &t, sizeof(U));
  return u;
}

и на конкретную сноску про то, как этот конкретный UB распространяется и на касты/union'ы.

Всё-таки не «распространяется на», а «не обходится ими».

Это, насколько могу судить, единственное существенное отличие — возможность скопировать первые sizeof<U> байт при разных размерах. И именно этим обусловлено UB при sizeof<U> > sizeof<T>.

Речь идет о касте T в U, а не наоборот, т.е. когда копируется больше байт, чем есть в T

И снова предупреждают только о разыменовании.

потому что всё остальное under development? Сформулируйте пожалуйста требования к указателю в rust, чтобы его разыменование гарантированно не приводило к UB.

Всё-таки не «распространяется на», а «не обходится ими».

как ни фразеологируйте, итог то один - UB при преобразовании T в U большего размера будет и при использовании transmute_copy, и при касте через указатели и union'ы.

UFO just landed and posted this here

Как справедливо заметили, transmute_copy принимает параметром не указатель, а ссылку, и это всё меняет.

приведение указателя к ссылке считается dereference'ом?

Ещё в списке не упонимается pointer provenance - тоже формально не описанное (или недоописанное) UB, но хорошо известное и принимаемое во внимание теми, кто пишет unsafe код. В остальном, список полный.

так pointer provenance и является причиной почему такой каст может приводить к UB...

UFO just landed and posted this here
UFO just landed and posted this here

из известных мне, по крайней мере

Из тех, с чем дело имел я, это еще и чистый Си и Pascal. И, если мне не изменяет склероз, Modula-2 и Ada. На счет Fortran-а не в курсе, дел не имел и даже не интересовался.

Этого нет ни в расте, ни в хаскеле, ни, насколько я знаю, во всяких сишарпах-джавах.

А теперь перестаньте изливать свою боль про нагадивший вам в шаровары C++ и попробуйте ответить, почему же в языках с GC нет таких страшных проблем с контролем лайфтаймов. И какой ценой в Rust-е попробовали достичь того же без GC (и сколько времени ушло на то, чтобы реальная альтернатива старым языкам без GC появилась в лице того же Rust-а).

UFO just landed and posted this here

Это был буфер, считайте, для обёрток над интами (у которых тоже бывают лайфтаймы, однако).

мне кажется вы просто разную планку ставите. Когда речь о с++, вы считаете что код работает только если он на 100% соответствует стандарту. А когда речь о других языках, то вас совершенно устраивает поведение, определенное одним конкретным компилятором. Так-то больша́я часть UB в с++ вполне себе well defined в компиляторах.

Этого нет ни в расте, ни в хаскеле, ни, насколько я знаю, во всяких сишарпах-джавах.

а еще в них нельзя делать то, что можно в плюсах.

а в C++ есть проблемы и с этим (вы не можете просто взять кусок памяти и проинтерпретировать его как int, до C++20 — даже если это кусок памяти от malloc(sizeof(int))).

удачи сделать это без UB в rust: "src must point to a properly initialized value of type T" (std::ptr::read_unaligned).

UFO just landed and posted this here

Хоть один компилятор имеет в своей документации фразу «даже невалидный по стандарту код, компилируемый сегодня нашим компилятором, будет компилироваться и иметь то же поведение всегда»?

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

Я не могу вспомнить ни один такой случай...

"не могу вспомнить" и "не было" это разные категории.

... который при этом не считался бы багом

"считается багом с точки зрения компилятора" и "считается багом с точки зрения стандарта" это тоже разные категории. В других языках просто нет последней инстанции в виде стандарта, и в них вас совершенно устраивают гарантии компилятора. Допустим завтра появится стандарт rust - ринетесь ли вы переписывать весь свой код чтобы он стал conformant?

Например?

ну с тем же rust простейший пример - каст & в &mut это сразу UB. А языки более высокого уровня либо в принципе не дают контроля над памятью, либо проще уж писать на плюсах, чем скажем на джаве, но не пользуясь GC.

 С конструированием объекта GC не помогает, а в C++ есть проблемы и с этим (вы не можете просто взять кусок памяти и проинтерпретировать его как int, до C++20 — даже если это кусок памяти от malloc(sizeof(int))).

И как с этим помогает GC? Я так понимаю, что в используемых вами языках с GC такая операция является штатной и делается на раз-два, не так ли?

Да, в Java такого нет.

Пишу на C++ (всё, что понимают последние Clang/Gcc в RHEL). Знаю много хороших трюков и полезных библиотек. Никогда нигде не пишу и не говорю, что знаю C.

Отучить себя от исключений, RAII, кодогенерации с шаблонами и других "удобств" и вместо одной стандартной библиотеки знать все ключевые особенности target platform programming interfaces (POSIX, BSD, etc.) совсем не просто.

UFO just landed and posted this here

Был случай...пришел на собеседование (товарищ пригласил). До этого писал на С и всякими аппаратными вещами занимался (но не разработкой с нуля). Сказал перед началом: "На С++ писал только сервисную муть для себя же, так что ничего профессионального не умею". Поспрашивали про области видимости, когда создаются static...и конечно про volatile)) ах, да и про диодный мост, RS422 (почему диф. сигнал такой хороший)...но в конце все-равно начали спрашивать про С++...я ничего не смог, но мне сказали "ничего страшного, вот есть хорошие книги". В итоге их компании прошлось отказаться от С++ в пользу С, ибо там было импортозамещение и памяти не хватало (в это можно поверить) и времени между циклами. Так что некоторое время можно еще пожить как в прошлом) Про компанию ничего конкретного не знаю, ибо остался на своем любимом заводе))

Использует C-style массивы вместо std::array;

Стоп, а на C++ что уже массивы с квадратными скобками устарели?

Ну и кстати, тут многие вещи это результат не "Пришел с си", а "Программиует на C++ больше 10лет и не изучал новые стандарты."

Нарушают кучу всего в CPP Core Guidelines - небезопасны, теряют размер при передаче как аргументов функции...

Так что да, устарели.

Articles