this->notes. – Telegram
this->notes.
4.54K subscribers
25 photos
1 file
330 links
О разработке, архитектуре и C++.

Tags: #common, #cpp, #highload и другие можно найти поиском.
Задачки: #poll.
Мои публикации: #pub.
Автор и предложка: @vanyakhodor.
GitHub: dasfex.
Download Telegram
#common #highload #cpp

Пару дней назад вышли записи прошломайского Highload++ Foundation 2022. Накидаю докладов из того, что уже посмотрел.

1. Как выжать 1 000 000 RPS. Андрей Аксёнов (link).
Классический доклад в своей нестандартной манере про бенчмарк Sphinx.

2. Поиск GPS-аномалий среди сотен тысяч водителей. Александр Царьков (link).

3. Как мы ускорили Яндекс Go на несколько секунд. Денис Исаев (link).
Я немного упоминал этот доклад тут в п.4. Просто и прикона.

4. Экскурсия в бэкенд Интернета вещей. Владимир Плизга (link).
Прикона было узнать, какие задачи в целом решают с помощью IoT. Правда я так до конца и не понял, что это такое.

5. Авито: root cause detector. Юрий Дзюбан (link).
Интересная попытка автоматизировать расследование проблем.

6. Избавляемся от кэш-промахов в коде для x86-64. Евгений Буевич (link).
Я не поклонник вот этих низкоуровневых штук, но может вам будет интересно. И заодно тогда можно глянуть LLVM Optimization Remarks by Ofek Shilon с осеннего CppCon 2022 (link).

7. Как наладить CI-процесс в монорепозитории. Алина Власова (link).

8. Настраиваем инцидент-менеджмент: от хаоса до автоматизации. Сергей Бухаров (link).
Тут скорее было интересно посмотреть, что у других. Имхо, у нас лучше.
Основные вопросы к системе это: насколько нужен скрайбер; почему оценкой импакта на бизнес занимаются разработчики в процессе инцидента; и зачем есть secondary дежурный, почему их только двое (понятно, что дальше это кому-то эскалируется, но почему не третьему, а SRE?)?

Пока местами прикона.
👍6
#highload #cpp #go

Досмотрел всё интересующее из майского highload, так что докидываю к прошлому посту (и пару докладов с плюсовых конф).

1. Как мы готовили поиск в Delivery Club. Иван Максимов (link).
Тут интересно, потому что я много работаю над штуками вокруг нашего поиска (не непосредственно качеством, а именно около). Когда ты чуть-чуть в теме, смотреть интереснее, что там чуваки намутили.

2. Хайлоад, которого не ждали. Дмитрий Самиров (link).
Тут аналогично, потому что чуваки намутили какую-то доставку продуктов из Я Еды. Почти Лавка. Только у нас круче.

3. Как мы устроили переезд 10+млн строк С++ кода на новый стандарт. Никита Старичков (link).

4. Статический анализ кода++. Анастасия Казакова (link).

5. Компилируем 200 000 файлов быстрее, чем distcc. Александр Кирсанов (link).
Это интересно с точки зрения, что в ВК намутили миллион каких-то своих способов компилироваться из PHP в C++ эффективнее. И по чуть-чуть чуваки эту свою инфру развивают. Прикольно посмотреть, что нового они там придумывают.

6. Почему всё сложно с оптимизацией и что с этим делать. Константин Шаренков (link).
В целом доклад с вопросами, но мб вам зайдёт. Мне не то чтобы прям, но выше среднего.

7. Go Map Internals. Егор Гришечко (link).

И плюсовые из закромов:

8. C++14 Reflections Without Macros, Markup nor External Tooling. Antony Polukhin (link).
Блин, оч крутой доклад с CppCon 16ого года, который я как-то пропустил. Антон рассказывает про magic_get/boost::pfr и всякие мозговзрывающие штуки, которые там использовались.

9. Back to Basics: C++ API Design. Jason Turner (link).

10. Метаклассы в C++17: фантастика? Реальность! Сергей Садовников (link).

Мб вам что-то на последних конфах понравилось? Можно поделиться в комментариях.
👍132
#cpp

Сходил на встречу российской РГ21. Накидаю инфы (что-то вы уже видели, но лишним, думаю, не будет).

В C++23:

- починили некорректные срабатываения static_assert(false):

if constexpr (constexpr_condition) {

} else {
static_assert(false, “message”);
}


- починили range based for. Link на proposal;
- добавили static operator[] (в записи приконый пример с таким оператором для енамов);
- монадические операции для std::expected;
- дополнения к алгоритмам ranges (например эффективная итерация по ключам/значениям мап и много другого), но из-за этого ренджи из 23его стандарта сильно несовместимы с ренджами из 20ого. Не оч понятно, что это значит. Подождём и потрогаем;
- std::stacktrace и std::print;
- constexpr много всего (например std::unique_ptr).

С++26:

Пока глобально планируется примерно вот так:
- library support for coroutines;
- executors;
- networking;
- pattern matching;
- reflection;
- и всякие другие штуки.

Конкретно:
- #embed (емнип приехал из C23);
- пытаются пропихнуть std::get для агрегатов;
- stacktrace for exceptions в комитете приняли с вопросами, будут поправлять;
- ABI ломать пока не будут, незачем, говорят.

Хех. Прикиньте, С++23 это вот в этом году. А мы только недавно на 20й стандарт перешли🥴🥴🥴
👍8🔥2
#cpp

Инициализация 1/2.

Как-то так получилось, что в плюсах даже инициализация довольно тяжкая тема. Как минимум потому что их огромное количество. К счастью, в 99% случаев вы об этом не думаете. Не думаете, какие отличия несут в себе разные способы инициализации. Но на случай, если вы вдруг встретитесь, давайте немного посмотрим на разные виды и их отличия.

Первый пример:

int i;
return i; // ub

struct S {
int x;
};
S s;
return s.x; // ub


Это default initialisation, что вообще-то немного путает, потому что переменная у вас не инициализируется ничем дефолтным (мусор я бы дефолтным не назвал). Можно поправить так:

S() : x(0) {}

Это member initialiser list. Не оч удобно, потому что надо повторять во всех конструкторах, потому в C++11 появился

struct S {
int x = 0; // default member initialiser
};


Наверное использовать dmi может быть хорошим советом, пусть и не лишённым проблем.
Далее:

int i = 2; // copy initialisation

Такой же способ инициализации используется, когда вы передаёте аргумент в функцию по значению или по значению возвращаете. Если тип инициализируемого объекта и значения справа от = не совпадают, будет произведена некоторая цепочка преобразований типов (насколько я понимаю, сколько угодно стандартных преобразований и может быть одно пользовательское, всё это в произвольном порядке, конечно).
Помните, что copy init это никогда не operator=.

Aggregate init:

int i[3] = {1, 2, 3};
int i[] = {1, 2, 3};

struct S { int x; int y; };
auto s{1, 2};


Агрегатная инициализация будет copy-инициализировать каждый из переданных аргументов. У такого init есть фича:

struct S { int x; int y; };
S s = {1};
return s.y; // какое значение? ub?


Если вы опускаете часть аргументов при использовании агрегатной инициализации, остальные будут zero initialisated. Т.е. у вас никогда при таком способе не будет неинициализированных данных. Потому и работает вот такой способ заполнения массивов нулями:

int a[10] = {};

Ещё тут есть такая фича как brace elision.

Далее static initialisation:

static int i = 1; // constant initialisation
static int j; // zero initialisation


Все эти способы были просто унаследованы плюсами от C. Теперь про плюсовые.

Direct initialisations (whenever the initialiser is an argument list in parens):

S s(1, 2); // constructor
int i(3); // also for built-in type


Для built-in типов никакой разницы от copy init, но для остальных это возможность передать более одного аргумента при инициализации и отсутствие цепочки преобразований (просто вызов конструктора по всем правилам перегрузок). Тут стоит быть осторожным:

struct S {
explicit S(int) {}
S(double) {}
};
S s1 = 1; // S(double)
S s2(2); // S(int)


Value init (whenever the initialiser is a pair of empty parens):

int f() { return int(); }

Если есть пользовательский default ctor, то вызовется он, иначе zero init. Тут можно словить всякие маслины. Осторожно.

Uniform init — способ универсальной инициализации из C++11 (с помощью {}). Тут же появились list inits:

S s{1, 2}; // direct list init
S s = {1, 2}; // copy list init


И тут же появился std::initializer_list. Думаю, как им пользоваться вы знаете, так что опустим. Про его проблемы можно глянуть тут.
Для агрегатов list init это agregate init. Для built-in типов пример выше. Для классов либо вызов подходящего конструктора, либо direct/copy init.
{} — особый случай. Это дефолтный ктор, иначе ктор от initializer_list, иначе value init ☺️🎧
При list init никаких narrowing conversions не происходит, так что int i{1.0} не закатит.

Ну и опуская всякое неинтересное, не забудем про C++20 designated initialisation:

struct S { int x; int y; };
S s{.x = 1, .y = 2};


Подробнее со всякими интересными примерами можете посмотреть в доклад Тимура Думлера.

Тут мы упомянули даже не всё что на гифке. А на гифке тоже пары штук не хватает🙂
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6🤯2
#cpp

Инициализация 2/2.

Непонятно что делать с этой инфой. Давайте посмотрим на пару примеров, которые помогут сделать выводы. Самый простой:

int m();


Вот я хочу инициализировать переменную m по месту. Но, учитывая most vexing parse, это у вас не переменная, а объявление функции. Всё сломалось. Или гораздо более стрёмный кейс:

vector<int> v(istream_iterator<int>(cin), istream_iterator<int>());

Fuck. Это тоже объявление функции (оно скомпилируется и вы получите ошибку в совсем другом месте!). Оставим разбор этого выражения читателю.

Из забавного (если конечно больно можно считать за смешно) следующие кейсы:

int i(1, 2);
int i = (1, 2);
int i = int(1, 2);
auto i(1, 2);
auto i = (1, 2);
auto i = int(7, 9);


Будет ли первое компилироваться? Конечно же нет. Наверное тогда и второе? Ошибка. Второе это оператор запятая, потому всё корректно. Третье, четвёртое и шестое (по аналогии с 3м), очевидно 🎧, тоже CE. Пятое по аналогии со вторым OK.

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

Из интересного ещё следующий момент. В C++11 следующая запись означала std::initializer_list<int>{1}:

auto i{1};

Однако в C++14 семантику подобного изменили, где это стало int{1}.

auto i = {1};


А что же тут? Это без сомнений init_list. Т.е. вы меняете тип объекта при инициализации с помощью =. Конечно интуитивно это вроде понятно, но кринж🥴, не правда ли? Говорят, что семантику такого поправлять не будут. Может просто запретят что-то такое делать в будущем (или нет, who knows). Мб потому стоит ещё и отказаться от инициализации с помощью =. Просто на всякий случай.
Хотя есть аргумент за такой стиль. У вас будет одинаковый способ “инициализации” переменных и типов:

auto x = …;
using T = …;


Для типов тогда лучше явно указывайте тип: auto i = int{1} (это всё мысли из AAA от Herb Sutter). В защиту такого подхода приводится поинт, что вы не забудете инициализировать вашу переменную. Хотя и тут есть траблы:

std::string x = “1”;
auto x = “1”s; // C++14


Вот эту s в конце (литерал) очень легко забыть/не обратить на неё внимания.
Ещё можно заметить, что такой способ работает (работал*) только для movable/copyable типов, иначе CE:

auto a = std::atomic<int>{9}; // CE before C++17

Ну и всякие другие проблемы бывают. Например с производительностью некоторых типов.

Чисто из опыта совет: помните, что значит первая буква в AAA — Almost. Потому что иногда поиск типа переменной становится неприятным муторным занятием, попросту тратящим время.

Ещё классический момент это инициализация вектора:

std::vector<int> v(3, 1); // 1 1 1
std::vector<int> v{3, 1}; // 3 1


Ну тут всё понятно. Не будем обсуждать.

В докладе Nicolai Josuttis можно посмотреть про это всё счастье подробнее. Там ещё много маслин словить можно.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1111
#cpp

1. Женя писал про попытку закоммитить в clang реализацию assume и про то, почему не получилось. Даже как-то и несмешно уже.

2. А Саша писал у себя про self-move. Почитайте. Там ещё в комментах есть интересное.

3. Тут краткий и понятный поинт про то, почему лучше использовать std::make_unique вместо прямой передачи указателя в конструктор std::unique_ptr. В целом понятно.
А тут можно посмотреть аналогичный ответ про std::make_shared (разница гораздо более ощутимая, но не такая однозначная).

4. Небольшая статья про использование aligned new.

5. Доклад Herb Sutter про видение обобщённого программирования на C++ аж из 2017ого, вполне, тем не менее, актуальный, т.к. многое из рассказанного ещё не завезли.

6. Небольшая статья с заметками про лямбды.

Такой вот минипостик получился.
👍14
#common

Базово про кодировки.

Очень важно различать два основных понятия: текст и его представление в виде байт. Есть две основные операции перевода из одной сущности в другую: из текста в байты с помощью какой-то кодировки (обычно называется encode), и наоборот (decode).

Собственно кодировка это способ/правило/функция отображения текста в байты и наоборот.

Одной из первых адекватных кодировок стала ASCII. Она в соответствие каждому символу ставит семибитовый код (т.е. хранится 128 символов). 32 символа - управляющие, остальные - стандартные английские буквы, цифры, знаки препинания и т.д. 8й бит использовался для контроля ошибок. Позже она была расширена до 256 символов (задействованы все 8 битов). Однако уже успели появиться другие однобайтовые кодировки, которые расширяли изначальные 128 символов. Например KOI-8 — кириллическая кодировка, имеющая интересную особенность: в случае, если используемый редактор не поддерживает эту кодировку, при преобразовании кириллических символов в ASCII получался транслит; соответственно русские буквы шли не по алфавиту. Или CP866 — кодировка, используемая в MSDOS, — и CP1251 — дефолтная кодировка при использовании кириллицы в windows до windows 10.

Со временем 256 символов конечно же перестало хватать. И наличие различных кодировок не является универсальным решением. Потому родился Unicode. Он также является отображением из кода в символ, только размеры чуть больше: 2^21 символов.

Unicode реализуют несколько основных кодировок:
- UTF-32. Каждый символ занимает ровно 4 байта. С ней легче всего работать, но она занимает больше всего памяти из-за фиксированного размера.
- UTF-16. Каждый символ представлен либо 2, либо 4 байтами.
- UTF-8. Символ кодируется 1, 2, 3 или 4 байтами. ASCII-часть кодируется одним байтом, причём коды символов полностью совпадают.

Устройство UTF-8 (наиболее популярная из вышеназванных).

Если говорить об ASCII-символе, то в начале идёт 0, а потом 7 бит символа.
Если символ не ASCII, тогда нужно понять, сколько байт нужно для представления символа. Для этого в самом первом байте вместо нуля пишется некоторое количество единиц. Это количество и указывает на то, сколько байт занимает символ. Т.е. если он занимает 3 байта, то пишется 111, после чего [что логично] идёт 0. Далее идут уже значащие для кода биты. Каждый последующий байт начинается с 10, после чего идут 6 значащих битов.
Зачем нужна такая избыточность 🤔 ?
При разработке кодировки требовалось сохранить обратную совместимость с уже большой кодовой базой, написаной на C, в котором нулевой символ является знаком окончания строки. Как видим, нулевой символ в UTF-8 только один. Ещё тут выполняется такое свойство, что ни один символ не является префиксом другого, что позволяет использовать некоторый набор старых функций, работающих с ASCII с UTF-8 (например поиск подстроки в строке).

Собственно если посчитать, сколько символов мы можем задать подобным образом, получится что-то около 2^21.

Вот хорошая статья про это всё.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍174🔥2
#cpp

Чуть-чуть из доклада Daisy Hollman (не путать с Nadin Holman) на CppCon 2022 (прошлый я упоминал тут).

Fun fact. Можно скрыть общий шаблон с помощью constraint’ов:

template <class T>
struct B {
void print() { cout << “unreachable”; }
};

template <> requires true
struct B<T> {
void print() { cout << “new default”; }
};


Теперь изначальный шаблон недостижим, потому что более частный с точки зрения компилятора предпочтителен, пусть семантически он ровно такой же.
По факту, вы можете полностью заменить определения каких-нибудь стандартных классов (на практике это вероятно уб, но теоретически да).

Правда если вы потом захотите сделать ещё одну специализацию:

template <class T> requires std::integral<T>
struct B<T> {
void print() { cout << “integral”; }
};


то для условного int получится неоднозначность, потому что по факту это выбор между двумя requires true. Но это можно обойти объявлением всегда истинного концепта:

template <class T> concept True = true;

И поменять сигнатуры на

template <class T> requires True<T>
// и
template <class T> requires std::integral<T> && True<T>


Это какие-то жоские преки. Словно про поломанность std::void_t вспомнил.

Второй fun fact (ну это конечно чуваки крутят-вертят как хотят уже).
Помним можно писать user-defined literal templates:

template <char… Chars>
constexpr auto operator “”_i();


И использовать их можно вот даже для чисел:

auto x = 123_i;

Компилятор распарсит 123 в три символа и внутри оператора можете творить что хотите.
Собственно на этом и построена возможность обращаться к элементу тупла через operator[]:

template <size_t I> struct index {};

// преобразовываем литерал в число
template <char… Chars>
constexpr auto operator “”_i () {
return index<[]{
size_t rv = 0;
for (auto c : {Chars…})

rv = rv * 10 + (c - ‘0’);
}()>{};
}


template <class… Ts>
struct index_tuple : std::tuple<Ts…> {
using std::tuple<Ts…>::tuple;

template <size_t I>
auto operator[](index<I>) {
return std::get<I>(*this);
}
};


auto t = index_tuple<int, double>{1, 2.2};
cout << t[1_i];


Ну и всё. Файная обёртка над std::get готова😁
Но помните, что это работает только для литералов.

Прикона конечно, но кринж, что такие простые хотелки решаются вот такими методами. Может на раст уйду. Хз.
Или вообще кодить перестану.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7
#nereklama

У нас в Лавке активно развивается b2b направление (это когда мы предоставляем возможность пользоваться нашими техническими решениями партнёрам), в связи с чем ребята семимильными шагами расширяют команду. Потому есть пару вакансий на C++ и python:
- руководитель команды разработки оркестратора в b2b Лавки;
- руководитель команды разработки логистики в b2b Лавки;
- разработчик на python в команду инфраструктуры b2b Лавки;
- разработчик на C++ в команду b2b Лавки.

Я работаю в Лавке уже примерно полтора года и словил сильный буст в хард- и, что довольно важно, софтскиллах. Успел запилить несколько крупных фич, которые имеют ощутимое влияние на пользователей и внутренние процессы. А ещё менторил ребят, собесил, участвовал в яндексовых образовательных проектах и выступал с рассказами на внутренних митапах. В общем, вижу и трогаю множество возможностей для развития в разные стороны. Вы тоже можете : )

Можно подаваться напрямую по вакансиям или написать мне в лс (@vanyakhodor) за рефером.
👾97👍3❤‍🔥11
#cpp #common

1. Прикольный доклад Andrei Alexandrescu про мысли о рефлексии в плюсах.

2. В комментах поста про кодировки упоминали про сложность устройства эмодзи. Вот хороший пост про это. У автора оригинала кстати есть канал, где он горит на всякое время от времени: @nikitonsky_pub.

3. Не самый свежий доклад, но довольно общий и широкий. Про то, что особенного в субд в оперативной памяти.

4. В дополнение к посту про инициализацию можно почитать две статьи про std::initializer_list: раз, два.
👍10
#cpp

Атрибуты в C++ 1/2.

Начнём со стандартных.

С точки зрения синтаксиса поддерживается два основных варианта:

[[attribute_name]] // C++11
[[using attribute-namespace: attribute_list]] // C++17


Из примеров с cppreference ещё можно увидеть такое:
- атрибуты с аргументами: [[deprecated(“text”)]];
- с неймспейсом: [[gnu::unused]].

Атрибуты можно применять к чему угодно: переменным, типам, функциям, классам, блокам кода и целым translation units. Но конечно это должно согласовываться с его реализацией.

[[noreturn]] говорит, что функция не возвращает control flow кода. Например она вызывает std::terminate, бросает исключение, содержит бесконечный цикл и т.д. В C можно использовать _Noreturn или noreturn для аналогичного.

[[carries_dependency]] может использоваться в случаях, когда у вас memory order consume. Я вообще в этих ваших многопоточках не оч шарю, но говорят, что введение такого понятия не самое удачное решение. Мб подскажете адекватные кейсы использования?

[[deprecated]], [[deprecated(“message”)]] говорит, что какой-то объект является deprecated (обычно компиляторы выдают ворнинги при использовании помеченых штук).

[[fallthrough]] говорит, что провал в следующий case в switch является намеренным -> компилятор не будет подсказывать ворнингами об этом.

[[nodiscard]], [[nodiscard(“message”)]] применяются к функции и означают, что не стоит игнорировать её возвращаемое значение. Если да, получите ворнинг.

[[maybe_unused]] говорит, что используемый объект может быть просто объявлен, но нигде в коде не заюзан. Если атрибут есть, вы не получите ворнинга. Удобно иногда, когда пишешь код частями и хочешь проверять, компилится ли, наставить их всему, что мешает. Потом с продвижением по решению таски поубирать. Или когда у вас какой-то общий интерфейс, но не все аргументы нужны для новой реализации.

[[likely]], [[unlikely]] говорят, что некоторый code flow выполняется чаще/реже. Соответственно компилятор может использовать эти знания и оптимизировать hot path. Или наоборот. Мб вы знаете кейсы, когда это прям помогает? Имхо компиляторы сейчас неплохо с такими вещами и так справляются.

[[no_unique_address]] говорит, что член класса может не обладать уникальным адресом в памяти, из-за чего несколько членов класса потенциально могут пересекаться в памяти. Подробнее расскажу про него вместе с empty base class optimization чуть позже.
В msvc игнорится (даже с С++20), потому надо юзать [[msvc::no_unique_address]].

[[assume]] говорит, что некоторое выражение, которое вы в него засунете, будет true c момента указания атрибута. Это может помочь компилятору провести какие-то оптимизации (опять не уверен, что это сильно поможет):

int x = f();
[[assume(x > 0)]];
// далее это утверждение может использоваться для более эффективных оптимизаций


int z = x;
[[assume((h(), x == z))]]; // после вызова h() переменные всё ещё будут равны


[[assume((g(z), true))]]; // g(z) вернёт true

Ещё примерчики можно посмотреть на cppref.

using в атрибутах это про сокращение записи. Например это может быть полезно для нестандартных атрибутов. Вместо

[[gnu::always_inline]] [[gnu::hot]] [[gnu::const]]

можно писать

[[using gnu: always_inline, hot, const]]

Тут и using, и сразу несколько атрибутов в одном attribute-specifier (это [[]]).
👍14
#cpp

Атрибуты в C++ 2/2.

Кроме стандартных атрибутов есть ещё всякие компилятороспецифичные.

Тут стоит сказать, что хотя конечно в целом компиляторы в основном умеют работать с атрибутами со стандартным attribute-specifier ([[]]), в зависимости от компилятора часто используются формы вроде __attribute__(()), __declspec() или, реже, прагм.

По самим атрибутам.

Если вы уверены, что что-то нужно сто процентов заинлайнить, в gcc можно использовать __attribute__((always_inline)). Он игнорирует все лимиты, по которым решается, инлайнить ли функцию/метод, и игнорирует -fno-inline. Есть вот небольшая преза с бенчмарками по этому атрибуту.

Есть и довольно специфические атрибуты. Например в clang атрибут [[clang::preferred_name(some_name)]] может быть применён к шаблону класса и указывает предпочтительный способ присвоения имени специализации шаблона.

Или например using_if_exists, который позволяет не ругаться на юзинги, если в них используются несуществующие/необъявленные сущности. Таким образом ошибки откладываются до момента использования:

namespace empty_namespace {};
__attribute__((using_if_exists))
using empty_namespace::does_not_exist; // no error!

does_not_exist x; // error: use of unresolved 'using_if_exists'


Или например noescape к параметру-указателю говорит, что этот указатель никуда не “убежит”. Т.е. вы его ни во что, что может использоваться дальше, не присвоите (вызвать free можно):

int* gp;
void no(__attribute__((noescape)) int* p) {
*p += 100; // OK.
}

void yes(__attribute__((noescape)) int* p) {
gp = p; // Not OK.
}


Из полезного ещё есть __attribute__((weak)) в clang (__attribute_((weak_import)) в gcc). Weak линковка это когда вы можете пометить некоторую реализацию функции подобным атрибутом, но если будет найдена другая без подобного атрибута, ей отдаётся предпочтение. Типичный пример — перегрузка operator new/operator delete.
В msvc подобного результата (насколько я знаю) можно достичь с прагмами. Рядышком есть ещё [[selectany]], которые разрешает компилятору выбрать любую реализацию.

Из прекрасного про msvc факт, что [[no_unique_address]], как уже говорилось, на нём не работает. Нужно юзать специфичный компиляторный. Но это только если у вас msvc. Если хотите под windows собирать с clang’ом, земля вам пухом.

И много других: enable_if (который делает примерно то, что вы и думаете), format (который говорит, что функция принимает printf/scanf-like format строку и аргументы для неё), malloc (говорит, что функция ведёт как функция для выделения памяти из системы, no_sanitize (отключающий проверки санитайзеров для конкретной функции или глобальной переменной), constructor (позволяет выполнить некоторый код до запуска main(), подробнее Женя писал в https://news.1rj.ru/str/cxx95/109) и destructor (после выхода из main()), call_once (утверждающий, что параметр-функтор вызывается ровно однажды).

Больше атрибутов можно увидеть тут: clang, gcc.
👍10
#cpp

Пачка фактов.

0. Оч кайфовый доклад про приемлемый способ починить большинство текущих проблем в C++: link.
Так сказать, never gonna give you up.

1. Вместо того, чтобы писать operator==, гораздо лучше отдать пользователю это решение. Пусть при каждом сравнении он сам скажет, равны ли объекты!

https://godbolt.org/z/9KT15jPor

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

Но лучше всегда возвращать true, потому что все равны.

2. Подруга мамы назвала своего сына std::victor.

3. Вы же знали про оператор стремления к нулю?

while (x —-> 0);

4. А про оператор головастик?
https://habr.com/ru/post/258811/

5. В Польше запретили std::abort.

6. Если вам не нравится std::vector<bool>, можете использовать std::vector<std::optional<std::monostate>>. Или даже лучше std::vector<std::variant<std::true_type, std::false_type>>.
😁30🔥7🤯6🤣5🐳2🌚2👍1
#cpp

Два факта про неочевидные (и к счастью редкие) кеки C++.

Первое про union.

Важно помнить, что у union в C++ могут быть конструкторы/деструкторы, потому что важно уметь выбирать кого вы конструируете по умолчанию, и соответственно уметь вызывать корректный деструктор в зависимости от активного члена. И вот если у вас объектам надо вызывать конструктор, и вы union’у его не реализовываете, то код просто не компилируется, потому что дефолтный по умолчанию удалён. Но это лирическое отступление. Имеем такой union:

union U {
std::string s_;
std::vector<int> v_;
U(std::string s) { new (&s_) std::string(s); }
U(std::vector<int> v) { new (&v_) std::vector<int>(v); }
~U() {}
};


И собственно сам кек. Т.к. по дефолту деструктор удалён, то в данном случае = default для него означает, что деструктора нет. Т.е. поведение при пустом кастомном деструкторе и при ~U() = default отличается.
Вы спросите, а почему в деструкторе ничего не делаем? Я отвечу: потому что этим должен заниматься пользователь union снаружи. Он знает, какой член является активным. Он пусть с этим и разбирается.
Ну и чтобы соблюсти формальности, уточним, что вызывать деструктор std::string не как

s_.~string()

а как

s_.~basic_string<char>()

потому что это же алиас.

Второе про function-try-block.

В C++ существует альтернативный синтаксис для определения тела функции, позволяющий навесить на него целиком перехват и обработку исключений:

// Стандартный способ
void f() {
try {
may_throw();
} catch (...) {
handle_error();
}
}

// Альтернативный синтаксис
void f() try {
may_throw();
} catch (...) {
handle_error();
}


Эта фича позволяет нам ловить исключения из списка инициализации в конструкторах:

struct S {
A a_;
B b_;

S(A a, B b) try : a_(a), b_(b) {
} catch (…) {
// handle
}
};


Правда тут есть один опасный момент. После обработки ошибки в catch ошибка будет неявно проброшена выше. Это в целом логично: если при инициализации полей класса вылетело исключение, мы никак не можем исправить ситуацию и починить объект.

А что с деструкторами? Из них же крайне нежелательно бросать исключения, потому словить всё было бы удобно. Но тут пранк — поведение такое же, как и в конструкторах. Т.е. catch из function-try-block неявно пробрасывает исключение выше. Вы только что нарушили неявный noexcept(true). Но в отличие от конструкторов, для деструкторов есть мегакостыль: просто добавьте return!

struct A {
~A() try {
throw std::runtime_error("err");
} catch (…) {
std::cout << “ERROR”;
return; // исключение не будет перевыброшено!
}
};


Таким образом словили кейс, когда return в конце void-функции меняет её поведение.

На всякий напомню, что ловить исключения через catch (…) не очень хорошо. Вообще можно почитать мою статью про обработку ошибок.

Ну а плюсы что. Кринж.
👍13😱5😁31
#common

Про ревью.

Что вы думаете, когда видите что-то такое:

> Это какой-то бред.

Оценочное суждение. Никакой конкретики, что же сделано не так. Никаких предложений по исправлению.

> Так лучше не делать.

Как??????????

Писать комменты в ревью адекватно — важный скилл. Как минимум чтобы коллеги не думали, что вы токсичный и неприятный. Как максимум, чтобы исправление ревью было простым и не тратило время вас двоих на выяснение, что же имелось в виду.

Примеры выше имеют несколько важных недостатков: они [вероятно] грубые и не несут никакой конкретики (ваш коллега не имеет никакого контекста о том, что было у вас в голове во время написания комментария).

Как сделать ваше ревью более полезным? Вот несколько набросов:
- цель код ревью в том, чтобы сделать код лучше. Потому при написании комментариев важно фокусироваться именно на коде и написанном, а не на авторе и других аспектах бытия;
- пишите конкретно.
Вот это не будет работать по таким-то причинам: раз, два, три. Можно сделать вот так. Вот ссылка на пример использования/функцию/метод/инструмент.
Донесите вашу мысль максимально чётко, чтобы автор кода понял вас с первого раза;
- не ленитесь и напишите (псевдо)код предлагаемого решения, если оно может показаться нетривиальным.
Вообще максимально старайтесь предлагать какие-то решения (вы же понимаете, как сделать лучше?). Это экономит время;
- по опыту, важно ещё и как вы оставляете комментарий. Если вы предлагаете что-то исправить в формулировке “Можем сделать ….?”, автор имеет полное право сказать “нет”, даже если это решаемый вопрос. Просто потому что не хочет. Напишите лучше что-то вроде “Давай сделаем …”;
- ну и в конце концов, будьте приятными и нетоксичными. Просто по-человечески добрыми. Это всегда приятно. Плюс к фидбеку охотнее прислушиваются, если он написан уважительно.

Будьте добрыми😎
Please open Telegram to view this post
VIEW IN TELEGRAM
👍46🥰10💔1
#cpp

Ух. Влетел с рассказом про обработку ошибок в плюсах на яндексовый Intern Meetup Week. В целом понятны точки роста, где над чем можно поработать, но опыт очень прикольный и полезный. Запись можно глянуть тут: https://www.youtube.com/live/5stJKC6UGyI?feature=share&t=532

Текстовая версия: https://habr.com/ru/articles/690038/
🔥39
#highload #cpp

Подсобирал ссылочек на разные приконые статьи и доклады.

1. Про то, как discord хранит сообщения. TLDR: история про смену БД.

2. Про переезд с монолита на микросервисы в Twitch: part one, part two.

Появились записи прошлогоднего C++ Russia. Пока правда не на youtube.

3. (Core) библиотеки: идеи и примеры про то, как писать общие библиотеки и различные подходы на двух примерах.

4. Reflection TS: будущее рефлексии в C++.

5. Маленький, но довольно глубокий Type Sanitizer: способ обнаружения нарушений правил strict aliasing в C++.
12👍4🤯2
#highload

Вышли записи Saint Highload++ 2022. Вот пару ссылочек из интересного. Сначала технические:

0. Какие архитектурные решения в Яндекс Go позволяют запускать десятки продуктовых экспериментов. Олег Ермаков.

Тут интересно с точки зрения того, что всем этим мы пользуемся абсолютно регулярно и постоянно. Обзорно оч хорошо даёт понимание.
Мб как-нибудь доберусь и базово про аб-тесты расскажу.

1. Векторный поиск в ClickHouse. Артур Филатенков.

2. Про историю и будущее поиска. Андрей Аксёнов.

3. Архитектура: история и будущее на примере ВКонтакте. Александр Тоболь.

Ну ВКонтакте со своими костылями это угар какой-то местами. Но доклад приконый и оч живой.

И не очень:

4. Под красным флагом: как инженер может понять, что в проекте происходит что-то не то. Даниил Подольский.

5. Как понять, что проекту плохо, если ты инженер. Юлия Белозёрова.

Тянка, кстати, жоская. Под конец доклада понял, что читал k её статей в vas3k.club. И все кайфовые.
🔥82👍1👎1
#common

Есть желание немножко поразбираться и пописать про поиск и связанные штуки, так что время от времени что-то с этим связаное будет возникать.

Сегодня про задачи и специфику поиска в различных контекстах.

Давайте представим, как может выглядеть простейший поиск. Это примерно просто нахождение подстроки в строке (или во множестве строк). И это вполне себе полезная задача. Например, вы сидите в своей любимой ide и грепаете что-то по файлику. Тут обычно не нужно мутить чего-то чуть более сложного вроде исправления опечаток, семантики запроса и т.д. Нужно просто найти, что просят (может, проигнорировав кейс). Чтобы не делать это втупую, можно заюзать Бойера-Мура или Кнута-Морриса-Пратта. Даже можно какое-нибудь суффиксное дерево по текущему файлику построить, но имхо звучит как оверинжиниринг. И тут это почти наверняка хорошо работает, потому что файлы у вас обычно небольшие (думаю, даже несколько тысяч строк подобным спокойно закрываются). Другой вопрос конечно, как сделать подобное быстро, если вы ищете по целому проекту или по огромной монорепе, но про такой codesearch как-нибудь потом.

Примерно та же задача возникает, если вы хотите искать по логам. Чаще всего вас интересуют какие-то параметры и конкретное вхождение текста. Тут правда уже такими подходами не обойдёшься, потому что объёмы данных огромны (легко порядка единиц-десятков-сотен террабайт). Но всё ещё можно не наворачивать опечаточник, работу с семантикой, ml и прочее.
Это примеры поиска по различным неестественным текстам, что обычному пользователю редко пригождается.

Обычно пользователей интересует поиск по естественным текстам: сайтам с их страничками, статьям, форумам и прочему. Причём поиск тут может вестись разными способами: по коротким фразам целиком, по каким-то длинным фрагментам, по различным ключевым словам (которые надо уметь выделять и оценивать их ценность в запросе), иногда даже по семантике (тут уже надо какой-то ml наворачивать). Учитывая, что хочется, чтобы ваш сайтик искался и индексировался лучше, рядом возникает задача search engine optimization (SEO).

Ну и часто вы хотите искать что-то по какой-то предметной области. Например по товарам в рамках конкретного сайта. Или по твитам. Или по фильмам, если вы какой-нибудь kinogo. В некоторых таких кейсах у вас есть какое-то количество информации об искомых объектах кроме тайтла (например описания). И по-хорошему хочется уметь искать не только по названиям, но и другой информации, но мб учитывая её с меньшим весом.
Тут обычно берут какой-нибудь поисковый движок вроде sphinx, lucene или какой-нибудь другой и кастомят под себя.

Тут получилось просто, абстрактно и общо. Дальше будет чуть больше конкретики.
👍8🤔84🐳2🗿2💔1