Комментарии 323
Virtual static жалко нету.
Еще было бы интересно non-virtual abstract, т .е.
void foo() = 0; // без virtual
типа требования к наследникам реализовать фунцию, но без рантайм-виртуальности а скорее для всякого шаблонного кода.
это решается концептом в месте применения, по аналогии с указанием ABC в виртуальном случае
Именно что в месте применения.
Если это CRTP, - то все проверки должны быть в месте вызова (например, внутри функций-членов), но не на уровне определения класса.
Можно обмазывать проверками - SFINAE, requires - шаблоны функций-членов. Потому что шаблоны будут инстанцированы уже после того, как финальный класс определён.
Если не CRTP, то писать void func(const DerivedFromBase auto& instance); выглядит вполне хорошо. Ничем не хуже олдскульного виртуального void func(const AbstractBase& instance). И при ошибке современные компиляторы в ошибке разжуют, почему именно концепт не применим.
Если CRTP, то да, надо закапывать в тело члена (в конструктор - мимо него не пройдешь, если это не класс-как-пространство имен чисто из статических членов). Не прям вот идеально, не template<DerivedFrombase T> , но вполне себе чистенько. Благо, с современными компиляторы static_assert(DerivedFromBase<T>); тоже будет по полочкам разложен в ошибке.
Дык концепт + crtp
Не получится.
Если в CRTP-базе сделать requires или static_assert на уровне класса, то при попытке унаследовать произойдёт довольно подлая штука. А именно, CRTP-шаблон параметризуется неполным типом! Вот так вот.
template<class T>
requires some_constrain<T>
struct base {
static_assert(something_about<T>);
.....
};
struct derived // в этом месте он ещё неполный
: base<derived>
// some_constrain<T> получил неполный тип
// something_about<T> получил неполный тип
{
.....
}; // и вот только здесь он стал полнымВ методе после полного определения можно будет уже проверить с помощью require и static assert, не? Редко пишу на шаблонах, не оч понимаю какая разница где проверять.
Разница в том, что на уровне определения класса могут возникать неразрешимые зависимости.
Например, derived::foo доступна тогда, когда доступна base::bar. Но base::bar доступна тогда, когда доступна (или не доступна) T::foo, где T = derived.
Ну а внутри функции можно делать ассерты. То есть, функция будет доступна, но, например, приводить к ошибке компиляции.
Кстати сказать, для создания неразрешимых зависимостей необязательно даже наследование. Достаточно сослаться на себя самого. Парадокс брадобрея, причём хоть переборчивого (который бреет только тех, кто не бреет себя), хоть просто любопытного (который бреет всех, но интересуется на входе).
struct barber {
template<class T>
void shaves(T& client)
requires (!requires { client.shaves(client); })
{} // этих рукожопов бреем так...
template<class T>
void shaves(T& client)
requires (requires { client.shaves(client); })
{} // а этих лентяев бреем этак...
static_assert(requires(barber b) { b.shaves(b); });
// рукожоп или лентяй?
};(Это плохая иллюстрация, - зависимость будет неразрешима и за пределами определения класса. Просто чот внезапно пришло в голову показать фокус).
Ну а попроще, более классически, - почему класс в месте определения класса неполный, -
struct selfy {
char c[sizeof(selfy)];
};
struct exploding {
char e[sizeof(exploding) + 1];
};
struct okay {
char o[123];
int foo() { return sizeof(okay); }
};void foo() = delete;Вводит и запрещает эту сигнатуру в этом скопе. В скопе наследников (или в другом неймспейсе) можно перегрузить и использовать оттуда.
Но это не будет требованием. Я не смогу писать Iface->foo, потому что нет гарантий, что у класса под интерфейсом этот foo есть. А если каждый раз нужно будет явно указывать derived, то начинает пропадать смысл интерфейса.
Если обращаешься через интерфейс, то каким волшебным образом там будет доступна наследничья реализация невиртуальной функции?
А для шаблонов - ну, концепты и констрейны в помощь. Хотя и с оговорками. Как я уже написал ранее, CRTP не дружит с констрейнами. https://godbolt.org/z/dnhqz51qb
Как я уже написал ранее, CRTP не дружит с констрейнами.
Дружит, если через соседский огород ходить: https://godbolt.org/z/EczfKTY54.
Но у этого есть очевидная проблема.
можно бахнуть final и молиться, что компилятор соизволит сделать тип плоским.
deduced this?
Вариант: его сделали таким ужасным, чтобы только избранные могли на нем писать, защищаясь например от вайб кодеров на нем:)
Предвидели видимо
Эзотерические языки как способ застолбить за собой рабочее место в шутку предлагали на Хабре ещё лет десять назад.
Вайбкодерам не нужен язык программирования, ни простой, ни сложный.
Иногда хотя бы основы языка надо знать, чтобы где то что то понять и т.д, совсем то нулевой вайбкодер, который в детстве даже Бейсик и паскаль не изучал, кажется вообще толком ничего не сделает..
Если вы не про продакшн решения, то примерно с января 26 знать уже не надо. Поймал себя на мысли, что последний раз заглядывал в код в прошлом году (подчеркну, не продакшен).
а что такого случилось в январе 26-го?
А если речь идёт не о совсем одноразовых решениях - то смотреть что там модели по-написали всё равно нужно, они регулярно пытаются переусложнить код, что потом аукается при доработках.
Чем язык сложнее, тем сложнее ИИ и "промпт-инженерам".
Про то, что inline для функции и для переменной противоположны - неправда.
И там и там в объектных файлах создаются объекты (в секции кода и в секции данных, соответственно), помеченные для линкера как "оставь единственный экземпляр". У MSVC для этого был атрибут __declspec(selectany).
При этом все указатели на инлайн-функцию и инлайн-переменную принимают одинаковое значение. Это важно, например, для параметров шаблонов.
И все статические переменные внутри тела функции также "оставь единственный экземпляр".
Собственно, это всё меры для реализации ODR.
Однако, у компилятора никто не отобрал право инлайнить тело функции (но не размножать её статические переменные). Но он это может сделать и без ключевого слова, кстати.
и в день, когда это слово становится ключевым, их код перестанет компилироваться
Вообще, если подумать при проектировании грамматики языка, то ключевые слова могут не конфликтовать с именами идентификаторов. Например, в Bash вполне валидная конструкция export export=export. Она присваивает переменной окружения по имени export значение export и все живы.
export export=export
Вот такого точно не надо, код станет еще запутаннее. И bash это тоже антипример.
А в том чтобы программа перестала компилироваться после введения в новую версию языка новых ключевых слов, я не вижу ничего плохого. Зачем тогда нужны программисты? Исправят, тем более компилятор укажет все такие места.
Реализация в PHP прекрасна: идентификатор переменной должен начинаться с $. Поэтому переменные в любых контекстах всегда различимы.
Вы, таки будете смеяться, но в крестах тоже можно начинать переменные с $.
но это везде порицаемо, спецсимволы не должны пролезать в имена, с $ видимо недоработка вышла
Нельзя так-то, но компиляторы разрешают как расширение. Есть https://wg21.link/P4234 предложение, чтобы стало можно там, где это можно.
Этак можно докатиться до венгерской нотации (https://ru.wikipedia.org/wiki/Венгерская_нотация )
чтобы это сделать нужно сделать лексер зависимым от контекста. Т.е. по разному разбирать символы на лексемы в зависимости от того какую конструкцию мы парсим. А это плохо и никому не нужно
А вы уверены, что тут нужно править именно лексер? Мне кажется, что подобная логика должна анализироваться даже не в парсере (так как с точки зрения синтаксиса тут все корректно), а на еще на уровень выше, в анализаторе.
Так лексер и стоит на уровень выше, перед парсером. И, собственно, в этом и проблема, что если имеется зависимость от контекста, то в лексер придётся тащить те вещи, которые в противном случае живут только на уровнях парсера и ниже.
Ниже-выше зависит от направления взгляда.
“Выше” я писал про уровень абстракции, тогда как лексер, это самый низкий уровень разбора входных данных, который реализуется с помощью конечных автоматов. Поэтому там в принципе не может быть зависимости от контекста, в отличии от более высоких по уровню абстракции уровнях.
поэтому там в принципе не может быть зависимости от контекста
В контекстно-независимом языке из "export export=export" лексер выплёвывает что-то типа {keyword, space, keyword, sign_eq, keyword}, потом парсер обламывает.
В контекстно-зависимом языке [где можно так писать] лексер должен выплюнуть {keyword, space, identifier, sign_eq, identifier} чтобы парсер не обломал. Вот для того чтобы понимать почему "export" то keyword, то identifier и требуется понимание контекста.
Вы, конечно, можете возразить, мол, пусть гонит первый вариант, а уже парсер пусть разбирает, что вот тут keyword это keyword, а вон там — identifier. Но тогда получается, что мы на парсер перекладываем работу лексера [по определению типа токена]. Жабогадюкинг.
Но тогда получается, что мы на парсер перекладываем работу лексера [по определению типа токена].
Так это и есть контекстно-зависиый синтаксис, о котором лексер на его конечных автоматах ничего знать не должен :-)
Даже если запретить контекстные ключевые слова - в современных языках лексеру всё равно есть где споткнуться. Например, на вложенной интерполяции строк.
мы на парсер перекладываем работу лексера
И чем это плохо?
export export=export — это вполне себе контекстно-свободная конструкция, а парсер может быть реализован без лексера (т. н. scannerless parser), его нет в требованиях грамматики.
Нифига. У C и С++ синтаксис контекстно-зависимый, к сожалению.
Типы, функции, переменные. Плюс шаблоны. Там синтаксическое дерево существенно разное, поэтому парсер должен знать, что стоит за тем или иным словом.
чтобы это сделать нужно сделать лексер зависимым от контекста. Т.е. по разному разбирать символы на лексемы в зависимости от того какую конструкцию мы парсим. А это плохо и никому не нужно
Парсер - да, но не лексер.
Парсер - да, а не только анализатор.
А вы уверены, что тут нужно править именно лексер? Мне кажется, что подобная логика должна анализироваться даже не в парсере (так как с точки зрения синтаксиса тут все корректно), а на еще на уровень выше, в анализаторе.
Внезапно, - но лексеру пофиг.
Лексер гонит поток лексем "закорючка такая, закорючка этакая, слово латиницей".
А вот уже парсер смотрит, является ли это слово служебным или рядовым. Или как следует трактовать ту или иную закорючку. >> это один сдвиг или две угловые скобки? А!
https://github.com/llvm/llvm-project/blob/main/clang/lib/Lex/Lexer.cpp
https://github.com/llvm/llvm-project/blob/main/clang/include/clang/Basic/TokenKinds.def#L372
лексер маппит напрямую "class" -> kw_class, превращая текст в поток токенов, так что
> парсер смотрит, является ли это слово служебным или рядовым
Не соответствует коду
чтобы это сделать нужно сделать лексер зависимым от контекста
Не нужно.
Я так понимаю, ++ нынче язык который невозможно выучить весь. Интересно, насколько у разных людей оказываются несовместимые знания областей языка...
Как разработчик компиляторов я пишу на разных диалектах C++:
в проекте LLVM - это https://llvm.org/docs/CodingStandards.html
в проекте OpenJDK Hotspot - это https://github.com/openjdk/jdk/blob/master/doc/hotspot-style.md
Когда меня приглашают на собеседования как знатока C++, я обычно отказываюсь. Надоело объяснять, что мой С++ особенный.
В OpenJDK Hotspot C++ ограничен по максимуму, отладка и стабильность во главе всего (попробуйте отладить JVM где JIT генерирует и перегенерирует код и куча потоков):
Features from the C++98/03 language may be used unless explicitly forbidden here. Features from C++11, C++14, and C++17 may be explicitly permitted or explicitly forbidden, and discussed accordingly here. There is a third category, undecided features, about which HotSpot developers have not yet reached a consensus, or perhaps have not discussed at all. Use of these features is also forbidden. … Historically, HotSpot has mostly avoided use of the Standard Library.
(It used to be impossible to use most of it in shared code, because the build configuration for Solaris with Solaris Studio made all but a couple of pieces inaccessible. Support for header-only parts was added in mid-2017. Support for Solaris was removed in 2020.)
LLVM более демократичный:
Unless otherwise documented, LLVM subprojects are written using standard C++17 code and avoid unnecessary vendor-specific extensions. … Instead of implementing custom data structures, we encourage the use of C++ standard library facilities or LLVM support libraries whenever they are available for a particular task. LLVM and related projects emphasize and rely on the standard library facilities and the LLVM support libraries as much as possible. … When both C++ and the LLVM support libraries provide similar functionality, and there isn’t a specific reason to favor the C++ implementation, it is generally preferable to use the LLVM library. For example, llvm::DenseMap should almost always be used instead of std::map or std::unordered_map, and llvm::SmallVector should usually be used instead of std::vector.
We explicitly avoid some standard facilities, like the I/O streams, and instead use LLVM’s streams library.
Это не разные диалекты, это с одной стороны LLVM - "поддерживаем С++17, озаботьтесь что ваш код компилируется на С++17 и не зависит от каких-то стрёмных расширений"
А с другой стороны какая-то идиотия джавы, которая фичи делит не по логике, а по хз чему
> Когда меня приглашают на собеседования как знатока C++, я обычно отказываюсь. Надоело объяснять, что мой С++ особенный.
моё личное мнение (как контрибьютора LLVM) - все знающие хорошо С++ знают его одинаково, с одинаковым набором фич. "диалектами" можно назвать QT и unreal engine, где есть свои кастомные расширения, но и там основа есть та же. И есть пара стилей
С-стайл - неоправданно близко к С
"слишком модерн С++" - шаблоны ради ничего, переусложнения, накрученные ranges
"поганая джава" - бесконечные интерфейсы и менеджеры
Все эти "стили" это разновидности плохо понятого С++, любой кто всё же осознал язык в итоге приходит к чему-то среднему и одному и тому же
На любом языке можно писать на фортране. На любом тьюринг-полном языке можно писать на хаскелле. И любая достаточно сложная программа содержит неявную кривущую реализацию лиспа.
Те, кто осознал C++, могут писать и на фортране, и на хаскелле, но стараются щадить своих коллег.
А лисп всё равно самозародится, как мыши в сене. Это неизбежно, смиримся.
На любом тьюринг-полном языке можно писать на хаскелле.
foo :: Eq a => a -> a -> BoolВот тут я чётко вижу, что функция позволяет делать x == y и x /= y. Я чётко вижу, что функция НЕ позволяет сделать, например, rm -rf.
template<typename T>
bool foo(T x, T y);Вот тут я чётко вижу, что функция позволяет сделать что угодно, что в принципе возможно сделать из C++.
Покажите, пожалуйста, как на C++ можно писать на Хаскелле хотя бы в той мере, чтобы foo из C++ стала такой же как foo из Хаскелля.
Для начала, нам нужен тайпкласс. Если мы не будем упарываться тем фактом, что в хаскелле тайпкласс неявно создаёт рантаймовую сущность "словарь виртуальных функций", - то аналог тайпкласса - это концепт.
#include <concepts>
// или руками напишем
template<class T> concept ad_hoc_eq = requires (T& x, T& y) { (bool)(x == y); };
template<std::equality_comparable T> auto foo(T, T) -> bool;
// вариант с GADT
// (когда мы не требуем, чтобы оба аргумента были одного типа)
// (и вообще не задумываемся про их тип)
auto foo(
std::equality_comparable auto,
std::equality_comparable auto
) -> bool;То, какой инстанс тайпкласса будет использоваться в каждом конкретном вызове, известно на этапе компиляции. Передача словаря - это деталь реализации, вполне можно было бы вместо словаря генерировать мономорфные реализации функции для каждого инстанса, наподобие того, как в C++ генерируются экземпляры шаблонных функций
Нет, неизвестно. На этапе компиляции дженерик-функции она ничего не знает про типы аргументов, кроме того, что они принадлежат указанным тайпклассам.
Впрочем, это действительно детали реализации.
В C++ можно с равным успехом параметризовать функцию комплектом невиртуальных функций россыпью, или пусть компилятор сам рыщет в поисках подходящих сигнатур (что, собственно, с шаблонами и происходит... из-за чего шаблоны оказываются более слабо типизированы, чем дженерики).
В смысле что мешает.
А что мешает в хаскелле в IO-функции писать факью одновременно с выполнением какой-то полезной работы? А что мешает в чистой функции сделать unsafePerformIO и написать факью оттуда?
Я думаю, это не то, что хотел увидеть вопрошающий. foo по прежнему может дернуть любой публичный член своих аргументов (звучит как-то... nsfw). Или может дернуть `system("rm -rf /");`. Если про дерганье членов я отписал рядышком, то вот увидеть по определению функции, что она точно не дергает ничего типа system в C++ действительно нельзя.
Строго говоря, operator== может всё ещё делать всё что душе угодно)
template<typename T> struct Eq
{
bool operator==(const T& other) = 0;
bool operator!=(const T& other) {return !(*this == other);}
};
template<typename T> bool foo(const Eq<T>& x, const Eq<T>& y);Как-то примерно так.
Что мешает мне внутри foo вызывать, скажем, print("fuck you")? Прочитайте ещё раз первый абзац прошлого комментария.
Ничто. Pure функции в C++ не завезли, увы. Впрочем, это не проблема C++, и он тут не одинок. Впрочем, вы не критиковали C++ per se, а отвечали на комментарий "на любом языке можно писать на хаскеле". Впрочем, этот комментарий имел в виду, что на нем можно писать в функциональном стиле, а не что можно из C++ сделать хаскель со всеми его гарантиями. Поэтому в foo не будет print не потому, что С++ это запретит, а потому, что вы его туда не напишете.
Поэтому в foo не будет print не потому, что С++ это запретит, а потому, что вы его туда не напишете.
Всё было бы хорошо, если не было плохо. Я-то сам, конечно, не буду себе в ноги стрелять (насколько могу, т.е.), но вот когда такая функция торчит из либы, то с гарантиями как-то интереснее.
Любая consteval функция - в принципе Pure. Программируйтся всё constexpr/consteval/constinit и будет вам Pure.
Ну... почти. Есть способы даже это обойти через дыры в ADL, причем через баги в стандарте языка, а не в компиляторе) Но это уже детали)
Всё что не constexpr - это просто функции с эффектами, но такие и в Haskell могут делать что угодно)
Любая consteval функция - в принципе Pure.
НЕТ. Результат вычисления чистой функция зависит только от входных параметров, тогда как consteval может использовать внешние данные. Лишь бы они были были вычисляемы на момент компиляции.
Так, стоп. Можно открыть википедию, но в целом, смысл термина "чистая функция" сводится к тому, что при одинаковых аргументах мы получаем одинаковый результат. Доп условие в том, что мы не "меняем внешний мир", но в consteval функции это сделать нельзя.
Про внешние данные ничего не говорится. Я могу использовать сколько угодно "вшешних" данных, до тех пор пока эти данные константны. А они константны, т.к. на этапе компиляции состояние хранить нельзя (ну, можно, но через ОЧЕНЬ грязные хаки с ADL и отложенной инициализацией шаблонов, о которых я рассказывать не буду).
Соответственно сколько consteval функцию не вызывай с одинаковыми аргументами, она всегда вернет одинаковое значение. А следовательно она чистая.
UPD:
Уточнение: "чистая в рамках одной единицы трансляции".
UPD2:
Ладно, убедили, через макросы можно сломать чистоту :) Ну штош, по ходу и правда нет чистых функций в С++.
Соответственно сколько consteval функцию не вызывай с одинаковыми аргументами, она всегда вернет одинаковое значение. А следовательно она чистая.
Тут ключевое не “константы”, а “при одинаковых аргументах мы получаем одинаковый результат”, что далеко не одно и тоже. Поэтому что внешние данные, это НЕ аргументы функции и не важно изменяются они во время компиляции или нет.
Важно то, что значение функции становится зависимым не от аргументов, а он некоторых внешних обстоятельств, который вы можете зафиксировать только на время компиляции. Но при повтором запуске компилятора у вас значению подобной функции может быть другим. Поэтому и называть её чистой (математический чистой) нельзя, так как её значение становится зависимым не только от аргументов.
consteval std::string_view compile_time() {
return __TIME__;
}
Почему-то внешние данные - это не аргументы?
В хаскелле например все функции чисты, включая лямбды. А они контекст захватывают.
Можно все consteval функции рассматривать как замыкания над внешним миром
Про макросы я сноску сделал отдельную, но если не использовать их внутри consteval функции, то она чистая.
UPD: Уточнение: “чистая в рамках одной единицы трансляции”.
UPD2: Ладно, убедили, через макросы можно сломать чистоту :) Ну штош, по ходу и правда нет чистых функций в С++.
На самом деле есть :-)
Просто чистые функции в С++ определятся пользователем с помощью атрибутов __attribute__((const)) или __attribute__((pure)), и они не гарантируются (не проверяется) компилятором и он полностью полагается на мнение программиста.
Поэтому в модулях без экспортирования макросов. Ломают все
А если всё-таки упороться, - то... ну, придётся затащить словари. Только это уже будет явно.
using Object = std::shared_ptr<void>; // раз уж начали городить рантайм
struct IEqTraits { virtual bool op_eq(Object, Object) const = 0; };
template<class T> struct EqTraits : IEqTraits {
bool op_eq(Object a, Object b) const override {
auto ta = std::static_pointer_cast<T>(a);
auto tb = std::static_pointer_cast<T>(b);
return *ta == *tb;
}
};
bool foo(
// словари идут впереди аргументов
IEqTraits const* eq,
// упороться каррингом - это отдельное развлечение (не сейчас)
Object a, Object b)
{
return eq->op_eq(a, b) && eq->op_eq(b, a);
}А с другой стороны какая-то идиотия джавы, которая фичи делит не по логике, а по хз чему
Как раз никокой идиотии нет. Исходникам JVM Hotspot порядка 25 лет. Там полно легаси кода. Просто так код никто не трогает. Если бы старались использовать все новые фичи C++, то было бы такое наслоение стилей. Никому не хочется заниматься отладкой C++. Без этого куча проблем с отладкой, особенно GC кода. Разработчики хотят работать на решением задач JVM, а не C++.
моё личное мнение (как контрибьютора LLVM) - все знающие хорошо С++ знают его одинаково
Перефразирую Сократа :) “я знаю, что ничего не знаю в C++” Мое знание С++ ограничено тем, что я использую часто. То что, не используется, очень быстро забывается.
Я уж решыл, што нейронка опять сломалась и переключила себе язык :)
Этот список ходит по инторнетам лет 25, примерно со второй книги Майерса.
Где-то по дороге потеряли 15 разных значений f(x).
За это время вышло штук 7 мажорных апдейтов языка, но в целом стало только хуже, потому что deprecated не завезли, и а вдруг вы захотите скомпилить код из 1969 года.
deprecated не завезли

Если auto_ptr стал deprecated в С++11, то должен был быть полностью выпилен в 14 или максимум в 17. Иначе это просто ни к чему не обязывающие пожелания каких-то потусторонних дядей.
Забавно, что даже в С выпиливание устаревших фич есть (напр., триграммы, синтаксис K&R). Но не в плюсах...
Ну так в стандарте языка С++17 auto_ptr был выпилен -- вон для кого выше написано "removed"? В списке классов стандартной библиотеки начиная с С++17 он отсутствует.
То, что GCC и clang только лишь выдают warning -- это самодеятельность компиляторов, они и кучу других отсутствущих в стандарте вещей предоставляют. MSVC код с использованием auto_ptr начиная с C++17 не компилирует.
Так код с auto_ptr не скомпилируется с включенным С++17 ( как и throw() в конце объявлении функции)

иногда бывает и такое :)
Презабавная статья!
Автор доказал, что C++ это, на самом деле, лучший язык программирования :) В условиях естественного отбора, при наличии разнообразных ограничений, выживают не самые сильные, красивые (и далее по списку), а те, кто способен к адаптации. В отношении C++ можно сказать, что это язык который поддерживает множество парадигм программирования:
процедурное
объектно-ориентированное
шаблонное (generic programmimg)
функциональное
параллельное (и, что очень важно, с четко определенной семантикой модели памяти)
Язык предлагает несколько уровней абстракций. Не буду их анализировать здесь. Это отдельная тема.
Производительность языка максимально близка к железу.
Существует огромное количество библиотек и фреймворков.
Сотни тысяч (миллионы) программистов знают (хотя и в разной степени) этот язык.
Язык хорошо стандартизирован (есть комитет по стандартизации, в котором представлены интересы больших корпораций) и документирован.
В совокупности, все эти факторы позволяет использовать язык для разработки практически любых классов приложений: моделирование, обработка данных, системы управления, системы реального времени, встроенные приложения (микроконтроллеры, GPU), системы высокочастотной торговли, сервисы, сетевые приложения, базы данных, игры, 99% модулей для Пайтона, и т.д. и т.п.
Ни один другой язык и близко не обладает всей совокупностью качеств C++. Вопрос лишь в том, каким образом его используют. В этом плане, C++ мало чем отличается от естественных языков на котором говорят люди. Например, на (условно) английском в мире говорят сотни миллионов людей. В этом языке 2.5 миллиона слов. Однако речь забулдыги из подворотни, жителя гетто, продавца автомобилей и представителя высшего сословия отличаются так, как если бы они говорили на совершенно разных языках. Та же самая картина и у программистов на C++.
Ну и еще один комментарий. Автор статьи явно не понимает почему генерация случайных чисел реализована именно так (хотя вопрос не возник бы в принципе, если бы автор прошел хотя бы вводный курс математической статистики):
std::random_device rd;
std::mt19937 gen(rd()); // а что такое mt19937?
std::uniform_int_distribution<int> dist(1, 100); // а почему отдельно?
int value = dist(gen); // ну наконец-тоОтвет здесь очень простой. Когда речь идет о генерации случайных чисел, то есть две вещи:
функция распределения (uniform, Gauss, etc.). Строка 3 вверху.
собственно генератор случайных (или псевдо-случайных) последовательностей. Строка 2 вверху.
Библиотека четко разделяет эти концепции, предлагая программисту максимальную свободу выбора генератора в соответствии с требованиями задачи. Например:
дефолтный алгоритм
mt19937генерирует псевдослучайную последовательность при умеренной производительности. Это делает его приемлемым для большинства приложений, где не требуется ничего иного.в ряде случаев (серьезная криптография) требуется более надежный генератор, либо генератор на основе физических эффектов (квантовых) с привлечением специализированного железа. Такие генераторы будут генерировать действительно случайные и невоспроизводимые последовательности.
либо может потребоваться очень быстрый генератор (заметьте, что
mt19937является довольно медленным). И тогда программист может разработать свою версию которая удовлетворяет стандартному интерфейсу.
Второй аспект модели генерации случайных чисел это функции распределения. Модель, в которой генераторы отделены от функций позволяют программисту разработать свою функцию распределения используя при этом любой генератор последовательностей.
Ну и если уж речь идет о том, что Вам непонятно что такое mt19937то напишите простой класс и используйте его в своем коде так, как если бы Вы писали на Пайтоне или Ржавчине :)
claсс RandomUniformInt {
public:
MyRandomInt(int minVal, maxVal) : _gen(_rd()), _dist(minVal, maxVal) {}
int next() { return _dist(_gen);}
private:
std::random_device _rd;
std::mt19937 _gen;
std::uniform_int_distribution<int> _dist;
};
// Client code
RandomUniformInt randInt(1, 100);
int val = randInt.next();
Ну и еще один комментарий. Автор статьи явно не понимает почему генерация случайных чисел реализована именно так (хотя вопрос не возник бы в принципе, если бы автор прошел хотя бы вводный курс математической статистики):
Зато автор пишет программы и понимает что нужно обычному программисту.
Не нужны ему все эти детали реализации в 99.99 процентах программ. Ему нужно случайное число от 0 до 100. И в удобных языках есть метод random(max_value) который дает именно то что нужно программисту в подавляющем большинстве случаев.
А все эти изыски с генераторами и особыми распределениями стоит оставить для сторонних библиотек. Их быстро напишут и все будет для тех кому надо.
"Oбычные" программисты (т.е. "джуны"), которые даже не понимают что стоит за словом "rand" скоро никому не будут нужны. Их сейчас увольняют пачками, успешно заменяя ИИ. Реально выживут (или проживут дольше) лишь инженеры с хорошим математическим образованием и архитектурным видением.
Библиотеки это, понятное дело, хорошо. Однако, для получения качественного продукта, необходимо хорошо понимать характеристики (в плане функциональности, масштабируемости и производительности) используемых библиотек. Вот даже автор статьи, похоже, понял разницу между std::map и std::unordered_map.
Обычному сеньору тоже нужно случайное число от 0 до 100. У него все отлично и со знаниями и с пониманием. Но ему все равно нужно только случайное число от 0 до 100 и ничего больше.
Эта разница как бы не в мемах уже объясняется.
Чтобы далеко не ходить: Обычный сеньор отлично понимает зачем делать джиттер в задержках перед ретраем. И ему нужно просто случайное число для этого. А его заставляют думать не над задачей, а над какой-то ерундой.
Если есть четкое понимание, что и зачем делается, что мешает просто использовать rand? Да, устаревшее, но в языке-то осталось.
В него правую границу передать нельзя. Наивные реализации правой границы работают неверно. Эта функция тоже не для людей сделана.
А потом такие вот "мне просто нужно случайное число" добавляют rand со статичным сидом в свою либу потому что "да какая разница-то чо там думать, ранд и все" и вместо джиттера получают статичную задержку
Обычному сеньору […] все равно нужно только случайное число от 0 до 100
Этот же самый “обычный сеньор” будет на каждом углу доказывать, что синглтоны и скрытый глобальный стейт — это плохо и антипаттерн. Так вот, функция, возвращающая “просто случайное число” — это и есть скрытый глобальный (скорее, тред-локальный) стейт.
А инженер на это ответит, что не нужно бездумно механически вычищать антипаттерны в местах, которые даже близко не являются нагруженными/генерирующими техдолг/иным образом причиняющими ущерб.
Я как бывший научный сотрудник рад, что в стандарт затащили заумные вещи типа хорошо управляемого random и комплексных чисел. Но могли бы затащить и что-то простое, напимер так
int a = std:dummy_random_in_range<int>(-5, 5); // Случайное число от -5 то 5 включительно
double f = std::dummy_random<float>(1); // случайное в интервале [0,1) (не включая 1)
Но уже тут возникают вопросы: а у этого random seed будет константый (т.е. повторяемый между запусками) или случайный? И для float возможно нужны несколько реализаций, включающих и не включающих граничные значения. Видимо потому и не включили, что не смогли договориться, как надо просто.
Ему нужно случайное число от 0 до 100
Для решения какой реальной задачи нужны (псевдо)случайные числа неизвестного происхождения с неизвестным распределением, неизвестной воспроизводимостью и неизвестной производительностью?
Генерируете ключ шифрования, IV или токен — вам нужен максимально настоящий и качественный рандом, с тем или иным источником энтропии из реального мира.
Генерируете входные воздействия для тестов или решаете что-то методом Монте-Карло — вам нужен генератор псевдослучайных чисел с определённым балансом между качеством и производительностью.
в удобных языках есть метод random(max_value)
Который, как правило, не подходит для серьёзных применений.
Для решения какой реальной задачи нужны (псевдо)случайные числа неизвестного происхождения с неизвестным распределением, неизвестной воспроизводимостью и неизвестной производительностью?
Именно. Равномерное распределение, обычная скорость. Эти параметры подходят для 99.99 задач где нужно случайное число.
Генерируете входные воздействия для тестов или решаете что-то методом Монте-Карло — вам нужен генератор псевдослучайных чисел с определённым балансом между качеством и производительностью.
Просто задайте сид. Один параметр и только там где он нужен.
Качество по дефолту подойдет в тех же 99.99 процентах случаев.
Генерируете ключ шифрования, IV или токен — вам нужен максимально настоящий и качественный рандом, с тем или иным источником энтропии из реального мира.
Я слишком туп чтобы писать криптографию.
Который, как правило, не подходит для серьёзных применений.
Более 99.9% всего кода не является "серъезным" в принципе. Да и речь же не идет о том, чтобы удалить специфичные версии. Речь о том, что должен быть удобный дефолт, а дальше уже можно делать конкретные библиотечные вариации. Сениор разберется, где ему не подходит дефолт. Джуниору никакого прока от этих специнструментов, он понятия не имееет ради чего его заставляют весь этот бойлерплейт писать.
Единственное что - если такой упрощенный дефолт в язык заводить, то целесообразно сразу требовать использование поточного шифра в качестве движка.
Речь о том, что должен быть удобный дефолт
rand чем не удобный? Просто сломанный.
Удобный дефолт это важно. Но вот рабочий дефолт это еще важнее.
Неудобен ещё и зависимостью RAND_MAX от платформы. И ещё плохо то, что в стандарте Си приводят шуточный генератор в качестве примера для библиотеки. Наверное, наиболее радикальный и частично ломающий обратную совместимость способ - это потребовать в стандарте Си использования поточного шифра в rand(). Уж если менять генератор, то так, чтобы ещё на 50-100 лет его хватило бы.
Я позволю себе не согласиться.
Нет такой вещи как "просто случайное число от 0 до 100". В принципе, нет ничего простого в случайных числах и случайности вообще. Кроме ну совсем "наколеночных" случаев, но там и просто вызов rand() сойдет.
В любом месте, где требуется случайность, неизбежно возникают вопросы о распределении и о скорости/криптостойкости. Если они не возникают, значит вы не знаете что вы делаете и скорее всего допускаете ошибку/уязвимость
Весь вопрос - что такое наколеночные случаи, как это формализовать. Я границу провожу просто - rand() подходит только в том случае, если устроит даже такой ГПСЧ как .
Для меня "наколеночный случай" - это когда "мне просто надо, чтобы тут циферка иногда менялась, но на саму циферку мне плевать") А там хоть итератор над кольцевым буфером сделать)
Насколько я помню, в DOOM так и делали, выдавали именно из закольцованного буфера, и всё нормально было. Но для меня сущая загадка, почему в rand() до сих пор остаются плохие алгоритмы, и man-страницы даже не предупреждают об этом. Если бы к ней подходили бы как к синусу или косинусу - там бы явно крутился бы по умолчанию поточный шифр.
С DOOM опять же не совсем корректный пример) Я не знаю как они использовали эти случайные числа, но скорее всего они озаботились о статистических свойствах циферок в кольцевом буфере.
Я говорил о совсем вырожденных случаях, когда нам просто нужно чтобы "изменения были", но мы вообще не заинтересованы в статистических свойствах этих изменений.
Например, учебные задачи или тестирование, когда нам нужно внести некий хаос в систему. Впрочем даже в последнем случае нам нужно понимать параметры этого хаоса)
Блин, реально ведь сложно придумать ситуацию, когда мы не хотим знать источник энтропии для засеивания ГПСЧ и распределение. Возможно только о криптостойскости мы иногда не хотим думать
Возможно только о криптостойскости мы иногда не хотим думать
Ну а если не хотим думать, то почему бы не использовать криптографические примитивы как ГПСЧ по умолчанию? Да, это может не быть полноценным криптогенератором, но в случае сидирования от /dev/urandom или его аналогов хотя бы не даст катастрофических последствий при использовании для генерации паролей или ключей.
Я говорил о совсем вырожденных случаях, когда нам просто нужно чтобы "изменения были"
Тогда почему бы не использовать обычный счётчик?
С учебными ситуациями и тестированием ситуация с моей точки зрения такая:
Если в учебной задаче хоть что-то вычисляется, то целесообразно считать, что в качестве ГПСЧ по умолчанию нужен поточный шифр. Компромиссы вроде MT19937 возможны, но должны осознаваться как компромиссы.
rand() для написания тестов в принципе непригодна, т.к. в стандарте языка алгоритм не указан, и на разных платформах будет разная последовательность.
Ну, если брать конкретно rand(), то это наследие С, он компилируется где угодно даже на самой распоследней ручке, включая bare metal, и никакие требования к источнику энтропии в стандарте особо не закрепить.
В С++, впрочем, история похожая.
Ну, а на счёт криптографических примитивов всегда - ломается zero cost abstraction) Я знаю что всем наплевать, но комитет часто прикрывается этой хернёй)
Да, на bare metal с источником энтропии может быть не очень просто, тут соглашусь. Впрочем, можно было бы сделать так:
1) Внести в стандарт C требование использования поточного шифра и системного криптогенератора для инициализации в функции rand(). В стандарты C++ обязать это использовать как ГПСЧ по умолчанию в модуле random. В C++26 уже добавили почти криптографический Philox (но он - не шифр всё же), жалко, что хотя бы его не обязали использовать как генератор по умолчанию.
2) В случае отсутствия криптогенератора программа с использованием rand() должна просто не компилироваться, отключаться это должно только чем-то вроде #define USE_BAD_RANDOM_SEEDS
3) В случае невозможности использовать в rand() поточный шифр - аналогично. Компиляция должна включаться чем-то вроде #define USE_BITHACK_PRNG. И некриптографический генератор даже в этом случае должен выдерживать 128 ТиБ в PractRand и батареи TestU01.
Но насчёт криптографических примитивов: их использование в rand() - это эквивалент реализации синуса и косинуса с максимально возможной точностью. Но почему-то к одной математической функции (rand) считается допустимым куда более безалаберный подход, чем к функции sin.
Также из-за мьютексов в rand() glibc оно всё равно ощутимо медленнее AES. Считаю, что нужно либо менять ГПСЧ, либо добавлять предупреждение о низком качестве генератора в документацию glibc и man-страницах.
Ну, на cppreference честно написано
There are no guarantees as to the quality of the random sequence produced. In the past, some implementations of
rand()have had serious shortcomings in the randomness, distribution and period of the sequence produced (in one well-known example, the low-order bit simply alternated between1and0between calls).
rand()is not recommended for serious random-number generation needs. It is recommended to use C++11's random number generation facilities to replacerand().(since C++11)
Это да, в C++11 уже есть два быстрых и приличных генератора: mt19937 и mt19937_64. Когда в C++26 появится Philox - вообще хорошо станет, т.к. он и удобен для многопоточности, и все тесты проходит.
Вот бы это предупреждение из cppreference про rand() еще и в документацию glibc и man-страницы добавить.
Это плохой класс, при таком использовании слишком часто будет создаваться новый mt19937, что убивает всю идею mt19937. Если уж так делать - проще сразу _dist(_rd) писать в next()
А если мы всё-таки используем std::mt19937, то надо делать как-то так (пишу по документации, код не проверял):
claсс Random {
private:
std::mt19937 _gen;
public:
Random() : _gen(std::random_device()()) { }
int next(int minVal, maxVal) {
std::uniform_int_distribution<int> dist(minVal, maxVal);
return dist(_gen);
}
static Random& shared() {
thread_local Random instance;
return &instance;
}
};
Мне так нравится эта наивная вера некоторые разработчиков, которые уверенно заявляют, что они-то вот настоящие инженеры, которых не уволят из-за ИИ. А других обязательно уволят, потому что они липовые программисты, так как не знаю особенности рандома.
На деле рыночек порешает всех по своим правилам, которые заранее нам знать не суждено. Но последние года до бума айти в среднем по больнице скорее смузихлёб на JS зарабатывал куда больше по сравнению с "настоящим" инженером на C/C++
Аминь, золотые слова. Плюсанул бы, да крив на лицо по стандартам хабра.
Ну, а "настоящий инженер С++" зарабатывает в среднем по больнице сильно больше научного сотрудника в НИИ или там даже инженера на атомной электростанции.
Значит ли это, что всех научных сотрудников и инженеров заменит ИИ?
Я могу наоборот сказать, что раз С++ программисты в среднем дешевле, значит и заменять ИИ их будут в среднем после того, как заменят всех смузихлёбов)
В стандартной библиотеке Rust вообще нет функции рандома и никто не требует.
В том-то и проблема, что более-менее приличный std::mt19937 - не дефолтный, а что будет выбрано по умолчанию - стандарт не специфицирует (default_random_engine теоретически может быть и какой-нибудь плохой генератор вроде minstd). А вообще я считаю, что ГПСЧ по умолчанию даже для некриптографических применений должен быть основан на каком-то поточном шифре вроде AES, ChaCha или ThreeFish.
Читаю и недоумеваю. Десятилетиями пишу на C++, с 98 по 23, никаких особых проблем не было никогда. У меня какой-то другой C++?
Не. У меня дед всю жизнь проездил на Запорожце, был полностью доволен, любил его даже: раз в неделю в нём что-то смазывал. /s
Ну уж если с авто сравнивать, C++ - это, скорее, Феррари. Водить надо уметь, но гоняет - другим не чета.
Сильно сомневаюсь, что он может быть быстрее любого другого компилируемого неуправляемого языка. Да и нужно это гораздо реже, чем кажется.
Оптимизатор у хорошего C++ компайлера даст 100 очков вперед другим языкам. Перформанс, по крайней мере, в моей области (электронные CAD-ы), важен примерно всегда, и почти всегда - узкое место.
Но не всем. Watcom C Compiler обойдет на 10 очков вперед. )))
Исключение, подтверждающее правило) Watcom - да, классный. Правда, я с ним не работал уже давно..
Ни одно исключение не подтверждает ни одного правила. Любое исключение из правила - опровергает правило. Возьмём правило: “у треугольника все углы острые”. И сразу легко находим треугольники-исключения, у которых один угол тупой или прямой. Правило опровергнуто, а никак не “подтверждено”. Люди, применяйте правильные поговорки. Неправильные не применяйте. Правильная поговорка (вернее это изречение, ни много ни мало - Цицерона) звучит (примерно) так: существование исключения, свидетельствует, что существует и правило, из которого сделано это исключение. Согласитесь, смысл совершенно другой, и вполне логичный. Ну, как бы, Цицерон был умный дядька. /зануда mode off
С++ это набор запчастей, из которых можно собрать ржавое ведро, а можно самолет в цветах Феррари. Зависит от того, что набрать, а что выкинуть.
Существует пласт системного ПО, созданного исключительно на ассемблере:
KolibriOS (и MenuetOS) - полноценные и современные операционные системы, которые включая ядро, драйверы, текстовые и графические редакторы, игры и браузер, написаны на ассемблере (FASM). При этом KolibriOS помещается на одну дискету, загружается за пару секунд и мгновенно работает даже на древних ПК.
FASM (Flat Assembler) - популярный современный компилятор ассемблера, который сам полностью написан на ассемблере. Он самостоятельно компилирует свой собственный код.
В сфере защиты данных ассемблер незаменим для предотвращения атак по сторонним каналам и ускорения математических вычислений:
OpenSSL / BoringSSL - криптографические библиотеки, обеспечивающие безопасность почти всего современного интернета (HTTPS). Самые ресурсоемкие алгоритмы шифрования (AES, RSA, ChaCha20) написаны на чистом ассемблере x86-64 и ARM для задействования аппаратного ускорения процессоров.
WireGuard - современный и быстрый протокол VPN. Его криптографическая база во многом опирается на оптимизированный вручную ассемблерный код.
Обработка видео и аудио в реальном времени требует колоссальной вычислительной мощности:
FFmpeg / x264 / x265 - движки, на которых работает практически всё современное цифровое видео (включая плееры VLC, YouTube, стриминги). Сверхбыстрое сжатие и декодирование видеопотоков обеспечивается за счет сотен тысяч строк кода, написанных на ассемблере под SIMD-инструкции (AVX-512, SSE, NEON).
FLAC / WebM - аудио и видеокодеки содержат низкоуровневые ассемблерные оптимизации для минимизации задержек при воспроизведении.
Можно продолжать ещё долго. Так что да, статья "обиженного джуна" из ютуба вполне имеет право на жизнь.
У ассамблера есть маленький недостаток - он камнезависимый.
Поэтому получится, что тот же ffmpeg на Intel и ffmpeg на Arm64 должен использовать разные коды, а в некоторых случаях и передавать выполнение встроенным в камень же аппаратным x264-декодерам.
Поэтому не везде можно применять ассемблер.
Ладно там ARM. Код на P и E ядрах одного и того же Intel процессора должен использовать разный ассемблер. Иначе P ядра не выйдут на всю силу. Что и было частью провала этой архитектуры: Intel компилятор так умеет, а остальные оси и компилируемые языки не очень.
я выложил все плюсы Ассемблера как альтернативу а мне за это минусов налепили и карму скинули, маньячиллы, ну и облизывайте свой C++ как конфету, тоже мне, умники...
Но у asm-а нет никаких плюсов) Вообще никаких) Я даже отброшу мою субъективную неприязнь к внешнему виду ассемблера и что код на нём - это слишпиеся макарошки :)
Вот список очевидных недостатков
1. Каждая команда - это считай что ключевое слово в языке. Многие ли способны запомнить набор команд для x86_64? А сразу для ARM и x86_64? Да ещё и для всей линейки процессоров. Это делает чистый assembler космически сложным языком просто для запоминания.
2. Современные процессоры давно уже все Out-of-Order, а следовательно и инструкции надо упаковывать так, чтобы оно работало с учётом устройства внутрянки каждого отдельного процессора.
3. Использование ассемблера требует нефига нетривиальных познаний в архитектуре ЭВМ. Даже данные правильно выравнивать будет нафиговой такой когнитивной нагрузкой.
Я не говорю, что у ассемблера нет применений. Я сам писал на нём крипту и переписывание "слово в слово" с чистого Си на ассемблер дало прирост в 15% на ровном месте. И в этом ассемблер оч хорош - числодробилки и общение с железом. Но в массе свой он нахрен не нужен) Никому)
1й пункт неточен, поскольку макроассемблеры изобрели оочень давно, еще до С.
А в остальном - современный компилятор С итп подбирает инструкции и их порядок лучше человека.
Я сам писал на нём крипту и переписывание "слово в слово" с чистого Си на ассемблер дало прирост в 15% на ровном месте
Статью написать думали?
Я могу выложыть все плюсы помидоров и меня тоже заминусуют, и это логично, ведь как это относитса к теме?
KolibriOS (и MenuetOS) - полноценные и современные операционные системы
Они не очень современные и совершенно точно не полноценные. Особенно учитывая, что любой код исполняется от уровня ядра - из-за отсутвия хотя бы базового разделения на kernel/userspace и aslr любой блокнот ваши пароли сольёт кому угодно. Гуй прибит гвоздями, usb-стек минимальный и про 3.0 ничего не знает, про bluetooth вроде вообще не задумывались, не говоря уже про BLE; туда же современный WiFi, NVME, GPU. С учётом отсутвия слоев совместимости с Linux, BSD или хотя бы POSIX внешние драйвера или иной софт уровнем повыше они также не умеют запускать. Виртуализация, дедупликация зависимостей, высокоуровневых API для каких-то оптимизированных вычислений также нет. Оно прикольно как POC, но совершенно точное не как современная ОС. Сделать блокнот, paint или солитера вообще не проблема, те ещё на дисковых ОС были.
Сказ про флоппик хорош как маркетинг для привлечения внимания, но не как selling point - вы с тем же успехом можете впихнуть ядро линукса на флешку, повыкидывав кучу драйверов, оставив примерно столько же, сколько их в колябрии есть и докинуть туда драйвер для видюхи и какой-нибудь hyperland для красоты включить в состав.
Линус Торвальдс много раз объяснял почему Linux на C и в нём никогда не будет C++. И одна из причин было то, что он не хочет, чтобы код в его проекте писали C++ программисты. При этом Rust был им допущен и уже есть в исходниках Linux.
Практически в любой теме по c++ к комментариях цепляются несколько человек, которые чуть ли не глотки грызут друг другу, доказывая что в плюсах правильно, что нет, как им правильно пользоваться/какими фреймворками и т.д.
Даже представить не могу как плюсовики для продакшена что то обсуждают и решают. И при этом язык пережил уже многих "убийц" и помирать не собирается
А он и не помрёт, ибо дофига легаси. И даже новые проекты активно появляются. Плюс весь геймдев.
Но ниша его сужается активно. Го вытеснил из сервисов всяких и веба, Раст из системного программирования потихоньку давит.
Не только из системного. Например, вымерли или были переписаны многие блокчейны, написанные не на Rust. Новые проекты в областях, где требуется быстрое создание системы разработчиками среднего уровня в сочетании с требованиями повышенной надёжности и секурности, практически безальтернативно начинаются на Rust.
Ну КОБОЛ тоже жив в каком-то смысле
Очень просто. В каждой фирме свой собственный диалект и кодстайлгайд С++, не совместимый со всеми остальными. Внутри одного коллектива со временем притираются, а когда через интернет контактируют два таких коллектива - получаем наблюдаемый эффект.
только небольшая поправочка, в С и Раст это определенный подход в котором нету нормального наследования, поэтому С++ не вытиснить, он органично вписывается куда надо, там где на С и Расте будет портянка нечитаемого кода с багованным наследованием(Раст от одного этого тейка или недоделки уже отлетает просто осознанно).
в котором нету нормального наследования
А оно точно надо? Вот прям никуда без него?
Ну если так рассуждать, то ничего точно не надо, и без всего можно обойтись в общем-то. Тут вон призывают все на Асме писать, и технически не невозможно же.
Я думаю, коллега подразумевал, что "нормальное наследование" - проблемная штука. Как goto, типа. Вспомните Банду четырёх, в частности: "Favor object composition over class inheritance". Такое наследование - удобный инструментарий, чтобы "выстрелить себе в ногу", и написать код, который сложно поддерживать и развивать.
В ранних версиях Rust'а было наследование в том смысле, который Вы вкладываете. От него сознательно отказались.
А так, наследование в духе интерфейсов в Rust'е есть.
Иногда надо, иногда нет.
А тут выбор уже сделали за вас.
Ну можно в язык тогда всё впихнуть, чтобы было всё на всякий случай жизни. Ну вдруг пригодится. Например, вот нужно 20 способов инициализации. Обычно парочка нужна, но иногда прям жить невозможно без других 18-ти. Или ромбовидное наследование, некоторые задачи прям нерешаемы без него.
Ой, да это же получился...
Да, и это как раз фишка языка - минимизировать количество способов выстрелить себе в ногу. Часто считают, будто Rust характерен только системой владения памятью, но на самом деле использование безопасных подходов для работы с памятью - это всего лишь один из элементов (хоть, безусловно, и самый заметный) общей стратегии.
Так, ещё в Rust нет goto. А ведь оно тоже, наверное, "иногда надо, иногда нет"?
goto. А ведь оно тоже, наверное, "иногда надо, иногда нет"?
Есть минимум два примера, когда с ним удобнее - выход из вложенных циклов и обеспечение единой точки выхода из процедуры, если используется такой код-стайл.
Оба случая прекрасно решаются и без goto.
выход из вложенных циклов
Вынести циклы в отдельную функцию и выходить через return. В таких случаях обычно логика всё равно слишком сложная для одной функции, и от такого разбиения понимать код становится проще.
обеспечение единой точки выхода из процедуры
Полезно не само по себе, а ради гарантии освобождения ресурсов. А с этим гораздо лучше справляется RAII.
Вынести циклы в отдельную функцию и выходить через return. В таких случаях обычно логика всё равно слишком сложная для одной функции, и от такого разбиения понимать код становится проще.
Ужасный ответ в стиле "зелен виноград", но вот вам костыли. Ситуация достаточно популярная, чтобы в нескольких языках был для этого добавлен оператор break label. Пример.
Линейный же вход/выход из функции удобен много для чего, например для красоты отладки или для постконтрактов.
Linux ядро буквально живет на том же с++ но в синтаксие С. Те же абстракции, тот же rtti и vtable, обработка исключений. В ядро с++ не пустил фанатизм и то что с++ был сильно позже стандартизирован чем С
Неистово плюсую, всё верно. И еще вот это всё привело к тому что язык стал... неэстетичным, что-ли. Вот к примеру std::move - почему такая громоздкая конструкция, если по смыслу это вообще должен быть унарный оператор? А есть еще std::forward, std::move_if_noexcept, std::decay, std::launder, std::common_type, std::remove_reference, std::remove_const, std::enable_if, std::conditional и прочие is_trivially_default_constructible. Особенно если все это навёрнуто в трехэтажных шаблонах.
Всё это выглядит как огромное количество "кишок наружу" - какие-то шаблонные конструции, которые вроде как и нужны (наверное, мне ни разу не понадобились), но... в них не хочется углубляться. На языке хочется писать прикладные программы, а не заниматься странными хаками компилятора.
В качестве контрпримера могу привести библиотеку Qt - огромная библиотека, но всё строго и по делу. Приятный синтаксис, богатые классы, решающие реальные прикладные задачи. Шаблоны используются строго для того, для чего они и задумывались - параметризация контейнеров. Даже camelCase выглядит приятнее чем snake_case.
Я что-то путаю или можно после заголовочных файлов написать using namespace std; и потом не использовать std:: перед каждым чихом?
Можно, но тогда в текущем пространстве имён внезапно могут появиться move, data, begin, end, size, span, list, map, queue, erase и прочие однословные классы и функции.
Можно но не нужно.
Можно и нужно писать using std::achoo;
Отсюда vector, потому что в численных вычислениях Scheme «вектор» означал одномерный непрерывный массив, так что в контексте Степанова имя было логичным. Потом спохватились и хотели назвать array , но имя уже примелькалось в стандарте и проектах, поэтому переименовать опять было нельзя, опять тоже самое, что и везде в языке.
На самом деле понятие вектор использовалось в математике и означало частный случай матрицы - либо строку (вектор-строка), либо столбец (вектор-столбец).
Спасибо за статью. Хоть в основном все вышеперечисленное знаю, почитать было интересно. C++ действительно не самый быстрый но самый совместимый. Но он настолько разнообразен, что на нем можно писать так, чтобы было быстро.
Но я бы упомянул еще несколько моментов:
В вариантах целочисленных типов не упомянуты size_t без std и intptr_t.
Название vector - вполне нормальное название для массива элементов на мой взгляд. Вектор в линейной алгебре будучи разложенным по некоторому базису как раз и представляет собой массив.
Самая большая боль C++ это undefined behavior. И дело даже не в разименовании неинициализированного или деаллоцированного указателя, а в том как компиляторы по факту трактуют стандарт. Если в стандарте написанно что XXX может производить - это UB, а разработчики стандарта считают что программист пишет без UB и делают соответствующие предположения о коде. Например известная история с переполнением Int.
Есть еще одна проблема c и c++, это strict aliasing rules. Дополнительные правила, которые усложняют каст указателя на один тип к другому чтобы поковыряться в битах.
Еще можно было бы сказать про расширения gcc которых не хватает в стандарте, но пожалуй пока хватит.
В том-то и суть, что вектор надо сначала разложить по базису, чтобы массив координат получить. А разложив его по другому базису - можно получить другой массив координат, при этом вектор будет тот же самый.
В плюсах же для большинства векторов базис, который имел бы смысл, и придумать-то не получится.
В стандарте C++ четко сказано: "Переменная вводится в программу объявлением (declaration) и имеет идентификатор (имя)".
это когда же у нас вызов функции и передача аргумента вдруг стало причиной инициализации переменной?
Конечно иногда аргументы функции передаются через стек, но в основном через регистры, и из-за этого считать что аргумент это вдруг отдельная переменная - как минимум странно. Да, конечно если рассматривать передачу через стек, то механизм схож с локальной переменной, но это всё равно другое. Ни разу не слышал, чтобы хоть кто-то из тех же реверс-инженеров говорил что аргумент функции это переменная.
struct S {
int x = 5;
int y{10};
S() : y{2}, x(1) {}
};
ВОТ ЭТО, ни как не переменная, сама структура (её описание) - это тип данных (чертеж), а переменная - это экземпляр (конкретный объект в памяти).
return x;
return {1, 2, 3};
throw x;
И это вообще и близко, тоже не переменные.
"Простые вещи просто не делаются"
Ну оберни в функцию и будет тебе всё по одной кнопке? За чем своё субъективное ощущение после условного питона(или джавы), выдавать за некую справедливую оценку языка?
"Проблемы кастинга"
Тут автор буквально пишет, что после джавы, ему тяжело запомнить приведение типов к другим типам и так же показывает, что читать в интернете зачем нужен каждый он не хочет, а зря, узнал бы зачем они нужны.
Короче дальше мне стало нудно читать ибо:
1. Автор выдаёт субъективное ощущение за объективное оценивание
2.Автор так же явно начал трогать С++, после более высокоуровневого языка (той же джавы которую он активно упоминает, что много даёт понять) , при этом открывать документацию или какие-то статьи он не хочет, возможно где-то пробежался, нахватался и теперь ноет на хабре.
Аргумент функции - и правда не переменная, а вот параметр (формальный) обладает всеми признаками переменной: имя, возможность присвоить значение, возможность получить адрес.
А на стеке она, в регистре или вовсе оптимизирована в ноль - нет разницы.
Кроме того, в математике параметры функций открыто называют переменными, и само слово “переменная” именно из математики и пришло.
ну да, перефразируя известное высказывание:
Демократия — наихудшая форма правления, если не считать всех остальных, которые пробовались время от времени
можно сказать
С++ — наихудший язык программирования, если не считать всех остальных, которые пробовались время от времени
С++ это язык именно программирования, именно для программистов, а не для физиков, лириков, ...
Программисты должны понимать откуда взялся, что значит, и почему он именно static, например.
Мне так нравится эта наивная вера некоторые разработчиков, которые уверенно заявляют, что они-то вот настоящие инженеры, которых не уволят из-за ИИ. А других обязательно уволят, потому что они липовые программисты, так как не знаю особенности рандома.
На деле рыночек порешает всех по своим правилам, которые заранее нам знать не суждено. Но последние года до бума айти в среднем по больнице скорее смузихлёб на JS зарабатывал куда больше по сравнению с "настоящим" инженером на C/C++
via https://habr.com/ru/articles/1047890/comments/#comment_30177360
Интересно будет послушать ваш анализ Rust, почему он не "именно язык программирования для программистов".
Ну как минимум потому что Rust выглядит как очередная кривая обёртка над С++.
Вот две функции для записи в память другого процесса:
RUST:
fn write_memory(handle: HANDLE, address: usize, data: &[u8]) -> Result<usize, Error> {
let mut bytes_written = 0usize;
unsafe {
WriteProcessMemory(
handle,
address as *mut _,
data.as_ptr() as *const _,
data.len(),
Some(&mut bytes_written),
)?;
}
Ok(bytes_written)
}C++:
SIZE_T WriteProcessMemoryEx(HANDLE hProcess, LPVOID address, LPCVOID buffer, SIZE_T size) {
SIZE_T bytesWritten = 0;
WriteProcessMemory(hProcess, address, buffer, size, &bytesWritten);
return bytesWritten;
}Вам нужно объяснять? Или вы проведёте сравнение и увидите простоту и лёгкость С++ в базовой задаче?
Вы действительно считаете что RUST выглядит и есть лучше чем C++ ?
P.s. На меня накинулись фанаты Rust, у которых от их языка уже проблемы с логикой, так как они не понимают что тут слова про память просто пример, а не аргумент
Вопрос в том, что считать “базовой задачей”. Для тех, кто предпочитает Rust, описанное вами - не базовая задача, а редкий случай, который надо сделать один раз, протестировать как следует и больше на него не смотреть. Для тех, кто предпочитает C++ - вероятно, наоборот.
Так в смысле, обёртка над функцией не базовая задача? Одно из самых частых это обёртка над функцией с некоторой логикой.
Я показал пример обёртки над функцией, можно даже не знать что она делает(функция).
Смысл в том, что в С++ всё красиво и аккуратно, не каких лишних букв и символ.
Вот зачем отделять тип и словесный ид двоеточием, что это за порнография?
-> Result<usize, Error>
А вот что это за порнография ??
обёртка над функцией не базовая задача?
Над функцией, которая лезет напрямую в WinAPI (и вообще в сторонний C API)? Не для всех уж точно.
Вот зачем отделять тип и словесный ид двоеточием, что это за порнография?
А. То есть вопрос на самом деле вообще не в “кривой обёрточности”, а просто в том, что синтаксис некрасивый?
Одно из самых частых это обёртка над функцией с некоторой логикой.
Только обычно логики 90%, а бойлерплейта 10% (условно), в вашем примере 0% и 100% соответственно.
в С++ всё красиво и аккуратно, не каких лишних букв и символ
Вы тут де-факто на Си пишете. Почему указатель + длина, а не std::span?
Вот зачем отделять тип и словесный ид двоеточием, что это за порнография?
Мысль гласит, что это ради того, чтобы не было несколько способов делать одно и то же. К тому же, много новых языков уже выбрали такой способ:
https://habr.com/ru/articles/532660/comments/#comment_22487588 (также https://habr.com/ru/articles/532660/comments/#comment_22487564, https://habr.com/ru/articles/532660/comments/#comment_22488240)
А вот что это за порнография ??
Это std::expected<std::size_t, your_error_type>.
Пережиток паскаля и делфи
Смысл в том, что в С++ всё красиво и аккуратно, не каких лишних букв и символ.
Серьезно? С++ один из самых ругаемых языков за непонятную мешанину из спецсимволов.
Действительно, ведь 95% целевой аудитории занимаются только тем, что пишут в память других процессов на Win32.
Функция на Rust сразу даёт информацию об успешности завершения и возвращает информацию об ошибке, если есть, когда как в C++-версии это придётся делать вызываемому коду. Во-вторых, ясное дело, что если вы будете вызывать из языка, где принято использовать другие, не Си-подобные приёмы, тонкую обёртку над сишной функцией, у вас будет некрасивый обвязочный код.
"Во-вторых, ясное дело, что если вы будете вызывать из языка, где принято использовать другие, не Си-подобные приёмы, тонкую обёртку над сишной функцией, у вас будет некрасивый обвязочный код."
Ну то есть, вы буквально говорите, что если не си-подобный приём, то будет не красиво.
ДА ПРИ ЧЁМ ТУТ ПАМЯТЬ, ЧТО ВЫ К НЕЙ ПРИСТАЛИ, ЭТО ПРОСТО КАК ПРИМЕР.
Вот сразу видно любителей Rust и т.п. У них проблемы с логикой, тут не важно что делает функция которую обернули, это просто пример.
Ну то есть, вы буквально говорите, что если не си-подобный приём, то будет не красиво.
Я такого не говорил ("Вот сразу видно любителей C++ и т.п. У них проблемы с логикой"), я писал, что вызовы из одного языка программирования функций из другого языка программирования обречены иметь бойлерплейт и прочий некрасивый код. Вы же из одного такого примера пытаетесь вывести, что весь Rust плохой, а C++ хороший.
ДА ПРИ ЧЁМ ТУТ ПАМЯТЬ, ЧТО ВЫ К НЕЙ ПРИСТАЛИ, ЭТО ПРОСТО КАК ПРИМЕР.
Успокойтесь, к памяти никто не приставал, я даже этого слова не писал. Вместо WriteProcessMemory могло быть большинство других сишных функций, и все аргументы остались бы действительны.
У них проблемы с логикой
Это у вас проблемы, по одному нишевому примеру судить о всём языке.
Ну да, если простая обёртка выглядит криво, то функция со сложной логикой точно будет хорошо.
Гениально.
"Во-вторых, ясное дело, что если вы будете вызывать из языка, где принято использовать другие, не Си-подобные приёмы, тонкую обёртку над сишной функцией, у вас будет некрасивый обвязочный код."
"Во-вторых, ясное дело, что если вы будете вызывать из языка"
что вызывать то?
"где принято использовать другие, не Си-подобные приёмы"
А почему? Си-язык, возник для удобств, что бы на ассемблере не строчить, и ни кто после ассемблерных мнемоник не хотел перегружать язык кучей лишних символов. То есть си-стайл уже сам по себе имеет девиз "Просто и логично" , но нет надо ведь всё испортить
"тонкую обёртку над сишной функцией, у вас будет некрасивый обвязочный код"
Ну да, конфета плохая не из-за производства, а из-за фантика, я вас понял.
Это я повторно ответил, потому что вы теперь отпираетесь и говорите что я вас не так понял.
Некрасивый обвязочный код, сугубо из-за того что сам язык такой
Ну да, если простая обёртка выглядит криво, то функция со сложной логикой точно будет хорошо.
Гениально.
(Выделение моё) Не "точно" -- из "обёртка -- криво" не следует "сложная логика -- хорошо", но! из "обёртка -- криво" не следует "сложная логика -- криво". Если вы хотите сравнить качество двух языков, то сравнивайте их на наборе примеров и использований, которые соответствуют их применениям в реальности.
"Во-вторых, ясное дело, что если вы будете вызывать из языка, где принято использовать другие, не Си-подобные приёмы, тонкую обёртку над сишной функцией, у вас будет некрасивый обвязочный код."
"Во-вторых, ясное дело, что если вы будете вызывать из языка"
что вызывать то?
(Выделение моё)
"где принято использовать другие, не Си-подобные приёмы"
А почему? Си-язык, возник для удобств, что бы на ассемблере не строчить, и ни кто после ассемблерных мнемоник не хотел перегружать язык кучей лишних символов. То есть си-стайл уже сам по себе имеет девиз "Просто и логично" , но нет надо ведь всё испортить
Не "просто и логично", а скорее "примитивно". Язык программирования обязан не только отражать намерения разработчика в машинный код, но и обеспечивать, по мере своих сил, поддерживаемость, безопасность кода и т.п.
Защищать Rust легко, так как это новый язык, у которого была возможность учесть весь опыт других языков, но вы к этому аргументу не аппелируете.
но нет надо ведь всё испортить
Это вы про C++?~
"тонкую обёртку над сишной функцией, у вас будет некрасивый обвязочный код"
Ну да, конфета плохая не из-за производства, а из-за фантика, я вас понял.
Это я повторно ответил, потому что вы теперь отпираетесь и говорите что я вас не так понял.
Некрасивый обвязочный код, сугубо из-за того что сам язык такой
Что было: в коде на Rust функции из C++ вызываются некрасиво;
Ваше обобщение: в коде на языке X функции из языка Y вызываются некрасиво => X -- плохой язык.
Проведите мысленный эксперимент, в котором вы из C/C++/любого другого языка вызываете код на Rust/Python/Java/любого другого языка, и поймите, почему такой подход неверен.
Ладно пойдём от обратного, примитивно, означает что всё прозрачно и понятно, "Если вам нужно сделать шаг вперёд, вы просто его делаете, вам не нужно делать кульбит", С++ как раз про это, он даёт решение многих действий , и эту же философию продолжают те кто разрабатывают на нём. Вот функция читает с файла, если не смогла прочитать, возвращает код ошибки, она не пытается сама завершить весь процесс и т.п.
Кстати единственно что не нравится это отсутствие типа данных для HEX чисел, приходится через указатель, но в RUST его тоже нет.
Вот сама идея функции: данные из вне могут быть, а могут и нет, выполняет некий код, и есть возможность вывода данных. Всё. Больше ни каких прикруток не нужных. Проще уже не сделать, а попытки псевдо-оптимизации путём прикручивания каких-то там состояний после выполнения функций, зачастую делают только хуже.
Вот про это были Си, и теперь же С++, упростить это нельзя.
И вот отсюда и простая реализация, указываешь есть ли данные на выходе, название функции и перечисление аргументов функции их тип и название. ВСЁ. Что вы туда запихнуть пытаетесь? Если ничего, так зачем порождать миллион языков, которые что то там пытаются?
примитивно, означает что всё прозрачно и понятно, "Если вам нужно сделать шаг вперёд, вы просто его делаете, вам не нужно делать кульбит", С++ как раз про это
Это, может быть, про Си, но не про C++:
decltype(auto) foo() {
int i = 1;
return (i); // UB
// return i; // Нет UB
}Обычно на все трудности С++ ответом является: программист должен это знать/никогда так не делайте и т.п., но эти аргументы верны для любого языка программирования вообще, так как представляют себе "бесконечно опытного" программиста и игнорируют необходимость обучения и то, что дизайн языка бывает более или менее качественным.
Вот функция читает с файла, если не смогла прочитать, возвращает код ошибки, она не пытается сама завершить весь процесс и т.п.
Ответственно заявляю, что в Rust именно такой подход и принят, и поддерживается он сильно в большей степени, чем в C++, так как в Rust изначально были Result и Option, синтаксический сахар, pattern matching и прочее, до чего C/C++ с errno и его аналогами и out-параметрами как до Луны, а std::optional и std::expected поддерживаются слабо.
Вот сама идея функции: данные из вне могут быть, а могут и нет, выполняет некий код, и есть возможность вывода данных. Всё. Больше ни каких прикруток не нужных. Проще уже не сделать, а попытки псевдо-оптимизации путём прикручивания каких-то там состояний после выполнения функций, зачастую делают только хуже.
?
<...> ВСЁ. Что вы туда запихнуть пытаетесь? Если ничего, так зачем порождать миллион языков, которые что то там пытаются?
Если вы довольны С++, то вас никто не убеждает перейти на что-то другое, но не вступайте тогда в дискуссии, если не интересуетесь темой обсуждения.
decltype(auto) foo() {
int i = 1;
return (i);
}
Вы где такое увидели вообще?
Вот и аргумент "никогда так не делайте". Этот фрагмент кода опровергает 'примитивно, означает что всё прозрачно и понятно, "Если вам нужно сделать шаг вперёд, вы просто его делаете, вам не нужно делать кульбит", С++ как раз про это'; также показывает неочевидность синтаксиса -- кто подумает, что (x) и x это семантически разные вещи.
Я сейчас про то, где вы нашли этот кусок кода, я просматривал куча проектов на плюсах, сам пишу, ни кто так, никогда не пишет, а встретить тип auto это чудо, а подобный конструкт тем более.
Все всегда пишут:
int foo() {
int i = 1;
return 1;
}
И так всегда и во всём, в том числе в коде от майкрософт.
Не нужно думать раз язык что-то позволяет, то ним так и пользуются
где вы нашли этот кусок кода
Он искуственный, да. https://habr.com/ru/articles/350186/comments/#comment_10693598
Не нужно думать раз язык что-то позволяет, то ним так и пользуются
Язык должен допускать как можно меньше возможностей сделать что-то не так -- это одно из свойств, определяющих, насколько язык хорошо или плохо спроектирован. Подумайте, почему на разных проектах используют линтеры, более строгие флаги компилятора, почему в Python завезли аннотации типов и инструменты для статического анализа, а вместо JavaScript во многом сейчас пишут на TypeScript.
Так, если в языке что-то возможно, это добавили по самым разным причинам, в первую очередь, гибкость.
Если вы или ещё кто-то, психически болен, и начинает усложнять конструкты, то кто вам виноват?
Попробуйте на языке ПРОГРАММИРОВАТЬ, а не искать извращённые способы чтобы потом кидаться ними в комментариях.
А единственный явный плюс у RUST, который выделяют это Borrow Checker . Ну то есть снова кто-то не осилил указатели и пошёл писать очередной сборщик мусора, но продвинутый.
То есть вся слава языка просто из-за этого.
Ну то есть были те, кто не смогли запомнить простое правило, написал new, напиши delete , или calloc/malloc, напиши free
А теперь самое интересное, внешние библиотеки ваша фича не проверяет, интересно сколько времени уйдёт у майкрософт на переписывание DirectX с плюсов на RUST ?
А единственный явный плюс у RUST, который выделяют это Borrow Checker . Ну то есть снова кто-то не осилил указатели и пошёл писать очередной сборщик мусора, но продвинутый.
У меня для Вас то ли страшная, то ли прекрасная новость: Borrow Checker - это не сборщик мусора ни в каком виде - ни продвинутый, ни отсталый. Его задача - не давать некорректно работать с памятью, а не мусор собирать.
А RAII в Расте есть, конечно, куда ж без него, только прямого отношения к Borrow Checker это не имеет. RAII и в C++ есть...
Ну надо ведь юмор уметь понимать.
Вдумайтесь в то что делает Borrow Checker и поймёт при чём тут "Сборщик мусора", мусор, в нашем случае это то, что не корректно работает с памятью, ну а сборщик, это тот алгоритм что собирает такой код, и показывает нам где это.
Получается сборщик мусора, и да, раз вы такой грамотный то RAII не сборщик мусора, это идея что бы в конструктор выделение памяти, а в деконструктор её освобождение
BC это как раз и есть вариация RAII
"Деконструктор"? Вы, наверное, имели в виду "деструктор"? Ну, конечно же, RAII - это не сборщик мусора, но автоматическое освобождение ресурса по выходу связанных с ним идентификаторов из области видимости - это самое близко к сборке мусора, что я смог найти, размышляя над Вашей шуткой.
То, что под сборкой мусора Вы подразумевали алгоритм, выявляющий ошибки программиста, простите, не догадался, и даже после Вашего объяснения эта Ваша мысль мне кажется слишком сильно натянутой на глобус совой...
Такое может получиться при разворачивании макроса (которые по известным причинам свои параметры в скобки оборачивают) который из-за ifdef'ов и/или других макрсов, которые он использует, в одном случае раскрывается в какое-то выражение, а в другом просто в (arg).
На Java, C# и Python обёртки вокруг C-шных функций из Win32 API будут выглядеть примерно так же. Красота самих обёрточных функций никого не волнует. Зато Rust-функция принимает на один параметр меньше.
На один параметр меньше, потому что функциональность неодинаковая.
Она и на С++ будет выглядеть примерно как на расте, если написать эквивалентной по функционалу.
std::expected<std::size_t, DWORD> write_memory(
HANDLE handle,
std::uintptr_t address,
std::span<const std::uint8_t> data
) {
SIZE_T bytes_written = 0;
BOOL success = WriteProcessMemory(
handle,
reinterpret_cast<LPVOID>(address),
data.data(),
data.size(),
&bytes_written
);
if (!success) {
return std::unexpected(GetLastError());
}
return bytes_written;
}Мне война языков по барабану, но пример явно неудачный. При взгляде на код на Расте я сразу вижу, что если записать в память не удалось, наверх побежала ошибка, которую нельзя не обработать. А в коде на С++ я вообще не вижу, что происходит в случае ошибки. Гугл говорит, что WriteProcessMemory возвращает BOOL, ну и т.д.
У WinAPI своя логика, подробная ошибка, вот что бы прям очень подробно, получаем через GetLastError, а так возвращение BOOL указывает на то успешно завершилась функция или нет.
Если не успешно, то дёргаем GetLastError , но это вообще не С++, а библиотека на нём
Код на С++ не проверяет возвращаемое значение. Откуда станет известно, что произошла ошибка?
Так вот код раста это проверяет, там не просто так Result и знак вопроса после вызова, а ваш код нет. Вы напишите на С++ полноценную обертку, с обработкой ошибок, с гарантией осмысленного результата, с гарантией что в api не передаётся nullptr или буфер размера меньше чем size, что буфер точно не деаллоцирован в момент передачи и точно инициализирован и так, чтобы на уровне компилятора все это проверялась, как это происходит в вашем же примере на раст. А потом уже сравним код.
Открываете аргументы принимаемые WriteProcessMemory и смотрим:
1. дескриптор процесса, его валидность проверяется при получении.
2. Дальше адрес куда записывать
3. Дальше адрес откуда читать
4. Кол-во данных которые необходимо записать
5. Опциональные параметры, сколько удалось записать.
Все эти пункты, тот кто использует API должен сам проверять, мы чётко обозначаем какие данные нам нужны на вход и предупреждаем, что защиты от дурака нет, если она им нужна пусть сделают сами.
Если вы уж так хотите защиту от дурака, просто добавляет пару if, и возвращение bool переменной или HRESULT, кому как нравится.
Если функция выполнилась успешно, возвращает true .
А если вдруг false, то достаточно вызвать GetLastError , если ошибка в переданных данных в функцию. А если ошибка была обнаружена нашей защитой, то логирование. Либо уже вместо bool , возвращает некое значение.
Самое интересное в том, что валидность дескриптора проверяется сугубо при его получении, сама функция возвращает HRESULT, по которому и смотрят.
Дальше, как вы собрались проверять адрес куда записывать? nullptr по сути является 0. И в теории при спец. задачах, по нему можно записать и считать. А ваша защита от дурака не даст, ведь она посчитает что это nullptr.
С адресом откуда читать, ситуация та же.
Ну разве что кол-во данных.. будет странно что оно равно нулю, хотя мало ли какая там логика у программы, извращения бывают самые разные.
И здесь внимание:
НАША API НЕ ДОЛЖНА СВЯЗЫВАТЬ РУКИ. Не нужно из-за своего скудомия делать по вашему мнению "Обработчик не верных данных". Потому что они могут быть вполне верными, просто очень специфичными.
Надеюсь читающий данный текст обладает интеллектом и не поставит минус
Все эти пункты, тот кто использует API должен сам проверять
Тогда зачем нужна ваша "обертка" если она ничего не делает?
Если вы уж так хотите защиту от дурака, просто добавляет пару if
Ну так добавьте и потом уже сравнивайте эквивалентный код
А если вдруг false, то достаточно вызвать GetLastError
Для этого люди и пишут обертки, чтобы скрыть от клиентского кода детали реализации.
Дальше, как вы собрались проверять адрес куда записывать
Лично я бы делал семантические типы вроде Process и ProcessMemory, которые оборачивали бы хэндлеры и поинтеры вместо порнографии с HANDLE hProcess, LPVOID address Потому что HANDLE может быть от чего угодно, а нам нужен именно от процесса.
И в теории при спец. задачах, по нему можно записать и считать
Нет таких задач, вы не можете в вин-апи писать в память процесса по nullptr и из nullptr.
Ну разве что кол-во данных.. будет странно что оно равно нулю
А что начет количества данных меньше передаваемого размера? Авось не случится, да?
НАША API НЕ ДОЛЖНА СВЯЗЫВАТЬ РУКИ
Ваше апи должна делать хоть что-то полезное, например вносить сематинтику и позволять избегать ошибок при использовании, иначе это не апи и враппер, а в лучше случае превдоним для вин-апи функции, а в худшем просто мусор.
Не нужно из-за своего скудомия
Скудоумие это не понимание разницы между оберткой и алиасом, не понимание что такое защищеное программирование, не понимание как выразить семантику программы в коде, не понимание какие проблемы могут быть при работе с прямыми указателями и как их избегать и не понимание чем С отличается от С++(ваш код это чистый С).
Но особенным скудоумием является невозможность осознать свою ошибку даже после того как несколько человек вам все разжевали с примерами кода и подробными объяснениями.
Код на расте это безопасная обертка над апи, к примеру гарантирующая согласованость передаваемые данных и размера. Ваш код на С(а это не С++) это просто прокси, который прокидывает все как есть, оставляя все проблемы вышестоящему коду, включая обработку ошибок.
про Rust не могу ничего сказать, ни разу не приходилось сталкиваться с каким-то кодом на Rust. Обрывки видел какие-то 5-10 строк, но ни разу не видел какого-то законченного алгоритма или решения задачи предметной области. Может это мне так не повезло, а может это отражение какой-то глобальной статистики на меня, я не знаю! В любом случае мое мнение совершенно субъективно, прошу воспринимать его именно так.
Кстати автор высказывания про демократию тоже наверно был не особо в курсе каких-то особенностей социализма, например, это же не значит что ему надо было запретить так высказываться.
Вывод: если вы не имеете оснований считать, что исследовали тему, не высказывайте своё мнение так резко. /thread
а в чем резкость-то? В том что я маленько историю знаю и могу проводить аналогии?
Если взялись советовать - советуйте до конца, иначе какой смысл в ваших советах?
аналогии
Систему новую и более лучшую, чем демократия, не нашли. Новых и более лучших (в зависимости от обстоятельств) языков программирования со времён C++ придумано достаточно -- аналогия некорректна. Применение широко известных цитат к какому-либо другому явлению (как сделали вы) заявляет об уверенности автора в его суждении.
Сказав "С++ это язык именно программирования, именно для программистов, а не для физиков, лириков. Программисты должны понимать откуда взялся, что значит, и почему он именно static, например" (выделение моё) вы проводите соответствие: пишет на C++ -- программист, не на C++ -- не программист. При том, что второе может быть верно, и языки "для физиков" тоже есть, вы утверждаете, что нет языков программирования, при работе с которыми "программисты должны понимать откуда взялся, что значит, и почему он именно static, например", и забываете тех, кто знает теорию, но может на C++ и не писать.
резко
Забыл слово "уверенно". Подбирал по значению "не оставляя простора для возражений, градаций; деля на 'настоящих' и 'ненастоящих'".
Но я действительно уверен в том что написал! Я также уверен что есть что-то в чем уверены вы и с чем я не смогу согласиться, но я бы не стал вам советовать в чем вы должны быть уверены в любом случае! Это и есть основа холиваров - священных войн, насколько я знаю.
Если вам интересно по поводу С++ , то у меня есть статистика по C#, например, что C# намного проще чем С++ (я знаю людей которые отказались писать на С++, при этом были счастливы писать на C#). И с точки зрения того что он проще он значит и лучше чем С++, но только относительно простоты(которая кстати тоже сложное понятие). Тем не менее интегрально, так сказать, С++ все таки будет лучше, мне кажется.
Раст -ассоциируется с чем то или с кем то )
С ностальгией можно вспомнить С++ образца 199х годов, когда он отличался от С только возможностью создавать классы и некоторыми вольностями в синтаксисе (на что С компилятор ругался).
Он был удобным. А не вот этот вот зоопарк.
Зато это обьясняет, почему некоторые с пеной доказывают, что "Ардуино это не С++!!!!" - хотя там как раз тот самый С с классами.
Ничего не мешает и сейчас писать на "си с классами".
В “С с классами” сейчас нет большого смысла, а вот сделать С++ с нормальным синтаксисом может быть и выстрелит.
Carbon уже есть. Не взлетел.
Скрытый текст

Также нету в топ-50 в https://innovationgraph.github.com/global-metrics/programming-languages.
Я же написал с нормальным синтаксисом :-) И у него в любом случае не было шансов заменить С++.
Очевидно что не взлетел, он даже не на стадии MVP
Что такое нормальный синтаксис? Примеры?
понятный и естественный для пользователей исходного языка.
Мне понятен и естественен синтаксис плюсов. Вы, конечно, можете привести пример типа []()}[{())(*) (условно, просто для примера), который я не пойму, но это будет из разряда Buffalo buffalo Buffalo buffalo buffalo buffalo Buffalo buffalo.
Что я хочу сказать: из того, что можно придумать дьявольскую конструкцию не следует, что нужно переделывать синтаксис.
Синтаксис C++ вполне понятный и естественный, но крайне сложный. А вот например синтаксис Python легкий, естественный но не всегда понятно, что происходит внутри
В смысле "никто не мешает?" Открываешь чужой проект, к которому нужно пристыковаться, а там в коде больше двоеточий, чем было дырок на перфокарте.
Он был ничерта не удобным. Вместо шаблонов - гигантские макросы или вообще внешняя кодогенерация. Или копипасты.
Он был удобным. А не вот этот вот зоопарк.
Никто не заставляет вас этот зоопарк использовать, хотите как в старые добрые — "-std=c++98, и танки наши быстры".
Немного про Rust, там легко выразить требования к типу. Нужно, что тип можно было сравнивать? Напиши так:
pub fn sort<T: Ord>(slice: &mut [T]) {
todo!();
}
static_cast - Преобразование указателей/ссылок в иерархии наследования вверх (к базовому классу) и вниз (к производному), но без проверок во время выполнения (вы должны быть уверены в типе сами). Вызов явных конструкторов или операторов преобразования.
dynamic_cast - Безопасное преобразование во время выполнения. Единственный каст, который работает во время выполнения программы. Если преобразовать указатель не получится - вернет nullptr. Если преобразовать ссылку не получится - выбросит исключение std::bad_cast. Работает только с полиморфными классами (у которых есть хотя бы одна virtual функция, обычно деструктор).
const_cast - Единственный каст, который умеет убирать (или добавлять) модификаторы const и volatile. Например, когда вы вызываете старую C-функцию, которая принимает char* (неконстантный), но вы уверены, что эта функция не будет менять данные, а у вас есть только const char*.
reinterpret_cast - Переинтерпретировать (прочитать заново) биты одного типа как биты другого типа. Компилятор просто берет адрес и считает его адресом другого типа, не генерируя никакого машинного кода для преобразования.
bit_cast - Появился в C++20 как альтернатива опасному reinterpret_cast. Он не переинтерпретирует указатели, а создает НОВЫЙ объект, полностью копируя биты старого в новый.
Зачем вы, не желая даже 5 минут погуглить, лезет со своим Rust? При чём тут он? Здесь другой язык, с более чем понятной и удобной логикой
Это было к:
Компилятор физически не мог сказать «вы нарушили требование X», потому что никакого X нигде не записано и мог только дотащить вас за шиворот на строку 4212 в недрах и показать, что там для вашего типа не определён operator<, а чтобы вы поверили, то приходится выложить весь стек инстанцирования по дороге.
Эти девелоперы на ×-расты еще и очень нервные, кстати. Видать, их надмозговый язык отнимает все силы - чуть что - сразу минусуют.
Тест простой - "Ты что, девелопернараст"?!
с более чем понятной и удобной логикой
5 кастов, и 1 из них депрекейтед. Очень удобно и понятно, хороший язык
pub fn sort<T: Ord>(slice: &mut [T]) {
Хе-хе )
Есть (до сих пор) такой замечательный язык perl, который замечателен в том числе тем что можно написать так:
foreach my $var (@arr){
$var = $var + 2;
}Даже если вы не знаете язык - можно догадаться что оно делает.
А можно так:
for(@arr){$_+=2;}
$_ += 2 for @arr;
@arr = map { $_ + 2 } @arr;И вроде как другие варианты "писать быстрее и занимает меньше места", но когда у вас есть простыня подобных закорюк - разобраться в них смогут не только лишь все.
А это значит, что рандомный "новый программист" плюнет, и возьмет что ему попроще и попонятнее с детства, например, python.
И вот теперь мы видим вот то, процитированное, в Rust - тоже "коротко и понятно" /s
Как и приведенная конструкция в C++ для получения рандомного числа от 1 до 100.
Такое отпугивает...
Кто мешает то же самое на плюсах?
template<std::comparable T, size_t N>
void sort(std::span<T,N> slice) {
.....
}
void sort(auto& slice)
requires requires {
// нам нужно, чтобы над разыменованными итераторами был оператор <
*std::begin(slice) < *std::begin(slice);
}
{
.....
}я думаю все довольно точно сказано) однозначно в точку
Замечательный язык, хочешь, пиши как на расте, хочешь, как на перле, хочешь, пиши как на си.
Понять бы, как писать на c++
Касты сами по себе зло, поэтому их сделали уродливыми специально.
C++ это неподражаемая смесь прагматизма и астронавтики. Причём, бо́льшая часть прагматизма унаследована от Си, а астронавтику привнесли с крестиками. Я вообще не представляю, как без сишной кодебазы и фанбазы такой язык смог бы стать популярным.
Процитированное это яркий пример астронавтики. «Касты зло». А то мы сами не знаем! Покажите мне программиста, который бы не смог спроектировать приложение без кастов. Однако, C++ в реальности не летает в вакууме космоса, он держится на таких вещах, как, например, WinAPI. Вызовы WinAPI с их WPARAM и LPARAM достаточно уродливы сами по себе. Имея необходимость с ними работать, я УЖЕ достаточно наказан жизнью. Но нет! Давайте сделаем их ещё уродливее! Зачем? Чтобы меня стошнило? Или они хотят, чтобы это воспитательное уродство удержало меня от вызовов WinAPI? Но тогда, простите, зачем мне C++? Тогда я бы писал на Шарпе. То же самое касается Direct3D, например. (Вернее, касалось, когда я последний раз на нём писал, а было это давно — не знаю, как там сейчас). И вообще COM. Короче, касты это суровая необходимость кода общаться с реальным внешним миром, а его всеми силами изолируют.
Смешнее этого только заявление Степанова, что он проектировал свою библиотеку так, чтобы поощрять программиста писать алгоритмы. Это как если бы вы шли в магазин за набором инструментов, а дома обнаруживали, что он состоит из заготовок отвёрток разной степени заточенности и шлифовального камня. Ну и что, что тебе надо было собрать мебель или разобрать телефон, и ты не можешь просто взять готовый инструмент для достаточно типовой задачи. Зато тебе будет удобно выточить себе любую отвёрточку!
Я всё жду, когда кто-нибудь добрый обернёт стандартную библиотеку во что-нибудь удобное. Чтобы вместе того же mt3999… я даже название его не помню, хотя пишу его регулярно… ну, как пишу — копипащу из ДипСика… писать random.GetRandomInteger(0, 100);. И плевать мне на несовместимость. Хорошее настроение важнее.
Насчет прагматизма... )
Я не настоящий программист - я научный работник.
Мне нужна была программка чтобы что-то там сделать со строками в большом файле, я попросил DeepSeek сгенерить и чтоб именно на Си, не используя С++, потому что я понимал , что в ++овском тексте я не разберусь. И... программка заработала сразу как надо, без всяких правок! Возможно потому что Си как раз более примитивный/низкоуровневый...
И… программка заработала сразу как надо
Не хочу вас расстраивать, но есть шанс, что так только кажется. Строки это объективно тёмная, запутанная материя (владение ими; правильный перебор; (не)фиксированность ширины; глифы, символы, кодепоинты и байты строки — в чём разница?; нули в качестве маркеров и хранение размеров; (не)обходимость +1 под буфер для каждой внешней функции; глубокое понимание Юникода; переполнения всех мастей и т.д. и т.п.), и написать на Си что-то стопроцентно правильно работающее со строками требует высочайших навыков. По крайней мере, я уже много лет гоняю строки на C/C++, но не рискнул бы поставить жизнь или безопасность на то, что у меня это получилось.
Я всё жду, когда кто-нибудь добрый обернёт стандартную библиотеку во что-нибудь удобное. Чтобы вместе того же
mt3999… я даже название его не помню, хотя пишу его регулярно… ну, как пишу — копипащу из ДипСика…
а что мешает самому обернуть под свои задачи за строк 10? Это же абсолютно элементарно делается. И потому и не добавлено в стандарт, потому что у всех разное получится под своё
Зато я знаю, какой язык самый лучший :)
История рассудила.
Из всего паскалевского семейства наиболее популярным стал Go.
Даже если родство сходу неочевидно =)
Pascal, Go, Ada все в топ 20 Тиоба с переменным успехом. Сам пишу на Делфе и буквально квадратными глазами смотрю на подобные статьи. Нет, и Паскали далеко неидеальные языки. Но если сравнить с, то буквально верх совершенства. Увы и очень жаль что именно они не выстрелили как Плюсы в свое время. Ну что же, будем далее поедать кактусы. Лично у меня к Плюсам только одна претензия: скорость компиляции. Два часа на проект? Нет, это точно не мое. Был небольшой практический опыт общения. И это еще надо сказать 'спасибо' реальной реализации компилятора. i7 все несколько часов был занят почти под завязку по CPU и 32 гига по памяти :)
К слову, с совместимостью вверх на Делфе всё прекрасно. Личный опыт: перенос двухмиллионострочного проекта на 7 мажорных версий комплятора вверх с буквально пятью правками.
Это сейчас компиляция тяжелая.
В 90-х Turbo Pascal 5.5 компилил мгновенно, в отличии от Turbo C++ 2.0, который тормозил нещадно.
А уж Watcom C был вообще ужасом.
Хоть что-то в жизни сделал не зря - убежал с плюсов когда они стали слишком большими (в 2011м стандарте, точнее раньше, когда его обещали под именем 0x)
Ваще я думал JS будет худшим языком)
Судя по тому как МНОГО хорошего и очень сложного и вообще разного крутого софта написано на "этом плохом языке" - статья 100% про то, как кому-то что-то мешает танцевать.
Неплохо конечно автор развёл всех на холивар :DDD
автор писал про свою боль, чужой мне не надо :)
да там тоже жизнь особо мёдом не была, как вспомню проекты начала нулевых, аж зубы болеть начинают. Немного лучше стало с приходом 11 стандарта, боле менее устаканилось в 17, или я просто стал спокойнее и перестал сильно агриться на новые фичи
Особенностью моей работы является то, что пока в одном проекте можно использовать фичи свежего стандарта, рядом обязательно будет проект, отстающий на один два стандарта. Например, сегодня работаешь на C++20, завтра вынужден откатываться до C++17 или C++14 (вот C++11 давненько не было, к счастью). И каждый раз сразу видно скольких хороших вещей тебя лишили. Это ощущалось всегда: и при переходе с C++20 на C++17, и с C++17 на C++14… Особенно при переходе с C++14 на C++98/03. Тут вообще как будто на другой язык переучиваться приходилось.
Тем удивительнее в 2026-ом слышать панегирики C++98/03. Даже не смотря на то, что дичи в современные стандарты добавляют изрядно.
В целом с текстом согласен, но вот это:
Rust по дизайну на порядок лучше. Стандартный компилятор, стандартная сборка, стандартный пакетный менеджер, никаких заголовочных файлов, лучшие что я видел сообщения об ошибках, нормальные значения по умолчанию, отсутствие неявных преобразований, нормальный UTF-8, sum-типы, и безопасность памяти на этапе компиляции.
верно только потому, что расту не нужна совместимость с С и он ещё молодой. Соответственно, у него нет груза обратной совместимости и необходимости добавлять новые фичи, чтобы использовать возросшие на порядки возможности машин разработчиков.
Большое спасибо за статью! Теперь, если меня спросят, почему я не пишу на C++ - мне будет куда ссылаться.
Мне кажется что сильные команды приходят к выделению собственных стандартов. Хочешь написать как у Саттера в книжке? У нас в проекте нельзя так писать, пиши проще, вот тебе список допустимых конструкций.
Ну, не так уж и много недостатков для такого большого языка.
Хрестоматийный пример сложной простоты это случайное число, и где нибудь в джаве или пайтоне вы просто напишете random.randint(1, 100) и пойдёте дальше кодить, но не здесь. Это слишком просто, чтобы быть правдой.
Я попытался это исправить типо-безопасной библиотекой.
А сам random_device стандарт разрешает делать детерминированным и на старом MinGW он годами выдавал одну и ту же последовательность при каждом запуске, то есть длинная корректная мантра в реальности и длинная и коррекная, но не везде работает, хоть rand() % 100 и короче и хотя бы работает.
Упоминая Rust, почему-то не вспоминают, как там решена задача обеспечения обратной совместимости. А ведь в контексте этой статьи это как раз самое интересное!
Там используются так называемые "редакции": это не имеющие полной совместимости сверху вниз версии языка. Просто для программы указывается, на какой редакции языка она написана. Для отдельных компонентов можно указать другую редакцию. Поэтому устаревшие конструкции из языка выпиливаются или модернизируются, не ломая совместимость с махровым легаси.
Если программист пишет на современном Расте (Rust 2024), ему в подавляющем большинстве случаев не нужно знать, что там было в Rust 2015.
В целом, вполне себе возможная стратегия и для будущих вариантов C++, в общем-то, на мой взгляд...
На практике C++ ровно так и используют. Трудовой коллектив договаривается до версии языка и кодстайла. Например, что пишут на C++20 и запрещают использовать то-нибудь из него (что-то слишком заумное) и заодно весь голимый си. На кодревью палкой по морде дают за странный код, и всего делов, все пишут на "нормальном" подмножестве. Про большинство ужасов из этой статьи разработчики и не слышали никогда и не услышат - всё это им не нужно и никак не мешает жить, этого в их жизни нет. Если это вырезать из языка и закрыть под флагом компиляции, никто и не заметит, что из языка пропала половина. Именно таким способом достигается тот факт, что перегруженный монструозный C++ никакой гирей для разработчиков не является, изучать всё это не обязывает и про это даже никто не слышал. Вы спросите - "как это не обязывает, вот достанется тебе старый проект...". Не достанется, на старый проект я не пойду просто. Точно такой же свободный выбор, как "не ходить в C++, а использовать Go/Rust". Страдают писатели компиляторов, но их боль никто не слышит, потому что их физически мало на планете.
Ну, по-моему, это всё же несколько другое.
Во-первых, "Бить по морде на кодревью" это не то же самое что "автоматически отклонять компилятором". Если нет автоматического отклонения, можно что-то пропустить. Ну, для этого, конечно, есть всякие линтеры и прочее, которые обеспечивают выполнение требований по стилю кода, но это менее эффективно, чем прямое отклонение компилятором.
Во-вторых, всем приходится изобретать свои "велосипеды", придумывая, какое подмножество языка разрешать, а какое запрещать. Это порождает фрагментацию экосистемы - и в смысле кода, и в смысле навыков сотрудников (устраивающийся на работу должен будет знать все варианты, которые могут прийти в голову собеседующему, например). Это же касается и использования внутренних библиотек, компенсирующих какие-то недостатки стандарта - про это в статье упомянуто, см. "// антипаттерн: «мне надоело писать static_cast»"; локальные руководства по стилю тоже порождают подобное, в общем-то, хоть и в меньшей мере.
В-третьих, эти локальные требования по стилю кода не решают вопрос неудачных названий и конструкций.
Так что опыт Rust с его редакциями, на мой взгляд, для C++ мог бы быть полезен.
Традиционная ссылка на самый подробный разбор недостатков C++ (правда, на английском): https://yosefk.com/c++fqa/
К множественных примеров инициализации ещё можно добавить варианты с дедукцией и без дедукции типов.
std::vector a{a.begin(), a.end()}; // получится vector<decltype(asd)::iterator>
std::vector<int> b{a.begin(), a.end()}; // попытается скопировать интыplacement new тоже в списке не вижу
std::vector<std::byte> buffer(1024);
MyClass* ptr = new (buffer.data()) MyClass();
ptr->~MyClass(); // звать деструктор надо ручкамиВ качестве отдельной категории можно ещё вспомнить thread local инициализацию.
еще есть скрытый
rvalue_cast, но о нем чуть ниже.
core guidlelines добавляют ещё narrow_cast для сужения более длинных типов к более коротким: narrow_cast<int32_t>(int64_t{}). Всё для демонстрации понимания намерения.
Забыли написать про главное отличие С++ от остальных вариантов. Он стандартизированный. А это часто является одним из обязательных требований к инструменту.
И это то. Чего нет у большинства альтернатив и убийц С++
Спасибо за статью, прочел на одном дыхании, поностальгировал так что словил симптомы аллергии, чихал с пару минут. Надо было ещё пройтись по UB для знаковой и беззнаковой арифметики, а то Ардуинщики очень любят millis() через переполнения..
Это к Свиридкину и @Andrey2008 лучше я не силен в UB, в смысле написать могу, но с починкой хуже
Да, добро пожаловать :) Путеводитель C++ программиста по неопределённому поведению: часть 2 из 11
Итого:
C++ худший язык программирования всех времён. Да, вот перечень его недостатков: 1, 2, 3, .....
C++ лучший язык программирования всех времён. Да, вот перечень его достоинств....
summon @antoshkka
Куда только смотрит комитет по стандартизации C++!!! <sarcasm. no offence :) >
Так что да, язык ужасный, поэтому открывайте уже свою ужасную IDE, и продолжайте писать на худшем языке программирования.
Во дни сомнений, во дни тягостных раздумий о судьбах моей родины, — ты один мне поддержка и опора, о великий, могучий, правдивый и свободный… (И.Тургенев)
А как правильно подходить к изучению c++ новичку с таким количеством нюансов?
как правильно отметили хабровчане выше, 95% этих нюансов вам понадобится очень не скоро. Поэтому, просто пиши код
Рекомендую поиграться с контроллером Arduino, для него используется упрощенная версия C++
К контроллеру можно подключать разные датчики и исполнительные устройства, то есть можно написать программу и сразу видно как она работает - например, измеряет температуру и выводит на дисплей.
Можно и без "железа", на эмуляторе всё делать - но это не так интересно :)
Все эти нюансы - рекламная компания Rust или чего-то ещё. В реальной жизни реальные коммерческие C++ разрабы в каком-нибудь Yandex про это всё никогда не слышали и никогда не услышат, это нигде и никогде не встретится в реальной работе в нормальном проекте.
Не знаю как там яндекс, а мost vexing parse случился на прошлой неделе. В прошлом месяце были проблемы с перфом от std::mt19937, который вдруг стал показывать 2% в топе функций на фрейме после минорного обновления компилятора, а сегодня я пробую понять откуда взялись столлы от недавно закомиченных статик переменных в функциях. Так что про реальную работу тут не угадаешь, когда это понадобится.
В яндексе может и не слышали, а в бигтех компаниях не просто так создаются свои новые языки.
Подумал про раст, лол
Обычно "многословный" это речь про Java, но никак не плюсы
в конце должна была быть цитата Создателя:
Есть только два типа языков программирования: те, на которые все жалуются, и те, на которых никто не пишет

Худший язык программирования всех времён /s