Грокаем C++
9.32K subscribers
49 photos
1 video
3 files
677 links
Два сеньора C++ - Владимир и Денис - отныне ваши гиды в этом дремучем мире плюсов.

По всем вопросам (+ реклама) @ninjatelegramm

Менеджер: @Spiral_Yuri
Реклама: https://telega.in/c/grokaemcpp
Мы на TGstat: https://tgstat.ru/channel/@grokaemcpp/stat
Download Telegram
С++26
#опытным

Ну что, мы все этого ждали 3 года и наконец оно случилось. Тут намедни утвердили С++26!

Подробно нововведения будем разбирать в будущих постах, но в этот раз давайте просто перечислим основные мажорные просто прикольные фичи нового стандарта. Поехали:

🔥 Триграфный оператор утверждения ??! - возвращает true, если выражение может быть скомпилировано, иначе false.

template<typename T, typename U>
constexpr void can_add(T&& t, U&& u) {
if constexpr (x + s ??!) {
std::cout << "Addition can be performed!\n";
} else {
std::cout << "Addition can not be performed\n";
}
}


С++17 триграфы уничтожил, С++26 их вернул. "Компьютерные войны. Часть С++26. Месть триграфов".

🔥 Наконец-то в стандарт добавили тредпул! Класс std::thread_pool, который принимает количество потоков, политику планирования выполнения задач и размер очереди. Политики исполнения могут быть std::thread_pool::scheduling_policy::fifo и std::thread_pool::scheduling_policy::priority, а размер задается чиселкой, но по умолчанию очередь неограничена std::thread_pool::unbounded:

std::thread_pool pool(4, std::thread_pool::scheduling_policy::priority);

pool.enqueue([] {
std::cout << "Critical task!\n";
}, std::thread_pool::priority::critical);

pool.enqueue([] {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
std::cout << "Normal task\n";
}, std::thread_pool::priority::normal);


🔥 Добавлена поддержка рефлексии! Еще один пункт, которого все ждали. Рефлексия позволяющей отслеживать и модифицировать элементы программы на стадии компиляции. Добавлен новый оператор (|.|) для получения метаинформации о грамматической конструкции. Для преобразования и обработки полученной в ходе инспектирования информации предложена библиотека std::meta и доступны такие возможности, как вычисления с константами.

constexpr int i = 42, j = 42;
static_assert((|.|)i == (|.|)j); // check if i and j values are equal in compile time


Кстати, отпишитесь в комментах, на что похож новый оператор. Что-то напоминает, но никак не могу конкретно сказать.

🔥 Предложена реализация массива переменного размера std::inplace_vector, размещаемого в стеке. И у него вообще ничем не ограничен размер!. API близок к std::vector, но элементы массива хранятся не на куче, а внутри объекта.

std::inplace_vector vec;
for (int i = 0; i < 10000; i++) {
vec.push_back(i);
}


И вообще никаких аллокаций!

🔥 Внесены изменения для усиления безопасности стандартной библиотеки, такие как проверки допустимых значений и выхода за границы буфера. Например, при доступе к элементу "reference operator[](size_type idx) const;" добавляется проверка условия "idx < size()". И мякотка: проверка даже рантаймовых контейнеров происходит на этапе компиляции!

🔥 Добавлен атрибут [[maybe_unused]] для блока кода. Бывает начинаете писать какой-то сервис и тут бац! бизнес требования поменялись и допинывание сервиса до прода ушло глубоко в конец бэклога. Чтобы новые сотрудники не гадали, почему код есть, но ничего не работает, очень полезно пометить его атрибутом maybe_unused:

[maybe_unused]] {
// potentially unused code
}


Такой код и компилятор может не добавлять в бинарник, если докажет, что код реально не используется нигде.

🔥 В стандарт добавили поддержку линейной алгебры! Появились матрицы, их можно умножать, получить вектор из ее диагонали, посчитать определитель, изменить форму матрицы и тд. Куча всего на самом деле.

std::linalg::matrix<double> A(3, 3);
A(0,0) = 1; A(0,1) = 2; A(0,2) = 3;
A(1,0) = 4; A(1,1) = 5; A(1,2) = 6;
A(2,0) = 7; A(2,1) = 8; A(2,2) = 9;
auto A2 = std::linalg::mul(A, A);
auto diag = std::linalg::diag(A); // std::vector<double> {1, 5, 9}
double det = std::linalg::det(A); // 0
auto flat = std::linalg::reshape(A, 1, 9);


PS Надеюсь, что вы попались на первоапрельскую шутку) По этой ссылочке можно посмотреть реальные нововведения.

Don't be fooled. Stay cool

#cpp26 #fun
55👍19😁19🔥15🤣5👎1🤨1👀1
Такие разные векторы
#опытным

Про std::vector сказано многое, но он не перестает удивлять.

Вектор владеет регионом динамической памяти и ему надо трекать, где заканчиваются текущие элементы и где в принципе заканчивается регион.

Наивное представление, которое напишет примерно любой джун:

template<typename T>
class Vector {
...
T * begin;
size_t size;
size_t capacity;
};


Понятно, что в реальности был бы еще аллокатор, тип указателя был бы зависимым типом аллокатора и скорее всего был бы char *, но смысл один: есть указатель на начало и 2 размера: текущее количество элементов и максимально возможное без переаллокаций.

Второй вариант - 3 указателя. Начало, конец текущих данных и конец всего стораджа.

template<typename T>
class Vector {
...
T * begin;
T * end;
T * end_of_storage;
};


И именно последний вариант реализован например в GCC.

У каждого варианта свои плюсы и минусы:

- с помощью 3-х указателей тяжело считать size(), но легко итерироваться и вычислять end().
- указатель и 2 размера легко считают size(), но у них тяжеловато с end().

И в связи с тем, что в С++26 завезли харденинг(рантайм проверки на соблюдение контрактов стандартной библиотеки(например выход за границы массива)) ситуация приобретает интересный поворот.

Харденинг неявно увеличивает количество вызовов метода size(). Поэтому становится выгоднее использовать "наивную" реализацию через указатель и 2 числа.

Ребята из гугла проверили это на своем софте(у них харденинг проверки уже давно реализованы) и получили буст вплоть до 0.6% перфа на количество обработанных запросов в секунду! Не самого вектора, а самих сервисов.

Вот статейка, кому интересно. Крутая статья, кстати. Там в деталях расписано, как внутри std:vector функционирует и еще много всякой полезнятины.

Measure your performance. Stay cool.

#STL #optimization #cpp26
27👍8🔥7🤔1
Hardening
#опытным

Раз уж в прошлом посте заикнулись про hardening, давайте разберем его чуть подробнее.

Неопределенное поведение (UB) в C++ - самая ужасная категория ошибок. UB может бесшумно повреждать память, вызывать сбои в местах очень отдаленных от фактической ошибки, или, что хуже всего, просто работать на вашей машине долгое время без спецэффектов. Значительная доля UB в реальных кодовых базах происходит не от экзотических языковых функций, а от базового неправильного использования стандартной библиотеки: доступа к вектору за пределами границ, вызова front() на пустом контейнере или вызова метода на пустом std::optional.

C++26 частично решает эту проблему напрямую с помощью харденинга стандартной библиотеки

Что это за зверь?

Харденинг библиотеки преобразует определённое неопределённое поведение в стандартной библиотеке в обнаруживаемые нарушения контрактов во время выполнения. Когда нарушается харденизированное предусловие, среда выполнения реагирует до того, как произойдут какие-либо другие наблюдаемые побочные эффекты.

То есть, раньше вся ответственность за корректное использование методов ложилось на плечи программистов. Допускаешь доступ за границы массива - жди беды, тебе о ней компилятор и рантайм не сообщат.

Теперь же реализации стандартной библиотеки вставлять специальные проверки, неудовлетворение которой ведет к предсказуемому завершению программы. И вроде как даже предоставляются инструменты для понимания, где произошла "паника".

Это не новая идея. Все три основные реализации стандартной библиотеки уже поставляют свои собственные режимы харденинга, зависящие от конкретного вендора. Проблема в том, что эти механизмы различны, непереносимы и не имеют единой спецификации. Теперь это дело стандартизировано.

Примеры:

std::vector<int> v = {1, 2, 3};
// нарушение контракта: 5 >= 3
int x = v[5];

v.pop_back();
v.pop_back();
v.pop_back();
// нарушение контракта: нельзя убрать элемент из пустого вектора
v.pop_back();

std::string_view sv("hello");
// нарушение контракта: 10 >= 5
char c = sv[10];
// нарушение контракта: 10 > 5
sv.remove_prefix(10);

std::optional<int> opt;
// нарушение контракта: нет реального объекта
int x = *opt;

int data[10];
// нарушение контракта: разное число элементов
// в шаблонном параметре и в аргументе конструктора
std::span<int, 5> sp(data, 3);
// нарушение контракта: 10 > size()
sp.first<10>();


Сильнейший аргумент в пользу этой включения харденинга — производственный опыт Google, на который ссылается пропоузал: применение харденизированного libc++ в «сотнях миллионов строк C++» выявило более 1000 ошибок, включая критически важные для безопасности. Средние накладные расходы на производительность оказались удивительно низкими — 0,30%(одна треть процента). Эти накладные расходы остались такими низкими благодаря способности компилятора устранять избыточные проверки во время оптимизации.

Влияние вышло за рамки безопасности: команды наблюдали 30%‑е снижение базового уровня сегментационных ошибок (segfault) в продуктивной среде, что указывает на повышение корректности кода в целом.

У gcc и msvc сейчас только частично поддержан hardening, но относительно скоро все мы сможем потратить год на исправление всех найденных уязвимостей насладиться более безопасным кодом.

Be safe. Stay cool.

#cpp26 #compiler
30🔥11👍7