Грокаем C++
Open in Telegram
Два сеньора C++ - Владимир и Денис - отныне ваши гиды в этом дремучем мире плюсов. По всем вопросам (+ реклама) @ninjatelegramm Менеджер: @Spiral_Yuri Реклама: https://telega.in/c/grokaemcpp Мы на TGstat: https://tgstat.ru/channel/@grokaemcpp/stat
Show more9 384
Subscribers
+924 hours
+147 days
+1430 days
Posts Archive
9 385
std::apply
#опытным
Метапрогеры очень любят работать с компайл-тайм структурами, типа std::array, std::pair и std::tuple. Когда работают с такими структурами, то интересны прежде всего элементы этих структур. И очень хочется иногда как-то единообразно передавать их распакованные элементы куда-то в другую функцию.
Именно этим и занимается std::apply, которая появилась в С++17. По факту, эта такое дженерик решение для того, чтобы вызвать какую-то функцию с аргументами из элементов tuple-like объектов.
Простейшее, что можно с ней делать - вывести на экран все элементы тапла.
const std::tuple<int, char> tuple = std::make_tuple(5, 'a');
std::apply([](const auto&... elem)
{
((std::cout << elem << ' '), ..., (std::cout << std::endl));
}, tuple);
Здесь мы применяем fold-expression и оператор-запятая. Можете освежить знания в этом посте.
Можно придумать чуть более сложную задачу. Надо написать функцию, которая принимает неограниченное число параметров, в том числе и tuple-like объекты. Все параметры надо распаковать в строку, а tuple-like объекты выделить с помощью фигурных скобок. Объекты естественно могут быть вложенные.
Может получится что-то такое:
template <typename T, typename = void>
struct is_tuple_like : std::false_type {};
template <typename T>
struct is_tuple_like<T, std::void_t<decltype(std::tuple_size<T>::value), decltype(std::get<0>(std::declval<T>()))>> : std::true_type {};
template <typename T>
constexpr bool is_tuple_like_v = is_tuple_like<T>::value;
template<typename Tval, typename ... T>
void serialize_tuple_like(std::stringstream &outbuf, const Tval& arg, const T& ... rest) noexcept {
if constexpr (is_tuple_like_v<Tval>){
outbuf << "{ ";
std::apply([&outbuf](auto const&... packed_values) {
serialize_tuple_like(outbuf, packed_values ...);
}, arg);
outbuf << " }";
}
else{
outbuf << arg;
}
if constexpr(sizeof...(rest) > 0){
outbuf << ' ';
serialize_tuple_like(outbuf, rest ...);
}
}
template<typename ... T>
std::string args_to_string(const T& ... args) noexcept {
std::stringstream outbuf{};
if constexpr(sizeof...(args) > 0){
serialize_tuple_like(outbuf, args ...);
}
return outbuf.str();
}
int main(){
std::cout << args_to_string("test", 1,
std::tuple{"tuple1", 2, 3.0,
std::tuple{"tuple2", "boom"}},
std::pair{"pair", 4},
std::array{5, 6, 7, 8, 9});
}
Вывод будет такой:
test 1 { tuple1 2 3 { tuple2 boom } } { pair 4 } { 5 6 7 8 9 }
Даже не знаю, как эту лапшу разбирать. Идея такая что is_tuple_like_v проверяет аргумент на соответствие tuple-like объекту. Если нам на очередном вызове serialize_tuple_like попался такой объект, то мы берем и распаковываем его параметры в рекурсивный вызов serialize_tuple_like. Если у нас не tuple-like объект, то просто выводим его в стрим. Наверное, нужны проверки на то, что объект можно вывести в стрим, но решил, что это немного борщ для этого кода.
У функции довольно специфичные кейсы использования. Впрочем, как и у всей метапроги.
Don't live in metaverse. Stay cool.
#template #cpp179 385
👍Узнайте, как создавать приложения на Си с GUI грамотно!
На бесплатном уроке онлайн-курса «Программист С» — «Создаем приложение на С с графическим интерфейсом пользователя»: регистрация
Приложения на Си с GUI сочетают в себе высокий уровень контроля над ресурсами системы с богатым пользовательским интерфейсом, что обеспечивает высокую производительность, гибкость, кроссплатформенность, эффективность взаимодействия с системным окружением.
На бесплатном вебинаре рассмотрим:
- Основные технологии создания приложения с GUI
- Обзор библиотеки GTK+ для создания GUI на языке С
- Разработаем простое приложения для работы с базой данных на GTK+
🔥После вебинара вы сможете продолжить обучение на курсе по спеццене, в том числе, в рассрочку.
Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576
9 385
Почему приватные методы находятся в описании класса?
#опытным
Публичные, защищенные методы - понятно, они нужны либо для пользователей класса, либо для наследников. Поля - понятно, они влияют на размер объекта, а клиенты должны уметь правильно аллоцировать нужное количество памяти под объект. Ну а приватные-то методы зачем раскрывать?
Приватные методы - это вообще-то детали реализации. Если я в своем фреймворке по-приколу назову какой-нибудь приватный метод KillTheNigga, то другим людям уже нельзя будет пользоваться этим фреймворком. Закенселлят еще меня. Хотя какая блин разница, какие у меня там приватные методы? Они типа и названы private, чтобы показать, что они МОИ ЛИЧНЫЕ(никому не отдам). А их оказывается нужно еще и показывать всем. Что-то не сходится.
Ну во-первых. Представим, что этого ограничения нет. Тогда все приватные методы объявлялись и определялись бы в файле реализации, который никто не видит. Но если я могу в файле реализации определить приватный метод, то кто угодно может это сделать. Это будет давать рандомным людям прямой доступ к закрытым полям класса. Если мы завершили определение класса, то у нас нет способов как-то пометить именно наши файлы, как "благословленные владельцем". Есть всего лишь юниты трансляции и они равнозначны. Получается, что единственный способ сказать, что вот этот набор методов официально одобрен создателем - это объявить его в описании класса.
На самом деле, в С++ мы имеем прямой доступ к памяти, в значит, мы легко можем поменять байтики для приватных полей и все. Или даже создать тип, с таким же описанием, только с дополнительным методом. Кастануть недоступный тип к своему и вуаля, вы можете как хотите вертеть объектом во всех удобных вам позах. Но это уже хаки, мы такого не одобряем. Не используя манипуляции с памятью, мы не сможем добавлять рандомную функциональность в рандомный класс.
А во-вторых, оказывается в С++ приватные методы участвуют в разрешении перегрузок(внезапно). В целом, так оно и должно быть. Никто не мешает вам определить публичный метод и перегрузить его приватным методом. Проблема(или фича) в том, что этап разрешения перегрузок стоит перед проверкой модификатора доступа. То есть даже если метод приватный и его никто не должен увидеть, он все равно участвует в разрешении перегрузок наряду со всеми остальными методами. Поэтому каждый клиент должен видеть полный набор приватных методов. Об этом мы уже говорили в контексте pimpl идиомы.
В чем прикол такого дизайн решения?
Давайте представим, что такого правила нет. И вот у нас есть две перегрузки, одна приватная для double, другая публичная для int. И перегрузка с double всегда отбрасывалась бы только лишь по причине того, что она приватная. Тогда мы легко можем вызвать публичную функцию с дробным числом 1.5 и нам ничего не будет. Оно просто неявно приведется к int и все на этом.
А теперь посмотрим, что будет, если мы поменяем модификатор приватной перегрузки на публичный? Ничего не упадет, НО! наш вызов метода с аргументом 1.5 теперь пойдет в другую функцию! То есть изменится поведение объекта. Комитет хотел избежать таких ситуаций, поэтому ввели такое вот ограничение. Наверное, причина не одна. Но это весомый аргумент.
Однако, такой протокол поведения влечет за собой различные сайд-эффекты. Я могу удалить(=delete) приватную перегрузку публичного метода, например какую мы обсуждали выше. И вызвать публичный метод опять с дробным числом. Но компилятор на меня наругается, потому что я попытался вызвать удаленную перегрузку метода. Хотя она вообще-то объявлена, как приватная! А то, что я ее удалил - это детали реализации. Получается раскрытие деталей реализации.
Stay aware. Stay cool.
#design #howitworks #cppcore #hardcore
9 385
Все надоело
Айтишечка - очень напряженная сфера труда. Вам, чтобы развиваться и достигать новых высот, нужно не просто идти в ногу со временем, а буквально бежать. Постоянно новые фичи, новые стандарты, новые модномолодежные фреймворки. Все это надо изучать. На работе еще нужно перформить, аки папа Карло, чтобы хорошие оценки были на перфоманс ревью.
Не удивительно, что все рано или поздно выгорают. Если у фрезеровщиков профессиональная болезнь - отсутствие пальцев, то у программистов - выгорание.
Пропадает улыбка, интерес к работе, да и в целом к жизни. Все больше хочется позалипать в смартфончике на ютубчик или котиков.
Многие думают, что дело в работе. Берут долгий отпуск, меняют компанию. Но потом все равно возвращаются в ту же точку.
Изнутри системы очень сложно справиться с проблемой. Нужен опытный человек снаружи, который поможет ее решить.
Психолог взрослого человека - канал для айтишников, у которых периодически опускаются руки и отключается мозг.
▪️ Как научиться отвлекаться от работы и отдыхать?
▪️ Как совместить кучу рабочих задач и время с семьей?
▪️ Как справиться с прокрастинацией?
▪️ Как не растерять запал, даже если начальник и коллеги 💩 и кажется, что ничего не выходит?
На все эти вопросы вам поможет найти ответы Вадим Петров, автор канала. Он постоянно проводит живые сессии и по полочкам разбирает насущные специфические проблемы айтишников.
На одном коде жизнь не заканчивается. Подписывайтесь на канал @vadimpetrov_psy и работайте без упахивания, выгорания и ущерба для личной жизни!
👨🏻💻 Псс. Обязательно загляните в закреп канала - там вас ждут много полезных плюшек, и даже бесплатный мини-курс по выходу из апатии.
Рекомендую подписаться: https://t.me/+LX08JcPGX8NiMWQy
9 385
const this
Все мы знаем, что this - указатель на объект, на котором сейчас находится контекст исполнения. Будь то метод, default member initializer или лямбда выражение. И вот есть в этих ваших интернетах такое мнение, что этот this - нулевой неявный аргумент метода и он передается в него в таком виде:
void Foo::bar(Foo * const this, Type1 param1, Type2 param2) {}
То есть типа this - константный указатель.
Так наверное можно думать, но это не совсем правда. Точнее так. Действительно, this подразумевает под собой адрес неявного аргумента-объекта. Но нигде не написано, что он константный.
Да ему нельзя ничего присваивать. Например
void Foo::change(Foo *foo) { this = foo; }
При компиляции кода появится примерно такая ошибка: lvalue required as left operand of assignment. Но если приглядеться, то ни про какой const там речь не идет. Ему не хватает lvalue слева.
Все потому что this - prvalue выражение. То есть можно даже сказать, что это и не указатель. Это выражение, значение которого равно адресу объекта. Оно не может быть использовано слева от знака равенства и у него нельзя взять адрес.
И именно поэтому ему нельзя ничего присваивать, а не потому что это константный указатель.
Но вот cv-квалификация метода может повлиять на cv-квалификацию указателя на объект. Тип this в обычном методе - Foo *(указатель на Foo). Однако для cv-квалифицированных методов this становится cv Foo *. То есть:
void Foo::ConstMemberFunction(Type1 param1, Type2 param2) const {
// this - const Foo *
this->field = param1; // Error!
}
Сделано это, естественно, чтобы мы никак не могли изменить объект, на который указывает this, в константном методе.
Так что this - prvalue и точка!
Make points in your life. Stay cool.
#cppcore9 385
Double lookup
#опытным
Решил сделать небольшое дополнение к предыдущему посту по результатам дискуссии в комментариях.
Не нужно использовать методы count(key) и contains(key), если вы потом собираетесь работать с этим ключом в ассоциативном контейнере(например изменять объект по ключу). Это может привести к так называемому double lookup. То есть двойной поиск по контейнеру.
Возьмем для примера std::map для показательности. И вот такой код:
std::map<std::string, std::string> map;
std::string get_value(const std::string& key) {
if (!map.contains(key)) {
std::string value = longCalculations(key);
map[key] = value;
return value;
} else {
return map[key];
}
}
Здесь мы по ключу key выдаем какое-то значение value. Эти значения вычисляются при помощи долгой функции longCalculations, поэтому было решено закэшировать все уже вычисленные значения в мапе. Так мы сможем обойти долгие вычисления и быстро дать ответ в случае, если уже искали это значение.
Только вот в чем проблема. Поиск по мапе - логарифмическая по времени операция. И в этом примере мы всегда делаем 2 поиска: первый раз на contains(мы должны пройтись по контейнеру, чтобы понять, есть ли элемент) и второй раз на operator[](нужно пройтись по контейнеру, чтобы вставить в него элемент/получить к нему доступ). Долговато как-то. Может можно получше?
В случае, если ключ есть в мапе, то мы можем делать всего 1 поиск! С помощью метода find и итератора на элемент.
std::string get_value(const std::string& key) {
auto it = map.find(key);
if (it == map.end()) {
std::string value = longCalculations(key);
map[key] = value;
return value;
} else {
return it->second;
}
}
Мы в начале попытались найти конкретный элемент мапы по ключу. И если его нет, то нам все-таки придется выполнить второй поиск, чтобы найти подходящее место для элемента. Но вот если ключ есть, тогда мы можем использовать сам итератор для возврата значения и второго поиска не будет!
Методы count и contains нужно использовать только тогда, когда у вас нет надобности в получении доступа к элементам контейнера. Find в этом случае, по моему мнению, немного синтаксически избыточен. А вот говорящие методы - самая тема. Например, у вас есть множество, в котором хранятся какие-то поля джейсона. И вам нужно как-то трансформировать только те значения, ключи которых находятся во множестве.
std::set<std::string> tokens;
std::string json_token;
Json json;
if (tokens.contains(json_token)) {
transformJson(json, json_token);
}
Все прекрасно читается и никакого двойного поиска!
Don't do the same work twice. Stay cool.
#cppcore #goodpractice9 385
📶 С 1 декабря для граждан РФ открыт доступ к платным материалам по программированию
Вот отсортированные базы с тонной материала(книги, курсы, ресурсы и гайды). Выбирай своё направление::
👩💻 Frontend 👩💻 PHP
⚙️ Backend 👩💻 Моб. Dev
📱 GitHub 👩💻 GameDev
🤓 Всё айти 👩💻 DevOps
👩💻 Python 🖥 Data Science
👩💻 Java 🐞 Тестирование
👩💻 C# 🤔 Хакинг & ИБ
👩💻 С/С++ 📱 Маркетинг
🖥 SQL 🖥 Дизайн
👩💻 Golang 👣 Rust
Скачивать ничего не нужно — все выложили в Telegram с доступом по ссылке
9 385
Проверяем вхождение элемента в ассоциативный контейнер
Нужно вот нам по ключу проверить вхождение элемента допустим в мапу.
Обычно мы пишем:
if (map.count(key)) {
// do something
}
Но для контейнеров без приставки "multi" это выглядит довольно странно. Действительно, если я знаю, что в мапе однозначное соответствие ключа и значения, зачем мне знать сколько вхождений элементов с этим ключом? Я хочу просто знать, есть ли он.
Такие вот маленькие семантические несостыковочки. С ними вроде все смирились, но осадочек остался...
И 20-е плюсы наконец нам подарили замечательный публичный метод для всех ассоциативных контейнеров contains. Он проверяет, если ли в контейнере элементы с данным ключом. Теперь код выглядит так:
if (map.contains(key)) {
// do something
}
И вот уже стало чуть приятнее и понятнее читать код.
Если есть доступ к 20-м плюсам, то переходите на использование этого метода.
Make things clearer. Stay cool.
#STL #cpp209 385
ХОЧЕШЬ ПОВЫСИТЬ ГРЕЙД В 2024 ГОДУ? 🚀
Чтобы стать Senior C# разработчиком сегодня, нужно не только знать язык программирования и фреймворки. Нужно уметь строить гибкую архитектуру приложения, которую легко тестировать и менять под задачи бизнеса. Стань экспертом в построении гибкой архитектуры приложения!
👉 Стартуем 11 декабря.
Курс ведет действующий архитектор и Principal Engineer Кирилл Ветчинкин.
Ты научишься:
✅ Разбивать приложение на слои в соответствии с Clean Architecture
✅ Формировать Domain Model и применять тактические паттерны DDD
✅ Реализовывать Use Case как Command/Query
✅ Делать синхронные и асинхронные интеграции, не загрязняя ядро приложения
✅ Писать 3 вида тестов для разных слоев приложения
Полная программа ТУТ 👉 https://microarch.ru/courses/ddd?utm_source=posev&utm_medium=erid:2Vtzqw84rvt&utm_campaign=7
А главное — ты с нуля разработаешь и запустишь микросервис, который максимально приближен к реальности "Диспетчеризация заказов на курьеров". Это будет крутым проектом в портфолио или основой для рабочих задач.
А еще:
✅ Проверим все домашки
✅ Поддержим в чате
✅ Проведем живые разборы
✅ Ответим на все вопросы
📕 Сертификат об участии по итогам прохождения курса.
🔥 Не откладывай свой рост на потом: https://microarch.ru/courses/ddd?utm_source=posev&utm_medium=erid:2Vtzqw84rvt&utm_campaign=7
Реклама. ИП Ветчинкин К.Е. ИНН: 773376451099 Erid: 2Vtzqw84rvt
9 385
Addressof
#опытным
Говорят вот, что питон - такой легкий для входа в него язык. Его код можно читать, как английские английский текст. А вот С/С++ хаят за его несколько отталкивающую внешность. Чего только указатели стоят...
Кстати о них. Все мы знаем, как получить адрес объекта:
int number = 42;
int * p_num = &number;
Человек, ни разу не видевший код на плюсах, увидит здесь какие-то магические символы. Вроде число, а вроде какие-то руны * и &. Но плюсы тоже могут в читаемость! Причем именно в аспекте адресов.
Вместо непонятного новичкам амперсанда есть функция std::addressof! Она шаблонная, позволяет получить реальный адрес непосредственно самого объекта и доступна с С++11. Для нее кстати удалена перегрузка с const T&&
template< class T >
T* addressof( T& arg ) noexcept;
template< class T >
const T* addressof( const T&& ) = delete;
Это делает функцию еще одним примером использования константной правой ссылки .
Это конечно круто, что можно в плюсах словами брать адрес, но в чем прикол? Зачем было заводить отдельную функцию для того, что уже есть в самом языке?
А вот теперь мы возвращаемся к предыдущему посту про перегрузку оператора взятия адреса. Так как его можно перегружать, то мы можем возвращать вообще любой адрес, который потенциально никак не связан с самим объектом. В этом случае не очень понятно, как взять трушный адрес объекта. Как раз таки std::addressof - способ получить валидный адрес непосредственно самого объекта.
Также большим преимуществом является шаблонная природа функции. Это позволяет обобщенному коду работать, как с обычными классами, так и с классами, у которых перегружен оператор взятия адреса.
А с С++17 она еще и констэкспр, это для любителей компайл-тайма.
Вот вам примерчик:
template<class T>
struct Ptr
{
T* pad; // add pad to show difference between 'this' and 'data'
T* data;
Ptr(T* arg) : pad(nullptr), data(arg)
{
std::cout << "Ctor this = " << this << '\n';
}
~Ptr() { delete data; }
T** operator&() { return &data; }
};
template<class T>
void f(Ptr<T>* p)
{
std::cout << "Ptr overload called with p = " << p << '\n';
}
void f(int** p)
{
std::cout << "int** overload called with p = " << p << '\n';
}
int main()
{
Ptr<int> p(new int(42));
f(&p); // calls int** overload
f(std::addressof(p)); // calls Ptr<int>* overload, (= this)
}
// OUTPUT
// Ctor this = 0x7fff59ae6e88
// int** overload called with p = 0x7fff59ae6e90
// Ptr overload called with p = 0x7fff59ae6e88
Здесь какие-то злые персоналии перегрузили оператор взятия адреса у класса Ptr так, чтобы он возвращал указатель на одно из его полей. Ну и потом сравнивают результат работы оператора с результатом выполнения функции std::addressof.
Видно, что трушный адрес объекта, полученный с помощью this и адрес, возвращенный из std::addressof полностью совпадают. А перегруженный оператор возвращает другое значение.
Express your thoughts clearly. Stay cool.
#cpp #cpp11 #cpp179 385
Перегружаем оператор взятия адреса
#опытным
Перегрузка операторов - дело не для слабонервных. Этим пугают детей и пытают людей. Только прожженные плюсовики могут сходу сказать разницу в перегрузке префиксного и постфиксного декремента/инкремента.
Но не только в этом дело. Есть такие операторы, которые как бы можно перегружать, но непонятно, для чего это делать.
Вот например, оператор взятия адреса. Да его можно перегружать. Можно его перегружать как метод:
struct Class {
int* operator &() {
return &member;
}
int member;
};
Можно, как свободную функцию:
struct A {
int member;
};
int* operator &(Class& obj) {
return &obj.member;
}
Но странновато это все.
Есть объект. У него есть занимаемое место. Зачем вообще заменять такое интуитивно понятное поведение?
Решение сделать этот оператор кастомным очень спорное. В реальном коде вы такого скорее всего никогда не встретите. А если подумаете, что неплохо было бы в своем коде заюзать эту фичу, то подумайте еще раз 300.
Но все-таки если оно есть, значит кто-то этим пользуется.
Например, для врапперов. Вы написали обертку для класса и хотите прозрачно передать контроль над объектами класса в обертку. Тогда вам возможно может понадобиться возвращать из оператора & указатель на внутренний объект, а не на объект враппера.
Однако тогда встает вопрос, как нормально взять адрес у обертки, если это пригодиться?
Но и на этот вопрос есть ответ. Но обсудим мы его несколько позже.
Однако уже сейчас видно, что для пользователя кода создаются серьезные препятствия для его понимания и использования. Если нужно из обертки доставать адрес оборачиваемого объекта - сделайте геттер, как у умных указателей и не парьтесь с перегрузкой.
Follow the beaten path. Stay cool.
#cppcore9 385
🇷🇸 Как стать гражданином Сербии
Знаете ли вы, что Сербия — одна из самых привлекательных стран для жизни и ведения бизнеса в Европе?
С паспортом вы получаете:
• Безвизовый доступ в Шенгенскую зону и ряд других стран
• Комфортные условия для ведения бизнеса с налоговой ставкой 15%
• Возможность приобрести недвижимость по цене от 1700€ за м² — одна из самых низких в Европе
А ещё Сербия — в первых рядах на вступление в ЕС, что в будущем даст её гражданам все преимущества Евросоюза. Программа гражданства комфортная, не требует отказа от родного паспорта, постоянного проживания в стране и даже знания языка [подробнее]
Получить гражданство можно всего за 8 месяцев с полным сопровождением Bespalov Finance (у них хорошие отзывы и сотни довольных клиентов). А для детей до 18 лет оформление будет бесплатным.
За подробностями пишите в Telegram: @AleksandrBespalovFinance
9 385
CamelCase vs Under_Score
Вдохновился прошлым постом и родилось это.
Есть такое ощущение, что программирование - одна из самых холиварных специальностей в мире. А в добавок к этому программисты по своему складу характера зачастую сами по себе ярые холиварщики. Это взрывоопасное комбо, которое приводит к смешным для стороннего человека проблемам.
Одна из них - как записывать многословные переменные. Уже десятилетиями люди спорят и никак не могут выработать один универсальный вариант. Поэтому приходится писать этот пост, чтобы во всем разобраться.
В настоящее время существует много стандартов наименования переменных, но два из них являются наиболее популярными среди программистов: это camel case («Верблюжья» нотация) и underscore (именование переменных с использованием символа нижнего подчеркивания в качестве разделителя).
Верблюжья нотация является стандартом в языке Java и в его неродственнике JavaScript, хотя ее можно встретить также и в других местах. Согласно этому стандарту, все слова в названии начинаются с прописной буквы, кроме первого. При этом, естественно, не используется никаких разделителей вроде нижнего подчеркивания. Пример: яШоколадныйЗаяцЯЛасковыйМерзавец. Обычно данный стандарт применяют к именам функций и переменных, при этом в именах классов, структур, интерфейсов используется стандарт UpperCamelCase(первая буква заглавная).
В стандарте underscore слова пишутся с маленькой буквы, а между ними стоит _ ( типа такого: йоу_собаки_я_наруто_узумаки). Обычно этот стандарт используется в названиях функций и переменных, а для названий классов, структур, интерфейсов используется стандарт UpperCamelCase. Обычно используется с родственных С языках.
Каждый из этих двух стандартов имеет свои сильные и слабые стороны. Вот основные:
👉🏿 Нижнее подчеркивание лучше читается: сравните стандарт_с_нижним_подчеркиванием и стандартНаписанияПрописнымиБуквами
👉🏿 Зато camel case делает более легким чтение строк, например:
my_first_var=my_second_var-my_third_var
и
myFirstVar=mySecondVar-myThirdVar
Очевидно, что camel case читается лучше: в случае с нижним подчеркиванием и оператором «минус» выражение с первого взгляда вообще можно принять за одну переменную.
👉🏿 Подчеркивание сложнее набирать. Даже при наличии intellisense, во многих случаях необходимо набирать символ нижнего подчеркивания. И имена получаются длиннее.
👉🏿 Camel Case непоследователен, потому что при использовании констант (которые иногда пишутся целиком заглавными буквами) нам приходится использовать нижнее подчеркивание. С другой стороны, стандарт underscore может быть полным, если вы решите использовать в названиях классов (структур, интерфейсов) нижнее подчеркивание в качестве разделителя.
👉🏿 Кроме того, для камел кейса не так уж и просто работать с аббревиатурами, которые обычно представлены в виде заглавных букв. Например, как вот правильно iLoveBDSM или iLoveBdsm?. Непонятно. Можете написать в комментах, как это по-вашему пишется)
В плюсах реально во многих проектах(и по моему опыту в частности) склоняются к использованию подчеркиваний в именах переменных и функций, а UpperCamelCase используется для имен классов. Но почему-то это нигде особо не упоминается и этому нигде не учат. Это как-то само приходит со временем.Хотя на начальном этапе карьеры, когда проходят первые ревью, то много непоняток в голове по поводу именований. Поэтому, вообще говоря, лучше заранее с командой обсудить этот вопрос и выработать ваш подробный код стайл, чтобы все были на одной волне по этому вопросу.
Расскажите в комментах, какую нотацию вы используете? Интересно иметь большую репрезентативную статистику.
Choose your style. Stay cool.
#fun
9 385
Поля_класса
#опытным
Очень часто в компаниях прибегают к различного рода код-стайлам для обозначения полей класса и выделения из на фоне серой массы локальных переменных.
Это очень удобно, когда читаешь код класса и сразу понимаешь, что вот это локальная переменная, а вот это поле класса. Читаемость сразу повышается.
Удобно еще бывает различать открытые и закрытые поля с помощью наличия или отсутствия подчеркивания.
Чисто идейно мне нравится такой стайл: открытые члены без подчеркиваний, защищенные с одним подчеркиванием, приватные - с двумя. Спереди или сзада - тут уже вкусовщина.
Но вот беда. У нас проблемы, Хьюстон.
В С++ есть определенные навязанные ограничения на нейминг сущностей со стороны underscore. Стандарт говорит:
Certain sets of names and function signatures are always reserved to the implementation: - Each name that contains a double underscore (__) or begins with an underscore followed by an uppercase letter is reserved to the implementation for any use. - Each name that begins with an underscore is reserved to the implementation for use as a name in the global namespace. Such names are also reserved in namespace ::std (17.4.3.1).Имплементация загробастала себе права на имена сущностей, которые содержат двойное подчеркивание или начинаются с одного подчеркивания и буквы верхнего регистра, в любых скоупах. А также для глобального неймспейса зарезервированы имена начинающиеся на одинарный underscore. Итого, чтобы не было коллизии имен, лучше избегать использования имен с префиксом подчеркивания или содержащих двойной underscore. Закрытые поля можно помечать одной черточкой с обеих сторон. Только если решите использовать спереди - не пишите дальше заглавную букву. И будет вам счастье. В питоне кстати именно через нижнее подчеркивание помечаются "непубличные" части API. Но там правда есть один нюанс. Зацените веселый мем по этой теме. Так что сегодня мем про питухон. Be happy. Stay cool. #cppcore
9 385
❓Хотите создавать идеальные C++ API, которые не ломаются на первой же нагрузке?
👉 Тогда не пропустите этот бесплатный вебинар! 3 декабря в 20:00 мск — открытый урок, который кардинально изменит ваш подход к проектированию API на C++!
**Что вас ждет?**
- Понимание плохого и хорошего API: как отличить чудовищное API от шедевра?
- Умение правильно именовать сущности и разбивать их на атомарные элементы. Прокачаем навыки, чтобы не было «кучи кода» и «головной боли».
- Идеи data-oriented подхода для создания API в высоконагруженных приложениях.
**Кому это будет полезно?**
- Разработчикам, кто только знакомится с C++ или переходит с других языков.
- C++-программистам, которые хотят прокачать свои навыки разработки API.
Вы научитесь проектировать удобный, стабильный и эффективный API для C++, который будет работать как часы.
⭐️ Спикер Андрей Рыжиков — разработчик в НИИ обработки аэрокосмических изображений.
Успейте записаться на открытый урок и получите скидку на большое обучение «C++ Developer».
Для участия зарегистрируйтесь: https://otus.pw/rUbl/
Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576, www.otus.ru
9 385
Return в main
На собеседовании тут встретил сеньора-помидора, который говорил, что код не скомпилируется, если не написать return 0 в конце. Думал, что все уже знают эту особенность, но раз нет - придется рассказать. Правда это довольно коротко будет.
В С++ разрешено не указывать возвращаемое значение для функции main. Но только для нее! Это единственное исключение.
Однако процессу требуется какой-то код возврата по завершению работы, это требование ОС. Поэтому, если исполнение дошло до конца main и ничего плохого не произошло - подразумевается, что возвращается 0. Компилятор также добавляет соответствующую инструкцию в асм.
На счет сишки точно не знаю, но вроде в С99 можно было опускать инструкцию возврата.
#include <iostream>
int main() {
std::cout << "Hello, World!" << std::endl;
}
Поэтому этот код абсолютно валидный и не противоречит стандарту.
Так что все в порядке, город может спать спокойно без return.
Sleep well. Stay cool.
#cppcore9 385
Инкапсуляция и структуры
#новичкам
Всем мы знаем, что раскрывать детали реализации класса - это плохо, этого надо избегать и если вы помыслите об обратном, то придет серенький волчок и укусит за бочок.
Однако, как и со многими такими догматами случается, не всегда нужно следовать этой концепции. Давайте посмотрим на следующий код:
class MyClass
{
int m_foo;
int m_bar;
public:
int addAll() const;
int getFoo() const;
void setFoo(int foo);
int getBar() const;
void setBar(int bar);
};
int MyClass::addAll() const
{
return m_foo + m_bar;
}
int MyClass::getFoo() const
{
return m_foo;
}
void MyClass::setFoo(int foo)
{
m_foo = foo;
}
int MyClass::getBar() const
{
return m_bar;
}
void MyClass::setBar(int bar)
{
m_bar = bar;
}
Выглядит солидно. Сеттеры, геттеры, все дела. Но вот души в этом коде нет. Он какой-то.. Бесполезный чтоли.
Методы класса не делают ничего такого, что пользователь класса не смог бы сделать своими руками. Скорее руками это все сделать будет даже короче.
Этим грешат новички: поначитаются модных концепций(или не очень модных) и давай скрывать все члены.
Brah... Don't do this...
Здесь нет никаких инвариантов, которые нужно было бы сохранять. Интерфейс класса и так позволяет все, шо хош делать с объектом.
Если ваш класс используется просто как хранилище данных, то это ближе не к классу, а к сишной структуре. Сделайте вы уже поля открытыми и замените ключевое слово class на более подходящее struct.
struct MyClass
{
int m_foo;
int m_bar;
}
Делов-то. Зато прошлым примером можно хорошо отчитываться за количество написанных строчек кода=)
Show your inner world to others. Stay cool.
#cppcore #goodpractice #OOP9 385
Результаты ревью
Всем участникам спасибо за развернутые ответы. Самый объемный комментарий с большим количеством правильных предположений относительно кода написал Alex D. Давайте ему все вместе похлопаем👏👏👏.
Также удивило, что некоторые предоставили прям свои исправленные версии кода. Спасибо большое за вовлеченность😌. Ваши примеры помогают новичкам по-другому посмотреть на задачу и увидеть альтернативные и более корректные версии ее решения.
Но мы не будем уж погружаться совсем далеко в дебри и не будет совсем переписывать кусок, чтобы в нем появлялись атомики, тредпулы, кондвары и бечконечный разбор очереди до graceful shutdown. Уже в одном из прошлых ревью разбирали, как может выглядеть более совершенная версия воркера для очереди. Все ваши комменты и примеры кода с более продвинутой обработкой в любом случае имеют смысл. Просто этот пример довольно учебный и планировал показывать вполне конкретные вещи, не претендуя на что-то сложное.
Но и на этом поле есть, где развернуться.
Итак, пойдем по в рандомном порядке:
💥 Очередь копируется в воркер потока, поэтому мы обрабатываем не просто копию очереди, а копии!. Каждый поток имеет свою копию. Передача по значению не была бы прям уж сильной проблемой, если бы был один воркер. А их тут 2. И вся работа будет делаться дважды. Это конечно не порядок. Меняем параметр функции на ссылку.
💥 Но если вы думаете, что вы так передадите потоку ссылку - вы ошибаетесь! На самом деле для всех объектов-аргументов потоковой функции во внутреннем сторадже нового потока создается их копия, чтобы не иметь дело с висячими ссылками. Если вы хотите передать истинную ссылку в поток, то надо воспользоваться std::ref.
💥 Нет join'ов у потоков. Надо их либо вызвать, либо использовать jthread из С++20.
💥 Одна из самых больших непоняток в коде для комментаторов - что за переменная lck? На самом деле автор кода немного с ним экспериментировал и просто допустил ошибку, не переименовав lck в mtx. Такое бывает, особенно с новичками. Особенно в проектах без CI.
💥 Если вы используете мьютекс, это не значит, что все автоматически становится потокобезопасным. В этом примере мьютекс никак не защищает критическую секцию! А как он может защитить, если он является локальной переменной. То есть каждый поток будет иметь свою копию этого мьютекса. Не порядок. Его надо сделать либо глобальным вообще, либо статической локальной переменной.
💥 Использование чистых вызовов lock и unlock на мьютексах является очень плохой практикой. В случае изначально пустой очереди или такого распределения расписания потоков, что очередь окажется пуста до входа в while, один из потоков никогда не отпустит лок.
Да и вот это засыпание после execute выглядит очень криво.
Нужно использовать RAII обертки, типа std::lock_guard.
💥 Но в нашем случае lock_guard будет плохим решением. Во-первых, не очень понятно, куда его вставлять. Изначальная проверка на пустоту тоже должна быть "обезопашена". Тогда надо ставить до while. Но в этом случае один из потоков будет выполнять всю работу целиком. А нам хотелось бы использовать преимущества многопоточки.
И еще один нюанс: у нас под локом выполняется довольно тяжелая функция, которая долго выполняется. И все это время другие потоки будут простаивать без дела. А если у нас со временем их станет больше? Еще больше простоя. А время - это деньги.
В общем, нехватает гибкости. Хочется иметь возможность отпускать замок на время, а потом опять его захватывать. Но и преимуществами RAII тоже хочется пользоваться.
Выход - std::unique_lock. Он позволяет делать и то, и то.
💥 Так как выполнение задачи теперь не под локом, то sleep в воркере не нужен, другие потоки имеют достаточно времени, чтобы захватить мьютекс.
💥 Неплохо бы обернуть выполнение задачи в блок try-catch. Так если вылетит исключения из выполнения одной задачи, мы можем как-то обработать эту ситуацию и пойти работать дальше, а не просто свалиться в std::terminate.
Пусть этот пример останется "учебным", но теперь хотя бы корректным.
ПРОДОЛЖЕНИЕ В КОММЕНТАРИЯХ
Fix your flaws. Stay cool.
9 385
Погрузитесь в мир передовых технологий и узнайте, как использовать мощь C++ для создания молниеносных веб-приложений с помощью WebAssembly! Регистрируйтесь на бесплатный урок от профессионалов: https://otus.pw/CgizV/
На вебинаре:
- Откройте для себя WebAssembly: Узнайте, как эта революционная технология позволяет запускать C++ код в браузере с почти нативной производительностью.
Мы рассмотрим, как компилировать C++ код в WebAssembly с использованием Emscripten и создадим работающий веб-сервер.
Изучим техники оптимизации WebAssembly модулей для достижения максимальной эффективности.
Узнайте методы интеграции C++ кода с JavaScript для создания гибридных веб-приложений.
Поймите особенности работы с памятью и управление ресурсами в WebAssembly.
⚡Полезно:
- Всем разработчикам: Расширьте свои возможности в веб-разработке и создавайте высокопроизводительные приложения.
- Разработчикам сложных проектов: Работайте над вычислительно-сложными веб-проектами с новыми инструментами.
- Специалистам по кроссплатформенной разработке: Интересуйтесь новыми технологиями и их применением.
😉 По итогам вебинара вы:
1. Создадите свой первый WebAssembly модуль на C++: Получите практический опыт создания и запуска модулей.
2. Получите навыки отладки и оптимизации: Научитесь отлаживать и оптимизировать ваши WASM-приложения.
3. Разработаете гибридные веб-приложения: Научитесь комбинировать C++ и JavaScript для создания мощных приложений.
4. Освоите инструменты WebAssembly: Узнайте, как использовать основные инструменты экосистемы WebAssembly.
🔼 Зарегистрируйтесь сейчас и прокачайте навыки создания высокопроизводительных веб-приложений с C++ и WebAssembly!
Реклама. ООО «Отус онлайн-образование», ОГРН 11777466185769 385
Ревью
Сегодня вам на суд выносится довольно простой, может даже игрушечный, кусок многопоточного кода, чтобы даже начинающие смогли высказать, чем они в нем недовольны. Однако там несколько не самых очевидных подводных камней, так что будьте внимательны.
Для новичков, кто еще не видел эту рубрику. В рубрике #ревью мы выкладываем сюда отрывок кода, а вы в комментах по существу говорите, что в этом коде не так и как бы вы это исправили. Как бы вы это делали на реальном ревью в своем проекте. Того, кто найдет больше всего недостатков, я завтра упомяну в итоговом посте-компиляции из всех найденных проблем.
Вот собственно код:
поливать г ревьюить код.
Ask for objective critique. Stay cool.
struct Task {
void Execute() {
// pretend that this is very long calculations
std::this_thread::sleep_for(std::chrono::seconds(2));
}
};
void WorkingThread(std::deque<Task> queue) {
std::mutex mtx;
mtx.lock();
while (!queue.empty()) {
auto elem = queue.front();
queue.pop_front();
elem.Execute();
lck.unlock();
// to get other threads opportunity to work
std::this_thread::sleep_for(std::chrono::milliseconds(1));
lck.lock();
}
}
int main() {
std::deque<Task> queue(10);
std::thread thr1{WorkingThread, queue}, thr2{WorkingThread, queue};
}
Чего ждем? Айда в комменты
Available now! Telegram Research 2025 — the year's key insights 
