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

По всем вопросам - @ninjatelegramm

Менеджер: @Spiral_Yuri
Реклама: https://telega.in/c/grokaemcpp
Мы на TGstat: https://tgstat.ru/channel/@grokaemcpp/stat
Download Telegram
Почему РКН не сможет полностью заблокировать VPN в России Ч2

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

Начнём, как всегда, с базы, а точнее с экономики. Большинство адекватных IT-компаний в России использую впн для доступа к корпоративным ресурсам (надо ещё учитывать, что сейчас даже самая облезлая собака считает себя ИТ-компанией). С учётом увеличивающегося хакерского давления на отрасль, использование корпоративного VPN - критическая необходимость для сохранения конфиденциальности информации и защиты сети от несанкционированного доступа. Также специалистам нужны многие сервисы, к которым в России доступ запрещён: линкедин для эйчаров, quora для прогеров и прочих работяг, перечислять можно много. Исходя из всего этого, чисто экономической точки зрения, представьте потери отрасли от блокировки впн трафика. Это десятки и сотни миллиардов рублей. И непозволительная роскошь для страны, которая сильно нуждается в отечественных ИТ-продуктах.

Помимо этого есть и главный идейный фактор. Люди на атаку любой сложности рано или поздно изобретают защиту для этой атаки. Естественная гонка вооружений. На любой интернетный запрет от нашего правительства найдётся лазейка. VPN был такой лазейкой в 18 году, когда РКН блокировал телеграмм. Дальше на помощь придут так называемые технологии обфускации трафика.

Цель обфускации - маскировка VPN трафика, делая его похожим на обычный трафик, чтобы обойти DPI. Например, данные можно скрыть под видом изображений или звуковых файлов. Обфусцирующие протоколы, типа Shadowsocks, прекрасно проявляют себя в Китае, как средство обхода их великого файервола.

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

Stay calm. Stay cool.

#net #howitworks #fun
Предсказуемость

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

На одной из экскурсий по архитектуре моего города экскурсовод обратил внимание на восприятие человеком красоты архитектуры. Оно складывается не только из чисто субъективного ощущения прекрасного, но и визуального ощущения надежности строения. Иначе говоря, будете ли вы считать здание красивым, если ваше внутреннее чувство говорит о его хрупкости? Человек желает видеть перед собой прочное, монументальное здание. То есть поведение этого здания предсказуемо - оно не обрушится, там безопасно. Это является одним из факторов, определяющим дизайн здания и выбор отделочных материалов. Предсказуемость затрагивает многие сферы жизни. Строительство, отношения людей и т.д., в том числе программирование.

Предсказуемость поведения тех или иных участков кода позволяет нам с уверенностью взять их в работу и без сомнений доверить новые вычисления. Иначе это будет порождать в нас тревогу, а ни не рухнет ли там ничего? А правильно ли мы используем тот или иной инструмент для решения конкретных задач? Да, конечно, лопатой можно забивать гвозди, но эффективно ли это? Лопата быстро сломается, а вот молотком работать и удобнее, и быстрее.

Давайте вспомним предыдущие посты: магические числа, идиома RAII, комментарии в коде, Ссылки vs Указатели, optional, как правильно запретить копирование - не об этой ли предсказуемости шла речь в данных постах? Мы там всегда пытаемся что-то гарантировать, что-то показать, что-то сделать явным и однозначным.

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

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

#howitworks #fun
Проверяем число на степень двойки

У меня всегда была страсть к решению разного рода задачек и меня всегда восхищало то, как более компетентные люди используют неочевидные способы решения таких задач. Это всегда подстегивает энтузиазм и желание изучать новое. Сегодняшняя тема однажды привела меня в шоковое состояние, когда я проверял своё решение.

Казалось бы. Очень простая задача. Узнать, является ли число степенью двойки. Решаем через цикл, пока число не равно единице, делим на два и если ни при одном делении нет остатка - число является степенью двойки. Если хоть один остаток от деления был, тогда не является.
Или сдвигаем число вправо побитово на один бит на каждой итерации и если он нулевой всегда, то возвращаем true.

Написали, запускаем тесты. Тесты проходят. Победа. Небольшая конечно, задача-то плевая. А потом ты заходишь в решения и видишь, что кто-то решил эту задачу за константную сложность. What. The. Fuck. ???

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

Решили? Надеюсь у нас здесь сборник гениев и все решили. А для таких, как я, рассказываю.

Когда число является степенью основания системы счисления, оно представляется как единица с несколькими нулями, например, 1000 в двоичной - это 8, 10000 - это 16. Ну вы поняли. С десятичной же тоже самое. Причём количество нулей равно показателю степени числа.

Ещё вводные. Если мы вычтем единицу из нашего числа, то получим новое число, количество разрядов которого ровно на один меньше, и каждая цифра которого сменится с нуля на единицу. Например, 100 - 1 = 11, 1000 - 1 = 111, 10000 - 1 = 1111.

Теперь магия. Когда мы применим операцию логического И к исходному числу Х и к числу Х - 1, то мы получим ноль. Х & (Х - 1) = 0.

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

Строгое доказательство данного утверждения опущу за ненадобностью, смельчаки в комментах могут попробовать его оформить.

А вы знаете какие-нибудь удивительные решения простых задачек? Оставьте ответ в комментах.

Stay optimized. Stay cool.

#algorithms #fun
Реактивность

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

«‎Реактивность – (от латинского reactia – противодействие) – свойство организма реагировать изменениями жизнедеятельности на воздействие различных факторов окружающей среды» - учебно-методическое пособие по медицине.

Реактивное программирование - парадигма, ориентированная на потоки данных и автоматическое распространение изменений от нижестоящих моделей к вышестоящим.

Рассмотрим пример:
1. b = 3; c = 5;
2. a = b + c;
3. b = c;
4. c = 2;

В императивном программировании ожидается, что a = 8; b = 5; c = 2;, т.к. при вычислении результата каждой операции используется значение переменной на текущий момент исполнения.

В реактивном программировании ожидается, что значения выражений будут равны: a = 4; b = 2; c = 2;, т.е. изменения, примененные к нижестоящим моделям (в данном контексте, переменные b и c), автоматически распространят свои изменения на вышестоящие модели (переменные a и b). Получаются такие цепочки зависимостей c -> a и c -> b -> a.

Лучше понять этот пример можно, если представить что в качестве значения каждой переменной используются не просто числа, а лямбда-функции: https://compiler-explorer.com/z/Ge589afdW

Давайте подумаем, какие преимущества дает данное свойство реактивности:
1. Удобное распространение изменений
Разработчику не нужно проверять изменения каждой переменной, т.к. в механизм изменений заложено предсказуемое поведение.

2. Высокая отзывчивость
Изменения распространяются сразу, как только было сделано изменение, а результат вычислений будет вычислен тогда, когда он понадобится (в отличие от каких-то механизмов пересчета изменений).

3. Масштабируемость
Достаточно изменить лишь какую-то часть алгоритма, чтобы добавить новую функциональность.

4. Удобное создание сложных алгоритмов обработки
Алгоритм обработки не требует полного пересоздания с нуля, а может усложняться там, где это удобнее всего сделать.

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

#howitworks #fun
Копаемся в маллоке

Короче. Попробуем новый формат на канале - статьи. Вот пожалуйста ссылочка https://telegra.ph/Nahodim-razmer-bloka-malloc-11-30. Это продолжение песни, начатой здесь. Инфы там многовато, поэтому пришлось в немного более длинном формате ее упаковать.

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

Пишите свои мысли по поводу вопроса. Буду рад пообсуждать в комментах.

You are the best.

#fun #howitworks #memory #goodoldc #hardcore
Как обмануть nodiscard?

В комментах к предыдущему посту Евгений правильно заметил, что аттрибут nodiscard можно заигнорировать. Правда непонятны кейсы, в которых это нужно делать и которые еще не притянутые были бы за уши. Думаю, что при корректном использовании атрибута, такой надобности не возникнет. Ну да ладно. Об этом мы поговорим попозже. Сейчас я перечислю некоторые способы обхода nodiscard, чисто из научного интереса. Предупреждаю сразу. Уберите маленьких детей от экрана и ни в коем случае не повторять дома. За последствия не отвечаю.

std::ignore. На этот вариант и ссылался Евгений. Суть в том, что этому безтиповому можно присвоить любое значение и не использовать его. Тогда и возвращаемое значение типа было использовано для преобразования в ignore, и мы потом этот ignore можем игнорировать. Подробнее тут. А для любителей покопаться в костях динозавров есть функция boost::ignore_unused.

Скастовать возвращаемое значение в void. Типа вот так: (void)someFunction(). Или более по-плюсовому co static_cast.

Присвоить возращаемое значение какому-то объекту. Но не использовать его.
Тогда появится варнинг, что переменная, которой мы присвоили возвращаемое значение, не используется нигде. А вот чтобы это обойти, нужно пометить эту переменную другим атрибутом [[maybe_unused]]. Например так: [[maybe_unused]] int i = foo ();

Сделать красивую шаблонную обертку над предыдущим пунктом, с variadic-templates и прочими радостями. И назвать ее discard.

Отличные новости для пользователей clang! Можно обернуть вызов функции в
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Weverything"
#endif
func_with_result();
#pragma clang diagnostic pop
#endif

Тогда и никаких варнингов генерироваться не будет. Для gcc есть что-то подобное, но там нельзя вроде все сразу отключить.
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-result"
func_with_result();
#pragma GCC diagnostic pop

На этом моя фантазия кончилась. Но получилось все равно солидно)
Повторю, что в большинстве случаев вы этим будете стрелять себе в лицо, и это скорее всего признак того, что вы что-то делаете не так или система спроектирована плохо.
Возможно вы знаете какие-нибудь еще способы? Обязательно делитесь ими в комментариях)

Stay dangerous. Stay cool.

#fun #cpp17 #compiler
Терминал

Вот иногда живешь-живешь, учишь иностранный язык или в какую-то другую сферу погружаешься, и в какой-то момент тебе приходит озарение по поводу ориджина простых вещей, которые мы все принимаем как данность. Например, слово банкнота. Для нас это одна единица бумажных денег. И мы не задумываемся, почему это слово обозначает одну деньгу. А все просто. Записка из банка. Bank note. Взорвало мозг? Если нет, то вы либо очень умный, либо потеряли энтузиазм к жизни.

Хочу поделиться с вами похожим приколом только из мира computer science. Думаю, что все мы хоть раз в жизни открывали графический терминал на своих Unix системах(реальных или виртуальных), ну или хотя бы подключались удалённо к ним. Все-таки, знание команд для unix - это маст хэв и де факто стандарт для сферы разработки. Если вы хоть раз разрабатывали не локально, то с 99% вероятности вы подключались к Линукс системе и ей надо бы уметь управлять.

Ну дак вот. Помните, какие раньше были компьютеры? Я вот тоже не помню, потому застал время уже полностью персональных компьютеров, где все было соединено вместе. А лет 50 назад нормальной практикой в компании было иметь один здоровый ЭВМ, размером с самомнение веганов, и много-много отдельных «терминалов», через которые сотрудники могли общаться с эвм. Они имели клавиатуру, дисплей, печатающее устройство, динамик и ещё пару простых прибамбасов. Пользователь вводит команду, команда по проводам попадает в эвм, обрабатывается и передаётся в виде текстовой или графической информации на терминал.

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

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

Stay surprised. Stay cool.

#fun #tools #OS
Вызываем метод класса через указатель

Настоящего плюсовика не должны пугать указатели на функции. И хоть в С++ есть нормальная обертка над всеми сущностями, которые можно исполнить - std::function - она довольно тяжеловесная и медленная. Да и с сишным апи с ней не поработаешь. К чему это я. Да. Указатели на функции. С ними, в целом, все просто.
int func() {
return 1;
}
using func_ptr = int (*) ();
func_ptr ptr = func;
std::cout << ptr();

Этот код со всеми обертками должен написать единичку на экран. С более сложными функциями сделать что-то похожее не составит труда. Но вот как насчет методов класса? Можно ли вызвать метод класса через указатель на него, а не через объект?

Ответ - можно. Но прежде чем посмотреть на рабочий код, попробуйте сами это сделать. Уверяю, что это не так тривиально)
Если читаете с телефона или нет времени, лучше вернитесь к этому посту после того, как попробуете сами. Эмоции будут совершенно другие)
Ну а для тех, кто уже попробовал или для ленивых дяденек, продолжаю.

Первое, что приходит на ум - такой же подход, как и с функциями.

auto ptr = RandomType::MemberFunction;

И тут же гцц плюнет вам в лицо с фразой error: invalid use of non-static member function ‘void RandomType::MemberFunction()’
auto ptr = RandomType::MemberFunction;

На этом я и закончил пробовать и пошел ресерчить. А снизу код, который у меня получился по итогам моих поисков. Все в духе С, ничего не понятно, одни указатели. Выглядит как фокус и по факту им и является. В следующий раз объясню, почему все так.

Stay hardcore. Stay cool.

#fun #hardcore #memory #cppcore
Продолжение про вызов метода через указатель

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

18 строка - Объявляется синоним к типу указателя на функцию, которая ничего не возвращает и принимает указатель на тип RandomType. Позже будет понятно, зачем это нужно.

19 строка - Есть 2 возможных синтаксиса для получения указателя на функцию: с амперсантом(сюрприз, кто не знал) и без. Без амперсанта не работает, потому такой синтаксис уже используется для вызова статических методов и будет путаница. Итак, мы получили какого-то неизвестного нам типа указатель на функцию. Идем дальше.

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

Мы можем посмотреть не на сам указатель, а на ячейку памяти, которая хранит наш указатель. И сказать, что по этому адресу лежит не указатель на функцию, а указатель на число. Вот так сделать можно. А потом кастуем указатель на число к указателю на функцию. Так тоже можно сделать.

И, наконец, важнейший пункт. Помните в книжках всегда говорили, что в методы скрытно передается указатель на вызывающий его объект this? Так вот сейчас вам скорее всего впервые понадобится это знание на практике!
Методы неполиморфных классов - те же самые обычные функции(кто не знал). От остальных их отличает лишь этот параметр this, который скрытно передается первым аргументом. Именно поэтому, чтобы вызвать нестатический метод, нам нужен объект. Чтобы передать его первым параметром в функцию. Иначе вызов будет не соответствовать сигнатуре.
Поэтому мы берем созданный на стеке объект, находим адрес первого байта и передаем его в метод в качестве аргумента.

И вуаля. Все работает. Выводится пятёрочка.

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

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

Конструируй абстракции и погружайся в детали. Stay cool.

#fun #memory #howitworks #cppcore #hardcore
Вызов метода через указатель на метод

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

И его на самом деле уже проспойлерил Александр, причем сразу же во время публикации первого поста из серии. Все-таки в нашем канале сидят крутые спецы)

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

using fnptr = void (RandomType::*)();

В этом объявлении синонима явно указывается, что это указатель на метод конкретного класса RandomType. И вызывается конкретный экземпляр этого метода через объект и оператор точка. И все. Никаких танцев с бабнами и прочих шалостей. В общем, все можете посмотреть в коде на картинке.

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

Stay versatile. Stay cool.

#fun #cppcore