tgindex
Маша С++
@masha_cppрусский

Учу C++ и не только вместе с вами, мои котятки)

Последний пост
10 апр.
Последнее чтение
11:57
Постов за неделю
0
Всего постов
21
Тип
открытый
Язык
русский
В каталоге с
14 авг.
Подписчики
104
0 за 2 дн.
Сутки
0
0,00%
Неделя
 
Месяц
 
Просмотров на пост
277
21 постов
Вовлечённость
266,3%
к подписчикам
Постов в день
0,0
всего 21
Упоминаний
0
каналов
Охват размещения
оценка
1/24сутки в ленте
1/48двое суток
1/72трое суток

Оценка по просмотрам недавних постов: пост набирает почти всё за первые сутки.

Посты

  • Чистовик: Сайт отслеживает переход популярных проектов на модули C++. По прогнозу к 2167 году переедут все 😛: https://arewemodulesyet.org/

  • Утверждён стандарт C++26. Мне понравились новые няшные операторы ^^ 😇: static_assert(^^i != ^^j); https://www.opennet.ru/opennews/art.shtml?num=65102

  • Тут у ребят nullptr не всегда равен 0 (как могло бы ожидаться), а иногда -1 (название канала намекает). https://t.me/cpp_durka/34 Лучше один раз увидеть, чем сто раз услышать nullptr

  • Хороший курс лекций по плюсам (на рутубе) - язык русский, а вот слайды почему-то на английском (на канале на рутубе есть и версия с русскими слайдами). https://t.me/cpp_lects_rus/333 Cмотря какой rutube, смотря сколько slides

  • Начиная с С++23 официально доступны директивы #elifdef и #elifndef, так что теперь например вместо: #ifdef WINDOWS // #elif defined(MACOS) // #elif defined(LINUX) // #endif можно будет писать короче и консистентнее: #ifdef WINDOWS // #elifdef MACOS // #elifdef LINUX // #endif Краткость — сестра таланта (и брат нового стандарта)

  • #мем что ли выложить Идет корова по лесу, видит, проект горит. Села в нее и выгорела.

  • Интересна ли тема Qt? Сегодня речь пойдет про QPointer. Как известно, в Qt всё строится вокруг QObject со своей механикой владения объектами, отличной от использования "стандартных" умных указателей, типа std::shared_ptr. Давайте посмотрим на такой пример: в наш класс передается некий наследник QObject, которым мы не владеем. Далее в какой-то момент времени нам необходимо удостовериться, а существует ли этот объект еще или нет. struct Class: { Class(QObject * obj) : obj_(obj) {} void do() { if (obj_) { // жив ли объект по указателю? //obj_->method(); // UB, если нет }; } QObject * obj_; }; В стандартном C++ мы бы воспользовались weak_ptr. Но в Qt есть специальный класс с довольно абстрактным названием QPointer, который ведет себя как обычный сырой указатель, однако автоматически очищается когда хранимый им указатель удаляется. Поэтому мы можем проверить, удалился ли объект по указателю. Внесем простые изменения в класс: QPointer<QObject> * obj_; Теперь проверка if (obj_) будет работать как ожидается, при этом нам не обязательно оборачивать указатель в QPointer при передаче в конструктор класса. В чем отличия от weak/shared ptr? Во-первых не нужно изначально оборачивать указатель в shared, что может быть не всегда и возможно. Во-вторых работает только с наследниками QObject. Ну и механизм работы отличается, что кстати может легко сломаться в многопоточном коде. О мёртвых указателях либо хорошо, либо ничего.

  • Интересное. В ядре линукса руками заинлайнили две функции, тем самым повысив на 12% скорость обработки входящих UDP пакетов. https://www.opennet.ru/opennews/art.shtml?num=64783 У меня есть шутка про UDP, но она до тебя не дойдет (на 12% быстрее).

  • Использует ли std::optional указатели для реализации? Нет. std::optional не выделяет память в куче, он может полностью работать на стеке. Объект создается на месте в стековом куске памяти (размером с объект) внутри опшинала. Поэтому нет и накладных расходов на переход по указателю (но есть накладные расходы на bool флаг для состояния инициализации + выравнивание). Однако возникает вопрос, расходует ли std::optional память под неинициализированный объект? Ведь хранилище выделятся всегда на стеке. Думаю, что вы можете ответить на него самостоятельно (а еще посмотреть на sizeof для жирного объекта). Выходит, что std::optional и не такой уж optional, память то всегда жрет) Кстати, по причине такой реализации optional нельзя создавать с некоторыми типами, например не пойдут cсылки, функции, void, массивы.

  • #Шпаргалка-повторялка. Rvalue или Lvalue? Упрощенно Lvalue (left value) это то, что стоит слева от оператора присваивания =, а rvalue (right value) справа: lvalue = rvalue. Однако, все немного сложнее. Если можно получить адрес некоего выражения, то зачастую это lvalue. Если нельзя - то это обычно rvalue. Rvalue концептуально часто временный объект без имени (например результат работы функции, временное значение, литерал). А вот lvalue обычно имеет имя. А еще rvalue могут быть перемещены, но об этом в другой раз.

  • По умолчанию, при форматировании вывода в поток булевых значений, будет использоваться 1 и 0 для true и false. Пример: bool t = true; bool f = false; std::cout << t << " " << f << std::endl; // Выведет: 1 0 Возможно, что удобнее (особенно для отладки) выводить булевы значения строкой, также как они задаются в коде. Для этого нужно добавить в поток манипулятор boolalpha: std::cout << std::boolalpha; std::cout << t << " " << f << std::endl; // Выведет: true false Интересно то, что манипулятор действует на все последующие записи булевых значений для этого потока. То есть он будет применяться и после ; и после std::endl. Отменить форматирование булевых флагов можно записав в поток манипулятор std::noboolalpha: std::cout << std::noboolalpha << t << " " << f << std::endl; // 1 0 (Интересно, а для ввода это работает? кто проверит?) Честно говоря, мне бы хотелось увидеть нормальные true/false по-умолчанию, но видимо обратная совместимость. Еще одной интересной особенностью форматирования вывода является округление значений с плавающей точкой. Посмотрим на вывод такого примера (на вашем окружении может быть другой результат): double d1 = 0.1234567890; double d2 = 0.1234564890; std::cout << d1 << std::endl; std::cout << d2 << std::endl; // Вывод: // 0.123457 // 0.123456 Поток выбрал точность в 6 знаков после точки (мы эту точность не задавали, поэтому может быть другой), и проведет не отсечение, а округление в последней цифре.

  • В честь наступающего Нового Года запощщу тупой мем 🤩🤩 @jokespp

  • Давайте посмотрим на такой пример заголовочного файла (.h) c использованием указателя на incomplete тип (forward declaration): #include <memory> class ForwardDClass; class WithSmartPtr { public: WithSmartPtr() = default; ~WithSmartPtr() = default; private: ForwardDClass* rawPtr_; //std::shared_ptr<ForwardDClass> sPtr_; //std::unique_ptr<ForwardDClass> uPtr_; }; int main(int argc, char* argv<::>) { WithSmartPtr w; return 0; } Не будем вдаваться в корректность, посмотрим для начала на компилируемость. Такой код, конечно же соберется, ведь компилятор знает размер указателя в байтах (размер одинаков как для определенного, так и неопределенного типа). Если мы раскомментируем строку с shared_ptr, то код тоже скомпилируется (по крайней мере у меня собрался). Не будем говорить, про корректность его работы, но напомним, что вцелом удаление неопределенного типа - UB. А вот если мы раскомментируем строку с unique_ptr, то код перестанет собираться. Многие используют паттерн pimpl и потому знают, что реализацию деструктора нужно сделать в других исходниках (например .cpp), где forward declaration тип определен и проблема этим решается. Но почему компилятор не ругается на shared_ptr? Дело в том, что unique_ptr и shared_ptr требуют полного определения типа в разных местах. Детали сложны (и о них я не знаю), но первому нужен статический deleter, тогда как второму - динамический. Вывод: в нашей копилке костылей еще одно неправильное решение - заменить unique_ptr на shared_ptr 😊.

  • Как подсчитать число выставленных битов (единиц) в целом беззнаковом? Давайте возьмем C++ 20, где в <bit> появилась функция std::popcount, которая делает, то что нам нужно: #include <iostream> #include <bit> #include <cstdint> int main() { uint8_t u1 = 0b00011101; unsigned u2 = 123; // 0b1111011 std::cout << std::popcount(u1) << " " << std::popcount(u2) << std::endl; // 4 6 return 0; } Скучно? На первый взгляд да, но в C++ всегда можно закопаться в детали =) Во-первых, что за странное название? Почему не какой-нибудь bit_set_count? Оказывается, что popcount это сокращение от “population count”. Вроде немного понятнее, но лучше не стало) Чтож, почитаем хабр: https://habr.com/ru/articles/467083/ Кроме пространного списка задач, где для решения используется эта функция, из статьи мы узнаём что во многих процессорах есть специальная инструкция (н-р POPCNT на X86), которая выполняет данное вычисление очень быстро (за такт?). Кроме того, в GCC и CLANG есть встроенные функции, которые также вычисляют popcount очень быстро и что-то мне подсказывает, что они могут вызывать эту единственную инструкцию (а может и нет). В GCC эта функция наз-ся __builtin_popcount. Пример можно посмотреть тут: https://godbolt.org/z/84ETrhfnK Как видим, в дебаге GCC вставит встроенную __popcountdi2 функцию, а для std::popcount будет другая. Если же добавить -O3, то разницы не будет никакой. Замеры делать не буду)

  • Многим известно как работает dynamic_cast в C++. Зная, что за указателем на базовый класс стоит объект производного, мы можем скастовать базовый до производного и вызвать его метод. Пример: struct Base { virtual ~Base() = default; }; struct Derived : public Base { void doDerivedJob() { std::cout << "derived" << std::endl; } }; void cast(Base* baseP) { Derived* d = dynamic_cast<Derived*>(baseP); if (d) { d->doDerivedJob(); } else { std::cout << "ptr: unable to cast" << std::endl; } } int main(int argc, char* argv<::>) { Base b; Derived d; cast(&b); // ptr: unable to cast cast(&d); // derived return 0; } Все, что нужно для корректной работы dynamic_cast - это наличие хотя бы одного виртуального метода. И тогда мы можем в рантайме проверить результат кастования на nullptr. Если это nullptr - то мы не угадали и указатель смотрел на объект базового класса. Обычно такой подход рассматривается как результат неудачного проектирования, но иногда все же приходится так делать. К тому же проверка указателя на nullptr довольно обычная операция. Но что делать, если мы имеем не указатель, а ссылку на базовый класс? Оказывается, что dynamic_cast работает как ожидается и со ссылками - можно преобразовать ссылку на базовый класс в ссылку на производный. Однако, что делать, если преобразование в рантайме невозможно? Ссылка, в отличие от указателя не может указывать на nullptr. В случае ошибки преобразования dynamic_cast выбросит исключение std::bad_cast. Пример: void cast(Base & baseRef) { try { Derived& d = dynamic_cast<Derived&>(baseRef); d.doDerivedJob(); } catch (const std::bad_cast& ex) { std::cout << "ref: unable to cast: " << ex.what() << std::endl; // baseRef.doWork(); } } int main(int argc, char* argv<::>) { Base b; Derived d; cast(b); // ref: unable to cast: Bad dynamic_cast cast(d); // derived return 0; } Однако, этот пример показывает плохой стиль программирования, потому что использует исключения для потока выполнения.

  • Как можно оформить в C++ длинный строковый литерал и разбить его на нескольксо строк в коде? Допустим, мы хотим разбить длинную строку в коде на несколько линий, но так чтобы переносы строк и отступы из самого кода не повлияли на результат. Один из проcтейших способов - просто написать строки в кавычках друг за другом: std::string str1 = "Если ты, следуя правому разуму, будешь старательно," " ревностно и любовно относиться к делу, которым ты в" " данный момент занят..."; std::cout << str1 << std::endl; // напечатает всё в одну строку Такой вариант склеит строки без переносов \n, но тут важно самостоятельно не пропустить пробелы в местах разрывов. Однако, есть и другой способ: можно использовать кавычки только в начале и конце всей строки, вставив символ \ как самый последний (!) на каждой строке: std::string str1 = "Если ты, следуя правому разуму, будешь старательно, \ ревностно и любовно относиться к делу, которым ты в \ данный момент занят..."; std::cout << str1 << std::endl; // напечатает всё в одну строку При этом начинать новую строку следует с самого начала, иначе любой отступ в коде попадет и в саму строку. Неудобно, правда? А как же raw string literals? Те самые, что заключаются в R"( и )" (или другие разделители)... Да, они замечательно подходят, чтоб не экранировать разные символы, вроде кавычек и обратных слешей: std::string json = R"( { "path": "C:\Program Files\", } )"; Но что будет, если взять нашу цитату, перенесенную по строкам в коде: std::string str1 = R"(Если ты, следуя правому разуму, будешь старательно, ревностно и любовно относиться к делу, которым ты в данный момент занят...)"; Оно вставит переносы строк \n и отступы из кода в результат. Мой победитель: просто строки в двойных кавычках на каждой строке. Просто, привычно, не вставляет ненужных символов и позволяет форматировать код.

  • Многим известно, что std::string можно создать из указателя на char * (допустим из некой C-шной функции): #include <iostream> #include <string> static std::string str = "OK"; const char * getStr() { return str.c_str(); } int main() { std::cout << "str: " << std::string(getStr()) << std::endl; } Но что будет, если нам прилетит nullptr и мы передадим его в конструктор std::string? const char* getStr() { return nullptr; //return str.c_str(); } Можно подумать, что создастся пустая строка. В конце концов, почему бы и нет, я бы например реализовала строку именно так. На самом же деле передавать nullptr в конструктор строки нельзя и в общем случае будет UB. На gcc у меня это приводит к std::logic_error. А вот на msvc уже будет Access violation reading location 0x0000000000000000. Ситуация немного прояснилась с выходом C++23. Создавать строку из nullptr более нельзя: basic_string( std::nullptr_t ) = delete; // C++23 std::string s = nullptr; // нельзя Хотя кажется, что проблему с возвратом nullptr это не решает.

  • Дочитываю книгу "Многопользовательские игры. Разработка сетевых приложений". Неплохое введение в компьютерные сети для тех кто хочет быстро разобраться или вспомнить как работает IP, Nat и тп, а также вводная глава про программирование сетевых сокетов. Примеры на C++. #книги

  • Объединения (union-ы) позволяют хранить данные разных типов в одной области памяти. Пример: #include <iostream> union IPv4Address { uint32_t i; uint8_t c[4]; }; int main(int argc, char* argv<::>) { IPv4Address a; memset(&a, 0, sizeof(a)); a.c[0] = 10; a.c[1] = 0; a.c[2] = 0; a.c[3] = 1; std::cout << (int)a.c[0] << std::endl; std::cout << a.i << std::endl; // don't do this, just an example return 0; } Но можно ли наследовать union? Давайте попробуем: struct IPv4AddressWithPort : IPv4Address { uint16_t port = 0; }; Получим ошибку компиляции: enum/union cannot be used as a base class Объединения нельзя использовать как базовый класс. Напишите в комментариях, кто знает почему)

  • Нам всегда говорили, что бросать исключения из деструктора это плохая идея. Один из примеров плохого результата - это раскручивание стека при обработке исключения, когда вызываемые деструкторы приводят к повторному исключению, которое кроме как вызовом terminate() толком не обработаешь. Но что, если выбросить исключение очень хочется, ведь мы сделаем всё "аккуратно"? Давайте посмотрим на такой код: #include <iostream> struct ThrowsInDtor { ~ThrowsInDtor() { std::cout << "~ThrowsInDtor" << std::endl; throw std::runtime_error("holy..."); } }; int main(int argc, char* argv<::>) { try { ThrowsInDtor dtor; } catch (const std::runtime_error& ex) { std::cout << "ex: " << ex.what() << std::endl; } return 0; } Что тут может пойти не так, вроде же всё ОК? Одно исключение, мы его ловим. По факту, данный код приведет к вызову terminate(), так как все деструкторы по-умолчанию неявно помечены как noexcept, то есть не выбрасывающие исключений. Если очень сильно хочется вернуться во времена до C++11, то помечаем деструктор как noexcept(false) и получаем наше исключение (не понятно только зачем): ~ThrowsInDtor() noexcept(false)