Грокаем C++
الذهاب إلى القناة على Telegram
Два сеньора C++ - Владимир и Денис - отныне ваши гиды в этом дремучем мире плюсов. По всем вопросам (+ реклама) @ninjatelegramm Менеджер: @Spiral_Yuri Реклама: https://telega.in/c/grokaemcpp Мы на TGstat: https://tgstat.ru/channel/@grokaemcpp/stat
إظهار المزيد9 383
المشتركون
-224 ساعات
+27 أيام
+1030 أيام
أرشيف المشاركات
9 385
Квиз
Сегодня потенциально самый необычный #quiz на канале. Воистину, C/С++ - это самые интересные языки из существующих. Столько безобразия можно с ними натворить. С одной стороны, это мешает языкам выходить в мейнстрим "патамушта небезопасна". С другой стороны, здесь максимальная свобода творчества и полета фантазии.
У меня для вас всего один вопрос: Что произойдет в результате попытки компиляции и запуска этого С-кода:
const int main[] = {
-98693133, -443987883, 440, 113408,
-1922629632, 4149, 1227264, 84869120,
15544, 266023168, 1970231557, 1701994784,
1701344288, 1936024096, -1878384268, 258392925
};
Тут даже думать не нужно, если вы не компьютер. Выбирайте то, что подсказывает ваше сердце.
Сhoose with your heart. Stay cool.9 385
+5
💀 А ваше трекер — это тоже кладбище тикетов с кучей комментариев «не ко мне»?
Мы в Яндекс Go тоже прошли через эту проблему. Бесконечные споры, кто крайний и тикеты, которые пинают между командами, пока те не протухнут. Точность ручной разметки задач у нас была всего 67%. И в итоге огромное количество времени разработчиков уходило не на код, а на споры, кто должен заниматься задачей.
В итоге решили, что так дело не пойдёт, и создали DutyGPT — внутреннюю нейронку, которая автоматически определяет ответственную команду. А потом ещё и написали об этом статью.
👇 В статье без прикрас рассказываем, как мы:
— С треском провалились с первой версией на BERT, которая работала ещё хуже, чем люди.
— Нашли подходящее решение в дообученной модели YandexGPT и подняли точность разметки до 92%.
— Сейчас прикручиваем RAG, чтобы DutyGPT сам находил дубликаты и похожие тикеты.
Да, кейс не на плюсах, но боль у всех одна. Получилась честная история о том, как мы взяли классическую инженерную проблему и решили её с помощью ML.
Все подробности — в статье
Реклама. ООО «Яндекс.Такси» ИНН 7704340310
9 385
std::to_address
#опытным
В этом посте мы поговорили о том, как доставать настоящий адрес объекта с помощью функции std:addressof. В основном она предназначена для получения настоящего адреса любых объектов, даже тех, у кого перегружен оператор взятия адреса.
Однако есть и другая, похожая задача. Вам приходит на вход объект, который представляет из себя какого-то рода указатель на объект и из него нужно получить адрес самого объекта.
Дело это не совсем тривиальное. Со всеми стандартными классами, типа умных указателей и итераторов(которые называют общим выражением fancy pointer) может прокатить вот такое выражение:
obj.operator->(). Однако для простых указателей это не прокатит: они не классы и у них нет методов. Да и не прокатит для любых других объектов, у которых не определен этот оператор. Что делать?
Использовать C++20 функцию std::to_address! Вот ее примерная реализация:
template<class T>
constexpr T* to_address(T* p) noexcept {
static_assert(!std::is_function_v<T>);
return p;
}
template<class T>
constexpr auto to_address(const T& p) noexcept {
if constexpr (requires{ std::pointer_traits<T>::to_address(p); })
return std::pointer_traits<T>::to_address(p);
else
return std::to_address(p.operator->());
}
То есть для указателей она просто вовращает их значения наружу, а для объектов fancy pointer'ов она спрашивает, определено ли свойство std::pointer_traits для этих типов. Если же не определено, то пытается достать указатель с помощью вызова метода operator->().
Обычно эта функция требуется для вызова сишного апи в обобщенном коде:
void c_api_func(const int*);
template<typename T>
void call_c_api_func(T && obj) {
c_api_func(std::to_address(obj));
}
std::vector<int> data{10, 20, 30};
call_c_api_func(data.begin()); // works
auto ptr = std::make_unique<int>(42);
call_c_api_func(ptr); // works
call_c_api_func(ptr.get()); // also works
Use the right tool. Stay cool.
#cpp20 #cppcore9 385
Почему важно проверять входные данные
#новичкам
Вы делаете какие-то вычисления и среди них есть целочисленное деление:
void fun(int a, int b) {
// some calculations
auto res = a / b;
// some calculations
}
Вы уже наслышаны о том, что в С++ есть механизм обработки исключений и просто оборачиваете вашу функцию в try-catch и думаете, что С++ позаботится о том, что вы будете информированы о всех проблемах, которые могут произойти в коде:
try {
fun(1, 2);
} catch(std::exception& ex) {
std::cout << "Calculation error: " << ex.what() << std::endl;
}
Однако помните, что вы пишите на С++. Здесь уровень заботы примерно, как у бати, который вывозит неумеющего плавать сына на середину реки и бросает его в виду с криками "Плыви, сынок!".
Если вдруг вы передадите в fun вторым параметром ноль:
try {
fun(1, 0);
} catch(std::exception& ex) {
std::cout << "Calculation error: " << ex.what() << std::endl;
}
то С++ не пошлет исключение об этой ситуации.
Стандарт явно говорит: целочисленное деление на ноль приводит к неопределенному поведению. У меня например программа просто падает с надписью: Floating point exception. Очень иронично со стороны компилятора обрабатывать эту ситуацию, выводя в консоль текст о появлении исключения, хотя его тут нет и его никак не отловить.
Поэтому нужно проверять входные данные и самим явно кидать исключения или использовать другой способ обработки ошибки:
void fun(int a, int b) {
if (!b) {
throw std::runtime_error("Devision by zero!");
}
// some calculations
auto res = a / b;
// some calculations
}
try {
fun(1, 0);
} catch(std::exception& ex) {
std::cout << "Calculation error: " << ex.what() << std::endl;
}
Тогда на консоль явно выведется ожидаемое Calculation error: Devision by zero!.
Так что будьте осторожны и проверяйте входные данные.
Stay alert. Stay cool.
#cppcore9 385
+4
⚡️ Linux теперь в Telegram!
Ребята сделали крутейший канал про Linux, где на простых картинках и понятном языке обучают работе с этой ОС, делятся полезными фишками и инструментами
Подписывайтесь: @linuxos_tg
9 385
🔞 Если принимаете сырой указатель, как параметр - не забудьте проверить его на nullptr. Меньше будете голову ломать при будущем дебаге.
🔞 compareData зачем-то принимает параметры по значению. В смысле, понятно зачем: чтобы было double free, а вам было интереснее ревьюить. Лучше все-таки принимать по константной ссылке аргументы.
🔞 Поле buffer публичное как раз для того, чтобы к нему доступ имела compareData. Мы все-таки за инкапсуляцию и мир во всем мире. Поле надо сделать приватным, а compareData другом. А еще лучше убрать compareData и определить нативный оператор сравнения.
Но если очень хочется оставить compareData, то лучше все равно определить оператор, не делать compareData другом класса, и пусть она использует нативный оператор внутри себя без нарушения инкапсуляции.
🔞 Data имеет конструктор от одного аргумента и лучше бы его сделать explicit. В таком случае, чтобы запретить неявные преобразования.
🔞 Многие отметили, что класс стоит пометить как final. Видимо это какой-то кодстайл, чтобы запретить другим классам наследоваться от этого. Причина, как я понял, это отсутствие виртуального деструктора. Поэтому если кто-то хочет отнаследоваться от класса, то он явно видит, что пока классом нельзя пользоваться полиморфно, убирает final и добавляет виртуальный деструктор.
Все же от Data можно безопасно наследоваться, если не использовать потом наследников полиморфно. А при добавлении виртуального метода можно и самому понять, что надо еще и деструктор соотвествующим сделать.
Мне final кажется оверкиллом для одиночных классов в небиблиотечном коде , но тут кто как привык.
Фух, на этом вроде все.
Вот что вышло по итогу исправлений:
class Data final {
private:
std::unique_ptr<char[]> buffer;
size_t buf_size = 0;
public:
Data(const char* input, size_t size)
: buf_size(size) {
if (input == nullptr && size != 0) {
throw std::invalid_argument("Input cannot be null for non-zero size");
}
if (size > 0) {
buffer = std::make_unique<char[]>(size);
std::copy(input, input + size, buffer.get());
}
}
template <size_t N>
explicit Data(const char (&str)[N])
: Data(str, N) {
}
// Копирующий конструктор
Data(const Data& other)
: Data{other.buffer.get(), other.buf_size} {
}
// Универсальное присваивание
Data& operator=(Data other) {
swap(*this, other);
return *this;
}
// Перемещающий конструктор
Data(Data&& other) noexcept {
swap(*this, other);
}
~Data() = default;
// Оператор сравнения
bool operator==(const Data& rhs) const noexcept {
return std::string_view{buffer.get(), buf_size} == std:;string_view{rhs.buffer.get(), buf_size()};
}
// Вспомогательная функция для обмена
friend void swap(Data& first, Data& second) noexcept {
using std::swap;
swap(first.buffer, second.buffer);
swap(first.buf_size, second.buf_size);
}
};
[[nodiscard]] bool compareData(const Data& data1, const Data& data2) noexcept {
return data1 == data2;
}
Пишите, если что забыл.
Critique your solution. Stay cool.
#cppcore #OOP #goodpractice #design9 385
Разбор ревью
#новичкам
Большое спасибо всем участникам ревью, которые проявили активность под предыщущим постом. Не ожидал такой вовлеченности и огромного количества больших развернутых комментариев. Всем и каждому посылаю лучи благодарности!
Как всегда было сложно выбрать один комментарий, у каждого свое видение итогового решения и разные предложения. Решил отметить двух подписчиков: @Ivaneo(коммент), как наиболее продуктивного по количеству замечаний, и @monah_tuk(коммент), за более нативный формат. Давайте похлопаем им 👏👏👏.
Теперь к сути. Есть пара критических проблем в этом коде, из-за которых мы стопроцентно уйдет в неопределенное поведение, еще несколько проблем, которые влияют на корректность поведения в отдельных сценариях, остальное рюшечки, да бантики для красоты. Пойдем от критичных недостатков к косметике.
Обычно при ревью мы пытаемся максимально сохранить идею кода вымышленного автора при обсуждении решении проблем. А то можно было бы сказать "надо использовать std::vector и мозг себе не блендерить". Так не интересно)
Поехали!
🔞 Создаем массив через new[], а удаляем через delete. Это UB, нужно использовать одинаковые операторы для создания и удаления объектов и массивов.
🔞 Автор кода хочет, чтобы его класс обладал всеми специальными методами, и при этом надеется, что компилятор сможет сгенерировать их так, как надо автору. Надеяться не надо, потому что компилятор довольно тупой и выполняет поверхностное копирование и перемещение в самосгенеренных методах. Он просто копирует значение указателя в другой объект. В compareData аргументы передаются по значению, значит будет копирование. Значит после выхода из скоупа функции будет double free при разрушении второго объекта. Надо писать свои специальные методы по правилу пяти. До кучи можно использовать swap idiom.
🔞 compareData сравнивает объекты по указателю. Это неверный подход, потому что такое сравнение не предполагает сравнение самих данных. Такое сравнение будет давать в результате true только для одного и того же объекта. Нужно сравнивать данные по этим указателям, а не сами указатели.
🔞 Автор был невнимательным и забыл единичку при копировании входных данных. Таким образом в данные не попадает null-terminator, а значит не представляется возможным по одному указателю определить размер данных.
Тут вопрос на самом деле намного шире: а что мы вообще может принимать, в качестве аргументов конструктора? Туда спокойно можно передать указатель на обычный char массив, у которого нет null-terminator'а. Более того, в середине такого массива может быть валидный нулевой символ, просто как часть общего массива.
Поэтому вы правильно заметили, что будет лучше передавать вместе с указателем еще и размер данных в конструкторе. Тогда мы четко знаем размер данных и будем хранить этот размер в течение всей жизни объекта.
Плюс, если мы допускаем нулевой символ в середине, то функции типа std::str* нам не подходят, потому что ориентируются на терминатор. Лучше использовать std::copy.
Если уж очень хочется передавать строковые литералы и не передавать для них размер можно использовать такой трюк:
template <size_t N>
explicit Data(const char (&str)[N])
: Data(str, N) {
}
Второй конструктор, который будет вызываться только для чар массивов, чем на самом деле и является строковый литерал. Тогда и обычные char массивы можно передавать без размера.
Можно накрутить конструктор от std::string_view и от любых буфероподобных сущностей, но оставим это за скобками для будущих улучшений.
🔞 Использование сырых указателей попахивает С-style'ом в плюсовом коде. У нас давно появились умные указатели и если уж так хочется пользоваться чаровскими буферами(ударение не на 3-й слог!) то можно использовать std::unique_ptr<char[]>. Тогда реализация всех специальных методов становится проще, а деструктор - вообще тривиальным.9 385
Ревью
#новичкам
Сегодня у нас #ревью довольно простого кода, который показывает простые механики С++. Однако не расслабляйтесь! Это мир С++, а он не такой уж солнечный и приветливый даже в самых простых случаях.
Напоминаю, что вообще происходит. В рамках этой рубрики мы на ваш суд выставляем отрывок кода, а вы в комментах пытаетесь найти все существующие в коде проблемы. Вы конкретно песочите код и интеллектуальные способности виртуального автора, а завтра выходит пост с компиляцией всех проблем. Комментарий того, кто найдет больше всех проблем, выложим вместе с ответом.
Сегодняшний лот:
#include <cstring>
#include <iostream>
class Data {
public:
char* buffer;
Data(const char* input) {
buffer = new char[strlen(input)];
std::strcpy(buffer, input);
}
~Data() {
delete buffer;
}
};
bool compareData(Data data1, Data data2) {
return data1.buffer == data2.buffer;
}
int main() {
Data d("Hello");
bool result = compareData(d, d);
std::cout << "Result: " << result << std::endl;
}
Продуктивной прожарки всем!
Critique your solutions. Stay cool.9 385
Как определять константы локальных функций
#новичкам
Когда мы определяем константу в скоупе функции у нас есть несколько вариантов, как это сделать: пометить const, constexpr, const static, constexpr static. Какой вариант выбрать?
Идентификатор constexpr говорит о том, что значение переменной обязательно должно быть известно во время компиляции. Просто const перед локальной переменной такого не требует, это обычная константа времени выполнения. Ну а static в этом контекcте продлевает время жизни переменной до конца работы программы.
Особенности функционала диктуют кейсы применения.
1️⃣ Если константа зависит от входных данных функции, то ее нужно помечать, как const. constexpr здесь по определению не подойдет, не-consteval функции могут выполняться в рантайме, а в consteval функциях входные параметры не считаются за constexpr. static тоже, потому что один раз определив статическую константу, вы ее не сможете поменять.
std::pair<double, double> normalizeVector(double x, double y) {
const double LENGTH = std::sqrt(x * x + y * y); // Зависит от x и y
return {x / LENGTH, y / LENGTH};
}
2️⃣ Если константа не зависит от входных данных функции и она известна во время компиляции, то лучше ее определить, как constexpr static. Тогда такая локальная переменная гарантированно не будет занимать пространство на стеке при каждом вызове функции, ее инициализация точно пройдет в compile-time и об этом будет явно сказано в коде программы.
double metersToMiles(double meters) {
constexpr static double METERS_IN_MILE = 1609.34;
return meters / METERS_IN_MILE;
}
Вообще этот пост в принципе родился из вопроса: "как помечать локальные константы, значения которых известны на этапе компиляции?". В большинстве случаев можно пометить константу просто constexpr, компилятор скорее всего ее оптимизирует и вставит ее значение в места вызовов, но это не гарантируется. Например, если вы по каким-то причинам будете использовать адрес этой локальной константы, то компилятор будет вынужден класть ее на стек на каждом вызове.
Зачем делать ненужные действия? Если значение переменной не меняется от вызова к вызову, то она больше подходит на глобальную константу. А чтобы не засорять глобальный скоуп переменной, которая используется только в этой функции, то можно сделать ее статической локальной переменной и получить тот же результат.
Be expressive. Stay cool9 385
🚀Углубленные навыки разработки на C++ востребованы в самых крупных IT-компаниях, готовы перейти на новый уровень?
Курс «C++ Developer. Professional» создан для разработчиков, которые хотят углубить свои знания в C++ и подготовиться к решениям реальных задач. Вы освоите передовые практики, такие как многопоточное программирование, новые стандарты C++ 20 и 23, а также научитесь работать с сетями и базами данных.
Пройдите обучение с OTUS и получите знания, которые сделают вас ценным специалистом в любой компании. Получите диплом OTUS, который признают ведущие работодатели.
⌛️Время ограничено! Успейте пройти вступительное тестирование и получить скидку на обучение. Старт курса уже скоро — не упустите свой шанс: https://otus.pw/sN9I/
Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576, www.otus.ru
9 385
std::clamp
#новичкам
Иногда у модулей приложения могут быть ограничения по диапазону значений. Например, скорость игрока. Как бы он не замедлялся, скорость не будет меньше нуля, и как бы он не ускорялся, она не будет больше max значения. Или координаты персонажа на ограниченном игровом поле.
И чтобы не городить подобных конструкций:
if (value < min) {
value = min;
}
if (value > max) {
value = max;
}
есть замечательная стандартная функция std::clamp. Она полностью инкапсулирует логику ограничения значения сверху и снизу:
template<class T>
constexpr const T& clamp( const T& v, const T& lo, const T& hi );
Теперь ограничивать значения переменным проще, чем написать Hello, World.
double increment_speed(double curr_speed, double acceleration, double time_delta) {
curr_speed += acceleration * time_delta;
return std::clamp(curr_speed, kMinSpeed, kMaxSpeed);
}
Не самая широкоизвестная функция, однако круто, что в стандарте есть такие нишевые утилитарные инструменты. Они уже протестированы за вас, максимально обобщены и адекватно названы.
Вряд ли вы заходите в такие глубины стандарта и cppreference, чтобы std::clamp попалась вам на глаза. Поэтому и решил здесь рассказать про нее.
Don't reinvent the wheel. Stay cool.
#STL9 385
Ищем Middle/Senior C++ разработчик в Mobile Core 2ГИС
Пишем фундамент под всё, что происходит в мобильном 2ГИС: поиск, карточки, карта, данные, архитектура, скорость. Если приложение летает — значит Core справился.
Что делает команда:
— проектирует архитектуру мобильного приложения
— отвечает за масштабируемость и стабильность
— реализует поисковые сценарии, работу карты, открытия карточек— выбирает хранилища и настраивает пайплайн доставки данных
В работе:
C++20, modern CMake, профилирование, оптимизации
Задачи — и продуктовые, и инфраструктурные
OS и IDE — на твой вкус
Пользователи — миллионы
Чем будешь заниматься:
— проектировать архитектуру и пайплайны
— писать продуманный, тестируемый код
— доставлять фичи на бой— оптимизировать по памяти и скорости— проводить код-ревью
— прокачивать кодовую базу вместе с командой
Что важно:
— 3+ года на C++14/17/20
— уверенность в алгоритмах и структурах данных— самостоятельность от требований до релиза
— умение писать понятный, эффективный код
Что предлагаем:
Работа — где угодно: удалёнка или офис
ДМС, телемедицина, терапевтмитапы, поездки в горы, хакатоны, 2FEST
Хочешь писать код, который держит на себе весь мобильный 2ГИС?👉 Присоединяйся
9 385
Just throw it forward
#опытным
Работа с исключениями - целое искусство. В этой теме куча фишек, которые помогут извлекать из исключений максимум пользы.
Пусть вы хотите работать с какой-то сущностью в коде эксклюзивно:
void func(std::shared_ptr<Resource> res) {
if (res->TryToAcquire()) {
// do dirty things
res->Release();
} else {
// you don't have permission to do dirty, so return
return;
}
}
Что будет, если в этом коде в результате обработки эксклюзивно захваченного ресурса произойдет исключение?
Правильно, утечка. Утечка ресурса. Так как вы его явно не освободили через метод Release, вы его больше не сможете захватить и он так и останется в подвешанном состоянии.
Хорошо бы отловить исключение и освободить ресурс. Однако привычная обработка исключений тут особо не поможет. Мы на этом слое вызовов не можем адекватно обработать исключение. У нас просто недостаточно информации и полномочий, чтобы принять решение, как действовать дальше
То есть нам как-то нужно сделать 2 вещи одновременно: дать исключению выйти наружу и освободить ресурс. Плюс к этому хочется залогировать ошибку.
Автоматически освободить ресурс нам поможет RAII обертка, которая освободит ресурс в деструкторе, если его получилось захватить, а залогировать ошибку поможет совместное использование catch + простой вызов "throw".
void func(std::shared_ptr<Resource> res) {
auto raii_res = RaiiWrapper(res);
if (raii_res->TryToAcquire()) {
try {
// do dirty things
} catch (const std::exception& ex) {
Log("Houston, we have a problem");
throw;
}
} else {
// you don't have permission to do dirty, so return
return;
}
}
Инструкция throw делает единственную вещь - пробрасывает то же самое исключение выше по стеку вызовов.
Получается, что можно не до конца обрабатывать исключение, а сделать свои дела и пробросить исключение дальше.
Don't leak your resources. Stay cool.
#cppcore9 385
Почему еще важен std::forward
#опытным
Подписчик @Ivaneo предложил новую рубрику #ЧЗХ, в рамках которой мы будем рассматривать мозголомательные примеры кода и пытаться объяснить, почему они работают так криво.
Также спасибо ему за предоставление следующего примера:
#include <iostream>
void bar(float&& x) { std::cout << "float " << x << "\n"; }
void bar(int&& x) { std::cout << "int " << x << "\n"; }
void foo(auto&& v) { bar(v); }
int main() {
foo(1);
foo(2.0f);
}
Как думаете, что выведется на консоль? Подумайте пару секунд.
Ну нормальный человек ответит:
int 1
float 2
Однако командная строка вам выдаст следующее:
float 1
int 2
Если не верите, по посмотрите в годболте. И можете уже сейчас написать в комментах: "ЧЗХ", "WTF", "WAT" и прочее.
А нам пораразбирацца.
Тут используется auto в аргументах функции, значит эта функция неявно шаблонная. Посмотрим, что нам выдаст cppinsights по этому коду:
#ifdef INSIGHTS_USE_TEMPLATE
template<>
void foo<int>(int && v)
{
bar(static_cast<float>(v));
}
#endif
#ifdef INSIGHTS_USE_TEMPLATE
template<>
void foo<float>(float && v)
{
bar(static_cast<int>(v));
}
#endif
Просто прекрасно. Какого черта компилятор кастит переменные к противоположным типам?
Первое, что важно понимать: внутри функции foo переменная v - это уже lvalue, так как имеет имя. Значит просто так вызвать перегрузки для правых ссылок он не может.
Но у компилятора в кармане есть стандартные преобразования, которые и идут в ход, когда нет подходящих перегрузок. Обычно это неявные преобразования из одного типа в другой. Не преобразования из одного типа ссылочности в другой тип ссылочности, а прям в другие типы данных.
То есть происходит следующее: компилятор понимает, что подходящей перегрузки нет, поэтому начинает применять стандартные преобразования в другие типы. Любой каст дает временный объект. А временный объект типа int легко биндится к float&&, как и временный объект float легко биндится к int&&.
Вот и получается обмен вызовами.
Чтобы такого не происходило, применяйте перед сном std::forward. Если есть контекст вывода типов, то он помогает правильно передавать категорию выражения объекта во внутренние вызовы.
#include <iostream>
void bar(float&& x) { std::cout << "float " << x << "\n"; }
void bar(int&& x) { std::cout << "int " << x << "\n"; }
void foo(auto&& v) { bar(std::forward<decltype(v)>(v)); }
int main() {
foo(1);
foo(2.0f);
}
В этом случае вывод будет ожидаемым.
Be amazed. Stay cool.
#cppcore #cpp11 #template9 385
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 #cpp209 385
Ищем Middle C++/QML разработчика в команду Android 2ГИС
Твоя зона ответственности — всё, что пользователь видит и трогает в приложении 2ГИС на Android.UI, взаимодействие с core-SDK, логика поверх API — если это на экране, значит это мы.
Что делаем:
— UI и механика Android-приложения
— интеграция с Java/Kotlin и нативными SDK
— бизнес-логика на C++ поверх core
— проектирование архитектуры
— фиксим, оптимизируем, покрываем тестами
Команда:
20 разработчиков
3-недельные спринты, внутренние митапы, хакатоны, пятничные онлайн-чаепития и, конечно, забота о 20+ млн пользователей
Наш стек:
C++20 • Qt • QML • Java/Kotlin (интеграции). Многопоточность, UI, архитектура, автотесты — всё как ты любишь.
Что важно:
— 3+ года на C++
— Опыт с QML - обязательно от 2-х лет
— уверенность в многопоточке
— умение оценивать и декомпозировать задачи
— готовность к кросс-функциональному взаимодействию
Условия:
Удалёнка или офис (на выбор)
ДМС, телемедицина, тимтусы, поездки в горы, 2FEST, митапы, хакатоны, всё по классике
Хочешь делать интерфейс, которым пользуются миллионы каждый день? 👉 Откликайся
9 385
Ответ на квиз
#новичкам
В этом коде:
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 #memory9 385
Улучшите читаемость и надежность кода на C++: узнайте основы эффективного рефакторинга
⏺️ На открытом уроке вы научитесь двум мощным приемам дизайна ПО: декомпозиции и абстрагированию.
▸ Мы покажем, как с их помощью улучшить производительность, сохраняя при этом ключевой принцип C++: zero overhead.
▸ Вместе с вами выполним рефакторинг небольшого приложения и повысим его читаемость и тестируемость.
❗️ В результате вы получите практические навыки, которые сможете сразу применить в своей работе. Это поможет вам улучшить кодовую базу проекта и сделать его более надежным и удобным для разработки.
Посетите открытый урок в преддверие старта курса «C++ Developer» и получите скидку на обучение!
🔴 Встречаемся 19 июня в 20:00 МСК.
Регистрируйтесь прямо сейчас, чтобы не пропустить: https://otus.pw/TqCdQ/
Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576, www.otus.ru
9 385
Квиз
Сегодня #quiz на любимую тему всех плюсовиков - утечку памяти.
Исключения в конструкторе представляют особый интерес с точки зрения исследования цикла жизни объекта и на собесах нет-нет да и спросят что-нибудь про исключения в конструкторах. Поэтому в этой теме надо бы разбираться.
Спасибо Сергею Борисову за идею для поста)
Ну а сейчас у меня для вас всего один вопрос. Будет ли в этом коде утечка памяти или нет?
#include <iostream>
#include <stdexcept>
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;
}
}
Challenge yoursef. Stay cool
متاح الآن! بحث تيليغرام 2025 — أهم رؤى العام 
