Маша С++
104 subscribers
54 photos
2 files
65 links
Учу C++ и не только вместе с вами, мои котятки)
Download Telegram
Язык программирования Birb, состоящий только из emoji-значков птиц. Конечно же, Тьюринг-полный.

🐦🐦🕊🐧🕊 wrapper for 1+(++0)
🐧🐦🐧🕊🐧🕊 wrapper for add ++(++0)
🪽🐧 add
🦢🐧 inc
🦢🐧 inc
🐥🐦 zero
🦢🐧 inc
🐥🐦 zero
Добавленный в C++11 std::forward_list реализует односвязный список, в отличие от двухсвязного std::list, и потому занимает меньше памяти. Однако за это приходится своеобразно платить. Допустим, мы находимся на элементе 3 и хотим его удалить. Для этого нужно изменить связь предыдущего элемента 2, но мы не можем к нему вернуться, так как связь идет в одну сторону (в двухсвязном в обе). Поэтому методы добавления и удаления элементов в forward_list изменяют элемент после указанного, так что мы можем обновить ссылку. Для этого в таком списке используется например не метод erase (которого вообще нет), а метод erase_after.

А что же делать с первым элементом, спросите вы, мои котятки? Как его удалить, если erase_after(begin) удалит элемент после первого, то есть второй? Для этого разработчики придумали интересный костыль, а именно метод before_begin(), который возвращает итератор на несуществующий элемент перед первым (аналогично тому, как во всех контейнерах end() указывает на несуществующий элемент после последнего). Вот так вот 😉.

#cpp
👍3
🙃
Найдите ошибку:
    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
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/
Как же забавно выглядит дискретная видеокарта, подключенная к малинке (зеленая плата) 🥰.
У меня кстати Raspberry 3 B+ где-то валяется, у кого тоже есть одноплатник?

#hardware
Часто бывает, что большой жирный класс реализует несколько интерфейсов и можно запутаться к какому именно принадлежит виртуальный метод. Было бы здорово, если имена методов можно было предварять именем интерфейса. Мне кажется, что это бы повысило читаемость. А может быть можно использовать для разрешения конфликта одинаковых имен от разных интерфейсов?

Или что-то такое уже есть?

class Foo : public Interface, public Callback
{
public:
virtual bool Interface::start() override {}

virtual void Callback::onData() override {}
};

#cpp
Вышел Qt 6.6. Изменения, конечно, забавные. Если улучшение модуля Qt TextToSpeech еще с точки зрения UI-фреймворка как-то можно понять (поддержка людей с ограниченными возможностями из коробки это хорошо), то вот добавление какой-то СУБД Mimer SQL (ни разу не слышала про такую), добавление класса для захвата содержимого отдельного окна QWindowCapture и тп вызывает ощущение дикого раздувания фреймворка. Лучше бы Qt Widgets полировали. (А вообще, конечно, кьют - ванлав 🫶).

https://www.opennet.ru/opennews/art.shtml?num=59907

#cpp #qt
На собеседовании спросили, что делает эта функция:

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, который в частности позволяет найти сумму чисел в векторе (третий параметр задает начальное значение):
#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
В продолжении темы про std::accumulate. Как оказывается, тип третьего параметра определяет тип результата и это может приводить к неожиданным последствиям. Найдем сумму вектора double, передав первый раз 0 (то есть как int), а второй 0.0 (то есть как double):

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
Узнала, что есть такая структура данных как "кактус стек". Это стек, в который кроме обычного элемента можно добавить другой стек. Получается альтернативная "ветка" со своим стеком. Если нарисовать на бумажке, то получится как кактус на картинке (отсюда и название). Кажется, что за пределами языков/компиляторов не особо применяется. Например, можно сделать замыкание (лямбду) со своим собственным стеком и ответвить его от стека текущей функции. Не очень то полезный пост получился😆
👍21
Когда нужно сделать безопасный callback из другого потока и время жизни коллбека не известно или не контролируется, часто храню коллбек как std::weak_ptr. Это позволяет lock()-нуть его до shared_ptr и во-первых проверить что он есть, а во вторых убедиться что он будет существовать всё время его вызова.
Код примерно такой (пишу по памяти):
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
👍21
В Telegram beta для десктопа появилась подсветка синтаксиса. Язык нужно указывать сразу после трех `, для плюсов это будет cpp. Результат на картинке. Также появилась кнопка копирования исходников.
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
🌚1