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

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

Менеджер: @Spiral_Yuri
Реклама: https://telega.in/c/grokaemcpp
Мы на TGstat: https://tgstat.ru/channel/@grokaemcpp/stat
Download Telegram
​​Третий аргумент main
#новичкам

Почти всегда вы пишите функцию main вот так:

int main() {
// some code
}


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

int main (int argc, char* argv[]) {
// argc - количество переданных аргументов
// argv - массив из переданных аргументов

// some parsing
}


Однако в стандарте описан еще один способ определения main:


int main(/ implementation-defined /) {body}


Стандарт разрешает компиляторам давать возможность пользователям как-то по-другому задавать аргументы для main. И хоть это будет не переносимо, нам не всегда нужна кроссплатформенность.

Самая часто встречающаяся нестандартная сигнатура main следующая:

int main(int argc, char*  argv[], char* envp[]) {}


Третий аргумент main - это массив строк переменных окружения в формате
"KEY=value". Массив завершается null pointer'ом.

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

Вот минимальный примерчик:

#include <iostream>

int main(int argc, char* argv[], char* envp[]) {
std::cout << "Environment variables:\n";

for (char** env = envp; *env != nullptr; env++) {
std::cout << *env << "\n";
}

return 0;
}


Возможный вывод:

PATH=/usr/local/bin:/usr/bin:/bin
USER=grokaem_cpp
...


Вы не так часто можете увидеть этот формат сигнатуры main по уже очевидным для вас причинам:

- нестандарт
- а самое главное - это дело надо парсить. Засовывать в мапу какую-то и искать потом по ключу нужную переменную. А зачем, если есть std::getenv или его брат getenv из Сей.

Рассказываю про это, чтобы вы при встрече в таким форматом аргументов main не думали, что что-то базовое упустили при изучении плюсов. Ну или просто для расширения кругозора)

Expand your horizons. Stay cool.

#NONSTANDARD #goodoldc
👍4618🔥4😱4
​​environ
#опытным

В POSIX-совместимых системах (Linux, macOS, BSD) существует еще один альтернатива параметру envp в функции main() и функции std::getenv() для получения значений переменных окружения. Это глобальная переменная extern char** environ, которая предоставляет прямой доступ ко всему окружению процесса.

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

#include <stdio.h>

extern char** environ;

void foo() {
for(char** env = environ; *env != NULL; env++) {
printf("%s\n", *env);
}
}


В остальном, особенности работы такие же, как и у envp.

Но если мы уже зашли по колено в Posix, то там есть функция setenv, которая позволяет менять переменные окружения уже в ходе выполнения программы:

#include <stdlib.h>  

int setenv(const char *envname, const char *envval, int overwrite);


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

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

Интересно, как много в С|С++ методов получения окружения процесса. Но рекомендуется использовать конечно стандартный вариант std::getenv.

Be visiable. Stay cool.

#NONSTANDARD
2015👍7🔥7
Офер в Яндекс за 48 часов: ищем бэкендеров

В команду нужны опытные бэкенд-разработчики на C++, Python, Java и Go. Приглашаем на Мультитрек — онлайн-программу быстрой адаптации.

Всего за 2 дня вы можете получить офер:
• До 18 августа подать заявку и пройти предварительный отбор
• 23 августа решить задачи на технических секциях
• 24 августа пройти финальное собеседование и получить офер
После этого будет возможность поработать с тремя командами и выбрать проект по душе.

Создаём технологии, которые меняют мир. Присоединяйтесь! Оставляйте заявку на сайте.
👎138🔥4👍3😁3🗿2
Ревью
#новичкам

Пролистывал намедни один тг канал по С++, его название начинается на Senior и заканчивается на С++ Developer. Там обычно постится очень "качественный контент" и мне на глаза попался код, который я бы хотел закинуть вам на тряпкозакидательство.

В рамках #ревью мы приводим кусок кода, а вы в комментариях прожариваете его до полного well done: говорите, что вам не нравится, и как это исправить. Комментатора с самым большим количеством отмеченных проблем упомянем в завтрашнем посте с разбором.

struct Message {
int data;
};
std::queue<Message *> msgQueue;

void sender() {
for (int i = 0; i < 20; ++i) {
Message *msg = new Message();
msg->data = i;
msgQueue.push(msg);
std::cout << "Sent: " << msg->data << std::endl;
}
}

void receiver() {
while (true) {
if (msgQueue.empty()) {
break;
}
Message *msg = msgQueue.front();
msgQueue.pop();
std::cout << "Received: " << msg->data << std::endl;
delete msg;
}
}
int main() {
std::thread t1(sender);
std::thread t2(receiver);
t1.join();
t2.join();
return 0;
}


Вот такой код. Под оригинальным постом с этим кодом коллеги призвали руки рубить за такой код. Давайте сделаем так, чтобы он не вызывал испанского стыда, а только возвышенные чувства платонической любви к С++.

Раз, два, три, код в порядок приведи!

Critique your decisions. Stay cool.
124😱8🔥5👍4👎2😁2
​​Разбор ревью
#новичкам

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

Было непросто выбрать самый эффективный по критике комментарий, потому что некоторые люди предлагали странные решения. В итоге, мы выбрали @seweeex и вот его коммент. Давайте похлопаем ему 👏👏👏.

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

Поехали разбирать.

🔞 Зачем-то в очереди хранятся сырые указатели. Смысла в этом особого нет, кроме как подложить себе свинью на будущее и поджечь жёпы комментаторов. Тут даже умные указатели не нужны, зачем дополнительные аллокации? В очереди можно хранить сами объекты и никаких проблем с менеджментом памяти не будет.

🔞 Использование сырых указателей приводит например к тому, что при вылете исключения из метода push, произойдет утечка памяти. Да, элементов мы закидываем в очередь немного, но концептуально проблема есть. Решается это опять же через хранение обычных объектов.

🔞 Слон в посудной лавке здесь - это конечно отсутствие синхронизации на очереди. Это в принципе ub и дальше не о чем говорить. Нужна не стандартная, а потокобезопасная очередь.

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

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

🔞 Бесконечный цикл в ресивере выглядит не очень. Если можно не писать бесконечных циклов и не вставлять брейки, то лучше этого не делать. break и continue усложняют понимание кода. Лучше перенести забирание элемента из очереди прям в шапку цикла.

🔞 Гонка на потоконебезопасном логировании. Нужен мьютекс, чтобы сообщения не интерферировали.

По сути все. Главное изменение - вынесение блокирующей потокобезопасной очереди в отдельный шаблонный класс, который хранит объекты типа Т. С шаблонами можно долго играться и далеко зайти, поэтому приведем самую простую реализацию, которая справляется со своими задачами в данном кейсе, но может быть улучшена для корректной работы с самыми разными типами:

#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <memory>
#include <optional>
#include <print>

struct Message {
int data;
};

template<typename T>
class ThreadSafeQueue {
public:
void push(T msg) {
{
std::lock_guard lock(mutex_);
queue_.push(std::move(msg));
}
cv_.notify_one();
}

std::optional<T> pop() {
std::unique_lock lock(mutex_);
cv_.wait(lock, [this] { return !queue_.empty() || stopped_; });

if (stopped_ && queue_.empty()) {
return std::nullopt;
}

auto msg = std::move(queue_.front());
queue_.pop();
return msg;
}

void stop_receive() {
stopped_ = true;
cv_.notify_all();
}

private:
std::queue<T> queue_;
std::mutex mutex_;
std::condition_variable cv_;
std::atomic<bool> stopped_ = false;
};

void println(const std::string& str) {
static std::mutex mtx;
std::lock_guard lock(mtx);
std::cout << str << std::endl;
}

void sender(ThreadSafeQueue<Message>& msgQueue) {
for (int i = 0; i < 20; ++i) {
auto msg = Message(i);
println("Sent: " + std::to_string(msg.data));
msgQueue.push(std::move(msg));
}
msgQueue.stop_receive();
}

void receiver(ThreadSafeQueue<Message>& msgQueue) {
while (auto msg = msgQueue.pop()) {
println("Received: " + std::to_string(msg.value().data));
}
}

int main() {
ThreadSafeQueue<Message> msgQueue;
std::thread t1(sender, std::ref(msgQueue));
std::thread t2(receiver, std::ref(msgQueue));

t1.join();
t2.join();
}


Пишите свои дополнения, если что-то забыли.

Make things better. Stay cool.
1628👍8👏5🤨1
В России можно посещать IT-мероприятия хоть каждый день: как оффлайн, так и онлайн

Но где их находить? Как узнавать о них раньше, чем когда все начнут выкладывать фотографии оттуда?

Переходите на канал IT-Мероприятия России. В нём каждый день анонсируются мероприятия со всех городов России

📆 в канале размещаются как онлайн, так и оффлайн мероприятия;
👩‍💻 можно найти ивенты по любому стеку: программирование, frontend-backend разработка, кибербезопасность, дата-аналитика, osint, devops и другие;
🎙 разнообразные форматы мероприятий: митапы с коллегами по цеху, конференции и вебинары с известными опытными специалистами, форумы и олимпиады от важных представителей индустрии и многое другое

А чтобы не искать по разным форумам и чатам новости о предстоящих ивентах:

🚀 IT-мероприятия Россииподписывайся и будь в курсе всех предстоящих мероприятий!
😁54👍3🔥1😢1
​​or and not
#новичкам

В С/C++ всегда был не очень дружелюбный синтаксис операторов. Показать вот такой код человеку, который не в зуб ногой в программировании:

if (x > 0 && y < 10 || !z) {
// ...
}

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

Однако знали ли вы, что в С/C++ есть альтернативный синтаксис токенов? Символы операторов заменяются на короткие слова и код выше становится почти питонячьим:

if (x > 0 and y < 10 or not z) {
// ...
}


Выглядит свежо! Хотя было доступно еще с С++98.

Вот полный список альтернативных токенов:

&& - and
&= - and eq
& - bitand
| - bitor
~ - compl
!= - not eq
|| - or
|= - or eq
^ - xor
^= - xor eq
{ - <%
} - %>
[ - <:
] - :>
# - %:
## - %:%:


Последние токены для скобок - это конечно дичь. Но остальные - вполне интересные варианты.

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

А вы используете в продакшен коде альтернативные токены?

Evolve. Stay cool.

#fun #goodoldc
9🔥5👍4