move constructor
Move-конструктор — это специальный конструктор, который позволяет эффективно перемещать ресурсы из одного объекта в другой, без необходимости копирования данных. Он используется для реализации семантики перемещения (move semantics) и оптимизации работы с временными объектами.
Move-конструктор принимает rvalue ссылку (&&) на объект, который будет перемещен, и выполняет простое копирование указателей на данные, а не их фактическое копирование.
Использование move-конструктора позволяет избежать лишних копирований данных и повысить производительность при работе с большими или ресурсоемкими объектами.
Move-конструктор — это специальный конструктор, который позволяет эффективно перемещать ресурсы из одного объекта в другой, без необходимости копирования данных. Он используется для реализации семантики перемещения (move semantics) и оптимизации работы с временными объектами.
Move-конструктор принимает rvalue ссылку (&&) на объект, который будет перемещен, и выполняет простое копирование указателей на данные, а не их фактическое копирование.
Использование move-конструктора позволяет избежать лишних копирований данных и повысить производительность при работе с большими или ресурсоемкими объектами.
memmove
Функция memmove используется для копирования блока памяти из одного места в другое. Она объявлена в заголовочном файле. Она принимает аргументы типа void * и const void *, что позволяет ей работать с любыми типами данных. Она просто копирует указанное количество байтов из исходного буфера в целевой.
memmove может обрабатывать перекрывающиеся буферы. В отличие от memcpy, которая просто копирует данные из одного места в другое, memmove может безопасно перемещать данные, даже если исходный и целевой буферы перекрываются.
Функция memmove может быть полезна для удаления элементов из массива. Например, если вы хотите удалить элемент из массива и сдвинуть оставшиеся элементы влево, вы можете использовать memmove для перемещения данных в массиве.
Функция memmove используется для копирования блока памяти из одного места в другое. Она объявлена в заголовочном файле. Она принимает аргументы типа void * и const void *, что позволяет ей работать с любыми типами данных. Она просто копирует указанное количество байтов из исходного буфера в целевой.
memmove может обрабатывать перекрывающиеся буферы. В отличие от memcpy, которая просто копирует данные из одного места в другое, memmove может безопасно перемещать данные, даже если исходный и целевой буферы перекрываются.
Функция memmove может быть полезна для удаления элементов из массива. Например, если вы хотите удалить элемент из массива и сдвинуть оставшиеся элементы влево, вы можете использовать memmove для перемещения данных в массиве.
Генерируем пароли с помощью C++
Эта программа создает константы для допустимых символов в пароле и для длины пароля. Затем она инициализирует генератор случайных чисел и генерирует пять случайных паролей.
Каждый пароль формируется путем выбора случайного символа из всего набора символов. Этот процесс повторяется до достижения желаемой длины пароля.
Эта программа создает константы для допустимых символов в пароле и для длины пароля. Затем она инициализирует генератор случайных чисел и генерирует пять случайных паролей.
Каждый пароль формируется путем выбора случайного символа из всего набора символов. Этот процесс повторяется до достижения желаемой длины пароля.
Написание собственного стека TCP/IP может показаться сложной задачей. Действительно, за более чем тридцать лет существования TCP накопилось множество спецификаций. Однако основная спецификация на удивление компактна — важные части включают разбор заголовков TCP, автомат конечных состояний, контроль перегрузок и вычисление времени ожидания повторной передачи.
Наиболее распространенные протоколы второго и третьего уровней — Ethernet и IP, соответственно, — в сравнении с TCP гораздо проще. В этой серии статей мы реализуем минимальный стек TCP/IP в пространстве пользователя для Linux.
Цель этих публикаций и создаваемого ПО исключительно образовательная — углубленное изучение сетевого и системного программирования.
TUN/TAP устройства
Чтобы перехватывать сетевой трафик низкого уровня из ядра Linux, мы будем использовать TAP-устройство Linux. Если кратко, TUN/TAP устройства часто применяются приложениями в пространстве пользователя для работы с трафиком на уровне L3 и L2 соответственно. Популярным примером является туннелирование, когда пакет инкапсулируется внутри полезной нагрузки другого пакета.
Преимущество TUN/TAP устройств в том, что их легко настроить в программе в пространстве пользователя, и они уже используются во множестве программ, таких как OpenVPN.
Поскольку мы хотим строить стек сетевого взаимодействия, начиная со второго уровня, нам потребуется TAP-устройство. Мы создаем его следующим образом:
/*
* Taken from Kernel Documentation/networking/tuntap.txt
*/
int tun_alloc(char *dev)
{
struct ifreq ifr;
int fd, err;
if( (fd = open("/dev/net/tap", O_RDWR)) < 0 ) {
print_error("Cannot open TUN/TAP dev");
exit(1);
}
CLEAR(ifr);
/* Flags: IFF_TUN - TUN device (no Ethernet headers)
* IFF_TAP - TAP device
*
* IFF_NO_PI - Do not provide packet information
*/
ifr.ifr_flags = IFF_TAP | IFF_NO_PI;
if( *dev ) {
strncpy(ifr.ifr_name, dev, IFNAMSIZ);
}
if( (err = ioctl(fd, TUNSETIFF, (void *) &ifr)) < 0 ){
print_error("ERR: Could not ioctl tun: %s\n", strerror(errno));
close(fd);
return err;
}
strcpy(dev, ifr.ifr_name);
return fd;
}
Наиболее распространенные протоколы второго и третьего уровней — Ethernet и IP, соответственно, — в сравнении с TCP гораздо проще. В этой серии статей мы реализуем минимальный стек TCP/IP в пространстве пользователя для Linux.
Цель этих публикаций и создаваемого ПО исключительно образовательная — углубленное изучение сетевого и системного программирования.
TUN/TAP устройства
Чтобы перехватывать сетевой трафик низкого уровня из ядра Linux, мы будем использовать TAP-устройство Linux. Если кратко, TUN/TAP устройства часто применяются приложениями в пространстве пользователя для работы с трафиком на уровне L3 и L2 соответственно. Популярным примером является туннелирование, когда пакет инкапсулируется внутри полезной нагрузки другого пакета.
Преимущество TUN/TAP устройств в том, что их легко настроить в программе в пространстве пользователя, и они уже используются во множестве программ, таких как OpenVPN.
Поскольку мы хотим строить стек сетевого взаимодействия, начиная со второго уровня, нам потребуется TAP-устройство. Мы создаем его следующим образом:
/*
* Taken from Kernel Documentation/networking/tuntap.txt
*/
int tun_alloc(char *dev)
{
struct ifreq ifr;
int fd, err;
if( (fd = open("/dev/net/tap", O_RDWR)) < 0 ) {
print_error("Cannot open TUN/TAP dev");
exit(1);
}
CLEAR(ifr);
/* Flags: IFF_TUN - TUN device (no Ethernet headers)
* IFF_TAP - TAP device
*
* IFF_NO_PI - Do not provide packet information
*/
ifr.ifr_flags = IFF_TAP | IFF_NO_PI;
if( *dev ) {
strncpy(ifr.ifr_name, dev, IFNAMSIZ);
}
if( (err = ioctl(fd, TUNSETIFF, (void *) &ifr)) < 0 ){
print_error("ERR: Could not ioctl tun: %s\n", strerror(errno));
close(fd);
return err;
}
strcpy(dev, ifr.ifr_name);
return fd;
}
Узнаем длину аудио файла
В этом примере мы открываем файл с помощью std::ifstream, находим его размер, а затем вычисляем длительность аудио файла, зная частоту дискретизации (в данном случае 44100 Гц). Выводим длительность в минутах и секундах.
Убедитесь, что заменили "your_audio_file.wav" на путь к вашему аудио файлу, а также, если частота дискретизации вашего аудио файла отличается от 44100 Гц, замените это значение на соответствующее значение вашего аудио файла.
В этом примере мы открываем файл с помощью std::ifstream, находим его размер, а затем вычисляем длительность аудио файла, зная частоту дискретизации (в данном случае 44100 Гц). Выводим длительность в минутах и секундах.
Убедитесь, что заменили "your_audio_file.wav" на путь к вашему аудио файлу, а также, если частота дискретизации вашего аудио файла отличается от 44100 Гц, замените это значение на соответствующее значение вашего аудио файла.
Это отличный ресурс для программистов, работающих с C++. Можно найти подробную документацию по стандартной библиотеке, STL, различным версиям стандарта C++, а также примеры кода и объяснения по ключевым аспектам языка.
Справочник по C++
C++11, C++14, C++17, C++20, C++23, C++26 │ Поддержка компиляторами C++11, C++14, C++17, C++20, C++23, C++26
Справочник по языку C
C89, C95, C99, C11, C17, C23 │ Поддержка компиляторами C99, C23
Справочник по C++
C++11, C++14, C++17, C++20, C++23, C++26 │ Поддержка компиляторами C++11, C++14, C++17, C++20, C++23, C++26
Справочник по языку C
C89, C95, C99, C11, C17, C23 │ Поддержка компиляторами C99, C23
Pipes
В C++ пайпы (pipes) представляют собой механизм для односторонней связи между процессами. Они позволяют передавать данные из одного процесса в другой, где один процесс выступает в роли писателя (write end), а другой процесс выступает в роли читателя (read end) пайпа.
Для работы с пайпами вы можете использовать системные вызовы, такие как pipe, fork и функции чтения/записи (read и write), доступные в POSIX-совместимых операционных системах.
Обратите внимание, что дескрипторы чтения и записи пайпа должны быть закрыты в соответствующих процессах с помощью close, чтобы гарантировать правильное завершение операций чтения и записи.
В C++ пайпы (pipes) представляют собой механизм для односторонней связи между процессами. Они позволяют передавать данные из одного процесса в другой, где один процесс выступает в роли писателя (write end), а другой процесс выступает в роли читателя (read end) пайпа.
Для работы с пайпами вы можете использовать системные вызовы, такие как pipe, fork и функции чтения/записи (read и write), доступные в POSIX-совместимых операционных системах.
Обратите внимание, что дескрипторы чтения и записи пайпа должны быть закрыты в соответствующих процессах с помощью close, чтобы гарантировать правильное завершение операций чтения и записи.
std::hash
Это структура шаблонного класса, определенная в заголовочном файле. Она предоставляет хеш-функцию для хэширования различных типов данных, включая встроенные и пользовательские.
std::hash используется, например, в ассоциативных контейнерах, таких как std::unordered_map и std::unordered_set, для быстрого доступа к элементам по ключу.
Для пользовательского типа данных требуется явная специализация структуры std::hash для корректной работы хэширования.
Это структура шаблонного класса, определенная в заголовочном файле. Она предоставляет хеш-функцию для хэширования различных типов данных, включая встроенные и пользовательские.
std::hash используется, например, в ассоциативных контейнерах, таких как std::unordered_map и std::unordered_set, для быстрого доступа к элементам по ключу.
Для пользовательского типа данных требуется явная специализация структуры std::hash для корректной работы хэширования.
Бинарный поиск
Чаще всего бинарный поиск (бинпоиск) используют, чтобы найти элемент в отсортированном массиве. Мы начинаем искать с середины массива. Если находим то, что нужно, или если больше нечего рассматривать, мы останавливаемся.
В противном случае мы решаем, в каком направлении — вправо или влево от середины — мы должны продолжить поиск. Так как пространство поиска после каждой проверки делится на два, то время выполнения алгоритма — O(log n).
Код выводит следующее:
бинарный поиск: нашли по индексу 4
Если искомый элемент не найден, но мы хотим найти ближайший элемент меньше или больше запроса, то можно использовать функции STL lower_bound() и upper_bound().
Чаще всего бинарный поиск (бинпоиск) используют, чтобы найти элемент в отсортированном массиве. Мы начинаем искать с середины массива. Если находим то, что нужно, или если больше нечего рассматривать, мы останавливаемся.
В противном случае мы решаем, в каком направлении — вправо или влево от середины — мы должны продолжить поиск. Так как пространство поиска после каждой проверки делится на два, то время выполнения алгоритма — O(log n).
Код выводит следующее:
бинарный поиск: нашли по индексу 4
Если искомый элемент не найден, но мы хотим найти ближайший элемент меньше или больше запроса, то можно использовать функции STL lower_bound() и upper_bound().
При каких условиях ReadFile или WriteFile могут не передать все байты, и как это обнаружить?
Один из клиентов хотел понять, в каких случаях функции ReadFile и WriteFile могут не передать все байты, и как определить, что это произошло.
Очевидная причина, по которой ReadFile может не прочитать все байты, - это то, что просто нет такого количества данных для чтения.
- Для файлов на диске это обычно происходит, если попытаться читать за концом файла.
- То же может случиться и с другими типами дескрипторов: для pipe в неблокирующем режиме в пайпе может не оказаться достаточного количества данных.
- Если это message pipe, сообщение может быть меньше, чем размер вашего буфера.
- Или при обращении к устройству - оно может не иметь всех байтов в наличии.
Аналогично, очевидная причина, по которой WriteFile может не записать все байты, — это нехватка места.
- Для файлов на диске это может быть полный диск или достижение лимита квоты.
- Для pipe в неблокирующем режиме запись может быть укорочена, если в буфере пайпа недостаточно места, чтобы принять все запрошенные данные.
Во всех таких случаях укороченную запись можно обнаружить, проверив, меньше ли число реально записанных байт по сравнению с запрошенным.
Если количество реально переданных байт больше нуля, то функции ReadFile и WriteFile вернут успех, но при этом передадут меньше байт, чем было запрошено.
Один из клиентов хотел понять, в каких случаях функции ReadFile и WriteFile могут не передать все байты, и как определить, что это произошло.
Очевидная причина, по которой ReadFile может не прочитать все байты, - это то, что просто нет такого количества данных для чтения.
- Для файлов на диске это обычно происходит, если попытаться читать за концом файла.
- То же может случиться и с другими типами дескрипторов: для pipe в неблокирующем режиме в пайпе может не оказаться достаточного количества данных.
- Если это message pipe, сообщение может быть меньше, чем размер вашего буфера.
- Или при обращении к устройству - оно может не иметь всех байтов в наличии.
Аналогично, очевидная причина, по которой WriteFile может не записать все байты, — это нехватка места.
- Для файлов на диске это может быть полный диск или достижение лимита квоты.
- Для pipe в неблокирующем режиме запись может быть укорочена, если в буфере пайпа недостаточно места, чтобы принять все запрошенные данные.
Во всех таких случаях укороченную запись можно обнаружить, проверив, меньше ли число реально записанных байт по сравнению с запрошенным.
Если количество реально переданных байт больше нуля, то функции ReadFile и WriteFile вернут успех, но при этом передадут меньше байт, чем было запрошено.
Что такое виртуальный деструктор и зачем он используется в C++?
В C++ виртуальный деструктор используется для правильного освобождения памяти при удалении объекта через указатель на базовый класс. Если базовый класс имеет виртуальный деструктор, то при удалении объекта через указатель на базовый класс будет вызван деструктор не только базового класса, но и всех его производных классов. Это позволяет избежать утечек памяти и неопределенного поведения при работе с полиморфными объектами.
Если виртуального деструктора не объявлено в базовом классе, то при удалении производного объекта через указатель на базовый класс будут вызваны только деструкторы базового класса, что может привести к утечкам памяти и неопределенному поведению.
В C++ виртуальный деструктор используется для правильного освобождения памяти при удалении объекта через указатель на базовый класс. Если базовый класс имеет виртуальный деструктор, то при удалении объекта через указатель на базовый класс будет вызван деструктор не только базового класса, но и всех его производных классов. Это позволяет избежать утечек памяти и неопределенного поведения при работе с полиморфными объектами.
Если виртуального деструктора не объявлено в базовом классе, то при удалении производного объекта через указатель на базовый класс будут вызваны только деструкторы базового класса, что может привести к утечкам памяти и неопределенному поведению.
Состоялся релиз третьей мажорной версии легковесной интегрированной среды разработки Red Panda C++ для языков C, C++ и GNU Assembly. Предыдущая стабильная мажорная версия проекта Red Panda C++ 2.1 вышла в октябре 2022 года.
Согласно данным OpenNET, проект Red Panda C++ продолжает развитие IDE Dev-C++, которая была переписана с Delphi на С++ с использованием библиотеки Qt.
Исходный код проекта опубликован на GitHub под лицензией GPLv3. Изначально среда разработки Dev-C++ поддерживала только платформу Windows, но в Red Panda C++ добавлена поддержка платформ Linux и macOS.
Особенности проекта Red Panda C++:
• поддержка экранов с высокой плотностью пикселей;
• автодополнение кода и проверка синтаксиса во время редактирования, сворачивания блоков кода, автоформатирование;
• расширенные возможности отладчика, такие как стек вызовов и интерфейс для просмотра содержимого памяти;
• поддержка тем оформления и цветовых схем. Наличие тёмного режима интерфейса;
• поддержка кодировки UTF-8;
• системы для ведения списков задач (TODO) и закладок;
• значительно улучшенные функции поиска и замены;
• возможности для проведения рефакторинга (переименования имён);
• поддержка компилятора SDCC.
Основные изменения и дополнения в Red Panda C++ 3.0:
• расширена поддержка символов Unicode и внесены улучшения, связанные со шрифтами;
• поддержка визуализации управляющих ASCII-символов и пробелов;
• улучшена подсветка синтаксиса, добавлена подсветка атрибутов C++ и подсветка слов в строках и комментариях;
• проведена оптимизация кода для загрузки и редактирования файлов;
• добавлена поддержка анонимных классов и лямбда-выражений в C++;
• в настройки добавлена панель со списком шрифтов;
• в таблицу с трассировкой стека в отладчике добавлены всплывающие подсказки;
• обеспечено открытие в редакторе файлов ".def" (определения состава модуля);
• добавлена возможность обращения к элементам меню через горячие клавиши;
• переработан интерфейс для управления темами оформления;
• появились дополнительные режимы форматирования кода;
• добавлено меню для управления дополнением вводимого кода.
Согласно данным OpenNET, проект Red Panda C++ продолжает развитие IDE Dev-C++, которая была переписана с Delphi на С++ с использованием библиотеки Qt.
Исходный код проекта опубликован на GitHub под лицензией GPLv3. Изначально среда разработки Dev-C++ поддерживала только платформу Windows, но в Red Panda C++ добавлена поддержка платформ Linux и macOS.
Особенности проекта Red Panda C++:
• поддержка экранов с высокой плотностью пикселей;
• автодополнение кода и проверка синтаксиса во время редактирования, сворачивания блоков кода, автоформатирование;
• расширенные возможности отладчика, такие как стек вызовов и интерфейс для просмотра содержимого памяти;
• поддержка тем оформления и цветовых схем. Наличие тёмного режима интерфейса;
• поддержка кодировки UTF-8;
• системы для ведения списков задач (TODO) и закладок;
• значительно улучшенные функции поиска и замены;
• возможности для проведения рефакторинга (переименования имён);
• поддержка компилятора SDCC.
Основные изменения и дополнения в Red Panda C++ 3.0:
• расширена поддержка символов Unicode и внесены улучшения, связанные со шрифтами;
• поддержка визуализации управляющих ASCII-символов и пробелов;
• улучшена подсветка синтаксиса, добавлена подсветка атрибутов C++ и подсветка слов в строках и комментариях;
• проведена оптимизация кода для загрузки и редактирования файлов;
• добавлена поддержка анонимных классов и лямбда-выражений в C++;
• в настройки добавлена панель со списком шрифтов;
• в таблицу с трассировкой стека в отладчике добавлены всплывающие подсказки;
• обеспечено открытие в редакторе файлов ".def" (определения состава модуля);
• добавлена возможность обращения к элементам меню через горячие клавиши;
• переработан интерфейс для управления темами оформления;
• появились дополнительные режимы форматирования кода;
• добавлено меню для управления дополнением вводимого кода.
Обогащаем исключение
#новичкам
Еще один простой и известный лайфхак, как можно более эффективно работать с исключениями.
Вот пилишь проект на 100500 строк кода. Запускаешь и вылетает ошибка: "абракадабра, ты хреновый программист". И как понять, откуда это исключение прилетело?
И начинается. Все обкладывается принтами и смотрится после какого принта программа падает. Но можно сделать немного умнее.
Пусть у вас в проекте активно используется библиотека для работы с базой данных. При возникновении непредвиденной ситуации библиотека генерирует исключение. Однако мест, где вы используете базу, много. И вам хотелось бы понимать примерный путь пролетания исключения, чтобы лучше понимать ситуацию для отладки.
В прошлом посте мы просто прокидывали исключение дальше, без добавления в него какой-то информации. Но можно бросить новое исключение и передать туда сообщение об ошибке первого:
В итоге вы на верхнем слое приложения финально обрабатываете все исключения и получаете фактически стек вызовов, которые они пролетают. И уже намного проще найти место конкретной проблемы, особенно если есть информативные логи.
Track your problems to the bottom. Stay cool.
#новичкам
Еще один простой и известный лайфхак, как можно более эффективно работать с исключениями.
Вот пилишь проект на 100500 строк кода. Запускаешь и вылетает ошибка: "абракадабра, ты хреновый программист". И как понять, откуда это исключение прилетело?
И начинается. Все обкладывается принтами и смотрится после какого принта программа падает. Но можно сделать немного умнее.
Пусть у вас в проекте активно используется библиотека для работы с базой данных. При возникновении непредвиденной ситуации библиотека генерирует исключение. Однако мест, где вы используете базу, много. И вам хотелось бы понимать примерный путь пролетания исключения, чтобы лучше понимать ситуацию для отладки.
В прошлом посте мы просто прокидывали исключение дальше, без добавления в него какой-то информации. Но можно бросить новое исключение и передать туда сообщение об ошибке первого:
void ComplicatedCalculations() try {
// use db
} catch (std::exception& ex) {
throw std::runtime_error(std::string("ComplicatedCalculations Error:") + ex.what());
}
void HandlingCalculations() try {
ComplicatedCalculations();
} catch (std::exception& ex) {
throw std::runtime_error(std::string("HandlingCalculations Error:") + ex.what());
}В итоге вы на верхнем слое приложения финально обрабатываете все исключения и получаете фактически стек вызовов, которые они пролетают. И уже намного проще найти место конкретной проблемы, особенно если есть информативные логи.
Track your problems to the bottom. Stay cool.
Раскрываем вложенное исключение
#опытным
Каша заварилась, пришло время расхлебывать. В прошлом посте мы успешно завернули кучу исключений в одно, теперь нужно как-то все обратно развернуть. Для этого существует функция std::rethrow_if_nested. Работает она вот так:
Встречайте вложенные try-catch с заранее известной глубиной вложенности. Это конечно никуда не годится.
В реальности при повсеместном использовании вложенных исключений мы не знаем, сколько раз нам нужно сделать rethrow_if_nested. Но при этом мы точно знаем, что если исключение последнее в цепочке, то rethrow_if_nested ничего не сделает. Это же идеальное условие для остановки рекурсии!
Рекурсия решает проблему неопределенного количества уровней вложенности исключений и помогает удобно подряд печатать соответствующие сообщения об ошибках:
В этом случае на добавление дополнительной информации к исключению вы тратите всего 1 строчку, которая мало чем отличается от возбуждения нового исключения. Плюс в коде логики проекта вы одной строчкой обрабатываете все вложенные исключения. Красота!
Очень нишевая функциональность, которую вы вряд ли встретите в реальных проектах. Однако будет прикольно попробовать ее в своих пет-проектах, если вы не гнушаетесь исключений.
Have a good error handling system. Stay cool.
#опытным
Каша заварилась, пришло время расхлебывать. В прошлом посте мы успешно завернули кучу исключений в одно, теперь нужно как-то все обратно развернуть. Для этого существует функция std::rethrow_if_nested. Работает она вот так:
class nested_exception
{
exception_ptr _M_ptr;
public:
/// The default constructor stores the current exception (if any).
nested_exception() noexcept : _M_ptr(current_exception()) { }
[[noreturn]] void rethrow_nested() const {
if (_M_ptr)
// just throw _M_ptr
rethrow_exception(_M_ptr);
std::terminate();
}
...
};
template<class E>
void rethrow_if_nested(const E& e) {
if (auto p = dynamic_cast<const std::nested_exception*>(std::addressof(e)))
p->rethrow_nested();
}
Так как объект вложенного исключения - это наследник std::nested_exception, то мы имеем право динамически привести указатель переданного в rethrow_if_nested исключения к std::nested_exception. Если каст прошел успешно, то бросается исключение, сохраненное в nested_exception. Если каст провалился, значит E - это уже не наследник nested_exception и у нас в руках самое первое исключение в цепочке.
Обычно в статьях по этой теме приводят мягко говоря странные примеры того, как надо обрабатывать вложенные исключения:
int main() {
try {
Login("test@example.com", "secret");
} catch (SecurityError &e) {
std::cout << "Caught a SecurityError";
try {
std::rethrow_if_nested(e);
} catch (AuthenticationError &e) {
std::cout << "\nNested AuthenticationError: " << e.Email;
}
}
std::cout << "\nProgram recovered";
}Встречайте вложенные try-catch с заранее известной глубиной вложенности. Это конечно никуда не годится.
В реальности при повсеместном использовании вложенных исключений мы не знаем, сколько раз нам нужно сделать rethrow_if_nested. Но при этом мы точно знаем, что если исключение последнее в цепочке, то rethrow_if_nested ничего не сделает. Это же идеальное условие для остановки рекурсии!
Рекурсия решает проблему неопределенного количества уровней вложенности исключений и помогает удобно подряд печатать соответствующие сообщения об ошибках:
void print_exception(const std::exception &e, int level = 0) {
std::cerr << std::string(level, ' ') << "exception: " << e.what() << '\n';
try {
std::rethrow_if_nested(e);
} catch (const std::exception &nestedException) {
print_exception(nestedException, level + 1);
} catch (...) {
}
}
void ComplicatedCalculations() try {
// use db
} catch (std::exception &ex) {
std::throw_with_nested(std::runtime_error("Complicated Calculations Error"));
}
void HandlingCalculations() try { ComplicatedCalculations(); } catch (std::exception &ex) {
std::throw_with_nested(std::runtime_error("Handling Calculations Error"));
}
int main() {
try {
HandlingCalculations();
} catch (const std::exception &e) {
print_exception(e);
}
}
// OUTPUT:
// Handling Calculations Error
// Complicated Calculations Error
// Some DB ErrorВ этом случае на добавление дополнительной информации к исключению вы тратите всего 1 строчку, которая мало чем отличается от возбуждения нового исключения. Плюс в коде логики проекта вы одной строчкой обрабатываете все вложенные исключения. Красота!
Очень нишевая функциональность, которую вы вряд ли встретите в реальных проектах. Однако будет прикольно попробовать ее в своих пет-проектах, если вы не гнушаетесь исключений.
Have a good error handling system. Stay cool.
Динамический полиморфизм: разделяемые библиотеки
#опытным
В тему указателей на функции вкину еще один способ реализации полиморфизма в С++ - разделяемые или динамические библиотеки.
Обычно разделяемые библиотеки загружаются на самом старте программы(какие-нибудь libc и libstdc++ например неявно подгружаются на старте). Основную часть таких библиотек мы прописываем в опциях линковки.
Однако динамические библиотеки можно неявно подгружать прямо из кода! Для этого на разных системах существует разное системное апи, но для юниксов это dlopen+dlsym.
dlopen по заданному пути файла библиотеки возвращает void указатель на хэндлер этой либы. С помощью хэндлера, функции dlsym и текстового названия определенной функции можно получить указатель на эту функцию и вызвать ее.
Тут пример будет довольно длинный, поэтому начнем с начала.
У вас есть какой-то интерфейс и вы хотите передать реализацию этого интерфейса другой команде, которая имеет чуть больше скилла в данной доменной области:
Эта команда берет и реализует этот интерфейс:
Также вы договорились, что каждая реализация интерфейса предоставляет 2 функции: создания и уничтожения наследников.
Функции create_plugin и destroy_plugin обязаны иметь сишную линковку, чтобы достать указатели на них по их имени из библиотеки с помощью dlsym:
С помощью dlopen и пути к библиотеке-реализации интерфейса получает хэндлер либы. Дальше получаем указатели на функции создания и уничтожения плагина с помощью dlsym, хэндлера и текстовому имени функции.
Разве по имени функции можно получить указатель на нее? Похоже на какую-то рефлексию с первого взгляда.
Тут дело в именах функций и отображении их в символы бинарного файла при компиляции. В С нет никакого манглинга имен, поэтому в готовом бинарном файле можно найти символ, соответствующий названию функции, и связанный с ним адрес этой фукнции. Именно поэтому create_plugin и destroy_plugin помечены extern "C", чтобы их имена обрабатывались по правилам С.
По сути, это все еще про указатели на функции, просто интересно, что на момент компиляции программы у вас может не быть реализации этих функции.
Choose the right name. Stay cool.
#опытным
В тему указателей на функции вкину еще один способ реализации полиморфизма в С++ - разделяемые или динамические библиотеки.
Обычно разделяемые библиотеки загружаются на самом старте программы(какие-нибудь libc и libstdc++ например неявно подгружаются на старте). Основную часть таких библиотек мы прописываем в опциях линковки.
Однако динамические библиотеки можно неявно подгружать прямо из кода! Для этого на разных системах существует разное системное апи, но для юниксов это dlopen+dlsym.
dlopen по заданному пути файла библиотеки возвращает void указатель на хэндлер этой либы. С помощью хэндлера, функции dlsym и текстового названия определенной функции можно получить указатель на эту функцию и вызвать ее.
Тут пример будет довольно длинный, поэтому начнем с начала.
У вас есть какой-то интерфейс и вы хотите передать реализацию этого интерфейса другой команде, которая имеет чуть больше скилла в данной доменной области:
class PluginInterface {
public:
virtual int method() = 0;
};
extern "C" PluginInterface* create_plugin();
extern "C" void destroy_plugin(PluginInterface* obj);Эта команда берет и реализует этот интерфейс:
#include "PluginInterface.hpp"
#include <iostream>
class MyPlugin : public PluginInterface {
public:
virtual void method() override;
};
int MyPlugin::method() {
std::cout << "Method is called\n";
return 42;
}
extern "C" PluginInterface* create_plugin() {
return new MyPlugin();
}
extern "C" void destroy_plugin(PluginInterface* obj) {
delete obj;
}
Также вы договорились, что каждая реализация интерфейса предоставляет 2 функции: создания и уничтожения наследников.
Функции create_plugin и destroy_plugin обязаны иметь сишную линковку, чтобы достать указатели на них по их имени из библиотеки с помощью dlsym:
#include "PluginInterface.hpp"
#include <dlfcn.h>
#include <iostream>
typedef PluginInterface *(*creatorFunction)();
typedef void (*destroyerFunction)(PluginInterface *);
int main() {
void *handle = dlopen("myplugin.so", RTLD_LAZY);
if (!handle) {
std::println("dlopen failure: {}", dlerror());
return 1;
}
creatorFunction create = reinterpret_cast<creatorFunction>(dlsym(handle, "create_plugin"));
destroyerFunction destroy = reinterpret_cast<destroyerFunction>(dlsym(handle, "destroy_plugin"));
PluginInterface *plugin = (*create)();
std::println("{}", plugin->method());
(*destroy)(plugin);
dlclose(handle);
}
С помощью dlopen и пути к библиотеке-реализации интерфейса получает хэндлер либы. Дальше получаем указатели на функции создания и уничтожения плагина с помощью dlsym, хэндлера и текстовому имени функции.
Разве по имени функции можно получить указатель на нее? Похоже на какую-то рефлексию с первого взгляда.
Тут дело в именах функций и отображении их в символы бинарного файла при компиляции. В С нет никакого манглинга имен, поэтому в готовом бинарном файле можно найти символ, соответствующий названию функции, и связанный с ним адрес этой фукнции. Именно поэтому create_plugin и destroy_plugin помечены extern "C", чтобы их имена обрабатывались по правилам С.
По сути, это все еще про указатели на функции, просто интересно, что на момент компиляции программы у вас может не быть реализации этих функции.
Choose the right name. Stay cool.
Книга по C++, которую ты обязан прочитать в 2025 году
Я почему-то уверен, что она у тебя уже есть. Либо в списке литературы для самообразования, либо где-то среди скачанных pdf, а может, даже в печатном виде. Возможно, ты даже начинал её читать, но… работа, таски, собеседования… деньги вроде и так платят… и ты так и не осилил Мейерса "Эффективный и современный С++".
А ведь именно эта книга — ключ, который поможет тебе пробить потолок уровня middle и начать движение к senior.
Да, можно скачать список из 400 популярных вопросов, выучить их и бодро ответить на вопрос: «Расскажи про std::unique_ptr». А потом тебе прилетает дополнительный вопрос: «А как изменится размер std::unique_ptr при использовании пользовательского удалителя?» Иии… Я не буду додумывать твой уровень знаний. Попробуй сам ответить на этот вопрос и поставь себе оценку
И это только один из примеров тонких нюансов, которые описаны в этой книге и которые реально спрашивают на собеседованиях.
Ещё одна сильная сторона книги: почти каждая фича современного C++ иллюстрируется примерами её использования в реальном проде. А это большая редкость для технической литературы.
Хватит откладывать эту книгу на «потом». Поставь цель на этот год и, наконец-то, найди время, чтобы прочитать 300 страниц "Эффективного и современного С++".
Я почему-то уверен, что она у тебя уже есть. Либо в списке литературы для самообразования, либо где-то среди скачанных pdf, а может, даже в печатном виде. Возможно, ты даже начинал её читать, но… работа, таски, собеседования… деньги вроде и так платят… и ты так и не осилил Мейерса "Эффективный и современный С++".
А ведь именно эта книга — ключ, который поможет тебе пробить потолок уровня middle и начать движение к senior.
Да, можно скачать список из 400 популярных вопросов, выучить их и бодро ответить на вопрос: «Расскажи про std::unique_ptr». А потом тебе прилетает дополнительный вопрос: «А как изменится размер std::unique_ptr при использовании пользовательского удалителя?» Иии… Я не буду додумывать твой уровень знаний. Попробуй сам ответить на этот вопрос и поставь себе оценку
И это только один из примеров тонких нюансов, которые описаны в этой книге и которые реально спрашивают на собеседованиях.
Ещё одна сильная сторона книги: почти каждая фича современного C++ иллюстрируется примерами её использования в реальном проде. А это большая редкость для технической литературы.
Хватит откладывать эту книгу на «потом». Поставь цель на этот год и, наконец-то, найди время, чтобы прочитать 300 страниц "Эффективного и современного С++".
starts_with, ends_with
#новичкам
До (и включая) C++17, если вы хотите проверить начало или конец в строке на соответствие референсу, вы должны использовать самописные решения, буст или другие сторонние библиотеки. К счастью, это меняется с C++20.
В нем появляются стандартные методы
Как видите, они имеют три перегрузки: для string_view, одного символа и строкового литерала.
Кейсов применения этих методов предостаточно: валидация расширения файла, валидация url, html кода, префикса пути до файла, хэдэров http реквестов. В любом более менее большом проекте найдется местечко для этих методов.
Примерчик:
Здесь мы простейшим отображением ranges отфильтровываем строки вектора, которые являются тегами, и оставляем только текстовую часть.
В общем, полезные штуки, которые помогают заменить громоздкие кастомные проверки через find на использование выразительных функций.
Be expressive. Stay cool.
#новичкам
До (и включая) C++17, если вы хотите проверить начало или конец в строке на соответствие референсу, вы должны использовать самописные решения, буст или другие сторонние библиотеки. К счастью, это меняется с C++20.
В нем появляются стандартные методы
std::string/std::string_view .starts_with() и .ends_with():constexpr bool starts_with(string_view sv) const noexcept;
constexpr bool starts_with(CharT c ) const noexcept;
constexpr bool starts_with(const CharT* s ) const;
constexpr bool ends_with(string_view sv )const noexcept;
constexpr bool ends_with(CharT c ) const noexcept;
constexpr bool ends_with(const CharT* s ) const;
Как видите, они имеют три перегрузки: для string_view, одного символа и строкового литерала.
const std::string url { "https://isocpp.org" };
// string literals
if (url.starts_with("https") && url.ends_with(".org"))
std::cout << "you're using the correct site!\n";
// a single char:
if (url.starts_with('h') && url.ends_with('g'))
std::cout << "letters matched!\n";Кейсов применения этих методов предостаточно: валидация расширения файла, валидация url, html кода, префикса пути до файла, хэдэров http реквестов. В любом более менее большом проекте найдется местечко для этих методов.
Примерчик:
const std::vector<std::string> tokens {
"<header>",
"<h1>",
"Hello World",
"</h1>",
"<p>",
"This is my super cool new web site.",
"</p>",
"<p>",
"Have a look and try!",
"</p>",
"</header>"
};
auto text = tokens |
std::views::filter([](const std::string& s) {
if (s.starts_with("<") || s.ends_with(">"))
return false;
return true;
});
for (const auto& str : text)
std::cout << str << std::endl;
// OUTPUT:
// Hello World
// This is my super cool new web site.
// Have a look and try!Здесь мы простейшим отображением ranges отфильтровываем строки вектора, которые являются тегами, и оставляем только текстовую часть.
В общем, полезные штуки, которые помогают заменить громоздкие кастомные проверки через find на использование выразительных функций.
Be expressive. Stay cool.