ch
Feedback
C++ geek

C++ geek

前往频道在 Telegram

Учим C/C++ на примерах

显示更多
3 612
订阅者
+124 小时
-37
-3830
帖子存档
🚀 Подборка полезных IT каналов в Max Системное администрирование, DevOps 📌 https://max.ru/i_odmin Все для системного администратора https://max.ru/bash_srv Bash Советы https://max.ru/sysadminof Книги для админов, полезные материалы https://max.ru/i_odmin_book Библиотека Системного Администратора https://max.ru/i_devops DevOps: Пишем о Docker, Kubernetes и др. 1C разработка 📌 https://max.ru/odin1c_rus Cтатьи, курсы, советы, шаблоны кода 1С Программирование C++📌 https://max.ru/cpp_lib Библиотека C/C++ разработчика Программирование Go📌 https://max.ru/golang_lib Библиотека Go (Golang) разработчика Программирование React📌 https://max.ru/react_lib React Программирование Python 📌 https://max.ru/python_of Python академия. https://max.ru/BookPython Библиотека Python разработчика Java разработка 📌 https://max.ru/bookjava Библиотека Java разработчика GitHub Сообщество 📌 https://max.ru/githublib Интересное из GitHub Базы данных (Data Base) 📌 https://max.ru/database_info Все про базы данных Фронтенд разработка 📌 https://max.ru/frontend_1 Подборки для frontend разработчиков Библиотеки 📌 https://max.ru/programmist_of Книги по программированию https://max.ru/proglb Библиотека программиста https://max.ru/bfbook Книги для программистов Программирование 📌 https://max.ru/bookflow Лекции, видеоуроки, доклады с IT конференций https://max.ru/itmozg Программисты, дизайнеры, новости из мира IT https://max.ru/php_lib Библиотека PHP программиста 👨🏼‍💻👩‍💻 Шутки программистов 📌 https://max.ru/itumor Шутки программистов Защита, взлом, безопасность 📌 https://max.ru/thehaking Канал о кибербезопасности https://max.ru/xakkep_1 Хакер Free Книги, статьи для дизайнеров 📌 https://max.ru/odesigners Статьи, книги для дизайнеров Математика 📌 https://max.ru/Pomatematike Канал по математике https://max.ru/phismat_1 Обучающие видео, книги по Физике и Математике Вакансии 📌 https://max.ru/progjob Вакансии в IT Мир технологий 📌 https://max.ru/mir_teh Канал для любознательных Бонус 📌 https://max.ru/piterspb_78 Свежие новости Санкт-Петербурга https://max.ru/mockva_life Свежие новости Москвы

♻️ Идеальное преступление: Как создать утечку памяти с помощью умных указателей? С появлением std::shared_ptr в C++ многие выдохнули: "Наконец-то счетчик ссылок всё сделает за нас, больше никаких утечек!". Но умные указатели не обладают интеллектом. И их очень легко обмануть. Самая частая причина «фантомных» утечек памяти в современном C++ это Циклическая зависимость (Circular Dependency). 🪤 Ловушка: Змея, кусающая себя за хвост Представьте игру. У нас есть Игрок (Player) и Гильдия (Guild). • Игрок должен знать, в какой Гильдии он состоит. • Гильдия должна знать, кто её лидер (Игрок). Вы пишете такой код:

struct Player; // Предварительное объявление

struct Guild {
    std::shared_ptr<Player> leader;
    ~Guild() { std::cout << "Guild deleted\n"; }
};

struct Player {
    std::shared_ptr<Guild> myGuild;
    ~Player() { std::cout << "Player deleted\n"; }
};

void Play() {
    auto p = std::make_shared<Player>(); // ref_count(Player) = 1
    auto g = std::make_shared<Guild>();  // ref_count(Guild) = 1
    
    p->myGuild = g; // ref_count(Guild) = 2
    g->leader = p;  // ref_count(Player) = 2
} 
// Конец функции. Локальные p и g уничтожаются.
// ref_count(Player) падает до 1.
// ref_count(Guild) падает до 1.
Итог: Функция завершилась. Объекты больше никому в программе не нужны. Но их деструкторы никогда не вызовутся. Они держат друг друга в заложниках, потому что счетчик не упал до нуля. Вы потеряли память. ⚔️ Спаситель: std::weak_ptr std::weak_ptr - это умный указатель-наблюдатель. Он умеет смотреть на объект, которым владеет shared_ptr, но не увеличивает его счетчик ссылок. Чтобы разорвать цикл, мы должны определить, кто кем владеет (кто важнее), а кто просто ссылается. Допустим, Гильдия существует независимо от лидера, поэтому она будет просто "наблюдать" за ним. ✅ Правильный код:

struct Guild {
    // Слабая ссылка! Не влияет на время жизни Player.
    std::weak_ptr<Player> leader; 
};
Теперь при выходе из функции счетчик Player спокойно упадет до нуля. Player удалится. Его деструктор удалит shared_ptr на Гильдию. Счетчик Гильдии упадет до нуля, и она тоже удалится. Чистая победа! 👀 Как пользоваться weak_ptr? Так как weak_ptr не гарантирует, что объект еще жив (ведь он его не держит), из него нельзя просто так прочитать данные. Вы обязаны превратить его в shared_ptr с помощью метода .lock().

// Если Игрок еще жив, lock() вернет валидный shared_ptr.
// Если Игрок удален, lock() вернет nullptr.
if (std::shared_ptr<Player> ptr = g->leader.lock()) {
    std::cout << "Лидер на месте: " << ptr->name;
} else {
    std::cout << "Лидер покинул нас...";
}
💡 Золотое архитектурное правило: Стройте связи в виде дерева. Сверху вниз - владение (shared_ptr или unique_ptr). Снизу вверх - наблюдение (weak_ptr или обычный *, если время жизни жестко гарантировано). Никогда не делайте цикл из shared_ptr. #cpp #memory #smartpointers #leaks #oop #coding #tips ➡️ @cpp_geek

🧬 Двойная цена std::shared_ptr: Почему профи всегда пишут make_shared? Мы все используем умные указатели. Но то, как вы их создаете, кардинально меняет работу с памятью под капотом. Встречали такой код?

// 🐢 ПЛОХО: Классический подход
std::shared_ptr<User> user(new User());
Кажется, всё логично: выделили память через new, передали в shared_ptr. Но на деле вы заставляете программу сделать две аллокации (выделения памяти) вместо одной. ⚙️ Анатомия shared_ptr std::shared_ptr состоит из двух частей: 1. Сам объект (ваши данные User). 2. Контрольный блок (Control Block) - служебная структура, где лежат счетчики ссылок (reference count) и счетчики weak_ptr. Когда вы пишете std::shared_ptr<User>(new User()), происходит следующее: 1. Отрабатывает new User() - программа идет к ОС и просит кусок памяти. 2. Конструктор shared_ptr видит сырой указатель, понимает, что ему нужен Контрольный блок, и еще раз идет к ОС за вторым куском памяти. Два системных вызова. Фрагментация кучи (heap). Промахи кэша процессора, потому что объект и счетчик лежат в разных концах памяти. 🚀 Решение: std::make_shared

// 🚀 ХОРОШО: Единый блок памяти
auto user = std::make_shared<User>();
Что делает make_shared? Он считает размер вашего объекта User + размер Контрольного блока, и просит у операционной системы один большой кусок памяти за один раз. Плюсы:В 2 раза меньше аллокаций. Код работает быстрее. • Cache Locality. Объект и счетчик ссылок лежат в памяти впритык друг к другу. Процессор это обожает. • Безопасность. До C++17 старый подход с new мог привести к утечке памяти, если функция принимала несколько аргументов и один из них бросал исключение. С make_shared это исключено. 🦇 Темная сторона (О чем не пишут в туториалах) Есть ровно один случай, когда make_shared может навредить. Это связано со слабыми указателями (std::weak_ptr). Если вы удалили все shared_ptr, вызывается деструктор объекта User. Но если остался хотя бы один weak_ptr, Контрольный блок обязан жить! А так как make_shared склеил Контрольный блок и объект в один кусок памяти, оперативная память из-под объекта User не вернется системе, пока жив weak_ptr (даже если сам объект уже "мертв" и деструктор отработал). Если ваш объект весит 500 Мегабайт - вы получите «фантомную» утечку памяти. 💡В 99% случаев используйте std::make_shared. Используйте new std::shared_ptr только если у вас гигантские объекты, на которые подолгу смотрят «зависшие» weak_ptr, или если вам нужен кастомный удалитель (custom deleter). #cpp #memory #pointers #optimization #sharedptr #coding #tips ➡️ @cpp_geek

🚦 Многопоточность без тормозов: std::atomic против std::mutex Мы все знаем классику: если несколько потоков одновременно пишут в одну переменную, случается Data Race (гонка данных), и программа выдает мусор или падает. Первое, чему нас учат - ставьте std::mutex. Но мьютексы могут убить производительность вашего приложения. 🐢 Почему std::mutex такой медленный? Мьютекс - это тяжеловесный механизм операционной системы. Если Поток А захватил мьютекс, а Поток Б пытается сделать то же самое, ОС видит, что «дверь закрыта». ОС усыпляет Поток Б (происходит Context Switch) и отдает ядро процессора кому-то другому. Когда Поток А отпускает мьютекс, ОС должна снова «разбудить» Поток Б. Смена контекста и пробуждение — это тысячи потерянных тактов процессора. Использовать мьютекс ради того, чтобы просто сделать counter++ - это как вызывать спецназ, чтобы разнять дерущихся котят. 🚀 Решение: std::atomic (Lock-Free магия) Вместо того чтобы просить ОС усыплять потоки, мы можем использовать std::atomic. Он работает на уровне самого железа (процессора). Для атомиков компилятор генерирует специальные ассемблерные инструкции (например, с префиксом LOCK на архитектуре x86). Процессор сам на аппаратном уровне гарантирует, что инкремент произойдет неделимо (атомарно). Никаких обращений к ОС, никаких засыпаний! 🆚 Давайте сравним в коде:

// 🐢 ТЯЖЕЛОВЕСНО (std::mutex)
std::mutex mtx;
int counter = 0;

void AddMutex() {
    std::lock_guard<std::mutex> lock(mtx);
    counter++; // Заморозили поток ОС ради одной операции!
}

// 🚀 БЕЗ БЛОКИРОВОК (std::atomic)
std::atomic<int> counter = 0;

void AddAtomic() {
    counter++; // Выполняется за наносекунды на уровне CPU
}
Разница в скорости на простых операциях типа счетчиков или флагов может достигать 50-100 раз в пользу std::atomic! ⚖️ Когда что использовать? Нельзя просто взять и везде заменить мьютексы на атомики. • ✅ Используйте std::atomic, если вам нужно защитить только одну простую переменную (счетчик метрик, флаг остановки bool, указатель на узел в lock-free очереди). • 🛑 Используйте std::mutex, если вам нужно выполнить сложную логику, защитить кусок памяти (std::vector, std::map) или обновить сразу две и более переменных одновременно. 💡 Итог: Многопоточность - это искусство компромисса. Оставляйте тяжелые замки (mutex) для больших комнат, а для маленьких сейфов (int, bool) используйте умные аппаратные ключи (atomic). #cpp #multithreading #atomic #mutex #optimization #coding #tips ➡️ @cpp_geek

🚀 Подборка полезных IT каналов в Max Системное администрирование, DevOps 📌 https://max.ru/i_odmin Все для системного администратора https://max.ru/bash_srv Bash Советы https://max.ru/sysadminof Книги для админов, полезные материалы https://max.ru/i_odmin_book Библиотека Системного Администратора https://max.ru/i_devops DevOps: Пишем о Docker, Kubernetes и др. 1C разработка 📌 https://max.ru/odin1c_rus Cтатьи, курсы, советы, шаблоны кода 1С Программирование C++📌 https://max.ru/cpp_lib Библиотека C/C++ разработчика Программирование Python 📌 https://max.ru/python_of Python академия. https://max.ru/BookPython Библиотека Python разработчика Java разработка 📌 https://max.ru/bookjava Библиотека Java разработчика GitHub Сообщество 📌 https://max.ru/githublib Интересное из GitHub Базы данных (Data Base) 📌 https://max.ru/database_info Все про базы данных Фронтенд разработка 📌 https://max.ru/frontend_1 Подборки для frontend разработчиков Библиотеки 📌 https://max.ru/programmist_of Книги по программированию https://max.ru/proglb Библиотека программиста https://max.ru/bfbook Книги для программистов Программирование 📌 https://max.ru/bookflow Лекции, видеоуроки, доклады с IT конференций https://max.ru/itmozg Программисты, дизайнеры, новости из мира IT https://max.ru/php_lib Библиотека PHP программиста 👨🏼‍💻👩‍💻 Шутки программистов 📌 https://max.ru/itumor Шутки программистов Защита, взлом, безопасность 📌 https://max.ru/thehaking Канал о кибербезопасности https://max.ru/xakkep_1 Хакер Free Книги, статьи для дизайнеров 📌 https://max.ru/odesigners Статьи, книги для дизайнеров Математика 📌 https://max.ru/Pomatematike Канал по математике https://max.ru/phismat_1 Обучающие видео, книги по Физике и Математике Вакансии 📌 https://max.ru/progjob Вакансии в IT Мир технологий 📌 https://max.ru/mir_teh Канал для любознательных Бонус 📌 https://max.ru/piterspb_78 Свежие новости Санкт-Петербурга https://max.ru/mockva_life Свежие новости Москвы

🎯 Как избежать макросов в C++ и остаться довольным Сегодня я покажу вам, как можно избавиться от макросов в C++ и заменить и
🎯 Как избежать макросов в C++ и остаться довольным Сегодня я покажу вам, как можно избавиться от макросов в C++ и заменить их на более безопасные и выразительные конструкции. 🔴 Проблема: #define — это зло. Они не уважают область видимости, не отлаживаются нормально, не подчиняются типам и могут вызвать кучу проблем, особенно в больших проектах. 👉 Вместо #define PI 3.14 Используем:

constexpr double PI = 3.14;
👉 Вместо #define SQUARE(x) ((x)*(x)) Используем шаблон:

template<typename T>
constexpr T square(T x) {
    return x * x;
}
👉 Вместо #ifdef DEBUG ... #endif Используем:

#ifdef DEBUG
inline constexpr bool is_debug = true;
#else
inline constexpr bool is_debug = false;
#endif
А дальше просто:

if constexpr (is_debug) {
    std::cout << "Debug mode\n";
}
💡 constexpr, inline, template и if constexpr — это ваш новый арсенал для выразительного и безопасного кода без макросов. ➡️ @cpp_geek

🚀 Подборка полезных IT каналов в Max Системное администрирование, DevOps 📌 https://max.ru/i_odmin Все для системного администратора https://max.ru/bash_srv Bash Советы https://max.ru/sysadminof Книги для админов, полезные материалы https://max.ru/i_odmin_book Библиотека Системного Администратора https://max.ru/i_devops DevOps: Пишем о Docker, Kubernetes и др. 1C разработка 📌 https://max.ru/odin1c_rus Cтатьи, курсы, советы, шаблоны кода 1С Программирование C++📌 https://max.ru/cpp_lib Библиотека C/C++ разработчика Программирование Python 📌 https://max.ru/python_of Python академия. https://max.ru/BookPython Библиотека Python разработчика Java разработка 📌 https://max.ru/bookjava Библиотека Java разработчика GitHub Сообщество 📌 https://max.ru/githublib Интересное из GitHub Базы данных (Data Base) 📌 https://max.ru/database_info Все про базы данных Фронтенд разработка 📌 https://max.ru/frontend_1 Подборки для frontend разработчиков Библиотеки 📌 https://max.ru/programmist_of Книги по программированию https://max.ru/proglb Библиотека программиста https://max.ru/bfbook Книги для программистов Программирование 📌 https://max.ru/bookflow Лекции, видеоуроки, доклады с IT конференций https://max.ru/itmozg Программисты, дизайнеры, новости из мира IT https://max.ru/php_lib Библиотека PHP программиста 👨🏼‍💻👩‍💻 Шутки программистов 📌 https://max.ru/itumor Шутки программистов Защита, взлом, безопасность 📌 https://max.ru/thehaking Канал о кибербезопасности https://max.ru/xakkep_1 Хакер Free Книги, статьи для дизайнеров 📌 https://max.ru/odesigners Статьи, книги для дизайнеров Математика 📌 https://max.ru/Pomatematike Канал по математике https://max.ru/phismat_1 Обучающие видео, книги по Физике и Математике Вакансии 📌 https://max.ru/progjob Вакансии в IT Мир технологий 📌 https://max.ru/mir_teh Канал для любознательных Бонус 📌 https://max.ru/piterspb_78 Свежие новости Санкт-Петербурга https://max.ru/mockva_life Свежие новости Москвы

🎭 Сколько стоит virtual? Вся правда о полиморфизме и магии final Мы обожаем интерфейсы и ООП. Добавить virtual перед методом - минутное дело, и вот наш код уже гибкий и расширяемый. Но задумывались ли вы, чем мы за это платим на уровне железа? ⚙️ Анатомия виртуального вызова (vtable) Если в классе есть хотя бы одна виртуальная функция, компилятор втайне добавляет в каждый объект этого класса скрытое поле - vptr (указатель на виртуальную таблицу). Сама таблица (vtable) хранится где-то в памяти и содержит адреса реальных функций. Как происходит вызов obj->DoWork() под капотом: 1. Процессор идет по адресу объекта obj. 2. Читает скрытый указатель vptr. 3. Делает прыжок в память, где лежит vtable. 4. Находит там нужный адрес функции для конкретного класса-наследника. 5. Делает еще один прыжок, чтобы выполнить код. 🚨 Почему это бьет по производительности? Дело даже не в лишних прыжках по памяти (хотя промахи кэша процессора - это больно). Главная проблема: Виртуальность убивает оптимизации. Когда компилятор видит вызов виртуальной функции через указатель, он "слепнет". Он не знает, код какого именно наследника будет вызван во время работы программы (Run-time). Из-за этого он не может применить Inlining (встраивание тела функции вместо вызова) - а это самая мощная оптимизация в C++. 🛡 Спаситель из C++11: ключевое слово final Слово final запрещает дальнейшее наследование класса или переопределение метода. Но кроме защиты архитектуры, оно делает невероятное: включает Девиртуализацию (Devirtualization).

class Base {
public:
    virtual void Process() = 0;
};

// Мы жестко фиксируем класс: от него нельзя наследоваться!
class Derived final : public Base {
public:
    void Process() override { 
        /* важная логика */ 
    }
};

void RunOptimized(Derived* obj) {
    // Компилятор видит: тип obj — Derived. 
    // Derived помечен как final. Значит, никто физически 
    // не мог переопределить метод Process!
    
    // 🚀 МАГИЯ: Компилятор выбрасывает vtable, игнорирует vptr 
    // и превращает вызов в обычный, или вообще инлайнит (встраивает) его!
    obj->Process(); 
}

💡 Золотое правило современного C++: Относитесь к классам как к запечатанным. Пишите final для всех классов (особенно тех, что реализуют интерфейсы), если только вы не проектируете их специально для дальнейшего наследования. Вы получите защиту от глупых архитектурных ошибок и бесплатный прирост скорости! #cpp #cpp11 #oop #optimization #performance #coding #tips ➡️ @cpp_geek

При каких условиях ReadFile или WriteFile могут не передать все байты, и как это обнаружить? Один из клиентов хотел понять, в каких случаях функции ReadFile и WriteFile могут не передать все байты, и как определить, что это произошло. Очевидная причина, по которой ReadFile может не прочитать все байты, - это то, что просто нет такого количества данных для чтения. - Для файлов на диске это обычно происходит, если попытаться читать за концом файла. - То же может случиться и с другими типами дескрипторов: для pipe в неблокирующем режиме в пайпе может не оказаться достаточного количества данных. - Если это message pipe, сообщение может быть меньше, чем размер вашего буфера. - Или при обращении к устройству - оно может не иметь всех байтов в наличии. Аналогично, очевидная причина, по которой WriteFile может не записать все байты, — это нехватка места. - Для файлов на диске это может быть полный диск или достижение лимита квоты. - Для pipe в неблокирующем режиме запись может быть укорочена, если в буфере пайпа недостаточно места, чтобы принять все запрошенные данные. Во всех таких случаях укороченную запись можно обнаружить, проверив, меньше ли число реально записанных байт по сравнению с запрошенным. Если количество реально переданных байт больше нуля, то функции ReadFile и WriteFile вернут успех, но при этом передадут меньше байт, чем было запрошено. ➡️ @cpp_geek

C++: Заставьте компилятор работать за вас (constexpr и consteval) Вы когда-нибудь хотели, чтобы ваша программа мгновенно выдавала результат сложных вычислений в момент запуска? Это возможно, если переложить тяжелую математику на... ваш компилятор! В современном C++ мы можем «запекать» результаты функций прямо в итоговый .exe файл. Для этого есть два инструмента. 1. constexpr - «Вычисли до запуска, если сможешь» (C++11) Ключевое слово constexpr говорит компилятору: "Если все аргументы этой функции известны заранее, вычисли её прямо сейчас. Если нет - оставь до выполнения программы (Run-time)". Это невероятно удобно для универсальных функций.

// Функция может работать и до запуска, и во время!
constexpr int GetArea(int width, int height) {
    return width * height;
}

int main() {
    // 🚀 Вычислится компилятором! В код вставится просто "200".
    // Zero runtime cost.
    int a = GetArea(10, 20); 

    int w;
    std::cin >> w;
    // 🐢 Вычислится процессором во время работы (w неизвестно заранее).
    int b = GetArea(w, 20); 
}

2. consteval - «Вычисли до запуска, или умри!» (C++20) У constexpr есть проблема: мы не всегда уверены, вычислилась ли функция компилятором, или она тихо «соскользнула» в Run-time, замедляя программу. Поэтому в C++20 добавили consteval. Это строгий приказ (Immediate Function). Если компилятор не может выполнить функцию прямо сейчас - он выдаст ошибку компиляции.

// Обязана выполниться во время компиляции
consteval int MagicHash(std::string_view str) {
    int hash = 0;
    for (char c : str) hash += c;
    return hash;
}

int main() {
    // ✅ Отлично. Компилятор сам посчитает хэш слова "admin".
    int h1 = MagicHash("admin"); 

    std::string user_input = "test";
    // ❌ ОШИБКА КОМПИЛЯЦИИ! user_input нельзя знать заранее.
    int h2 = MagicHash(user_input); 
}

📈 Зачем это нужно? 1. Максимальная производительность: Вы переносите время выполнения на этап сборки программы. Для пользователя всё работает за O(1). 2. Замена #define: Раньше константы и простые формулы писали через макросы препроцессора. Теперь constexpr делает это безопасно, с проверкой типов. 3. Безопасность: С consteval вы гарантируете, что тяжелая инициализация (например, генерация таблиц поиска) не ударит по производительности в продакшене. 💡Итог: Пишете математику или чистые функции без побочных эффектов? Ставьте constexpr. Хотите 100% гарантию, что вычисления не попадут в готовый бинарник? Ставьте consteval. #cpp #cpp20 #constexpr #optimization #performance #coding #tips ➡️ @cpp_geek

🚀 Подборка полезных IT каналов в Max Системное администрирование, DevOps 📌 https://max.ru/i_odmin Все для системного администратора https://max.ru/bash_srv Bash Советы https://max.ru/sysadminof Книги для админов, полезные материалы https://max.ru/i_odmin_book Библиотека Системного Администратора https://max.ru/i_devops DevOps: Пишем о Docker, Kubernetes и др. 1C разработка 📌 https://max.ru/odin1c_rus Cтатьи, курсы, советы, шаблоны кода 1С Программирование C++📌 https://max.ru/cpp_lib Библиотека C/C++ разработчика Программирование Python 📌 https://max.ru/python_of Python академия. https://max.ru/BookPython Библиотека Python разработчика Java разработка 📌 https://max.ru/bookjava Библиотека Java разработчика GitHub Сообщество 📌 https://max.ru/githublib Интересное из GitHub Базы данных (Data Base) 📌 https://max.ru/database_info Все про базы данных Фронтенд разработка 📌 https://max.ru/frontend_1 Подборки для frontend разработчиков Библиотеки 📌 https://max.ru/programmist_of Книги по программированию https://max.ru/proglb Библиотека программиста https://max.ru/bfbook Книги для программистов Программирование 📌 https://max.ru/bookflow Лекции, видеоуроки, доклады с IT конференций https://max.ru/itmozg Программисты, дизайнеры, новости из мира IT https://max.ru/php_lib Библиотека PHP программиста 👨🏼‍💻👩‍💻 Шутки программистов 📌 https://max.ru/itumor Шутки программистов Защита, взлом, безопасность 📌 https://max.ru/thehaking Канал о кибербезопасности https://max.ru/xakkep_1 Хакер Free Книги, статьи для дизайнеров 📌 https://max.ru/odesigners Статьи, книги для дизайнеров Математика 📌 https://max.ru/Pomatematike Канал по математике https://max.ru/phismat_1 Обучающие видео, книги по Физике и Математике Вакансии 📌 https://max.ru/progjob Вакансии в IT Мир технологий 📌 https://max.ru/mir_teh Канал для любознательных Бонус 📌 https://max.ru/piterspb_78 Свежие новости Санкт-Петербурга https://max.ru/mockva_life Свежие новости Москвы

🌉 Забудьте про передачу указателей и размеров! (std::span) Помните, мы обсуждали std::string_view - легковесное «окно» для строк? В C++20 у него появился старший брат для массивов и векторов - std::span. До C++20 у нас была классическая проблема. Допустим, вы пишете функцию, которая должна обработать список чисел. 🐢 Как мы писали раньше: Вариант 1: Принимать const std::vector<int>&. Минус: Функция теперь намертво привязана к std::vector. Если у вас данные лежат в std::array или обычном си-массиве int arr[10], придется копировать их в вектор. Аллокации, тормоза. Вариант 2: Си-стайл (Указатель + размер). Минус: Легко ошибиться с размером, потерять контекст, код выглядит грязно.

void ProcessOld(const int* data, size_t size) { /* ... */ }

🚀 Как мы пишем теперь (C++20):

#include <span>

// Принимаем любой непрерывный кусок памяти!
void ProcessNew(std::span<const int> data) {
    for (int val : data) {
        std::cout << val << " ";
    }
}

👀 Что такое std::span? Как и string_view, это просто указатель на начало данных и их длина (обычно 16 байт). Он не владеет памятью, он только на нее смотрит. Магия в том, что std::span умеет автоматически создаваться из чего угодно:

std::vector<int> vec = {1, 2, 3};
std::array<int, 3> arr = {4, 5, 6};
int raw[3] = {7, 8, 9};

// Одна функция работает со всеми типами контейнеров! Без копирования!
ProcessNew(vec); 
ProcessNew(arr);
ProcessNew(raw);

✂️ Суперсила: Subspan (Подмассивы) Вам нужно передать в функцию только часть вектора, например, со 2-го по 5-й элемент? Никаких итераторов и копирования:

// Передаем кусок вектора за O(1)
ProcessNew( std::span{vec}.subspan(1, 4) ); 

⚠️ Важный нюанс: std::span не умеет изменять размер данных (никаких push_back). Но он может изменять сами элементы, если вы передадите std::span<int> (без const). 💡 Итог: Если ваша функция принимает набор данных только для чтения или изменения элементов на месте, всегда используйте std::span. Это золотой стандарт современного C++. #cpp #cpp20 #stdspan #optimization #memory #coding #tips ➡️ @cpp_geek

🗺 std::map или std::unordered_map: Битва за кэш Когда нам нужно хранить пары «Ключ - Значение», рука сама тянется написать std::map. Это стандарт, это удобно, это сортировка из коробки. Но с точки зрения производительности std::map это часто худший выбор. Почему? 🌲 1. std::map - Это Дерево (Red-Black Tree) Каждый элемент в map - это отдельный узел (Node), выделенный в куче (new). Узлы разбросаны по памяти хаотично. • Чтобы найти элемент, процессор прыгает по указателям: Root -> Left -> Right -> ... • Каждый прыжок - это потенциальный Cache Miss (промах кэша). Процессор ждет сотни тактов, пока данные подтянутся из RAM. • Сложность поиска: O(log N). ⚡ 2. std::unordered_map - Это Хеш-таблица Здесь нет деревьев. Ключ превращается в число (хеш), и мы сразу прыгаем в нужную ячейку массива (Bucket). • Массивы любят кэш процессора (Cache Locality). • Сложность поиска: O(1) (в среднем). Это мгновенно. 🐢 Насколько велика разница? На маленьких объемах (до 100 элементов) разницы почти нет. Но на 1,000,000 элементов std::unordered_map может быть в 3-5 раз быстрее просто за счет отсутствия прыжков по памяти. 🤔 Когда использовать std::map? Только в одном случае: Вам жизненно важен порядок ключей. Например, если вы хотите вывести пользователей по алфавиту или найти диапазон дат (lower_bound / upper_bound). 🚀 Бонус: C++23 std::flat_map В новом стандарте завезли std::flat_map. Это гибрид: интерфейс как у map (сортированный), но внутри - сплошной вектор. Это самый быстрый вариант для поиска, но медленный для вставки. Если у вас C++23 - присмотритесь! 💡 Итог: если вам не нужна сортировка, всегда пишите std::unordered_map. Не заставляйте процессор бегать по дереву указателей без причины. #cpp #stl #optimization #performance #map #hashing #coding #tips ➡️ @cpp_geek

😱 std::vector<bool>: Великий обман C++ Вы думаете, что std::vector<bool> это просто вектор, который хранит булевы значения? Нет. Это совершенно уникальный монстр, который нарушает правила стандартной библиотеки. 📉 В чем подвох? Обычный bool занимает 1 байт (минимум адресуемой памяти). Но создатели C++ решили сэкономить память. std::vector<bool> - это специализация. Внутри него каждый bool занимает всего 1 бит. В одном байте хранится сразу 8 значений true/false. Экономия памяти в 8 раз! Круто? 🛑 Проблема: Вы не можете взять адрес элемента В C++ нельзя создать указатель или ссылку на отдельный бит. Память адресуется байтами.

std::vector<int> nums = {1, 2};
int* p = &nums[0]; // ✅ ОК. Указатель на первый int.

std::vector<bool> flags = {true, false};
bool* b = &flags[0]; // ❌ ОШИБКА КОМПИЛЯЦИИ!
// Мы не можем получить адрес бита.

🤖 Проблема: Прокси-объекты Когда вы пишете flags[0], вектор возвращает не bool& (ссылку), а специальный временный объект - Proxy Class (std::vector<bool>::reference). Этот объект "притворяется" ссылкой. Когда вы присваиваете ему значение, он делает побитовые сдвиги и маски (&, |, <<), чтобы изменить нужный бит внутри байта. Это медленно. ⚠️ Ловушка с auto

std::vector<bool> vec = {true, false};

// Вы думаете, что val — это bool.
// На самом деле val — это 'std::vector<bool>::reference'.
auto val = vec[0]; 

vec.push_back(true); // Реаллокация памяти!

// 💥 Если val — это прокси, он может ссылаться на 
// старую, уже удаленную память вектора.
val = false; // Undefined Behavior / Crash

💡 Что делать? 1. Если вам важна память: Используйте std::vector<bool> (или std::bitset для фиксированного размера). 2. Если вам важна скорость: Используйте std::vector<char> или std::vector<uint8_t>. Это займет в 8 раз больше памяти, но будет работать мгновенно, и вы получите нормальные ссылки. 3. Осторожно с auto: Всегда пишите тип явно: bool val = vec[0];, чтобы заставить прокси превратиться в значение. #cpp #stl #vector #gotchas #memory #coding #tips ➡️ @cpp_geek

🪄 Магия std::string: Почему короткие строки работают быстрее? (SSO) Многие думают, что std::string - это всегда: 1. Выделение памяти в куче (new / malloc). 2. Копирование данных туда. 3. Освобождение памяти (delete) в деструкторе. Это медленно. Но если вы создадите строку "Hello", никаких аллокаций не произойдет. Почему? Благодаря Small String Optimization (SSO). ⚙️ Как это работает? Стандартная строка (на 64-битной системе) обычно занимает 24 или 32 байта (размер самой структуры sizeof(std::string)). В ней хранятся указатель на данные, размер и вместимость (capacity). Разработчики STL подумали: "Зачем нам тратить эти байты на указатели, если строка очень короткая? Давайте хранить текст прямо внутри объекта!" Внутри std::string используется union: ⚫️Вариант А (Длинная строка): Хранит указатель на кучу (Heap), размер и вместимость. ⚫️Вариант Б (Короткая строка): Использует те же байты памяти как буфер для хранения символов. 📏 Где граница? Это зависит от компилятора: ⚫️MSVC (Windows): ~15 символов. ⚫️GCC (Linux): ~15 символов. ⚫️Clang (libc++): ~22 символа (благодаря хитрому сжатию битов). Пример:

void Benchmark() {
    // 🚀 БЫСТРО (SSO):
    // Память не выделяется. Строка лежит на стеке, как char[16].
    std::string shortStr = "Hello World"; 

    // 🐢 МЕДЛЕННО (Heap Allocation):
    // Текст не влезает в буфер SSO. 
    // Вызывается malloc/new, данные летят в кучу.
    std::string longStr = "Hello World is a remarkably long phrase";
}

📉 Почему это важно для производительности? 1. Нет аллокаций: new и delete - это системные вызовы, они дорогие. SSO их исключает. 2. Cache Locality: Данные лежат на стеке, рядом с другими локальными переменными. Процессор обожает линейный доступ к памяти (L1 Cache), а прыжки в кучу (Heap) - ненавидит. 💡 Совет: Если вы оптимизируете структуру данных и у вас много коротких ID или имен (до 15 символов), обычный std::string будет работать великолепно без всяких хитростей. Не нужно менять его на char[] "для скорости" без замеров. #cpp #optimization #sso #memory #stdstring #coding #tips ➡️ @cpp_geek

🔒 const в C++: Скрытый смысл, о котором молчат Мы привыкли думать, что const после имени метода это просто защита от дурака: "Я обещаю не менять поля класса внутри этой функции". Но в современном C++ (и в стандартной библиотеке STL) const означает нечто большее. Это контракт потокобезопасности (Thread Safety Contract). 🧵 Золотое правило STL: 1. const методы можно вызывать из разных потоков одновременно без блокировок. (Safe for concurrent reads). 2. Не-const методы требуют внешней синхронизации, если их вызывают несколько потоков. 🚨 Где кроется ловушка? Ловушка в ключевом слове mutable. Оно позволяет менять поля даже внутри const метода. Обычно это используют для кэширования или ленивых вычислений. ❌ ОПАСНЫЙ КОД (Логический const, но физическая гонка):

class Widget {
    mutable int cachedValue_ = -1; // Можно менять в const методе

public:
    // Метод помечен const. Пользователь думает, что он безопасен 
    // для вызова из 10 потоков одновременно.
    int GetValue() const {
        if (cachedValue_ == -1) {
            // 💥 DATA RACE! 
            // Два потока могут одновременно зайти сюда и начать писать.
            cachedValue_ = HeavyCalculation(); 
        }
        return cachedValue_;
    }
};

Если вы пишете библиотеку и помечаете метод как const, пользователи будут вызывать его параллельно, не используя мьютексы. Если внутри у вас есть несинхронизированный mutable - программа упадет. ✅ Правильный подход: Если вы используете mutable, вы обязаны защитить его мьютексом.

class Widget {
    mutable std::mutex mtx_; // Мьютекс тоже должен быть mutable!
    mutable int cachedValue_ = -1;

public:
    int GetValue() const {
        std::lock_guard<std::mutex> lock(mtx_); // Блокируем поток
        
        if (cachedValue_ == -1) {
            cachedValue_ = HeavyCalculation();
        }
        return cachedValue_;
    }
};

💡 Итог: В C++ const - это не только "я не меняю данные". Это обещание: "Этот метод безопасен для одновременного вызова". Если вы нарушаете это обещание (используя mutable без защиты), вы создаете бомбу замедленного действия. #cpp #multithreading #const #safety #coding #tips ➡️ @cpp_geek

🏗 Тетрис в памяти: Почему порядок полей в классе важен? Вы создали простую структуру: bool, int и еще один bool. Математика проста: 1 байт + 4 байта + 1 байт = 6 байт. Вы проверяете через sizeof и видите... 12 байт. 🤯 Куда делись еще 6 байт? Вы только что потеряли 50% памяти на "воздух". Это называется Padding (Выравнивание). ⚙️ Как это работает? Процессор не любит читать данные по произвольным адресам. Ему удобно читать кусками по 4 или 8 байт (слова). Чтобы int (4 байта) не "разломился" посередине двух слов, компилятор вставляет пустые байты-заглушки. ❌ Плохой пример (Bad Layout):

struct Bad {
    bool a; // 1 байт
    // ... 3 байта PADDING (воздух) ...
    int b;  // 4 байта (должен начинаться с кратного 4 адреса)
    bool c; // 1 байт
    // ... 3 байта PADDING (чтобы выровнять общий размер) ...
};
// Итог: 12 байт

✅ Хороший пример (Good Layout): Просто меняем порядок полей. Правило: "От больших к маленьким".

struct Good {
    int b;  // 4 байта
    bool a; // 1 байт
    bool c; // 1 байт
    // ... 2 байта PADDING (добиваем до кратности 4) ...
};
// Итог: 8 байт

📉 Почему это важно? Кажется, что 4 байта ерунда. Но если у вас std::vector<Bad> на 1,000,000 элементов: ⚫️Bad: ~12 MB памяти. ⚫️Good: ~8 MB памяти. Вы экономите 4 мегабайта просто переставив строчки местами! Плюс, более плотные данные лучше ложатся в кэш процессора (CPU Cache), что ускоряет обработку. 💡 Совет: Объявляйте поля в порядке убывания их размера: 1. Указатели и double (8 байт) 2. int, float (4 байта) 3. short (2 байта) 4. bool, char (1 байт) #cpp #optimization #memory #alignment #coding #tips ➡️ @cpp_geek

🏗 Анатомия std::vector: Что происходит, когда место заканчивается? std::vector - самый популярный контейнер в C++. Мы просто пишем push_back, и магия работает. Но что происходит «под капотом», когда вы пытаетесь добавить элемент, а свободное место (capacity) закончилось? Происходит Реаллокация. И это гораздо дороже, чем просто добавление числа. ⚙️ Сценарий катастрофы (пошагово): Допустим, у вектора было место под 4 элемента, и оно занято. Вы добавляете 5-й. 1. Поиск новой земли: Вектор понимает, что текущий буфер полон. Он просит у операционной системы выделить новый блок памяти (обычно в 1.5 или 2 раза больше старого). 2. Великое переселение: Все элементы из старого блока копируются (или перемещаются) в новый. - Представьте: чтобы поставить на полку одну новую книгу, вам приходится переезжать в новую квартиру и перетаскивать туда всю библиотеку. 3. Зачистка: Старые объекты разрушаются (вызываются деструкторы), а старая память возвращается системе. 4. Вставка: И только теперь новый элемент добавляется в хвост. 🚨 Почему это проблема? 1. Удар по производительности Операция push_back обычно мгновенна (). Но при реаллокации она превращается в тяжелую операцию . Если вектор огромный, программа может «подвиснуть» в самый неподходящий момент. 2. Инвалидация ссылок (Источник багов №1) Это самое опасное. Как только произошла реаллокация, старая память удаляется. Все указатели, ссылки и итераторы, которые смотрели на элементы вектора, становятся невалидными.

std::vector<int> data = {1, 2, 3, 4};
int& ref = data[0]; // Ссылка на первый элемент

// Добавляем элемент -> места нет -> реаллокация!
data.push_back(5); 

// ☠️ ОШИБКА: ref ссылается на очищенную память.
// Получим мусор или краш программы.
std::cout << ref; 

🛡 Как лечить? Если вы знаете (хотя бы примерно), сколько элементов будет в векторе - используйте reserve().

std::vector<int> data;
data.reserve(1000); // Сразу выделяем память

// Теперь реаллокации точно не будет, 
// пока мы не превысим 1000 элементов.

💡 Итог: Помогайте вектору с помощью reserve(). Это спасает и от тормозов, и от сложнейших багов с памятью. #cpp #stdvector #memory #performance #coding #tips ➡️ @cpp_geek

🏗 Анатомия std::vector::push_back: Когда память заканчивается Мы все любим push_back. Это удобно: просто кидаешь данные в вектор, а он сам разбирается с памятью. Но что происходит, когда вы добавляете элемент, а место (capacity) закончилось? Происходит Реаллокация (Reallocation). И это дорогая операция. ⚙️ Что происходит «под капотом»? 1. Поиск новой земли: Вектор понимает, что текущий буфер полон. Он просит у операционной системы выделить новый блок памяти. Обычно он в 1.5 или 2 раза больше предыдущего (геометрический рост). 2. Великое переселение: Все элементы из старого блока копируются (или перемещаются, если есть noexcept move-конструктор) в новый блок. ⚫️Представьте, что вы перевозите 10,000 коробок в новый дом только ради того, чтобы поставить еще одну. 3. Зачистка: Для всех объектов в старом блоке вызываются деструкторы. 4. Снос: Старая память возвращается системе. 🚨 Почему это проблема? 1. Удар по производительности: Обычно push_back работает за амортизированное O(1) (мгновенно). Но в момент реаллокации сложность подскакивает до O(N). Это вызывает непредсказуемые лаги (latency spikes). 2. Инвалидация итераторов и ссылок (ОПАСНО): Это источник багов №1. После реаллокации старая память удалена. Все указатели, ссылки и итераторы, которые смотрели на элементы вектора, становятся недействительными.

std::vector<int> vec = {1, 2, 3};
int& ref = vec[0]; // Ссылка на первый элемент

// ... добавляем много элементов, вызывая реаллокацию ...
for(int i=0; i < 100; ++i) vec.push_back(i);

// 💥 Вектор переехал. Старая память удалена. 
// ref теперь указывает в мусор.
std::cout << ref; // Undefined Behavior (Crash или мусор)

🛡 Как лечить? Если вы хотя бы примерно знаете, сколько элементов будет в векторе, всегда используйте reserve().

std::vector<User> users;
users.reserve(1000); // Сразу выделяем память под 1000 мест

// Теперь первые 1000 push_back будут дешевыми
// и гарантированно не вызовут реаллокации.

💡 Итог: std::vector это мощный инструмент, но за его автоматическое расширение платит процессор. Помогайте ему через reserve(), чтобы код был быстрым и безопасным. #cpp #stdvector #performance #memory #coding #tips ➡️ @cpp_geek

📦 std::move vs std::forward: Когда и зачем? На собеседованиях часто спрашивают про rvalue-ссылки, но в реальном коде мы постоянно путаемся: когда делать move, а когда forward? Давайте разберем на жизненных примерах. 1. std::move - "Это мое, но забирай!" 🚚 std::move - это безусловное приведение к rvalue. Вы говорите компилятору: "Мне этот объект больше не нужен. Можешь выпотрошить его и забрать данные, не копируя их". Сценарий 1: Передача владения (unique_ptr) Это классика. std::unique_ptr нельзя скопировать, его можно только переместить.

auto ptr = std::make_unique<BigData>();

// process(ptr); // ❌ Ошибка компиляции! Копирование запрещено.
process(std::move(ptr)); // ✅ ОК. Владение передано, ptr теперь пуст.

Сценарий 2: Оптимизация тяжелых объектов У вас есть локальный вектор, который вы хотите сохранить в поле класса. Зачем его копировать?

void SetData(std::vector<int> newData) {
    // Мы крадем буфер памяти у newData. 
    // Копирования элементов НЕ происходит.
    this->data_ = std::move(newData); 
}

2. std::forward - "Я просто посредник" 📮 std::forward используется почти исключительно в шаблонах. Его цель - Perfect Forwarding (Идеальная передача). Представьте, что вы пишете функцию-обертку (wrapper). Она принимает аргумент и должна передать его дальше другой функции. ⚫️Если ей передали временный объект (rvalue) - она должна передать его как rvalue (чтобы сработал move). ⚫️Если передали обычную переменную (lvalue) - она должна передать как lvalue (копия). std::move здесь всё испортит (он всё превратит в rvalue). Тут нужен std::forward. Сценарий: Фабрики и Обертки

template <typename T>
void LogAndAdd(std::vector<T>& vec, T&& item) {
    std::cout << "Adding item...";
    
    // forward сохранит категорию значения item.
    // Если item был временным — сработает push_back(T&&) (перемещение).
    // Если item был переменной — сработает push_back(const T&) (копия).
    vec.push_back(std::forward<T>(item));
}

⚡️ Шпаргалка 1. std::move используем, когда мы знаем, что объект нам больше не нужен, и мы хотим отдать его ресурсы (обычный код). 2. std::forward используем, когда мы пишем шаблон, который принимает "универсальную ссылку" (T&&), и нам нужно пробросить аргумент дальше "как есть" (библиотечный код). #cpp #cpp11 #movesemantics #coding #interview #tips ➡️ @cpp_geek