
⚡️ Telega AI — персональный каталог и пост за 30 секунд
AI-агент подберет каналы и напишет рекламный пост на основе вашего продукта
В каталог

РегистрацияВойтиВойти
Скидка 3,5% на первые три заказа
Получите скидку на первые три заказа!
Зарегистрируйтесь и получите скидку 3,5% на первые рекламные кампании — промокод активен 7 дней.
30.1

Грокаем C++
5.0
7
Интернет технологии
627
11
Авторский канал о программировании на С++ и базе computer science. Простым и легким слогом рассказываем про сложные концепции С++. Самая активная и вовлеченная аудитория в тематике.
Поделиться
В избранное
Купить рекламу в этом канале
Формат:
keyboard_arrow_down
- 1/24
- 2/48
- 3/72
- Нативный
- 7 дней
- Репост
1 час в топе / 24 часа в ленте
Количество:
%keyboard_arrow_down
- 1
- 2
- 3
- 4
- 5
- 8
- 10
- 15
Стоимость публикации:
local_activity
8 391.60₽8 391.60₽local_mall
0.0%
Осталось по этой цене:0
Последние посты канала
Capture this
#новичкам
Бывают ситуации, когда вы хотите зарегистрировать коллбэк, в котором будет выполняться метод текущего класса.
Например, вы пишите класс приложения. Правила хорошего тона говорят вам, что нужно добавить поддержку graceful shutdown. Для этого получаем объект обработчика сигнала и регистрируем коллбэк на SIGINT и SIGTERM:
struct Application {
Application() {
SignalHandler::GetSignalHandler().RegisterHandler({SIGINT, SIGTERM}, [](){
Shutdown();
});
}
void Shutdown() {...}
};{}
Сработает ли такой код?
Он не соберется. Будет ошибка: 'this' was not captured for this lambda function
. Методу Shutdown нужен объект, на котором его нужно вызвать.
Для этого в С++11 вместе с лямбдами ввели захват this. Это значит, что в лямбду сохраняется указатель на текущий объект. А синтаксис лямбды позволяет в таком случае не использовать явно this в ее теле:
struct Application {
Application() {
SignalHandler::GetSignalHandler().RegisterHandler({SIGINT, SIGTERM}, [this](){
// this->Shutdown(); - don't need this syntax
Shutdown();
});
}
void Shutdown() {...}
};{}
До С++20 кстати можно было захватывать this в лямбду с помощью дефолтного захвата по значению и по ссылке:
SignalHandler::GetSignalHandler().RegisterHandler({SIGINT, SIGTERM}, [= /*& also works*/](){ // works
Shutdown();
});{}
Однако в С++20 запретили неявный захват this по значению(что очень хорошо). Теперь либо явных захват this, либо через default capture by reference.
Be explicit. Stay cool.
#cppcore #cpp11 #cpp204172
13:06
19.06.2025
Разбор ревью
#новичкам
Большое спасибо всем участникам ревью, которые проявили активность под предыщущим постом. Всем и каждому посылаем лучи благодарности!
Было непросто выбрать самый эффективный по критике комментарий, потому что некоторые люди предлагали странные решения. В итоге, мы выбрали @seweeex и вот его коммент. Давайте похлопаем ему 👏👏👏.
Теперь к сути. В этом коде не так уж и много проблем, просто они жирные и явно бросаются в глаза.
Поехали разбирать.
🔞 Зачем-то в очереди хранятся сырые указатели. Смысла в этом особого нет, кроме как подложить себе свинью на будущее и поджечь жёпы комментаторов. Тут даже умные указатели не нужны, зачем дополнительные аллокации? В очереди можно хранить сами объекты и никаких проблем с менеджментом памяти не будет.
🔞 Использование сырых указателей приводит например к тому, что при вылете исключения из метода push, произойдет утечка памяти. Да, элементов мы закидываем в очередь немного, но концептуально проблема есть. Решается это опять же через хранение обычных объектов.
🔞 Слон в посудной лавке здесь - это конечно отсутствие синхронизации на очереди. Это в принципе ub и дальше не о чем говорить. Нужна не стандартная, а потокобезопасная очередь.
Очередь должна быть блокирующей, чтобы не тратить активно ресурс CPU на ожидание нового сообщения. Это решается с помощью кондвара.
🔞 Ресивер может зашедулиться раньше сендера, увидит пустую очередь и выйдет из цикла, не обработав ни одной задачи. Поэтому нужна система сигнализации: очередь должна ждать прихода новых задач, пока ей не скажут, что больше задач нет.
🔞 Бесконечный цикл в ресивере выглядит не очень. Если можно не писать бесконечных циклов и не вставлять брейки, то лучше этого не делать. break и continue усложняют понимание кода. Лучше перенести забирание элемента из очереди прям в шапку цикла.
🔞 Гонка на потоконебезопасном логировании. Нужен мьютекс, чтобы сообщения не интерферировали.
По сути все. Главное изменение - вынесение блокирующей потокобезопасной очереди в отдельный шаблонный класс, который хранит объекты типа Т. С шаблонами можно долго играться и далеко зайти, поэтому приведем самую простую реализацию, которая справляется со своими задачами в данном кейсе, но может быть улучшена для корректной работы с самыми разными типами:
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <memory>
#include <optional>
#include <print>
struct Message {
int data;
};
template<typename T>
class ThreadSafeQueue {
public:
void push(T msg) {
{
std::lock_guard lock(mutex_);
queue_.push(std::move(msg));
}
cv_.notify_one();
}
std::optional<T> pop() {
std::unique_lock lock(mutex_);
cv_.wait(lock, [this] { return !queue_.empty() || stopped_; });
if (stopped_ && queue_.empty()) {
return std::nullopt;
}
auto msg = std::move(queue_.front());
queue_.pop();
return msg;
}
void stop_receive() {
stopped_ = true;
cv_.notify_all();
}
private:
std::queue<T> queue_;
std::mutex mutex_;
std::condition_variable cv_;
std::atomic<bool> stopped_ = false;
};
void println(const std::string& str) {
static std::mutex mtx;
std::lock_guard lock(mtx);
std::cout << str << std::endl;
}
void sender(ThreadSafeQueue<Message>& msgQueue) {
for (int i = 0; i < 20; ++i) {
auto msg = Message(i);
println("Sent: " + std::to_string(msg.data));
msgQueue.push(std::move(msg));
}
msgQueue.stop_receive();
}
void receiver(ThreadSafeQueue<Message>& msgQueue) {
while (auto msg = msgQueue.pop()) {
println("Received: " + std::to_string(msg.value().data));
}
}
int main() {
ThreadSafeQueue<Message> msgQueue;
std::thread t1(sender, std::ref(msgQueue));
std::thread t2(receiver, std::ref(msgQueue));
t1.join();
t2.join();
}{}
Пишите свои дополнения, если что-то забыли.
Make things better. Stay cool.3277
19:00
09.08.2025
Ответ на квиз
#новичкам
В этом коде:
class Bar {
public:
Bar() {
throw std::runtime_error("Error");
}
};
int main() {
Bar* bar = nullptr;
try {
bar = new Bar();
} catch(...) {
std::cout << "Houston, we have a problem" << std::endl;
}
}
Не будет утечки памяти. Стандарт нам это гарантирует при использовании new expression.
Дело вот в чем. В этом посте мы поговорили о том, что есть 3 вида new:
👉🏿 operator new, который выделяет память.
👉🏿 placement new, который вызывает конструктор на заданной памяти.
👉🏿 new expression, который в начале выделяет память через operator new, а потом конструирует объект на этой памяти через placement new. Это именно то, что используется в коде выше.
Так вот, new expression заботится о своих пользователях и оборачивает в try-catch вызов конструктора объекта. В catch оно освобождает память и пробрасывает исключение наружу:
Bar* bar = new Bar();
// инструкция выше эквивалентнас следующему коду:
Bar* bar;
void* tmp = operator new(sizeof(Bar));
try {
new(tmp) Bar(); // Placement new
bar = (Bar*)tmp; // The pointer is assigned only if the ctor succeeds
}
catch (...) {
operator delete(tmp); // Deallocate the memory
throw; // Re-throw the exception
}{}
И получается, что и исключение есть, и память освобождена. Таким образом new обеспечивает базовую гарантию исключений, чтобы программа осталась в согласованном состоянии и отсутствовали утечки памяти.
Вообще, это даже интересная техника. Ловить исключение, заметать следы преступления и пробросить исключение дальше. Может помочь избежать утечек памяти в конструкторах ваших классов, которые сами внутри выделяют память.
Спасибо, @PyXiion за предоставленную информацию)
Don't let your memory leak. Stay cool.
#cppcore #memory3715
13:00
18.06.2025
or and not
#новичкам
В С/C++ всегда был не очень дружелюбный синтаксис операторов. Показать вот такой код человеку, который не в зуб ногой в программировании:
if (x > 0 && y < 10 || !z) {
// ...
}{}
есть вероятность, что он подумает, что его прокляли шаманы тумба-юмба.
Однако знали ли вы, что в С/C++ есть альтернативный синтаксис токенов? Символы операторов заменяются на короткие слова и код выше становится почти питонячьим:
if (x > 0 and y < 10 or not z) {
// ...
}{}
Выглядит свежо! Хотя было доступно еще с С++98.
Вот полный список альтернативных токенов:
&& - and
&= - and eq
& - bitand
| - bitor
~ - compl
! - not eq
| - or
|= - or eq
^ - xor
^= - xor eq
{ - <%
} - %>
[ - <:
] - :>
# - %:
## - %:%:{}
Последние токены для скобок - это конечно дичь. Но остальные - вполне интересные варианты.
В сях альтернативные токены были введены в С95, поэтому до этого момента токенов не было в языке. Но даже с их введением все продолжали использовать привычный синтаксис. Видимо поэтому мы так до сих пор и остались на уровне наскальной живописи.
А вы используете в продакшен коде альтернативные токены?
Evolve. Stay cool.
#fun #goodoldc3314
13:00
11.08.2025
imageИзображение не доступно для предпросмотра
В России можно посещать IT-мероприятия хоть каждый день: как оффлайн, так и онлайн
Но где их находить? Как узнавать о них раньше, чем когда все начнут выкладывать фотографии оттуда?
Переходите на канал IT-Мероприятия России. В нём каждый день анонсируются мероприятия со всех городов России
📆 в канале размещаются как онлайн, так и оффлайн мероприятия;
👩💻 можно найти ивенты по любому стеку: программирование, frontend-backend разработка, кибербезопасность, дата-аналитика, osint, devops и другие;
🎙 разнообразные форматы мероприятий: митапы с коллегами по цеху, конференции и вебинары с известными опытными специалистами, форумы и олимпиады от важных представителей индустрии и многое другое
А чтобы не искать по разным форумам и чатам новости о предстоящих ивентах:
🚀 IT-мероприятия России — подписывайся и будь в курсе всех предстоящих мероприятий!
2943
10:00
11.08.2025
#include <filename> vs #include "filename"
#новичкам
Тот нюанс, который зеленые программисты С++ впитывают из окружающей среды, но зачастую не понимают его деталей.
Дано: файл, который нужно включить в проект. Задача: определить, включать его через треугольные скобки или кавычки. Какой подход выбрать?
Стандарт нам дает отписку, что поведение при обоих подходах implementation defined, поэтому надо смотреть на то, как ведут себя большинство компиляторов.
Для начала: #include - это директива препроцессора. На месте этой директивы на этапе препроцессинга вставляется тело включаемого файла. Но для того, чтобы вставить текст файла, его надо в начале найти. И вот здесь главное различие.
У компилятора есть 2 вида путей, где он ищет файлы - системные директории и предоставленные юзером через опцию компиляции -I.
Так вот #include <filename> в начале ищет файл в системных директориях. Например, в линуксе хэдэры устанавливаемых библиотек могут оказаться в директориях /usr/include/, /usr/local/include/ или /usr/include/x86_64-linux-gnu/(на x86 системах).
А #include "filename" в начале ищет файл в текущей директории и в директориях, предоставленных через опцию компиляции.
В конце концов, обычно, в обоих случаях все известные компилятору директории будут просмотрены на наличие подходящего файла, пока он не будет найден. Вопрос только в порядке поиска.
Так что в большинстве случаев разницы особо никакой не будет, кроме времени компиляции. Однако все равно есть определенные гайдлайны, которым следуют большинство разработчиков.
✅ Используем #include <filename> для включения стандартных файлов и хэдэров сторонних библиотек. Так как они скорее всего установлены по стандартным директориям, логично именно там начинать их поиск.
#include <stdio.h> // Стандартный заголовочник
#include <curl/curl.h> // Заголовочник из системной директории
int main(void) {
CURL *curl = curl_easy_init();
if(curl) {
printf("libcurl version: %s\n", curl_version());
curl_easy_cleanup(curl);
}
return 0;
}{}
✅ Используем #include "filename" для включения заголовочных файлов своего проекта. Препроцессор будет сначала искать эти файлы там, где вы ему об этом явно укажите с помощью опций.
// include/mylib.hpp - объявляем функцию из нашего проекта
#pragma once
void print_hello();
// src/main.cpp - используем локальный заголовочник через " "
#include <iostream> // Системный заголовочник
#include "mylib.hpp" // Заголовочник локального проекта, ищем в указанных путях
int main() {
print_hello();
return 0;
}
void print_hello() {
std::cout << "Hello from my project!\n";
}
// компиляция: g++ -Iinclude/ src/main.cpp -o my_program -std=c++17{}
See the difference. Stay cool.
#cppcore #compiler2974
12:00
13.08.2025
imageИзображение не доступно для предпросмотра
🧐Слышали о контейнерах в C++, но не уверены, когда и как их правильно использовать?
На открытом уроке «Контейнеры C++» 20 августа в 20:00 МСК мы разберём, как эффективно использовать стандартные и сторонние контейнеры в C++. Мы рассмотрим популярные STL-контейнеры — std::vector, std::list, std::deque, а также контейнеры-адаптеры и библиотеки сторонних разработчиков, такие как folly, boost и libcuckoo. Поймём, в каких случаях использовать каждый из них, чтобы повысить производительность и улучшить архитектуру программ.
Вы получите конкретные знания, которые можно сразу применить в реальных проектах, и сможете выбирать оптимальные решения для работы с данными в C++.
⚡️Регистрируйтесь на вебинар и получите скидку на курс «C++ Developer. Professional»: https://otus.pw/OlBI/
Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576, www.otus.ru
2295
10:00
15.08.2025
Последний элемент enum
#новичкам
С enum'ами в С++ можно творить разное-безобразное. Можно легко конвертить элементы enum'а в числа и инициализировать их числом. Мы в это сейчас глубоко не будем погружаться, а возьмем базовый сценарий использования. Вам дано перечисление:
enum class Color {
kRed,
kGreen,
kBlue
};{}
И в каком-то месте программы вам нужно узнать размер этого перечисления. Вопрос: как в коде получить его размер?
В таком варианте, когда элементам enum'а явно не присвоены никакие числа, каждому из них присвоен порядковый номер, начиная с нуля. kRed - 0, kGreen - 1, kBlue - 2.
Соответственно, чтобы получить количество элементов перечисления нужно сделать такую операцию:
auto size = static_cast<int>(Color::kBlue) + 1;{}
Это работает, но выглядит что-то не очень. Читающий этот код конечно догадывается, что если мы хотим получить размер, то kBlue должен быть последним элементом. Но это вообще никем не гарантируется. Особенно, если в какой-то момент цветов станет больше:
enum class Color {
kRed,
kGreen,
kBlue,
kBlack
};{}
И все. Код получения размера поломался. И надо везде его исправлять теперь. В общем, подход не расширяемый и требует модификации большого количество кода.
На этот случай есть проверенный прием: заранее вставлять в enum фейковый последний элемент, порядковый номер которого и будет равен размеру перечисления:
enum class Color {
kRed,
kGreen,
kBlue,
kCount
};
auto size = static_cast<int>(Color::kCount);{}
В этом случае расширять enum нужно приписывая элементы перед kCount. А код получения размера не меняется.
Эта фишка повсеместно используется в реальных проектах, поэтому новичкам полезно будет это знать.
Create extendable solutions. Stay cool.
#goodpractice #cppcore2169
13:00
15.08.2025
Cпецификатор, модификатор, квалификатор и идентификатор
#новичкам
Когда люди учат иностранные языки, то после определенного уровня они начинают изучать идиомы языка, чтобы звучать как нейтив спикеры.
При описании С++ кода тоже можно пользовать определенные словечки, чтобы все понимали, что ты "про". Среди них выделяются
спецификатор
, модификатор
, квалификатор
и идентификатор
. Они очень похожи и непонятно, в какой ситуации применять эти слова. Сегодня разрушим эту лингвистическую преграду к высотам артикуляции кода.
Начнем с простого. Идентификатор. Это просто имя, которым "идентифицируется" сущность. Имя переменной, константы, функции, класса, шаблона - это идентификаторы. Такие себе id'шники сущностей.
Спецификатор. Это слово скрывает в себе самое большое разнообразие сущностей. В основном это ключевые слова, уточняющие, что это за сущность:
- Спецификаторы типа. Ключевые слова, использующиеся для определения типа или сущности. class и struct(при объявлении класса указываем что идентификатор является классом), enum, все тривиальные типы(char, bool, short, int, long, signed, unsigned, float, double, void), объявленный прежде имена классов, enum'ов, typedef'ов.
- Спецификаторы объявления. typedef, inline, friend, conetexpr, consteval, constinit, static, mutable, extern, thread_local, final, override.
- Спецификаторы доступа к полям классов: private, protected, public.
Модификатор
Модификатор типа - это ключевое слово, которое изменяет поведение стандартных числовых типов. Модификаторами являются: short, insigned, signed, long. Например, unsigned int - это уже беззнаковый тип, в short int - короткий тип инт, который обычно занимает 16 бит вместо 32.
Это слово редко используется, потому что все модификаторы - это спецификаторы. Так что это вносит только путаницу.
Квалификатор
Существует всего 4 квалификатора. cv-квалификаторы: const и volatile. И ref-квалификаторы: & и &&.
Все. Теперь вы native говоруны и можете speak как про С++ coders.
Know the meaning. Stay cool.
#cppcore1595
13:00
17.08.2025
close
С этим каналом часто покупают
Отзывы канала
keyboard_arrow_down
- Добавлен: Сначала новые
- Добавлен: Сначала старые
- Оценка: По убыванию
- Оценка: По возрастанию
5.0
0 отзыва за 6 мес.
m
**cromarketing@****.ru
на сервисе с августа 2023
24.01.202513:24
5
Высокая конверсия
Показать еще
Новинки в тематике
Лучшие в тематике
Статистика канала
Рейтинг
30.1
Оценка отзывов
5.0
Выполнено заявок
29
Подписчики:
9.5K
Просмотры на пост:
lock_outline
ER:
21.8%
Публикаций в день:
1.0
CPV
lock_outlineВыбрано
0
каналов на сумму:0.00₽
Подписчики:
0
Просмотры:
lock_outline
Перейти в корзинуКупить за:0.00₽
Комментарий