Senior C++ Developer – Telegram
Senior C++ Developer
12.3K subscribers
1.36K photos
3 videos
610 links
Изучаем C++.

По вопросам сотрудничества: @adv_and_pr

РКН: https://www.gosuslugi.ru/snet/676e9a1e4e740947beca35ba
Download Telegram
#вопросы_с_собеседований
Почему рекомендуется не использовать паттерн Singleton?

Singleton гарантирует, что класс имеет только один экземпляр и обеспечивает глобальную точку доступа к нему.
Однако паттерн Singleton часто критикуют и не рекомендуют использовать по нескольким причинам:

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

2. Скрытие зависимостей: Singleton скрывает зависимости, затрудняя понимание и рассуждения о взаимодействии между различными компонентами системы.

3. Сложности тестирования: Singleton может усложнить модульное тестирование. Поскольку он полагается на глобальное состояние, становится трудно изолировать и тестировать отдельные компоненты по отдельности. Это может привести к зависимостям и сделать тесты более хрупкими.

4. Безопасность потоков: Экземпляры Singleton могут создавать проблемы с безопасностью потоков. Если несколько потоков пытаются одновременно получить доступ или изменить Singleton, это может привести к неопределенному поведению. А дополнительное обеспечение безопасности потоков добавляет сложности и может повлиять на производительность.
You Ain't Gonna Need It (YAGNI)

You Ain't Gonna Need It
(Вам это не понадобится) - это принцип разработки программного обеспечения, который поощряет простоту и избегает чрезмерной инженерии. принцип YAGNI побуждает разработчиков реализовывать только те функции, которые необходимы в данный момент, а не добавлять функциональность, которая может потребоваться в будущем, но в данный момент не нужна.

Вот ключевые аспекты применения принципа YAGNI:

- Минимализм: Пишите минимальный объем кода, необходимый для выполнения непосредственных требований.

- Избегайте спекулятивной разработки: Не реализуйте функции на основе спекулятивных будущих требований.

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

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

Помните, что принцип заключается не в ограничении гибкости, а в принятии обоснованных решений, основанных на реальных требованиях.
Curiously Recurring Template Pattern (CRTP)

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

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

Функция implementation() в базовом классе определяется как невиртуальная. Внутри функции implementation() используется static_cast<Derived*>(this) для приведения указателя к типу производного класса. Это позволяет каждому производному классу предоставлять свою собственную реализацию функции implementation().

Когда функция implementation() вызывается на экземпляре производного класса, она вызывает соответствующую реализацию в этом производном классе.
std::variant

Это фича C++17, обеспечивающая типобезопасное объединение, позволяющее хранить и манипулировать значениями разных типов в одном объекте. Она является частью стандартной библиотеки C++ и определена в заголовке <variant>.

Шаблонный класс std::variant похож на упрощенную версию union, но с дополнительной безопасностью типов и поддержкой различных операций.

Основными функциями std::variant являются index(), valueless_by_exception(), operator=, emplace.

В этом примере мы создаем объект var, который может содержать значения типов int, float или std::string. Мы присваиваем var различные значения и получаем их с помощью std::get.

Однако если мы попытаемся получить значение, используя неправильный тип (например, std::get<int>(var), когда вариант содержит std::string), это вызовет исключение std::bad_variant_access.
std::any

Это функция C++17, которая предоставляет безопасный с точки зрения типов контейнер для единичных значений любого типа. Она позволяет хранить и манипулировать значениями разных типов в одном объекте, подобно std::variant. Однако, в отличие от std::variant, который требует явного указания допустимых типов, std::any может хранить значения любого типа.

Класс std::any является частью стандартной библиотеки C++ и определяется в заголовке <any>. std::any предоставляет функции, такие как type(), has_value(), reset(), emplace() и другие, которые позволяют манипулировать и запрашивать хранимое значение.

В этом примере мы создаем объект val, который может хранить значения любого типа. Мы присваиваем val различные значения и извлекаем их с помощью std::any_cast и проверки типа с помощью typeid.

Однако при попытке извлечь значение, используя неправильный тип (в данном случае std::any_cast<int>), возникает исключение std::bad_any_cast, которое можно обработать с помощью try-catch.
#вопросы_с_собеседований
Как работает RTTI?

RTTI расшифровывается как "Run-Time Type Information" и является функцией в C++, которая предоставляет информацию о типе объекта во время выполнения. Она позволяет вам динамически запрашивать и манипулировать информацией о типе объекта.

В C++ информация о типе объектов обычно представлена механизмом, называемым классом type_info. Класс type_info является частью стандартной библиотеки C++ и определяется в заголовке <typeinfo>.

Чтобы использовать RTTI в C++, необходимо включить функцию RTTI, указав флаг компилятора -frtti или включив его в настройках проекта.

RTTI особенно полезен, когда у вас есть указатель на базовый класс или ссылка на объект производного класса. RTTI обеспечивает безопасную передачу указателя базового класса в производный класс и выполнение определенных операций. Для этого используется оператор dynamic_cast, который выполняет динамическое приведение и возвращает указатель или ссылку целевого типа, если приведение корректно, или нулевой указатель, или выбрасывает исключение, если приведение не удалось.
#вопросы_с_собеседований
Что такое copy elision и когда становится возможным? Какие особенности для разных стандартов?

copy elision - это техника оптимизации компилятора в C++, которая позволяет устранить ненужные операции копирования или перемещения при возврате объектов из функций или инициализации объектов.
Это позволяет компилятору оптимизировать создание и уничтожение временных объектов, в результате чего код становится более эффективным.

copy elision становится возможной в сценариях, определенных стандартом C++. Соответствующее положение стандарта называется правилом "as-if", которое позволяет компилятору оптимизировать программу до тех пор, пока она производит такое же наблюдаемое поведение, как и исходный код.

-
C++98/03: copy elision не является обязательной, но разрешена в качестве оптимизации. RVO* и NRVO* - обычные оптимизации, выполняемые компиляторами. *(Return Value Optimization, Named Return Value Optimization)
- C++11: Правила copy elision были пересмотрены. RVO и NRVO стали обязательными в некоторых случаях. copy elision также может происходить при выбросе исключений.
- C++17: Правила исключения копирования были еще более смягчены. Именованные переменные могут быть созданы или присвоены без необходимости выполнения операции перемещения. Эта оптимизация называется "mandatory copy elision".
- C++20: Правила исключения копирования остались такими же, как и в C++17.

Важно отметить, что хотя копирование является широко поддерживаемой оптимизацией, оно все еще зависит от реализации компилятором. Компиляторы могут иметь различное поведение или ограничения в отношении copy elision. Поэтому её использование для оптимизации поведения или производительности может быть непереносимым в различных компиляторах или версиях.
#вопросы_с_собеседований
Что такое динамический анализатор кода? Какие знаете?

Динамический анализатор кода C++ - это инструмент или программное обеспечение, которое анализирует код C++ во время выполнения или исполнения.
Он выполняет различные проверки и инспекции кода для обнаружения потенциальных проблем, ошибок, утечек памяти, узких мест в производительности или других проблем во время выполнения. В отличие от статического анализа кода, который анализирует код без его выполнения, динамический анализ дает представление о поведении кода во время его выполнения.

Примеры:

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

AddressSanitizer (ASan): Это детектор ошибок памяти, встроенный в компиляторы Clang и GCC. Он обнаруживает такие ошибки памяти, как переполнение буфера, использование после освобождения и т.д. ASan проверяет код во время компиляции, внедряя проверки во время выполнения.

Dr. Memory: Это инструмент отладки памяти для Windows и Linux. Он обнаруживает такие ошибки, как утечки памяти, незаконный доступ к памяти и неинициализированное чтение памяти.

GNU Electric Fence: Это инструмент отладки, который помогает обнаружить переполнения буфера и другие ошибки, связанные с памятью. Он использует технику под названием "защитные страницы" для защиты выделения памяти и обнаружения незаконных обращений.
std::unordered_map

std::unordered_map - это контейнерный класс, который предоставляет структуру данных, известную как хэш-карта или хэш-таблица
. Он доступен начиная с C++11.

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

std::unordered_map в качестве ключей поддерживает встроенные типы, типы, определяемые пользователем, и даже структуры или классы.

- В этом примере мы создаем myMap, которая сопоставляет целые числа со строками.
- С помощью функции insert() вставляются 3 пары ключ-значение.
- Затем мы получаем доступ и печатаем значение, связанное с ключом 2, используя оператор [].
- С помощью функции erase() удаляем значение с ключом 3.
- Далее мы выполняем итерации по карте с помощью цикла for, чтобы вывести все пары ключ-значение.
- Наконец, мы используем функцию find(), чтобы проверить, существует ли ключ 2 в карте.
#вопросы_с_собеседований
Как разработать систему плагинов на С++?

Разработка системы подключаемых модулей на C++ включает в себя создание структуры, позволяющей динамическую загрузку и обнаружение подключаемых модулей во время выполнения.
Ниже приведен обзор соответствующих шагов:

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

2. API для плагинов: Создайте API, который облегчает загрузку и управление подключаемыми модулями.

3. Динамическая загрузка библиотек: Используйте механизм динамической загрузки библиотек операционной системы для загрузки подключаемых модулей во время выполнения.

4. Обнаружение плагинов: Реализуйте механизм для обнаружения и регистрации доступных подключаемых модулей во время выполнения.

5. Жизненный цикл плагина: Определите жизненный цикл плагинов, включая инициализацию, настройку и очистку.

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

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

Стоит отметить, что разработка системы плагинов может быть сложной задачей, и есть существующие фреймворки и библиотеки, которые могут помочь упростить этот процесс. Некоторые популярные варианты в C++ включают Boost.Extension, Poco Foundation и Qt's Plugin System. Эти фреймворки предоставляют абстракции и инструменты для создания систем плагинов и могут сэкономить время и усилия разработчиков.

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

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

Существует несколько типов барьеров памяти, которые можно использовать в C++:

- Барьер приобретения: Гарантирует, что последующие операции с памятью не могут быть переупорядочены до барьера.

- Барьер освобождения: Гарантирует, что предшествующие операции с памятью не могут быть переупорядочены после барьера.

- Полный барьер памяти: Обеспечивает семантику как приобретения, так и освобождения.

- Барьер чтения-записи: Гарантирует, что предшествующие операции чтения не могут быть переупорядочены последующими операциями записи.

В C++ барьеры памяти обычно реализуются с помощью атомарных операций или примитивов синхронизации, предоставляемых языком, таких как std::atomic_thread_fence, std::atomic_signal_fence, или мьютексов и переменных состояния.
#вопросы_с_собеседований
Что такое CI/CD и какие преимущества дает разработчику?

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

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

Непрерывное развертывание (или Continuous Delivery) направлено на автоматизацию процесса развертывания программного обеспечения в производственных средах. Он включает в себя автоматизацию шагов, необходимых для упаковки, развертывания и настройки приложения.

Вот некоторые преимущества CI/CD для разработчиков C++:

1. Раннее обнаружение ошибок.
2. Более быстрый цикл обратной связи.
3. Качество и ремонтопригодность кода.
4. Сотрудничество и коммуникация между разработчиками.
5. Автоматизация сборки, тестирования и развертывания кода.
6. Непрерывное развертывание.
7. Масштабируемость.
std::conditional_variable

std::condition_variable является частью стандартной библиотеки и позволяет потоку ожидать наступления определенного условия перед продолжением выполнения.

Основной метод std::condition_variable - это wait(), который блокирует вызывающий поток до тех пор, пока другой поток не оповестит его с помощью метода notify_one() или notify_all(). Когда вызывается wait(), текущий поток освобождает блокировку, которая должна быть захвачена вместе с вызовом wait(), и ожидает, пока другой поток не вызовет один из методов оповещения. После получения оповещения поток пробуждается и пытается повторно захватить блокировку, и если блокировка успешно захватывается, поток может продолжить свою работу.

В этом примере создается поток workerThread, который ожидает сигнала от основного потока. Основной поток засыпает на 2 секунды, а затем устанавливает флаг ready в true и оповещает ожидающий поток с помощью notify_one(). workerThread пробуждается и выводит сообщение в консоль.
std::atomic_flag

std::atomic_flag является классом для реализации простой атомарной флаговой переменной. Он предоставляет механизм безопасной работы с флагом в многопоточной среде без необходимости использования блокировок.

std::atomic_flag имеет два основных метода: test_and_set() и clear():

Метод test_and_set() устанавливает флаг в "истину" и возвращает предыдущее значение флага. Если флаг уже был установлен, вызов test_and_set() вернет true, в противном случае он вернет false.
Метод clear() сбрасывает флаг в "ложь".

В этом примере создаются два потока workerThread1 и workerThread2, которые пытаются получить доступ к защищенному блоку кода. Флаг flag инициализируется значением true с помощью ATOMIC_FLAG_INIT. В цикле worker() каждый поток пытается вызвать test_and_set() для захвата флага. Если флаг уже установлен, поток ждет и повторно пытается его захватить. Когда флаг наконец захватывается, поток выполняет некоторую работу и вызывает clear() для освобождения флага.
thread-safe пул потоков

На первом изображении представлена реализация потокобезопасного пула потоков. В этой реализации класс ThreadPool управляет коллекцией рабочих потоков. Количество рабочих потоков определяется при создании пула потоков. Функция enqueue используется для передачи заданий в пул потоков.

На втором изображении представлен пример использования. функция Task передается в пул потоков с помощью функции enqueue. Функция Task просто печатает сообщение и "спит" в течение 1 секунды. После отправки всех заданий главный поток ждет некоторое время, чтобы дать заданиям завершиться перед выходом.

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

В C++ std::async - это шаблон функции, предоставляемый стандартной библиотекой <future>. Он используется для асинхронного выполнения функции или вызываемого объекта и получения объекта future, представляющего результат вычислений.

Функция std::async создает новый поток или использует существующий поток из пула потоков реализации для выполнения поставленной задачи. Она возвращает объект std::future, который можно использовать для получения результата или статуса вычисления.

В этом примере функция AddNumbers выполняется асинхронно с использованием std::async. Она принимает два целых числа в качестве аргументов и возвращает их сумму. Функция std::async вызывается с помощью AddNumbers и аргументов 5 и 10.
Функция std::async возвращает объект std::future<int>, который представляет собой результат вычисления. Вызывая функцию get() на объекте future, мы блокируем выполнение до завершения вычислений и получаем результат.
std::launch::async

std::launch::async - это один из флагов, которые можно использовать при запуске асинхронной операции с помощью std::async. Он указывает, что операция должна быть запущена немедленно в отдельном потоке.

При использовании std::launch::async с std::async создается новый поток выполнения для асинхронной операции. Это означает, что операция будет выполняться параллельно с основным потоком, который вызвал std::async. Фактическое время запуска операции зависит от реализации, но обычно она начинается сразу после вызова std::async.

В этом примере мы создаем асинхронную операцию с помощью std::async и флага std::launch::async. Операция calculateSum складывает два числа, но перед этим засыпает на 2 секунды для имитации длительной работы.

Затем мы вызываем futureSum.get(), чтобы получить результат асинхронной операции. Если результат еще не готов, основной поток будет блокирован до его завершения.
#вопросы_с_собеседований
Как размещается в памяти класс с множественным наследованием и виртуальными функциями в с++?

-
Каждый класс в иерархии наследования имеет свой собственный блок памяти, называемый подобъектом базового класса.
- Порядок размещения подобъектов определяется порядком, в котором они перечислены при определении класса-наследника.
- Для каждого класса-потомка, содержащего виртуальные функции, компилятор создает таблицу виртуальных функций, она содержит указатели на виртуальные функции класса.
- У каждого объекта класса, содержащего виртуальные функции, есть указатель на его таблицу виртуальных функций (VTable pointer).
- Размер класса равен сумме размеров его подобъектов базовых классов плюс любые дополнительные данные, определенные в самом классе.
- При вызове виртуальной функции компилятор использует указатель на VTable объекта, чтобы найти правильную функцию для вызова.

*Реализация размещения таких классов может отличаться в зависимости от компилятора и платформы, на которой выполняется код. Описанный выше процесс является общим принципом, но некоторые детали могут различаться.