Добавленный в C++11 std::forward_list реализует односвязный список, в отличие от двухсвязного std::list, и потому занимает меньше памяти. Однако за это приходится своеобразно платить. Допустим, мы находимся на элементе 3 и хотим его удалить. Для этого нужно изменить связь предыдущего элемента 2, но мы не можем к нему вернуться, так как связь идет в одну сторону (в двухсвязном в обе). Поэтому методы добавления и удаления элементов в forward_list изменяют элемент после указанного, так что мы можем обновить ссылку. Для этого в таком списке используется например не метод erase (которого вообще нет), а метод erase_after.
А что же делать с первым элементом, спросите вы, мои котятки? Как его удалить, если erase_after(begin) удалит элемент после первого, то есть второй? Для этого разработчики придумали интересный костыль, а именно метод before_begin(), который возвращает итератор на несуществующий элемент перед первым (аналогично тому, как во всех контейнерах end() указывает на несуществующий элемент после последнего). Вот так вот 😉.
#cpp
А что же делать с первым элементом, спросите вы, мои котятки? Как его удалить, если erase_after(begin) удалит элемент после первого, то есть второй? Для этого разработчики придумали интересный костыль, а именно метод before_begin(), который возвращает итератор на несуществующий элемент перед первым (аналогично тому, как во всех контейнерах end() указывает на несуществующий элемент после последнего). Вот так вот 😉.
#cpp
👍3
Найдите ошибку:
#cpp
std::vector<int> vec = {0,1,2,3,4,5,6,7,8,9};
auto mid = vec.cbegin() + 5;
auto findIt = std::find(vec.cbegin(), mid, 10);
if (findIt == vec.cend()) {
std::cout << "Not found";
} else {
std::cout << "Found";
}
Ошибочно выведется "Found", хотя 10 нет в векторе. Почему? Дело в том, что std::find не возвращает итератор end контейнера, если элемент не найден (указатель на end попросту негде взять). Вернет же функция второй параметр (last), если элемент не обнаружен. Правильно бы было сравнивать в if с mid (который не просматривается, к слову).#cpp
👍2
Интерактивная карта Ядра, можно местами потыкать код. Я офигела от количества макросов местами 😀
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