Как стать автором
Обновить

Комментарии 404

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

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

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

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

Я тоже раньше сомневался, пока не начал помогать проводить собеседования, и на практике оказалось, что таких каждый второй :(

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

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

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

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

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

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

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

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

А для всех остальных, кто не делает выводов по заголовку, а сначала читает содержимое статьи: если вы тот самый матерый плюсовик-затейник, умеете готовить Yocto и не прочь переехать жить и работать в Чехию - черканите мне, возможно нам есть о чем пообщаться :)

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

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

Я ее понимаю, почему вы уже второй комментарий подряд говорите про "менеджеров". Всё то, что я изложил выше - это не решения менеджеров и "тимлидов", менеджеры и тимлиды тут вообще ни коим боком, их нету в контексте. Это именно коллектив инженеров одной компании, многие из которых тащили смежные проекты на протяжении десятка лет от PoC до зрелости и повидали некое дерьмо, сами разработчики в итоге в процессе решения проблем и обсуждений совместно (и демократически) пришли к определенным выводам, сформулировали стандарты для платформы и обозначили хотелки для расширения команды.

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

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

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


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

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


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

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


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

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


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

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


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

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


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

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

слепо

А про "слепо" никто и не говорил. Перечитайте последний абзац.

применять аллоцирующий полиморфный враппер

Аллоцирующий он далеко не всегда. Пример.

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

Интероп - это отдельная тема. Есть проекты, где чистого интеропа с сишными библиотеками почти нет, а если и есть, то во всех точках соприкосновения написаны C++-врапперы.

std::function туда не пролезет при всем желании

Ну, в редких случаях, кстати, пролезет через target().

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

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

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

Во многих проектах исключения вообще выключены. Например, в Chromium.

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

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

безапелляционный

Перечитайте последний абзац статьи

А потом дети и сишники начитаются и начнут лепить explicit и virtual куда ни попадя, потому что "это С++, так в интернете написано".

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

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

Это совершенно логично. Разве я где-то утверждал обратное?

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

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

Да, все верно. Я в статье этот момент специально отметил:

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

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

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

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 вместо коллбеков когда можете" более чем валидный.

std::function аллоцирует только при наличии контекста

И даже при наличии контекста далеко не всегда, во многих реализациях там есть small size optimization. Пример выше уже скидывал.

Бывают каллбеки с контекстом, бывают без контекста.
Если он там есть — естественно, 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

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

Зашел в комменты написать именно это.


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

А есть объяснение, почему такую проверку не добавят в 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 -- это лично для меня большой вопрос.

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

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


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

НЛО прилетело и опубликовало эту надпись здесь

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

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

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

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

и этого нельзя достичь с использованием enum class без повсеместного ручного приведения типов

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

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

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

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

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

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

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

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

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

А ответ на вопрос "зачем" будет точно такой же, как и на "зачем вообще придумали enum class": типобезопасность. Когда у вас у функции два аргумента, каждый из которых представляет собой битовую маску, или один битовая маска, а другой вообще какое-то целое число, то перепутав их местами в одном случае вы получите ошибку компиляции, а в другом случае компилятор молча схватает это и скомпилирует программу с ошибкой. Или, например, вы по ошибке сделаете | между членами совсем разных enum'ов. В одном случае это тоже вызовет ошибку компиляции, а в другом случае компилятор промолчит. И более того, совпасть может так, что работать все будет абсолютно корректно (если позиции битов битов для конкретно этих значений в этих разных enum'ов совпадают), но совершенно внезапно сломается когда внесут изменения в один из них. И это только один из примеров.

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

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

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

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

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

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

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

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

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

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

НЛО прилетело и опубликовало эту надпись здесь

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

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

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

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

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

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

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

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

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

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

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

НЛО прилетело и опубликовало эту надпись здесь

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

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

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

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

Насчёт этих людей не знаю, но для себя я как-то понял, что единственный способ сохранить рассудок — не писать на плюсах.

Можно перегрузить &, |, &&, ||, ^, &=, |=у этого 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.

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

Атож. Напоминает классику: "Программист на Фортране пишет на Фортране на любом языке".

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

Вообще не особо понятно, зачем кто-то предлагает из монструозного и сложного C транслятором в одном месте сделать простой и лаконичный ассемблер (привет из 70-х).

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

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 описывающим флаги.

Когда я последний раз ковырял Qt, там были обычные enum, и с enum class оно не особо работало. Может, в шестерке поменяли, впрочем.

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

Бьёрна Страуструпа вы тоже "евангелистом всяких догм" назовете? :)

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

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

Буду знать. Интересно, но в русскоязычном сообществе устоявшимся написанием почему-то стало "Бьерн", видимо, стараниями издательств: https://www.ozon.ru/person/straustrup-bern-253179/category/knigi-16500/

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

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

Где провокация-то? Так, маленькая дружественная шпилька в сторону любителей Rust'а :) Что, впрочем, не отменяет факта, что Rust - прекрасный язык (ну, кроме его синтаксиса, имхо).

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 проверять строчку форматирования во время компиляции сводило преимущество в типизации к нулю. Что-то изменилось?

А это как? Ведь передача `int a[]`, `int a[20]` и даже `int a[static 20]` это всего лишь передача указателя, как можно передачу указателя перепутать с передачей самого массива?

А никто ничего и не путаетю Простой пример: у вас есть какая-то структура, внутри которой вы хотите хранить статический массив. Ну или просто у вас есть массив сам по себе, который вы где-то используете. std::array<int, 10> в памяти будет выглядеть точно так же, как и int[10], но в отличие от него 1) может быть передан/возвращен не только по ссылке/указателю, но и по значению; семантика точно такая же как и у остальных плюсовых контейнеров, хотите передавать по ссылке - передавайте 2) имеет готовый метод size(), что позволяет избежать классических ошибок с sizeof 3) умеет в итераторы, то есть совместим с range-based for и с алгоритмами из <algoritm>

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));

Да, здесь сработает, вариант с ручным std::begin/std::end я не учел.

А вот если вам передали массив просто как указатель, например int*, как это любят делать в сишном коде, то все становится гораздо сложнее :)

А это как? Ведь передача 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++ потоков нигде нет.

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

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


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

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

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

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

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

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

Ну тут все зависит от целевой платформы. Когда у вас какой-нибудь STM8, то придется ужиматься по-полной, когда у вас 4 ядра мощных и 2 ГБ оперативы, тогда можно развлекаться как душа пожелает, а когда у вас 500 мегагерц и 256 мегабайт, то шикануть уже не получиться, но и каждый байт и каждый такт считать не обязательно.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

как обычно, imho

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

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

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

Кстати, если собрать всех троих в одной команде, то может получиться отличное комбо!

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

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

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

В самой зарядке используется RTOS, а вот в коммуникационном шлюзе для группы зарядных станций уже крутится Embedded Linux. Учитывая, что этот шлюз должен уметь в OCPP (JSON поверх Websockets с TLS и авторизацией по сертификатам), MQTT (тоже поверх Websoсkets с TLS), Modbus, BACnet, NTP, всё это с IPv6, а в списке требований к смежным проектам (которые появились еще раньше) на той же платформе есть еще RestAPI с Redfish, Avahi, SNMP, авторизация по LDAP и Radius с ролевой моделью, remote syslog, SSH CLI, кастомные протоколы на базе protobuf, а бонусом еще и сертификация по UL-2900-2-2, то Linux тут является оптимальным решением для достижения гибкости и минимизации велосипедостроения.

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

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

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

Вообще-то об этом было изначально прямым текстом сказано в начале статьи, аж в третьем по счету предложении:

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

Не помогло.

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

Да, вы правы

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

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

(с) анекдот.

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

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

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

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

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

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

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

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

как обычно imho

ps

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

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

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

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

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

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

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

В Quake3 от C++ только классы но хуже он не стал. Уже сто раз видел проЭкты с невероятными мета гуру C++ которые через пол-года убегают оставляя после себя "чукча писатель не читатель".

И каждый второй мнит себя Кармаком, хотя даже близко на него не тянет, ага.

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

Тут блин в запросах или xsl через полгода уже бутылка требуется, даже когда сам писал)
Только подробное комментирование хотя бы того, зачем этот кусок здесь.
В 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% кода уже в опасности ). Вам надо срочно переписывать ядро! По крайней мере загрузчик уж точно можете написать, он же проще пареной репы.

Зачем? Человеко-лет на переписывание ядра потребуется слишком много, игра не стоит свеч. Пока что и ядро и загрузчик вполне себе удовлетворяют всем требованиям.

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

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

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

А причем здесь это-то? :) Для вашего конкретного проекта Linux не подходит, вы используете RTOS. А есть немало embedded проектов и устройств, где наоборот Linux вписывается очень хорошо, позволяя достичь гибкости и минимизировать велосипедостроение. Например тот, про который идёт речь во введении этой статьи.

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

Динамическая память не получается, так как некогда собирать мусор в памяти.

Динамическое выделение памяти не обязательно связано со сборкой мусора, там вполне может быть RAII и reference counting.

В RTOS нередко используют блочные аллокаторы и пулы (например, в ThreadX и FreeRTOS), но вообще при правильном подходе даже с использованием классических аллокаторов можно не бояться временных затрат на выделение/освобождение в куче и не пугаться фрагментации, подобающим образом реализованные malloc() и free() выполняются за постоянное время и даже с предсказуемой фрагментацией кучи.

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

Учитывая, что формально C++ является надмножеством C (за исключением пары моментов), то вопрос определения не такой уж и очевидный. Иначе бы не было этой статьи :)

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

C99 или C11 или C2x ?

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

Я бы сказал, об ANSI C++ 90-х годов и C89. Тогда там действительно количество "моментов" было в районе пары пары, если не считать те из них что уже тогда были анахронизмом даже в самом Си (типа K&R function definitions).

Естественно, потом пути языков начали расходиться сильнее (особенно после выхода C11 с VLA и restrict), но тем не менее и по сей день нередко можно встретить Си-программу которую удастся скомпилировать C++-компилятором вообще без проблем или с самыми минимальными модификациями.

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

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

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

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

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

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

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

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

Я не великий гуру C++, но стараюсь по мере сил. И мне список понравился. У меня лично есть некоторые вопросы к пункту 10* и очень большое сомнение в пункте 16: всё-таки неймспейсы в C++ - это совсем-совсем не то же самое, что неймспейсы в C# или Java. Они созданы для решения конкретной проблемы (конфиликт имён при линковке и сопредельных ситуациях), не предназначены для структурирования кода и плохи в этой роли. В общем, моё текущее мнение (оно может поменяться в будущем с новым опытом): пункт 16 неверен не в каких-то отдельных случаях, а прямо совсем, категорически.

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

* даже в том же 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);
}

Я ничего не путаю, я имею в виду разумеется `Box<dyn Fn...>`, и это по-моему очевидно из контекста.

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

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

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

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

Тут каждому свое. Я бы наоборот, в проекте, где используется C++ старше C++11 согласился бы работать только за двойную-тройную оплату, и то очень хорошо подумав перед этим :)

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

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

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

Я в статье как раз говорил, что если взять Си, добавить в него ООП и начать писать в объектном стиле, то у вас получится не C++, а Си с классами.

Сложность низкая, объемы кода видимо не высокие, всегда можно прошивку с нуля переписать и не нужно париться с кодом доставшимся в наследство))

Ради интереса, запустил cloc в дире с проектом (специально без внешних зависимостей и 3rd-party библиотек), получилось такое:

github.com/AlDanial/cloc v 1.72  T=18.71 s (282.3 files/s, 42135.6 lines/s)
-------------------------------------------------------------------------------
Language                     files          blank        comment           code
-------------------------------------------------------------------------------
C++                           1777          45598          57553         250059
C/C++ Header                  1961          34941          66222         137464
C                               76           3402           9709          40005
JSON                           123             38              0          23530
CMake                          632           3942           3311          22037
Python                         138           4386           8544          18588
Bourne Shell                   308           3304           3326          14073
Markdown                        46           2959              0          11168
make                            32           3036           1901           5730
XML                             75            185            211           3730
YAML                            12            133             59           2913
DOS Batch                       54            456            266           2148
ECPP                            22            174             38           1191
JavaScript                       4             67             30            673
Ruby                             6            107            135            310
CSS                              3             60             24            297
m4                               1              0              0            156
HTML                             2              3             10             56
INI                              9              4              9             50
-------------------------------------------------------------------------------
SUM:                          5281         102795         151348         534178
-------------------------------------------------------------------------------

Считать ли это "большим и сложным" - ну не знаю, не люблю меряться пиписьками.

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

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

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

Программированием интересуюсь еще с 13 лет, и вообще-то я всю свою сознательную жизнь и карьеру только и делаю, что выхожу за рамки рабочей рутины и пытаюсь получить другой опыт. Как в плане самих проектов на работе: в разное время занимался и разработкой веб-порталов, и программированием автоматики для нефтедобычи, разработкой диспетчерского контроля для аэропортов, разработкой браузеров (в том числе контрибьютил в Chromium), разработкой коммуникационных шлюзов. Из языков - C, C++, C#, JS. В свободное время иногда контрибьючу в разные open source проекты, пилю утилитки для себя, в том числе на языках, с которыми не работаю в основное время, таких как Go и Rust.

Чем похожи С и С++? Кроме синтаксиса и сопоставимого машинного кода на выходе компилятора - они похожи мало чем - буквами. Это два разных инструмента. Они созданы для работы в разных парадигмах и для решения разных задач. И философии программистов их использующих - не сопоставимы ни в кривь ни в кось: почти все что хорошо для С, есть ужас в С++ и наоборот.

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

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

Все верно. И еще раз: именно об этом эта статья, и именно эту мысль я несколько раз в разных формах в этой статье и повторял.

Возможно я просто чего то не понял. Без обид. Я тоже лох порядочный и не претендую на что то великое. Но. Ты возьми дезассемблированный код, или посмотри машинные коды в 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 за больший срок чем даже джун но на плюсах. К счастью, я их и не пишу, а плаваю как раз там где и нужен Си.

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

Спасибо. Вы все правильно поняли.

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

Как мне кажется, программистов на Си сильно больше чем на плюсах, тем более чем хороших программистов на плюсах. И преимуществ у Си много, но они постепенно пропадают. Из реальных областей которые у нас, остались это: малоресурсные мк, низкое потребление или драйвера. Сейчас внутри копеечного мк 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 кБайт оперативки и доволен Си. Автор же говорит о других областях, в которых подход Си устарел

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

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

НЛО прилетело и опубликовало эту надпись здесь

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

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

НЛО прилетело и опубликовало эту надпись здесь

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

Если в одном, слабо нагруженном месте я использую 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 уже не прокатит: недетерминированное поведение строки при передаче через очередь

Я специально написал в статье:

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

и пример, когда std::string не подойдет я описал там же в следущем абзаце.

А какая польза от 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

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

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

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

Я тут пытался написать своего рода "шаблонную" функцию которая принимает тип и потом возвращает значение этого типа. Так и не смог донести до питоновского 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.

Конкретно такое в силу особенностей используемых типов можно только в Idris 2, где есть quantitative type theory и можно паттерн-матчиться на типы.

Ну да, я пожалуй неправильно выразился. В 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++ системном программировании что бы это нельзя было сделать на си? Ядра ж в конце концов на нём пишут. Речь не идёт о ситуации когда вам проще поставить какой нибудь линукс в свой контроллер и писать уже обычные программы.

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

const в отличие от define обрабатывается не препроцессором, а самим компилятором, поэтому во-первых он уважает scope (если вы объявили его внутри {}, он будет доступен только там и не пролезет наружу, аналогично с неймспейсами и классами), а во-вторых обеспечивает проверку типов на этапе компиляции - меньше вероятности возникновения ошибок из-за неявных преобразований, более понятные сообщения компилятора. А еще есть constexpr, который тоже может сделать жизнь чуточку лучше.

А можно поточнее, о каком разделе эмбедеред идёт речь? 

Выше писал уже: разрабатываем коммуникационные шлюзы для ИБП и для зарядных станций автомобилей

... этот шлюз должен уметь в OCPP (JSON поверх Websockets с TLS и авторизацией по сертификатам), MQTT (тоже поверх Websoсkets с TLS), Modbus, BACnet, NTP, всё это с IPv6, а в списке требований к смежным проектам (которые появились еще раньше) на той же платформе есть еще RestAPI с Redfish, Avahi, SNMP, авторизация по LDAP и Radius с ролевой моделью, remote syslog, SSH CLI, кастомные протоколы на базе protobuf, а бонусом еще и сертификация по UL-2900-2-2.

> И совсем уж нубовский вопрос: какое такое сакральное преимущество даёт 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) Кажется лучше взять хорошего программиста на С и переучить его на нужный диалект С++, чем упарываться в пуризм.

Между прочим Бьярне ровно так и говорит - идите нахер, пуристы.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Да, это один из вариантов. Правда, там вроде бы могут быть приколы с fancy pointers, но я с этим не разбирался, это уже вообще отдельная история.

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 - это чисто с++ концепции (вызов конструктора/деструктора при аллокации/очистке памяти), к С не имеют никакого отношения

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

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

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

Да. Но в современном мире C++ уже как лет так 10 ручное управление ресурсами вместо RAII считается дурным тоном (за исключением специфических случаев где это уместно и необходимо). Поэтому использование new и delete часто является одним из признаков того, что человек пришел из языка, где нет умных указателей и подобных оберток и не привык к ним, либо вообще не подозревает о их существовании.

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

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

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

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

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

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

ясно

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

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

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

я уже давно живу в мире, где есть garbage collectors.

В мире C++ они хоть и не часть языка и стандартной библиотеки, но всё-таки есть. Например, Oilpan GC активно используется в Blink (движок рендеринга браузера Chromium и Android WebView). А ещё есть Boehm.

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

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

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

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

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

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

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

я уже давно живу в мире, где есть 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 == не знать плюсы.

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

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

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

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

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

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

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

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

конечно

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

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

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

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

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

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

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

как и на все фишечки std

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

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

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

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

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

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

В каждом языке есть свои хорошие практики и свои понятия чистого и идиоматического кода. Тут нюанс в том, что придти, например, к разработчикам на Java или на C# и начать писать на их языке как на Фортране или на чистом Си не получится, потому что большую часть подобного "олдскула" вам язык сделать так просто не даст, а за другую половину вам шарписты и джависты больно напихают на первом же ревью. А вот на C++ по ряду причин такое сделать получится легко, и как показывает практика, очень многие пытаются так делать. Об этом и статья.

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

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

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

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

Не помню, какой там из них в нативный код компилится

Оба в нативный код, но Go с собой рантайм таскает не самый легкий.

а на Go или там Rust народ не хочет переходить? 

К Rust присматриваются по-тихоньку.

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

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

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

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

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

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

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

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

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

Не на все фишечки. Условный std::any_of вполне может развернуться в тот же код, что и вручную написанный цикл, делающий то же самое. Кстати, я тут игрался и заметил, что


простой советский…
bool foo(int n)
{
    int arr[] = { 0, 1, 2, 3, 4 };
    return std::any_of(std::begin(arr), std::end(arr),
            [n](int v) { return v < n; });
}

компилируется gcc 11 в


_Z3fooi:
        test    edi, edi
        setg    al
        ret

тогда как


bool foo(int n)
{
    int arr[] = { 3, 2, 0, 1, 4 };
    return std::any_of(std::begin(arr), std::end(arr),
            [n](int v) { return v < n; });
}

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


_Z3fooi:
        cmp     edi, 2
        jg      .L3
        test    edi, edi
        setg    al
        ret
.L3:
        mov     eax, 1
        ret

Такие вот эвристики. Оптимизаторы всё-таки очень чувствительны к шевелению кода.


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

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


он адски сложный.

Это да, писать корректный код на нём невозможно.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Но суть все равно та же - автоматически освобождать ресурсы, когда они не нужны

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

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

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

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

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

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

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

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

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

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

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

Это я к тому, что в плюсах с этим проще, там 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 не могут ссылаться на один объект.

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

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

Понятно

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

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

в документации ничего про это не сказано

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

Более того, если вы пользуетесь только умными указателями и не достаете из них / не пихаете в них сырые, то у вас в принципе не может возникнуть ситуация, когда на один объект указывают сразу несколько unique_ptr'ов.

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

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

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

дык а как это обеспечивается на уровне реализации этого 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);
    }
}

в сложных codebases вручную все проконтролировать невозможно

Элементарно. Просто не использовать new вообще. Юники гораздо лучше создаются через make_unique(), шаредные через make_shared(): во-первых "контроллировать" ничего не придется, во-вторых при использовании, например, в аргументах метода, нет риска утечки при выкидывании исключения между созданием и присваиванием, в-третьих в случае с make_shared это даже выходит более производительнее (одна аллокация вместо двух).

http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rr-make_shared

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

У вас в принципе семантически некорректный код.

А при выходе из скоупа все работает: https://onlinegdb.com/hwvjZ64DM

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

auto_ptr уже 10 лет как deprecated и уже 4 года как удален из стандарта языка.

угу, вижу

Суть моих филиппик очень простая - сам по себе язык и окружение не гарантируют ни от чего; как и 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т

в силу особенностей языка использование unique_ptr на спасет ни от чего

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

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

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

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

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


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


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

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