uz
Feedback
Грокаем C++

Грокаем C++

Kanalga Telegram’da o‘tish

Два сеньора C++ - Владимир и Денис - отныне ваши гиды в этом дремучем мире плюсов. По всем вопросам (+ реклама) @ninjatelegramm Менеджер: @Spiral_Yuri Реклама: https://telega.in/c/grokaemcpp Мы на TGstat: https://tgstat.ru/channel/@grokaemcpp/stat

Ko'proq ko'rsatish
9 374
Obunachilar
-224 soatlar
+27 kunlar
+1030 kunlar
Postlar arxiv
⌨️ Открытый урок «Умные указатели в С++» 🗓 23 октября в 20:00 МСК 🆓 Бесплатно. Урок в рамках старта курса «C++ Developer. P
⌨️ Открытый урок «Умные указатели в С++» 🗓 23 октября в 20:00 МСК 🆓 Бесплатно. Урок в рамках старта курса «C++ Developer. Professional». 🎯 Что рассмотрим на вебинаре: ✔️ Узнаем, для чего нужны умные указатели ✔️ Рассмотрим правило "взял память - верни, когда больше не нужна" ✔️ Разберемся с разными типами умных указателей 👥 Кому будет интересно: - junior, junior+ C++ Разработчикам Чему научатся участники по итогам вебинара: - Научимся решать проблему управления ресурсами - Рассмотрим глупый умный указатель; unique_ptr.; Shared_ptr; weak_ptr; enable_shared_from_this - Научимся выбирать нужный умный указатель 🔗 Ссылка на регистрацию: https://otus.pw/aYgg/ Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576

Лайвлок #новичкам Лайвлок(livelock) — это ситуация в многопоточном программировании, когда потоки не блокируются полностью, как при дедлоке, а продолжают выполняться, но не могут продвинуться в решении задачи из-за постоянной реакции на действия друг друга. Потоки находятся в состоянии "живой блокировки" — они активны, cpu жжется, но их работа не приводит ни к какому прогрессу. Лайвлоки не всегда приводят к вечной блокировке потоков. Просто в какие-то рандомные моменты времени условный rps может неконтролируемо вырасти в разы, а то и на порядки. И так как ситуация сильно зависит от планирования потоков, то воспроизвести ее будет сложно. Однако у этой проблемы есть характерные черты, облегчающие ее поиск: 🔍 Активное ожидание — потоки постоянно проверяют какие-то условия и крутятся в циклах. 🔍 Взаимозависимость — действия одного потока влияют на условия выполнения другого. 🔍 Неблокирующие алгоритмы - активное ожидание обычно идет за ручку с lockfree алгоритмами. 🔍 Поддавки - при потенциальном конфликте интересов стороны предпочитают уступать. Аналогия из реальной жизни: вы идете по узкому тротуару и вам навстречу идет человек. Вы хотите разминуться, но отшагиваете вместе в одну и ту же сторону. И вы, как крабики, ходите вместе из стороны в сторону. Рано или поздно вы разойдетесь, но заранее нельзя сказать когда. К лайвлоку может привести и использование стандартных инструментов. Например, std::scoped_lock, которыq предназначен для безопасной блокировки нескольких мьютексов. Стандарт требует, чтобы tuj реализация не приводила к дедлоку. Они используют неопределенную последовательность вызовов методов lock(), try_lock() и unlock(), которая гарантирует отсутствие дедлока. Но не гарантирует отсутствия лайвлока. Алгоритм там примерно такой: попробуй заблокировать столько мьютексов, сколько можешь, а если не получилось, то освободи их и попробуй сначала. Тут есть и циклы, и активное ожидание, и взаимозависимость, и поддавки. Но компиляторы понимают эту проблему и современные реализации используют разные приемы, типа экспоненциального backoff'а, чтобы все-таки рано или поздно дать шанс одному из потоков полностью захватить все ресурсы. Вот более "надежный" пример:
std::atomic<bool> lock1 = false;
std::atomic<bool> lock2 = false;

void thread1_work() {
    while (true) {
        // lock lock1
        while (lock1.exchange(true))
            ;
        std::cout << "Thread 1 has acquired lock1, try to acquire lock2..."
                  << std::endl;
        // try to lock lock2
        if (!lock2.exchange(true)) {
            std::cout << "Thread 1 has acquired both locks!" << std::endl;
            lock2 = false;
            lock1 = false;
            break;
        } else {
            // Failed, release lock1 and try again
            std::cout << "Thread 1 failed to acquire lock2, release lock1..."
                      << std::endl;
            lock1 = false;
        }
    }
}

void thread2_work() {
    while (true) {
        // lock lock2
        while (lock2.exchange(true))
            ;
        std::cout << "Thread 2 has acquired lock2, try to acquire lock1..."
                  << std::endl;
        // try to lock lock1
        if (!lock1.exchange(true)) {
            std::cout << "Thread 2 has acquired both locks!" << std::endl;
            lock1 = false;
            lock2 = false;
            break;
        } else {
            // Failed, release lock2 and try again
            std::cout << "Thread 2 failed to acquire lock1, release lock2..."
                      << std::endl;
            lock2 = false;
        }
    }
}

int main() {
    std::jthread t1(thread1_work);
    std::jthread t2(thread2_work);
}
По сути это костыльная и наивная демонстрация принципа работы std::lock с помощью атомарных замков. Каждый поток пытается в своем порядке захватить замки и отпускает захваченный, если не получилось, и идет на следующую попытку. Можете позапускать этот код у себя и посмотреть, как много попыток захвата потоки будут делать от запуска к запуску. Unlock your life. Stay cool. #concurrency

​​Deadlock #новичкам Еще одна частая проблема из мира многопоточки. На канале уже много материалов про нее есть: Определение и демонстрация Начало серии статей про блокировку нескольких мьютексов, что часто приводит к дедлоку Сколько нужно мьютексов, чтобы задедлокать 2 потока? Что будет, если 2 раза подряд залочить мьютекс? Но это все для чуть более опытных ребят. Что если вы совсем не понимаете эти потоки и мьютексы на практике, но очень хотите понять, что такое дедлок? Есть знаменитая проблема обедающих философов. Формулируется она так:
Пять безмолвных философов сидят вокруг круглого стола, перед каждым философом стоит тарелка спагетти. На столе между каждой парой ближайших философов лежит по одной вилке.

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

Каждый философ может взять ближайшую вилку (если она доступна) или положить — если он уже держит её. Взятие каждой вилки и возвращение её на стол являются раздельными действиями, которые должны выполняться одно за другим.

Вопрос задачи заключается в том, чтобы разработать модель поведения, при которой ни один из философов не будет голодать, то есть будет вечно чередовать приём пищи и размышления.
В рамках этой проблемы можно продемонстрировать много проблем многопоточки, но сегодня о deadlock. Представьте 5 философов по кругу. И у них стратегия - брать всегда первой левую вилку, а затем правую. Что получится, если все философы одновременно возьмут левую вилку? Никто из них никогда не поест. Для еды нужны обе вилки, а у всех по одной, все ждут освобождения правой вилки и никто никому не будет уступать. В конце концов они все дружно и помрут. Это классический deadlock и наглядная его демонстрация. Вот так просто. Но это данная конкретная стратегия приводит к дедлоку, есть и более оптимальные, обсуждение которых за рамками поста. Как будто бы про дедлоки больше и не о чем писать. Если хотите разобрать какой-то их аспект - черканите в комментах. Be unblockable. Stay cool. #concurrency

IT_ONE Cup. Code & Analyst — хакатон для аналитиков и разработчиков, где ты узнаешь, как работает IT-команда, и получишь силь
IT_ONE Cup. Code & Analyst — хакатон для аналитиков и разработчиков, где ты узнаешь, как работает IT-команда, и получишь сильный кейс в портфолио. Выбери трек и реши одну из задач: → Проанализируй BPMN-модель кредитного процесса и подготовь ТЗ на систему мониторинга эффективности. → Разработай сервис, который в реальном времени следит за переводами и оповещает о подозрительных операциях. 🏆 Призовой фонд: 900 000 рублей 💻 Формат: онлайн 🗓 Регистрация до 16 октября: https://cnrlink.com/itonecupmsugrokcpp Приглашаем системных аналитиков, разработчиков и менеджеров проектов. Размер команды — от 1 до 5 человек. Что тебя ждёт: • Применишь навыки системного анализа, построения архитектуры и работы с потоковыми данными. • Получишь готовый проект в портфолио. • Для участников ТОП-5 команд в каждом треке — фирменный мерч. Задачи соревнования: Трек 1. Навигатор оптимизации. Проанализируй кредитный процесс банка, выяви узкие места и создай ТЗ для системы мониторинга производительности. Решение поможет оптимизировать критически важные процессы. Трек 2. Финансовый радар. Разработай сервис для анализа транзакций в реальном времени. Архитектура должна включать правила обнаружения мошенничества и поддержку различных алгоритмов обработки. Ждём тебя на IT_ONE Cup. Code & Analyst — старт 17 октября на Codenrock: https://cnrlink.com/itonecupmsugrokcpp

​​race condition #новичкам Теперь состояние гонки. Это более общее понятие, чем гонка данных. Это ситуация в программе, когда поведение системы зависит от относительного порядка выполнения операций в потоках. Внимание: состояние гонки есть даже в правильно синхронизированных программах. В однопоточной программе можно четко предсказать порядок обработки элементов. А вот если много потоков будут разгребать одну кучу задач - вы не сможете сказать заранее, какой выхлоп в следующий раз произведет конкретный поток. Потому что это зависит от шедулинга потоков. Но нам и не важно это предсказание, потому что имеет значение поведение всей программы целиком. Проблемы возникают, когда такие спорадические эффекты приводят к некорректным результатам. И такие ситуации могут происходить даже в программах без гонки данных. Например:
std::atomic<int> x = 2;

void thread_1() {
  x = 3;
}

void thread_2() {
  if (x % 2 == 0) {
    std::cout << x << std::endl;
  }
}
Может так произойти, что поток 2 выполнится в промежутке между условием и выводом x на консоль. Это очень маловероятная ситуация, однако на консоль может вывестись нечетное число 3 с учетом того, что перед выводом мы проверили на четность. Как минимум удивительный результат, хотя с программе нет гонки данных. Состояние гонки - это в основном ошибка проектирования в условиях многопоточности. Знаменитая проблема наличия метода size() у многопоточной очереди - состояние гонки:
template <typename T>
class ThreadSafeQueue {
...
  size_t size() {
    std::lock_guard lg{mtx_};
    return queue_.size();
  }
private:
  std::deque<T> queue_;
  ...
};


ThreadSafeQueue<int> queue;
...
if (queue.size() > 0) {
  auto item = std::move(queue.front());
  queue.pop();
  // process item
}
Если между успешной и потокобезопасной проверкой, что очередь непустая, придет другой поток и заберет последний элемент из очереди, вы получите ub в попытке увидеть фронтальный элемент. Основные черты состояния гонки: 🙈 Наличие логическое ошибки при проектировании системы 🙈 Зависимость от планирования потоков 🙈 Зависимость от времени выполнения операции. Вчера в чате скинули мем, иллюстрирующий эту зависимость. Многие путают или не понимают разницы между race condition и data race. Это даже частый вопрос на собеседованиях, на который 50% кандидатов отвечают что-то вообще невнятное. Но теперь вы подготовлены и вооружены правильным словарным аппаратом. Be independent of other's schedule. Stay cool. #design #concurrency #interview

💡 Хотите разобраться, как выбрать правильный сенсор — от простого индикатора света до компонента для лазерного интерферометр
💡 Хотите разобраться, как выбрать правильный сенсор — от простого индикатора света до компонента для лазерного интерферометра? 📆 21 октября в 20:00 МСК OTUS проводит открытый вебинар «Выбор фотодатчика для прецизионных измерений». На уроке разберём: – сферы применения фотодатчиков; – сравнение видов по чувствительности и быстродействию; – схемотехнику усилительных каскадов; – обзор интегральных решений для оптоволокна и дальномеров. ❗️ Урок будет полезен инженерам, embedded-разработчикам и разработчикам измерительной техники. 🔴 Мероприятие проходит в преддверии старта курса «Электроника и электротехника». 🎁 Все участники урока получат скидку на обучение. Регистрация открыта: https://otus.pw/3009y/ Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576, www.otus.ru

​​data race #новичкам Конкретных проблем, которые можно допустить в многопоточной среде, существует оооочень много. Но все они делятся на несколько больших категорий. В этом и следующих постах мы на примерах разберем основные виды. Начнем с data race. Это по сути единственная категория, которая четко определена в стандарте С++. Скажем, что два обращения к памяти конфликтуют, если: - они обращаются к одной и той же ячейке памяти. - по крайней мере одно из обращений - запись. Так вот гонкой данных называется 2 конфликтующих обращения к неатомарной переменной, между которыми не возникло отношение порядка "Произошло-Раньше". Если не вдаваться в семантику отношений порядков, то отсутствие синхронизации с помощью примитивов(мьютексов и атомиков) при доступе к неатомикам карается гонкой данных и неопределененным поведением. Простой пример:
int a = 0;

void thread_1() {
  for (int i = 0; i < 10000; ++i) {
    ++a;
  }
}

void thread_2() {
  for (int i = 0; i < 10000; ++i) {
    ++a;
  }
}

std::jthread thr1{thread_1};
std::jthread thr1{thread_2};
std::cout << a << std::endl;
В двух потоках пытаемся инкрементировать a. Проблема в том, что при выводе на консоль a не будет равна 20000, а скорее всего чуть меньшему числу. Инкремент инта - это неатомарная операция над неатомиком, поэтому 2 потока за счет отсутствия синхронизации кэшей будут читать и записывать неактуальные данные. Гонку данных относительно несложно определить по коду, просто следую стандарту, да и тред-санитайзеры, пользуясь определением гонки, могут ее детектировать. Поэтому как будто бы эта не самая основная проблема в многопоточке. Существуют другие, более сложные в детектировании и воспроизведении. Have an order. Stay cool. #cppcore #concurrency

​​WAT #опытным Спасибо, @Ivaneo, за любезно предоставленный примерчик в рамках рубрики #ЧЗХ. "Век живи - век учись" - сказал Луций Сенека. "Век живи - век учи С++" - реалии нашей жизни. Просто посмотрите на следующий код:
struct Foo
{
  void Bar();
};

void Foo::Foo::Foo::Foo::Foo::Foo::Foo::Foo::Foo::Foo::Foo::Foo::Bar()
{
    printf("Foofoo!");
}

int main()
{
    Foo f;
    f.Bar();
    return 0;
}
И он компилируется. WAT? Это называется injected class name. Имя класса доступно из скоупа этого же класса. Так сделано для того, чтобы поиск имени X внутри класса X всегда разрешался именно в этот класс. Такое поведение может быть полезно в таком сценарии:
void X() { }
class X {
public:
  static X Сreate() { return X(); }
};
injected class name гарантирует, что из метода Сreate будет возвращен именно инстанс класса Х, а не результат вызова функции Х. Это также полезно внутри шаблонов классов, где имя класса можно использовать без списка аргументов шаблона, например, используя просто Foo вместо полного идентификатора шаблона Foo<blah, blah, blah>. Ну и побочным эффектом такого поведения является возможность написания длиннющей цепочки из имен класса. Так что это не у вас в глазах двоится, это плюсы такие шебутные) Find yourself within. Stay cool. #cppcore

Готовы с нуля создавать телекоммуникационные решения для беспроводных мобильных сетей и сопутствующих услуг? Тогда участвуйте
Готовы с нуля создавать телекоммуникационные решения для беспроводных мобильных сетей и сопутствующих услуг? Тогда участвуйте в SPRINT OFFER C++ Software Engineer и получите оффер всего за 3 дня 💻 У нас: 🚀 Удалённый формат работы или в офисах городов присутствия (Москва, СПб, Нижний Новгород, Екатеринбург, Минск). 🚀 Реальный карьерный рост: как вертикальный, так и горизонтальный. 🚀 Амбициозные проекты и уникальная команда инженеров. 🚀 Учебный портал с лекциями от экспертов, участие в конференциях, изучение английского и дополнительное обучение на внешних курсах. 🚀 ДМС с первого дня, консультации юристов, психологов и экспертов по ЗОЖ. Мы в поиске: Разработчиков C++ (Middle/Senior/Tech Lead) с опытом в промышленной разработке от 3 лет, уверенным знанием C/C++ и Linux, а также сетей, базирующихся на TCP/IP. Направления, которым вы нужны: Telecom Platform и разработка базовых станций с поддержкой LTE/GSM. В команде Telecom Platform инженеры разрабатывают полное платформенное решение для телекоммуникационных систем, а разработчики базовой станции LTE/GSM создают высоконагруженные системы, которые обеспечивают связь как критически важных, так и новых поколений. 💙 Прочитать подробнее можно по ссылке. Отправляйте заявку до 19 октября и присоединяйтесь к YADRO!

​​Гарантия отсутствия исключений #новичкам Переходим к самой сильной гарантии - отсутствие исключений. В сам язык С++(new, dynamic_cast), и в его стандартную библиотеку в базе встроены исключения. Поэтому писать код без исключений в использованием стандартных инструментов практически невозможно. Вы конечно можете использовать nothrow new и написать свой вариант стандартной библиотеки и других сторонних решений. И кто-то наверняка так делал. Но в этом случае разработка как минимум затянется, а как максимум вы бросите это гиблое дело. Поэтому повсеместно предоставлять nothow гарантии с использованием стандартных инструментов не всегда реалистично. Но если такой термин есть, значит такие гарантии можно предоставлять для отдельных сущностей. Давайте как раз об этих сущностях и поговорим. Но для начала проясним термины. Под гарантией отсутствия исключений подразумевается обычно 2 понятия: nothrow и nofail. nothrow подразумевает отсутствие исключений, но не отсутствие ошибок. Говорится, что ошибки репортятся другими средствами(в основном через глобальное состояние, потому что деструктор ничего не возвращает) или полностью скрываются и игнорируются. Примером сущностей с nothrow гарантией является деструкторы. С С++11 они по-умолчанию помечены noexcept. В основном это сделано для того, чтобы при раскрутке стека не получить double exception. Но деструкторы могу фейлиться. Просто никаких средств, кроме глобальных переменных для репорта ошибок невозможно использовать. Они ведь ничего не возвращают, а исполняются скрытно от нас(если вы используете RAII конечно). nofail же подразумевает полное отсутствие ошибок. nofail гарантия ожидается от std::swap, мув-конструкторов классов и других функций с помощью которых достигается строгая гарантия исключений. Например в swap-идиоме std::swap и мув-конструкторы используются для определения небросающего оператора присваивания. nofail гарантиями также должны обладать функторы-коллбэки модифицирующих алгоритмов. std::sort не предоставляет никаких гарантий на состояние системы, если компаратор бросит эксепшн. В языке в целом эти гарантии обеспечиваются ключевым словом noexcept. При появлении этой нотации компилятор понимает, что для этой функций не нужно генерировать дополнительный код, необходимый для обработки исключений. Но у этого есть своя цена: если из noexcept функции вылетит исключение, то сразу же без разговоров вызовется std::terminate. Provide guarantees. Stay cool. #cppcore #cpp11

​​Строгая гарантия исключений #новичкам Базовая гарантия - это конечно хорошо, наше приложение будет корректно работать, даже если что-то пойдет не так. Но иногда этого недостаточно. Иногда нам нужно, чтобы ошибка операции вообще никак не повлияла на текущее состояние системы. Либо операция выполнилась и все хорошо, либо она бросила исключение, но после его отлова система находится в том же состоянии, что и до выполнения операции. Такое свойство операций называется транзакционность. Транзакция может либо выполниться полностью, либо все результаты промежуточных операций в ней откатываются до состояния до начала исполнения транзакции. Это важно, когда ваша операция требует выполнения нескольких промежуточных операций, постепенно меняющих систему. Если остановиться посередине, то уже невозможно или очень сложно будет восстановить консистентность данных. Давайте перепишем оператор присваивания класс IntArray из предыдущего поста так, чтобы он предоставлял строгую гарантию:
class IntArray {
    int *array;
    std::size_t nElems;

public:
    // ...

    ~IntArray() { delete[] array; }

    IntArray(const IntArray &that);  // nontrivial copy constructor

    IntArray &operator=(const IntArray &rhs) {
        int *tmp = nullptr;
        if (rhs.nElems) {
            tmp = new int[rhs.nElems];
            std::memcpy(tmp, rhs.array, rhs.nElems * sizeof(*array));
        }
        delete[] array;
        array = tmp;
        nElems = rhs.nElems;
        return *this;
    }

    // ...
};
В этот раз мы ничего не изменяем в самом объекте до тех пор, пока не выделим новый буфер и не скопируем туда элементы rhs. И только после этого выполняем обновление самого объекта с помощью небросающих инструкций. Хрестоматийный пример из стандартной библиотеки - вектор с его методом push_back. Если у типа есть небросающий перемещающий конструктор, то метод предоставляет строгую гарантию. Вот примерно как это работает:
template <typename T>
class vector {
private:
    T *data = nullptr;
    size_t size = 0;
    size_t capacity = 0;

    void reallocate(size_t new_capacity) {
        // allocate memory
        T *new_data =
            static_cast<T *>(::operator new(new_capacity * sizeof(T)));
        size_t new_size = 0;

        try {
            // Move or copy elements 
            for (size_t i = 0; i < size; ++i) {
                new (new_data + new_size) T(std::move_if_noexcept(data[i]));
                ++new_size;
            }
        } catch (...) {
            // Rollback in case of exception
            for (size_t i = 0; i < new_size; ++i) {
                new_data[i].~T();
            }
            ::operator delete(new_data);
            throw;
        }

        // cleanup
        // ...
    }

public:
    void push_back(const T &value) {
        if (size >= capacity) {
            size_t new_capacity = capacity == 0 ? 1 : capacity * 2;

            // save for rollback
            T *old_data = data;
            size_t old_size = size;
            size_t old_capacity = capacity;

            try {
                reallocate(new_capacity);
            } catch (...) {
                // restore
                data = old_data;
                size = old_size;
                capacity = old_capacity;
                throw;
            }
        }

        // actually insert element
        // ...
    }

};
в хэлпере reallocate используется std::move_if_noexcept, который условно кастит в rvalue ссылке, если мув конструктор noexcept. И только в этом случае можно предоставить строгую гарантию: если вы уже повредили один из исходных объектов, его уже никак не восстановить. А безопасное перемещение элементов гарантирует готовый к использованию новый расширенный буфер. Be strong. Stay cool. #cppcore

Платите без ограничений Иннокентий проснулся, позавтракал, вышел из дома, надел наушники и включил любимую подборку на Spotify по дороге в офис. Пришел в офис, попил кофеек и сел за задачи. Copilot настроен для автокомплита кода + есть доступ к Claude. Кенни сегодня молодец, решил много проблем и быстро закрыл задачу. После тяжелого трудового дня пришел домой и поиграл в новую игрушку из Steam'а. Или позалипал в новый сериальчик от Netflix. А потом реально проснулся и понял, что это все был сон😨. И взгрустнул, потому что у него нет иностранной карты, чтобы оплачивать все эти сервисы. Но это теперь не проблема, потому что появился сервис МТС Оплата! Там можно в пару кликов оплатить подписку более 150 зарубежных сервисов через СБП и с минимальной комиссией. Всего 5 минут и подписка уже у вас. И никто не попросит ваш паспорт и снилс. Только почта. Кенни теперь не грустит и вы тоже не грустите с МТС Оплатой🤗

​​Базовая гарантия исключений #новичкам Вспомним свойства базовой гарантии: после возникновения исключения в программе не должно быть утечек ресурсов и должны сохраняться все инварианты классов. И не всегда базовой гарантии легко удовлетворить. Поскольку исключения добавляют в программу дополнительные пути выполнения кода, крайне важно учитывать последствия работы кода по таким путям и избегать любых нежелательных эффектов, которые в противном случае могут возникнуть. Давайте посмотрим на примере:
class IntArray {
    int *array;
    std::size_t nElems;

public:
    // ...

    ~IntArray() { delete[] array; }

    IntArray(const IntArray &that);  // nontrivial copy constructor
    IntArray &operator=(const IntArray &rhs) {
        if (this != &rhs) {
            delete[] array;
            array = nullptr;
            nElems = rhs.nElems;
            if (nElems) {
                array = new int[nElems];
                std::memcpy(array, rhs.array, nElems * sizeof(*array));
            }
        }
        return *this;
    }

    // ...
};
Внутренний инвариант класса IntArray - член array является валидным (возможно, нулевым) указателем, а член nElems хранит количество элементов в массиве. В операторе присваивания освобождается память текущего array'я и присваивается значение счётчику элементов nElems до выделения нового блока памяти для копии. В результате, если из new вылетит исключение, то array будет нулевым, а размер массива нет. Это нарушение инварианта и таким объектом просто небезопасно пользоваться. Метод size потенциально вернет ненулевой размер, а закономерное использование следом оператора[] приведет к неопределенному поведению. Код, который избегает подобных нежелательных эффектов, называется exception safe. То есть предоставление базовой гарантии уже говорит о том, что ваш код "exception safe". Допустим, что ваш класс предоставляет базовую гарантию исключений. Какие выводы мы можем из этого сделать? Ну сохранены инварианты, а значения-то какие будут у полей? В том-то и дело, что конкретные значения неизвестны. И это сильно ограничивает практическое использование таких объектов. По сути единственное, что с ним можно гарантировано безопасно сделать - это разрушить. И это главное. Вся магия с раскруткой стека и вызовом деструктором локальных объектов работает только если деструкторы вызываются безопасно. А для этого объект должен быть в валидном, но необязательно определенном, состоянии. То есть вы в принципе не можете восстановить работоспособность приложения, если ваши инструменты не предоставляют хотя бы базовую гарантию. Здесь кстати можно провести параллель с мувнутыми объектами: ими тоже особо не попользуешься и по хорошему их надо просто удалить. Примером предоставления только базовой гарантии может быть использование какой-нибудь базы данных. Если при выполнении запроса фреймворк выкинул исключение, например потому что соединение отвалилось, то объект для работы с базой остался в валидном состоянии, но нет никакой информации о том, выполнился запрос или нет:
auto db = std::make_shared<DBConnection>(credentials);
try {
  auto result = db->Execute("UPDATE ...");
  process(result);
} catch (std::exception& ex) {
  std::cout << "We can cannot rely on table state and must retry with that in mind" << std::endl;
  // retry
}
Поэтому мы должны иметь ввиду это неопределенное состояние коннекшена и базы при выполнении ретраев. Provide guarantees. Stay cool. #cppcore

​​Гарантии безопасности исключений #новичкам Программа на С++ - очень интересное явление. Вроде мощный, подкаченный, умный и скоростной парень. Но вот проблема. Ходить не умеет нормально. То в ноги себе стреляет, то падает периодически. В общем, беда у него с ходьбой. У этого могут быть разные причины. Все из них даже трудно в голове удержать. Но сегодня обсудим, какие есть гипсы, лангеты и костыли, которые помогут этому парню нормально ходить при работе с исключениями. Даже не зная, что вы работаете с исключениями - вы уже работаете с ними. Даже обычный, казалось бы, безобидный new может кинуть std::bad_alloc. И это core языка. Стандартная библиотека пронизана исключениями. Обрабатывать исключения можно по-разному и это будет давать разные результаты. От того, как обрабатываются исключения в модуле, зависит, какие гарантии он может дать в случае возникновения исключительной ситуации. А это очень важная штука, потому что взаимодействующий с модулем код полагается на его адекватное поведение, которое с легкостью может быть нарушено, если нет гарантий безопасности. Итак, существует 3 гарантии безопасности исключений: Базовая гарантия. Формулировка разнится, но более общая звучит так: "после возникновения исключения и его обработки программа должна остаться в согласованном состоянии". Теперь на рабоче-крестьянском: не должно быть утечек ресурсов и должны сохраняться все инварианты классов. С утечками, думаю, все понятно. Инвариант - некое логически согласованное состояние системы. Если класс владеет массивом и содержит поле для его размера, то инвариантом этого класса будет совпадение реального размера массива со значением поля-размера. Для класса "легковая машина" инвариантом будет количество колес - 4. Обычная машина без одного или нескольких колес просто не едет. И для того, чтобы машина корректно работала, количество колес должно быть одинаково - 4. Или например, нельзя, чтобы дата создания чего-то была больше текущего дня, ну никак. Вот такие штуки должны сохраняться. Строгая гарантия. Если при выполнении операции возникает исключение, операция не должна оказать на систему никакого влияния. То есть пан или пропал. Либо вся операция выполняется успешно и ее результат применяется, либо система откатывается в состояние до выполнения операции. Это свойство программы называется транзакционностью. Гарантия отсутствия исключений. Ни при каких обстоятельствах не будет брошено исключение. Легко сказать, но тяжело сделать. С++ и его стандартная библиотека разрабатывались с учетом использования исключений. И это накладывает свои ограничения на отсутствие исключений. Мы все-таки не на Go пишем. Ну и есть еще одна гарантия - отсутствие каких-либо гарантий. Мама - анархия, во всей красе. Тема важная, будем потихоньку ее разбирать с примерами по каждой гарантии. Be a guarantor. Stay cool. #cppcore

⌨️ Открытый урок «Инструменты много поточного программирования в стандартной библиотеке на С++» 🗓 14 октября в 20:00 МСК 🆓
⌨️ Открытый урок «Инструменты много поточного программирования в стандартной библиотеке на С++» 🗓 14 октября в 20:00 МСК 🆓 Бесплатно. Урок в рамках старта курса «C++ Developer. Professional». 🎯 Что рассмотрим на вебинаре: ✔️Инструменты много поточного программирования в стандартной библиотеке ✔️Классы стандартной библиотеки, ответственные за создание многопоточности ✔️Примитивы синхронизации 👥 Кому будет интересно: - junior, junior+ C++ Разработчикам 🔗 Ссылка на регистрацию: https://otus.pw/Z9tf/ Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576

​​Ответ #новичкам Многие из вас подумали, что будет ошибка компиляции. В целом, логичная цепочка мыслей: ну как же можно мувнуть данные из константной ссылки? Она же неизменяема. Но она все же неверная. Правильный ответ: на консоль выведется "copy ctor". Копия? Мы же муваем! Сейчас разберемся. Но для начало вспомним сам пример:
#include <iostream>

struct Test {
    Test() = default;
    Test(const Test &other) {
        std::cout << "copy ctor" << std::endl;
    }
    Test(Test &&other) {
        std::cout << "move ctor" << std::endl;
    }
    Test &operator=(const Test &other) = default;
    Test &operator=(Test &&other) = default;
    ~Test() = default;
};

int main() {
    Test test;
    const Test &ref = test;
    (void)std::move(ref);
    auto emigma = std::move(ref);
}
На самом деле проблема в нейминге. Все вопросы к комитету. Это они имена всему плюсовому раздают. std::move ничего не мувает. Она делает всего лишь static_cast. Но не просто каст к правой ссылке, это не совсем корректно. Посмотрим на реализацию std::move:
template <class T>  
constexpr typename std::remove_reference<T>::type&& move(T&& t) noexcept {  
  return static_cast<typename std::remove_reference<T>::type&&>(t);  
}
Обратите внимание, что от типа отрезается любая ссылочность и только затем добавляется правоссылочность. Но константность-то никуда не уходит. По сути результирующий тип выражения std::move({константная левая ссылка}) это константная правая ссылка. Чтобы это проверить, перейдем на cppinsights:
Test test;
const Test &ref = test;
using ExprType = decltype(std::move(ref));

// под капотом ExprType вот чему равен

using ExprType = const Test &&;
Так как мы просто кастуем к валидному типу, мув успешно отрабатывает, но строчка (void)std::move(ref); не дает в консоли никакого вывода, потому что никаких новых объектов мы не создаем. Теперь про копирование. Вспомним правила приведения типов. const T&& может приводится только к const T&. То есть единственный конструктор, который может вызваться - это копирующий конструктор. Интересная ситуация, конечно, что "перемещение" может приводить к копированию в плюсах. Но имеем, что имеем. Терпим и продолжаем грызть гранит С++. Give a proper name. Stay cool. #cppcore #template

Какой результат попытки компиляции и запуска кода?
Anonymous voting

Квиз #новичкам Сегодня короткий, но от того не менее интересный #quiz, . Можно было бы разобрать вопрос: "а что случится, если я мувну константную ссылку?", но так не очень интересно. Поэтому давайте проверим ваше знание мув-семантики. У меня к вам всего один вопрос: Какой результат попытки компиляции и запуска следующего кода:
#include <iostream>

struct Test {
    Test() = default;
    Test(const Test &other) {
        std::cout << "copy ctor " << std::endl;
    }
    Test(Test &&other) {
        std::cout << "move ctor " << std::endl;
    }
    Test &operator=(const Test &other) = default;
    Test &operator=(Test &&other) = default;
    ~Test() = default;
};

int main() {
    Test test;
    const Test &ref = test;
    (void)std::move(ref);
    auto emigma = std::move(ref);
}
Challenge yourself. Stay cool.

​​Идиома IILE #опытным Неплохой практикой написания кода является определение переменных, которые не изменяются, как const. Это позволяет коду был более экспрессивным, явным, а также компилятор в этом случае может чуть лучше рассуждать о коде и оптимизировать. И это не требует ничего сложного:
const int myParam = inputParam * 10 + 5;
// or
const int myParam = bCondition ? inputParam * 2 : inputParam + 10;
Но что делать, если переменная по сути своей константа, но у нее громоздская инициализация на несколько строк?
int myVariable = 0; // this should be const...

if (bFirstCondition)
    myVariable = bSecondCindition ? computeFunc(inputParam) : 0;
else
    myVariable = inputParam * 2;

// more code of the current function...
// and we assume 'myVariable` is const now
По-хорошему это уносится в какую-нибудь отдельную функцию. Но тогда теряется контекст и нужно будет прыгать по коду. Хочется и const сделать, и в отдельную функцию не выносить. Кажется, что на двух стульях не усидишь, но благодаря лямбдам мы можем это сделать! Есть такая идиома IILE(Immediately Invoked Lambda Expression). Вы определяете лямбду и тут же ее вызываете. И контекст сохраняется, и единовременность инициализации присутствует:
const int myVariable = [&] {
    if (bFirstContidion)
        return bSecondCondition ? computeFunc(inputParam) : 0;
    else
       return inputParam * 2;
}(); // call!
Пара лишних символов, зато проблема решена. Тут есть одно "но". Вроде все хорошо, но немного напрягает, что все это можно спутать с простым определением лямбды, если не увидеть скобки вызова в конце. Тоже не беда! Используем std::invoke:
const int myVariable = std::invoke([&] {
    if (bFirstContidion)
        return bSecondCondition ? computeFunc(inputParam) : 0;
    else
       return inputParam * 2;
});
Теперь мы четко и ясно видим, что лямбда вызывается. В таком виде прям кайф. Эту же технику можно использовать например в списке инициализации конструктора, например, если нужно константное поле определить(его нельзя определять в теле конструктора). Be expressive. Stay cool. #cpp11 #goodpractice

Уже 23 октября узнаем больше о хакрдкорной разработке dev-to-dev решений Техплатформа Городских сервисов Яндекса проводит митап, на котором эксперты поделятся опытом создания архитектуры нагруженной системы, обрабатывающей сотни тысяч rps в брокере сообщений на MongoDB, и как писать IO-bound сервисы под высокими нагрузками на С++ так же как на Go. В программе выступления Антона Полухина, Ромы Елизарова, Лёши Иванова и Влада Назарова, а также нетворкинг в неформальной атмосфере. Если интересуетесь разработкой dev-to-dev решений и вы опытный разработчик, обязательно приходите. 👉 23 октября, сбор гостей с 18:00 👉 Москва, офлайн Регистрируйтесь и зовите коллег! Мероприятие бесплатное. Количество мест ограничено — пожалуйста, дождитесь нашего подтверждения. Реклама. ООО «Яндекс.Такси». ИНН 7704340310.