gram news
Грокаем C++ channel avatar

Грокаем C++

@grokaemcpp

Два сеньора C++ - Владимир и Денис - отныне ваши гиды в этом дремучем мире плюсов. По всем вопросам (+ реклама) @ninjatelegramm Менеджер: @Spiral_Yuri Реклама: https://telega.in/c/grokaemcpp Мы на TGstat: https://tgstat.ru/channel/@grokaemcpp/stat

ProjectsRUProgramming

9,360subscribers

Open the Channel

Latest posts

  • Грокаем C++

    21 Sept, 09:00edited

    ​​std::source_location #новичкамЕсли вы до сих пор не знали, как платформонезависимо(безо всяких PRETTY_FUNCTION) получать человеческий доступ к имени файла, имени функции и строчке кода, где вы сейчас находитесь, то пост для вас.В С++20 появился класс std::source_location, который инкапсулирует в себе информацию о точке исходного кода, где был создан соответствующий объект.Применение до боли простое. У класса есть набор методов, которые в сумме и дают понимание о том, где был создан объект. Вы просто создаете объект через единственный статический метод std::source_location::current и получаете, что нужно. Пример:#include <iostream> #include <source_location> #include <string_view>void log(const std::string_view message, const std::source_location location = std::source_location::current()) { std::clog << "file: " << location.file_name() << '(' << location.line() << ':' << loca
    1,24017951Open in Telegram
  • Грокаем C++

    18 Sept, 17:00

    ​​Виртуальный шаблонный метод #опытнымВо влажных мечтах С++ программистов всегда находится место для возможностей одновременного использования статического и динамического полиморфизмов. И тут прям напрашивается иметь виртуальный шаблонный метод. Давайте напишем:struct Base { template <typename T> virtual void do_something(T arg) = 0;virtual ~Base() = default; };struct Derived : Base { template <typename T> void do_something(T arg) override { // do something }
    1,6001454Open in Telegram
  • Грокаем C++

    16 Sept, 09:00

    ​​std::find vs std::ranges::find #опытнымС++20 библиотека диапазонов принесла нам много новых полезных алгоритмов и способов управления данными. Но не только это. Рэнджи задублировали и привычные нам "древние" стандартные алгоритмы.Хотя кажется "что какая разница, использовать новое апи или старое?" - разница все-таки есть. И сегодня поговорим об одном конкретном кейсе.Что делает std::find? Ищет элемент в промежутке между итератором начала и конца последовательности и возвращает на него итератор на этот элемент:auto it = std::find(vec.begin(), vec.end(), 42);Если элемент не найден, то find просто возвращает итератор на конец диапазона.Ровно такую же строчку можно написать с std::ranges::find и это заработает:auto it = std::ranges::find(vec.begin(), vec.end(), 42);Однако здесь открываются новые возможности. Все потому, что второй аргумент алгоритм - конец диапазона - не
    2,0201175Open in Telegram
  • Грокаем C++

    15 Sept, 12:00

    ​​Безопасный memcpy #новичкамmemcpy — это один из самых быстрых способов скопировать данные и один из самых быстрых способов получить UB. Функция принимает void и size_t и радостно съест всё, что вы ей дадите: полиморфный тип, std::string, объект с нетривиальным деструктором. Компилятор не скажет ни слова, а программа сломается в самом неожиданном месте.В прошлых постах мы поговорили о трейтах. Для заданного типа они определяют, обладает ли тип определенным свойством или нет.Давайте применим трейты на практике. Ограничим через них доступ к самописной обертке memcpy.И в этом нам помогут концепты. Если трейты - это свойство типа, то концепт - это требование для типа.Определим концепт, который требует, чтобы тип был объектом(не функцией и не ссылкой) и был тривиально копируемым:template <typename T> concept SafeForMemcpy = std::is_object_v<T> && std::is_trivially_copyable_v<T>;
    2,05025127Open in Telegram
  • Грокаем C++

    14 Sept, 12:00edited

    Базовые трейты объектов. Ч2 #новичкамВ прошлый раз у нас была довольно простая С-like структура, для которой мы смотрели стандартные свойства.struct A { int a; int b; int c; };Сегодня посмотрим, какие свойства типа изменятся, если совсем чуть-чуть изменить структуру.🔀 Добавим просто std::string в качестве поля структуры.struct A { int a; int b; int c; std::string s;
    1,8401184Open in Telegram
  • Грокаем C++

    11 Sept, 09:00

    ​​Базовые трейты объектов #новичкамТрейты(или свойства объектов) - мощнейший инструмент проверки требований для исполнения кода. Вы явно в апи через sfinae или концепты можете задавать ограничения на множество типов, с которыми хотите работать, производя проверку в compile-time.Сегодня обсудим для самой простой структуры, какими свойствами классов она удовлетворяет. Чисто, чтобы вкатиться в базовые стандартные трейты.Проверить свойства типа можно довольно просто. static_assert + использование самого трейта:struct A { int a; int b; int c; };// --- true --- static_assert(std::is_class_v<A>, "A is a class"); static_assert(std::is_aggregate_v<A>, "A is aggregate"); static_assert(std::is_standard_layout_v<A>, "A is standard-layout");
    2,4701698Open in Telegram
  • Грокаем C++

    9 Sept, 16:00

    ​​2 способа остановки воркеров #опытнымБолее менее всех опытные плюсовики знают классический подход к graceful остановке воркера. Это атомарный флаг, цикл внутри рабочей функции с проверкой и деструктор с установкой флага. Выглядит все примерно так:class Worker { public: Worker() : t([this] { while (!stop_flag.load(std::memory_order_relaxed)) { std::cout << "Working\n"; std::this_thread::sleep_for(std::chrono::milliseconds(100)); } std::cout << "Stopped\n"; }) {} ~Worker() { stop(); } void stop() { stop_flag.store(true, std::memory_order_relaxed); if (t.joinable()) {
    2,5601375Open in Telegram
  • Грокаем C++

    6 Sept, 09:00

    ​​Результаты ревью #опытнымСпасибо всем комментаторам за актив и зоркие глаза! Да, да, ревью - это не только про знания, но еще и про умение замечать все мельчайшие недочеты. Но это лирика, погнали по проблемам. Напомню код:#include <atomic> #include <mutex> #include <queue> #include <thread>struct Worker { Worker() : t( { while (!st.stop_requested()) { std::lock_guard<std::mutex> lock(m); q.push(1); } }) {}std::thread t;
    3,22024971Open in Telegram
  • Грокаем C++

    4 Sept, 09:00

    ​​Ревью #опытнымКак-то мы обходили стороной некоторые обновления в стандартной concurrency библиотеке С++.Можно было бы начать прям сразу, но зайдем с небольшого интерактива в рамках рубрики #ревью.Все просто: мы даем небольшой кусочек кода, а вы накидываетесь и оставляете комменты о том, что в этом коде на ваш взгляд не так. Все как в стандартном процессе кодревью.Единственное, надо учитывать формат "небольшого кусочка кода", который тяжело сделать практически полезным.Вот и сам код:#include <atomic> #include <mutex> #include <queue> #include <thread>struct Worker { Worker()
    3,100156421Open in Telegram
  • Грокаем C++

    30 Aug, 12:00edited

    Какой метод не переопределяется? #новичкамДля начала. Что будет если не пометить переопределенный в дочернем классе метод как override?Обычно говорят, что override генерирует проверку компилятора, что данный метод действительно переопределяет метод базового класса. И соотвественно ошибку в случае, если это не так.Проверки - это дело важное и нужное, их нужно использовать. Но в том-то и дело, что override - это лишь проверка. Она никак не влияет на то, реально ли переопределяется метод или нет.Взглядните на этот код:struct Base { virtual void foo(int i) const { std::cout << "Base" << std::endl; } virtual ~Base() = default; };struct Derived : Base { void foo(int) const {
    3,4801366Open in Telegram
  • Грокаем C++

    28 Aug, 10:00

    ​​Человеческие сообщения об ошибках в static_assert #опытнымОдной неприятной особенностью static_assert еще с С++11 были ограничения на сообщения об ошибках. По сути могли использоваться только строковые литералы. ТО есть просто фиксированные строки. Никаких динамических преобразований:template <class T> void g() { static_assert(sizeof(T) == 4, "Type T size is not 4"); }Из сообщения об ошибке компиляции: error: static assertion failed: Type T size is not 4 мы узнаем лишь то, что размер не равен 4.Хотя, вообще говоря, было бы неплохо увидеть размер переданного типа прямо в сообщении об ошибке для упрощения дебага. Но сделать мы этого не могли, даже константные выражения не могли использоваться.До С++26, когда завезли константные выражения в сообщения об ошибках. После запятой может быть объект msg, у которого должны быть 2 constexpr метода .data() для получения указателя на
    3,190271271Open in Telegram
  • Грокаем C++

    28 Aug, 07:00

    ❓Кто этот призрак в вашем коде: изучаем особенности работы с легаси на C++Легаси — вот что объединяет всех разработчиков на всех языках программирования. Мы учимся уживаться с «наследием», встраиваем его в современную кодовую базу или стараемся не трогать.Пришло время разобраться с легаси в email-проекте Ghost in the code.Вы получите семь писем от инженеров, в числе которых представитель России в Международной рабочей группе по стандартизации C++ Антон Полухин и эксперт по архитектуре Константин Владимиров. Вместе с ними и другими опытным разработчиками пройдете путь от навигации по «зрелому» коду с помощью AI до выстраивания процессов с учетом легаси.➡️Подписывайтесь на серию писем, это бесплатно. Оставляйте email-адрес на сайте проекта — первое письмо придет 15 сентября.
    Image from a post by Грокаем C++Image from a post by Грокаем C++
    3,0809643Open in Telegram
  • Грокаем C++

    25 Aug, 09:00

    ​​Какой тип nullptr? #опытнымДля многих nullptr - это магическая хреновина, которая магически и правильно "зануляет"(что бы это не значило) указатель. А как это работает - уже не важно. И даже на этом уровне понимания можно прекрасно писать хороший код и не знать забот. Но мы здесь собрались для грокания С++, поэтому будем разбираться.Для начала, nullptr - это литерал указателя. Есть литералы целочисленые(42, -100), есть литералы строковые("123", "qwerty"), а есть литерал указателя и nullptr - его единственный представитель. Литерал - это prvalue.Этот литерал обозначает константу нулевого указателя. То есть при присваивании указателю nullptr'а, он зануляется. Происходит это потому что существует неявное преобразование nullptr к значению нулевого указателя любого указательного типа.Но это не единственная константа нулевого указателя. Есть также литералы 0 и NULL.У каждого
    3,6602799Open in Telegram
  • Грокаем C++

    24 Aug, 10:00edited

    Критическая секция. Примитив синхронизации #опытнымСтандартный ответ на обычный вопрос "а какие примитивы синхронизации вы знаете?" - мьютексы, атомарные переменные, условные переменные. Для них есть привычные инструменты в языке - std::mutex, std::atomic и std::condition_variable.Иногда проскальзывают в ответах более опытных людей людей семафоры и барьеры - std::counting_semophore и std::barrier.Но очень очень редко кто-то да упомянет критическую секцию.Если вы пишите только на плюсах и кроссплатформенный код, то вряд ли вообще слышали про этот примитив. В стандартной библиотеке нет класса std::critical_section. Тем не менее такой примитив действительно есть, просто в чистом Windows API.По названию понятно, что примитив CRITICAL_SECTION защищает блок кода (критическую секцию в первом значении) от одновременного исполнения более чем одним потоком. Но общепринятый примив для
    3,25015653Open in Telegram
  • Грокаем C++

    20 Aug, 09:00

    ​​Критическая секция. Блок кода #новичкамТермин "критическая секция" имеет 2 значения и это несколько конфузит как новичков, так и опытных разрабов, кто просто не встречался с двумя личинами термина. Сейчас и в следующем посте разберем оба значения.Начнем с простого.Одна из самых больших опасностей многопоточного кода - это гонка данных. Когда 2 потока пытаются читать и записывать в одну ячейку памяти без использования синхронизации.Проблема гонки на самом деле в том, что потоки могут увидеть промежуточное состояние комплексной операции и вклиниться посередине. Даже банальный инкремент инта это 3 операции: чтение, модификация и запись.int counter = 0; // unprotected variablevoid increment() { for (int i = 0; i < 10000; ++i) { ++counter; // data race } }int main() {
    3,2703064Open in Telegram
  • Грокаем C++

    18 Aug, 10:00

    ​​Гибридные вектора #опытнымstd::inplace_vector воплощает в себя преимущества динамического расширения размеров и отсутствия аллокаций. Полезная штука, но и она ограничена. Literally: вы не можете добавить в массив элементов больше, чем capacity.И это нормальные ограничения. Нельзя иметь неограничено расширяемый массив на стеке.Но что если я знаю, что в 99% случаев мой массив будет содержать не больше N элементов. Но все же иногда нужно будет хранить потенциально намного больше элементов. Что делать?Ну для начала есть small object optimization для стандартного std::vector. Вместо того, чтобы хранить элементы в куче, небольшое число маленьких по размеру элементов можно хранить на стеке. И при добавлении элементов больше порога уже использовать динамические аллокации. Как SSO в std::string.Но это не гарантированная оптимизации и зависит от реализации стандартной библиотеки. Плюс
    3,44018105Open in Telegram
  • Грокаем C++

    15 Aug, 09:00

    ​​std::inplace_vector #опытнымСтандартная библиотека традиционно запаздывает с внедрением полезного функционала.Вот у нас есть std::vector. Прекрасный контейнер, расширяемый. более менее все им пользуются. Однако у него есть проблема - динамические аллокации. От них не уйти. А если вам они не нужны, то вы вынуждены использовать другие инструменты. Да и еще и ограниченное использование вектора в constexpr контексте.Ну ладно. Есть std::array. Нет скрытых аллокаций, давно можно использовать в constexpr. Красота.Но как бы не так: не расширяемый он. Еще и элементы должны быть созданы сразу все и должны соответствовать требованию DefaultConstructable.Короче опять недостатки.Но в С++26 появился контейнер, который объединяет преимущества std::vector и std::array. Называется он std::inplace_vector.По сути это динамически расширяемый массив с фиксированной в compile-time емкостью:1
    3,5402012643Open in Telegram
  • Грокаем C++

    10 Aug, 10:00edited

    Мапим значения на типы. Ч2 #опытнымНапомню проблему:enum class DataType: uint8_t { kInt32, kDouble, kUint64 };template<typename T> T create_from_buffer(const void buffer) { static_assert(std::is_trivially_copyable_v<T>, "Type T must be trivially copyable");T obj; std::memcpy(&obj, buffer, sizeof(T)); return obj; }
    3,67014771Open in Telegram
  • Грокаем C++

    9 Aug, 15:00edited

    Мапим значения на типы. Ч1 #опытнымИногда требуется на основе какого-то рантайм значения, например перечисления, получить какой-то соответствующий тип.Например, у вас есть шаблонная функция и вы хотите вызвать правильную ее инстанциацию:enum class DataType { kInt32, kDouble, kUint64 };template<typename T> T create_from_buffer(const void buffer) { static_assert(std::is_trivially_copyable_v<T>, "Type T must be trivially copyable");T obj; std::memcpy(&obj, buffer, sizeof(T));
    3,5801814105Open in Telegram
  • Грокаем C++

    5 Aug, 09:00

    ​​Ответ #опытным2 вещи нужно знать(помимо всего остального С++😆), чтобы правильно ответить на квиз выше:1️⃣ Сокрытие именКогда в классе объявляют метод с неким именем, все методы с этим именем из базовых классов становятся невидимыми (скрытыми), независимо от их параметров.То естьstruct A { void func(const std::string&); };struct B : A { void func(float); };B b;
    3,83024239111Open in Telegram