Интерактивная карта Ядра, можно местами потыкать код. Я офигела от количества макросов местами 😀
https://makelinux.github.io/kernel/map/
#linux
https://makelinux.github.io/kernel/map/
#linux
❤1🤔1
Сегодня 20 лет (!) известной статье Joel Spolsky (на минуточку сооснователя Stack Overflow) про "абсолютный минимум каждый разработчик абсолютно положительно должен знать о Юникоде и кодировках". Статья короткая, старая, но не потерявшая актуальности (перевод на русский). Читаем обязательно.
https://www.joelonsoftware.com/2003/10/08/the-absolute-minimum-every-software-developer-absolutely-positively-must-know-about-unicode-and-character-sets-no-excuses/
https://www.joelonsoftware.com/2003/10/08/the-absolute-minimum-every-software-developer-absolutely-positively-must-know-about-unicode-and-character-sets-no-excuses/
Часто бывает, что большой жирный класс реализует несколько интерфейсов и можно запутаться к какому именно принадлежит виртуальный метод. Было бы здорово, если имена методов можно было предварять именем интерфейса. Мне кажется, что это бы повысило читаемость. А может быть можно использовать для разрешения конфликта одинаковых имен от разных интерфейсов?
Или что-то такое уже есть?
Или что-то такое уже есть?
class Foo : public Interface, public Callback#cpp
{
public:
virtual bool Interface::start() override {}
virtual void Callback::onData() override {}
};
Вышел Qt 6.6. Изменения, конечно, забавные. Если улучшение модуля Qt TextToSpeech еще с точки зрения UI-фреймворка как-то можно понять (поддержка людей с ограниченными возможностями из коробки это хорошо), то вот добавление какой-то СУБД Mimer SQL (ни разу не слышала про такую), добавление класса для захвата содержимого отдельного окна QWindowCapture и тп вызывает ощущение дикого раздувания фреймворка. Лучше бы Qt Widgets полировали. (А вообще, конечно, кьют - ванлав 🫶).
https://www.opennet.ru/opennews/art.shtml?num=59907
#cpp #qt
https://www.opennet.ru/opennews/art.shtml?num=59907
#cpp #qt
www.opennet.ru
Релиз фреймворка Qt 6.6
Компания Qt Company опубликовала релиз фреймворка Qt 6.6, в котором продолжена работа по стабилизации и наращиванию функциональности ветки Qt 6. В Qt 6.6 обеспечена поддержка платформ Windows 10+, macOS 11+, Linux (Ubuntu 22.04, openSUSE 15.4, SUSE 15 SP4…
На собеседовании спросили, что делает эта функция:
В качестве подсказки предложили подставлять значения 1, 2, 3 , и тд.
Давайте попробуем:
[i bin(i) bin(i-1) bin(&) res]
1 0001 0000 0000 true
2 0010 0001 0000 true
3 0011 0010 0010 false
4 0100 0011 0000 true
5 0101 0100 0100 false
6 0110 0101 0100 false
7 0111 0110 0110 false
8 1000 0111 0000 true
Как видим, функция истинна если ее параметр это степень двойки (2^0=1, 2^1=2, 2^2=4). Это работает потому что степень двойки "перепрыгивает" на следующий разряд в двоичном представлении, а предыдущее число это все единицы, что при & дает 0.
#cpp
bool f(int i)
{
return i > 0 && (i & (i - 1)) == 0;
}
В качестве подсказки предложили подставлять значения 1, 2, 3 , и тд.
Давайте попробуем:
[i bin(i) bin(i-1) bin(&) res]
1 0001 0000 0000 true
2 0010 0001 0000 true
3 0011 0010 0010 false
4 0100 0011 0000 true
5 0101 0100 0100 false
6 0110 0101 0100 false
7 0111 0110 0110 false
8 1000 0111 0000 true
Как видим, функция истинна если ее параметр это степень двойки (2^0=1, 2^1=2, 2^2=4). Это работает потому что степень двойки "перепрыгивает" на следующий разряд в двоичном представлении, а предыдущее число это все единицы, что при & дает 0.
#cpp
👍3
Наверное, все знают про std::accumulate, который в частности позволяет найти сумму чисел в векторе (третий параметр задает начальное значение):
Недавно я увидела забавный способ конкатенации строк с помощью accumulate:
Дело в том, что accumulate начинает с типа и значения третьего параметра (строки в данном случае) и последовательно вызывает к нему operator+, что приводит к конкатенации всех строк.
#cpp
#include <algorithm>
std::vector<int> iv = { 0, 1, 2, 3, 4, 5 };
int isum = std::accumulate(iv.cbegin(), iv.cend(), 0);
std::cout << isum; // 15
Недавно я увидела забавный способ конкатенации строк с помощью accumulate:
std::vector<std::string> v = {"a", "bcd", "ef"};
std::string ssum = std::accumulate(v.cbegin(), v.cend(), std::string(""));
std::cout << ssum; // abcdefДело в том, что accumulate начинает с типа и значения третьего параметра (строки в данном случае) и последовательно вызывает к нему operator+, что приводит к конкатенации всех строк.
#cpp
❤3🤔1
В следующем выпуске CMake 3.28 будет поддержка C++ модулей.
C++20 модули поддерживаются генераторами
Ninja и VS 2022, требуется MSVC 14.34 toolset, LLVM/Clang 16.0 или GCC 14.
Багу всего 5 лет 😂.
#cpp20 #cmake
https://gitlab.kitware.com/cmake/cmake/-/issues/18355
C++20 модули поддерживаются генераторами
Ninja и VS 2022, требуется MSVC 14.34 toolset, LLVM/Clang 16.0 или GCC 14.
Багу всего 5 лет 😂.
#cpp20 #cmake
https://gitlab.kitware.com/cmake/cmake/-/issues/18355
В продолжении темы про std::accumulate. Как оказывается, тип третьего параметра определяет тип результата и это может приводить к неожиданным последствиям. Найдем сумму вектора double, передав первый раз 0 (то есть как int), а второй 0.0 (то есть как double):
Выведет:
3 4.77
В первом случае результатом будет int, что приводит к отбрасыванию дробной части при каждом прибавлении. Во втором случае результат будет верным. Обожаю плюсы)
#cpp
std::vector<double> iv = { 1.23, 1.55, 1.99 };
std::cout
<< std::accumulate(iv.cbegin(), iv.cend(), 0)
<< " "
<< std::accumulate(iv.cbegin(), iv.cend(), 0.0);
Выведет:
3 4.77
В первом случае результатом будет int, что приводит к отбрасыванию дробной части при каждом прибавлении. Во втором случае результат будет верным. Обожаю плюсы)
#cpp
❤3
Узнала, что есть такая структура данных как "кактус стек". Это стек, в который кроме обычного элемента можно добавить другой стек. Получается альтернативная "ветка" со своим стеком. Если нарисовать на бумажке, то получится как кактус на картинке (отсюда и название). Кажется, что за пределами языков/компиляторов не особо применяется. Например, можно сделать замыкание (лямбду) со своим собственным стеком и ответвить его от стека текущей функции. Не очень то полезный пост получился😆
👍2❤1
Когда нужно сделать безопасный callback из другого потока и время жизни коллбека не известно или не контролируется, часто храню коллбек как std::weak_ptr. Это позволяет lock()-нуть его до shared_ptr и во-первых проверить что он есть, а во вторых убедиться что он будет существовать всё время его вызова.
Код примерно такой (пишу по памяти):
#cpp #multithreading
Код примерно такой (пишу по памяти):
class ICallback {
public:
virtual ~ICallback() {};
virtual void onData() = 0;
};
class ObjectInThreadB {
public:
void setCallback(std::shared_ptr<ICallback> callback) {
_callback = callback; // shared => weak
}
void setData() {
if (auto callback = _callback.lock()) {
callback->onData();
}
}
private:
std::weak_ptr<ICallback> _callback;
};
class ObjectInThreadA
: public ICallback
, public std::enable_shared_from_this<ObjectInThreadA>
{
public:
void init(ObjectInThreadB * objB) {
objB->setCallback(shared_from_this());
}
virtual void onData() override {}
}
Вроде все правильно написано) Единственный минус, то что shared_from_this() нельзя вызывать из конструктора this и приходится делать в другом методе. Как вам такой подход, может что-то проще/современней есть?#cpp #multithreading
Любимая тема - кеш (который cache, а не наличка).
Если взять современный проц, то при частоте скажем 3ГГц будет 3 миллиарда тиков. Упрощенно, за тик делается одна простая операция. Так вот, тик настолько мал, что за его время фотон пройдет только около 10 см (!). И конечно это, кроме прочего, увеличивает время чтения данных из памяти (RAM) до 500 тиков. Чтобы это ускорить, прямо на чипе проца есть своя маленькая память для кеша, она быстрее, но дороже, а потому меньше по доступному объему (примерно в 1000 раз) чем основная память.
Если данные уже в кеше, то чтение займет всего несколько тиков. Если же данных в кеше нет (это называется промах), то исполнение приложения может остановиться на около 100 наносекунд для копирования данных из памяти в кеш. Поэтому важно соблюдать локальность данных. Временная локальность - когда к одним и тем же данным обращаются очень близко по времени. Пространственная локальность - когда данные лежат рядом с друг другом (по соседним адресам). Процессор копирует данные в кеш не побайтово, а кусками (cache line), типичным размером куска является 64 байта. Поэтому линейный последовательный перебор массива/вектора может оказаться значительно быстрее разбросанного в пространстве списка.
#hardware
Если взять современный проц, то при частоте скажем 3ГГц будет 3 миллиарда тиков. Упрощенно, за тик делается одна простая операция. Так вот, тик настолько мал, что за его время фотон пройдет только около 10 см (!). И конечно это, кроме прочего, увеличивает время чтения данных из памяти (RAM) до 500 тиков. Чтобы это ускорить, прямо на чипе проца есть своя маленькая память для кеша, она быстрее, но дороже, а потому меньше по доступному объему (примерно в 1000 раз) чем основная память.
Если данные уже в кеше, то чтение займет всего несколько тиков. Если же данных в кеше нет (это называется промах), то исполнение приложения может остановиться на около 100 наносекунд для копирования данных из памяти в кеш. Поэтому важно соблюдать локальность данных. Временная локальность - когда к одним и тем же данным обращаются очень близко по времени. Пространственная локальность - когда данные лежат рядом с друг другом (по соседним адресам). Процессор копирует данные в кеш не побайтово, а кусками (cache line), типичным размером куска является 64 байта. Поэтому линейный последовательный перебор массива/вектора может оказаться значительно быстрее разбросанного в пространстве списка.
#hardware
👍2❤1
Пару слов про нейминг и лицензии. Есть вот такой крутой технически проект, Peredvizhnikov Engine, полностью lock-free игровой движок на C++20. Сделан на модели акторов поверх корутин. При реализации задействованы: транзакционная память, lock-free очередь, lock-free сериализация, lock-free std::atomic_shared_ptr, lock-free аллокатор и тд. Как несложно догадаться фамилия автора движка — нет, не Передвижников, а Пермяков 😆.
Собственно, суть поста: сравните название любого популярного движка (Unity, Unreal, Godot, да даже наш Unigine) c Peredvizhnikov Engine с точки зрения иностранца. А теперь еще и посмотрите на лицензию - GPL (даже не LGPL!), которая не позволит сделать на нем закрытую игру (много ли в топе стима игр под GPL?).
https://github.com/eduard-permyakov/peredvizhnikov-engine
#github
Собственно, суть поста: сравните название любого популярного движка (Unity, Unreal, Godot, да даже наш Unigine) c Peredvizhnikov Engine с точки зрения иностранца. А теперь еще и посмотрите на лицензию - GPL (даже не LGPL!), которая не позволит сделать на нем закрытую игру (много ли в топе стима игр под GPL?).
https://github.com/eduard-permyakov/peredvizhnikov-engine
#github
GitHub
GitHub - eduard-permyakov/peredvizhnikov-engine: A fully lock-free game engine written in C++20
A fully lock-free game engine written in C++20. Contribute to eduard-permyakov/peredvizhnikov-engine development by creating an account on GitHub.
🌚1
На картинке два примера того, как процессор (не компилятор) предсказывает ветвление/переход и продолжает выполнение кода дальше, будто бы оно истинно (спекулятивное исполнение, чтобы не тормозить конвейер).
В первой строчке указатель как правило не нулевой, а потому можно (выгодно) продолжить выполнение кода в большинстве случаев. В 4-й строчке массив v очень большой и потому условие for будет нарушено только один раз, а потому выгодно считать себе дальше, а потом ненужное при промахе просто отбросить.
Но! Что же делать с ошибкой предсказания. Ведь в первом случае мы разыменуем нулевой указатель, а во втором изменим не "нашу" память. Как процессор обрабатывает эту ситуацию? Вот так: если произошла ошибка, то она удерживается, пока ветка не будет проверена, а все изменения в памяти не сбрасываются в основную память до окончания проверки (они удерживаются в буферах записи).
Подробности: https://youtu.be/g-WPhYREFjk?si=m2bjcQ7MbV_4tOKc&t=1284
#hardware
В первой строчке указатель как правило не нулевой, а потому можно (выгодно) продолжить выполнение кода в большинстве случаев. В 4-й строчке массив v очень большой и потому условие for будет нарушено только один раз, а потому выгодно считать себе дальше, а потом ненужное при промахе просто отбросить.
Но! Что же делать с ошибкой предсказания. Ведь в первом случае мы разыменуем нулевой указатель, а во втором изменим не "нашу" память. Как процессор обрабатывает эту ситуацию? Вот так: если произошла ошибка, то она удерживается, пока ветка не будет проверена, а все изменения в памяти не сбрасываются в основную память до окончания проверки (они удерживаются в буферах записи).
Подробности: https://youtu.be/g-WPhYREFjk?si=m2bjcQ7MbV_4tOKc&t=1284
#hardware
👍1
Увидела такой пример про использование оператора typeid, который возвращает ссылку на объект типа std::type_info с описанием типа. Поймала себя на мысли, что ни разу не встречала использования typeid в проде (что наверное хорошо, ведь скорей всего typeid это результат плохой архитектуры).
dynamic_cast приходилось использовать, но как правило в конкретных реализациях, а не в коде, где используются абстракции.
Вообще, если нужна какая-то иерархия, где вот прям куча классов наследуют интерфейс, возвращаю enum в виртуальном методе базового класса и дальше static_cast по значению enum. Кажется, это самое приемлемое решение, если все же нужно кастить. Так лучше по производительности, ведь тип по сути уже лежит в vtable.
#cpp #архитектура
dynamic_cast приходилось использовать, но как правило в конкретных реализациях, а не в коде, где используются абстракции.
Вообще, если нужна какая-то иерархия, где вот прям куча классов наследуют интерфейс, возвращаю enum в виртуальном методе базового класса и дальше static_cast по значению enum. Кажется, это самое приемлемое решение, если все же нужно кастить. Так лучше по производительности, ведь тип по сути уже лежит в vtable.
#cpp #архитектура
👍2❤1
Как средствами стандартной библиотеки проще всего удалить их контейнера дубликаты? Воспользоваться функцией std::unique. Она перемещает повторные элементы в конец контейнера, оставляя на месте только первый уникальный. НО! она делает проверку не по всему контейнеру, а только по соседним равным элементам, например если есть последовательность {1 3 3 3 5}, то она сможет переместить лишние 3-ки. Проще всего получить непрерывные последовательности одинаковых элементов путем сортировки контейнера (хотя это и не обязательно). Также особенностью unique является то, что работает она "на месте" не создавая новый контейнер, а возвращая итератор на конец диапазона уникальных значений. Пример:
В коде выше <*> показан итератор, возвращаемый функцией. Как видим, после итератора расположены повторяющиеся элементы. В последней строке мы копируем уникальные элементы в новый вектор.
Что будет, если убрать сортировку? std::unique отработате неверно, результатом будет uniqueV = {1, 5, 7, 8, 5, 1, 8, 3, 5}.
Причина, почему unique просматривает только соседние элементы, как обычно кроется в эффективности - O(n).
#cpp
std::vector<int> v = {1, 5, 7, 8, 5, 1, 8, 3, 5};
std::sort(v.begin(), v.end()); // {1, 1, 3, 5, 6, 7, 7, 8, 8}
auto endUnique = std::unique(v.begin(), v.end()); // {1, 3, 5, 7, 8, <*> 5, 7, 8, 8}
std::vector<int> uniqueV(v.begin(), endUnique); // {1, 3, 5, 7, 8}
В коде выше <*> показан итератор, возвращаемый функцией. Как видим, после итератора расположены повторяющиеся элементы. В последней строке мы копируем уникальные элементы в новый вектор.
Что будет, если убрать сортировку? std::unique отработате неверно, результатом будет uniqueV = {1, 5, 7, 8, 5, 1, 8, 3, 5}.
Причина, почему unique просматривает только соседние элементы, как обычно кроется в эффективности - O(n).
#cpp
👍1
Micro$oft в своем амплуа. Две комплементарные функции GetUserName и GetComputerName, первая возвращает строку С нулевым символом в конце, вторая БЕЗ. Почему? Да хз почему, но хоть в доке описано 😆.
#include <windows.h>#winapi
#include <Lmcons.h>
wchar_t buf[UNLEN + 1] = {0};
DWORD bufLen = UNLEN + 1;
if (GetUserNameW(buf, &bufLen)) {
userName = std::wstring(buf, buf + bufLen - 1); // includes extra \0
}
bufLen = UNLEN + 1;
if (GetComputerNameW(buf, &bufLen)) {
computerName = std::wstring(buf, buf + bufLen); // no extra \0
}
❤2👏1
Немного неожиданное поведение и, на мой взгляд, не совсем удачные названия. Всем известно, что std::string::find ищет первую подстроку в строке, тут все понятно. А теперь посмотрим на результат вызова std::string::find_first_of:
#cpp
std::string str("How to Lose a Guy in 10 Days");
auto pos1 = str.find("10"); // 21
auto pos2 = str.find_first_of("0123456789"); // 21
В обоих случаях результат одинаковый - 21. Дело в том, что find_first_of ищет любой элемент из аргумента в строке и возвращает позицию первого найденного. В данном случае мы ищем первую цифру (0-9) в строке. Код конечно компактный, но вот я бы назвала такую функцию find_any_of.#cpp
❤3
std::any - это безопасный void* с контролем времени жизни, проверкой типа при доступе и другими возможностями. Например, можно проверить, выставлено ли значение с помощью has_value и у нас получится функционал std::optional. Используя std::any_cast можно получить не только сами данные, но и ссылку на данные или даже указатель. А также можно запросить тип данных в рантайме. Код:
1. any не шаблонный класс (в отличие от std::variant)
2. если типы не фиксированы заранее (иначе лучше то std::variant)
Вообще, не фиксированность типов редкая штука, но возможно бывают случаи, когда нужно разрешить клиенту вашего кода хранить что угодно и отдать тип на откуп ему же.
#cpp
std::any a;Зачем же нужен any, если есть optional и variant?
std::cout << a.has_value();
a = 1; // храним int
a = std::string("Mary"); // а теперь строку
try
{
std::cout << std::any_cast<std::string>(a); // Mary
std::any_cast<std::string&>(a) = "Masha"; // кастуем до ссылки
std::cout << std::any_cast<std::string>(a); // Masha
a = 2.0; // поменяем на double
double * ap = std::any_cast<double>(&a); // указатель на данные
std::cout << *ap; // 2.0
if (a.type() == typeid(double)) { // проверяем тип
std::cout << "I'm double";
}
}
catch (const std::bad_any_cast& e) // any_cast не сработал
{
std::cout << e.what() << std::endl;
}
1. any не шаблонный класс (в отличие от std::variant)
2. если типы не фиксированы заранее (иначе лучше то std::variant)
Вообще, не фиксированность типов редкая штука, но возможно бывают случаи, когда нужно разрешить клиенту вашего кода хранить что угодно и отдать тип на откуп ему же.
#cpp
❤5
Почему важно изучать свежие источники про C++, где описаны ноые стандарты? Читаю в одном месте, что в лямбдах нельзя делать аргументы по умолчанию. Думаю, что за ерунда, в чем техническая проблема? Оказалось, что начиная с С++14 можно так делать:
#cpp14
Кстати, cout не нужно захватывать в лямбдах, так как они могут непосредственно использовать статические локальные переменные и переменные, объявленные вне функции.
auto l = [](int i = 4){
std::cout << i;
return i;
};
std::cout << l() << std::endl; // 44
#cpp14
👍2