Разработчик C/C++ (Профессиональный уровень)
Курс «Разработчик C/C++ (Профессиональный уровень)» — это углублённая практико-ориентированная программа, предназначенная для подготовки квалифицированных специалистов в области системного и прикладного программирования. Программа охватывает широкий спектр тем, начиная с продвинутых возможностей языков C и C++, и заканчивая разработкой производительных, отказоустойчивых и масштабируемых приложений.
Содержание программы:
- Углублённое изучение языка C: указатели, арифметика указателей, управление памятью (malloc/free), работа с файлами, макросы препроцессора, оптимизация кода.
- Язык C++: современные стандарты (C++11/14/17/20), RAII, move семантика, шаблоны, перегрузка операторов, исключения, SFINAE, Concepts.
- STL: контейнеры (vector, map, unordered_map, list и др.), алгоритмы (sort, find, transform и др.), итераторы, адаптеры.
- Шаблоны проектирования: Singleton, Factory, Strategy, Observer, Command, Template Method, Builder.
- Многопоточность: std::thread, std::mutex, std::atomic, async, packaged_task, futures, condition variables, thread pools.
- Работа с сокетами: сетевое программирование TCP/UDP, использование библиотек Boost.Asio и POSIX sockets.
- Разработка под ОС: Linux (POSIX API, сигналы, процессы, потоки), Windows API (работа с Win32, реестр, службы).
- Отладка и тестирование: GDB, Valgrind, AddressSanitizer, Unit-тестирование (Google Test, Catch2).
- Библиотеки: Boost (smart pointers, filesystem, optional, variant, any), собственные библиотеки (static/dynamic linking).
- Архитектура и оптимизация: работа с x86/x64, inline-ассемблер, интринсики, SIMD-инструкции, оптимизация под CPU pipeline.
- Инструменты сборки: CMake, Makefile, Conan, vcpkg.
- Проектная деятельность: разработка полноценного многопоточного сервера, реализация собственных библиотек, участие в командных проектах.
Ожидаемые результаты после прохождения курса:
-
Должен знать:
- Современные стандарты C и C++.
- Принципы объектно-ориентированного и обобщённого программирования.
- Архитектурные шаблоны и паттерны проектирования.
- Устройство операционных систем и принципы системного программирования.
- Структуры данных и алгоритмы, их эффективную реализацию на низком уровне.
- Принципы работы компиляторов, линкеров и загрузчиков.
- Механизмы отладки, профилирования и тестирования.
-
Должен уметь:
- Разрабатывать высокопроизводительные и надёжные приложения на C/C++.
- Использовать STL и Boost на профессиональном уровне.
- Писать чистый, безопасный и легко поддерживаемый код.
- Реализовывать многопоточные приложения с корректной синхронизацией.
- Выполнять оптимизацию по времени и памяти.
- Отлаживать сложные ошибки (memory leaks, race conditions, undefined behavior).
- Проектировать архитектуру ПО с применением известных паттернов.
- Работать в среде Linux и Windows, использовать системные вызовы и API.
- Создавать и использовать собственные библиотеки.
- Собирать проекты с помощью CMake и других современных инструментов.
Формат обучения:
Лекции с теоретическими примерами, практические занятия, домашние задания, регулярные мини-проекты и финальный проект, имитирующий реальную задачу из индустрии. Возможно участие в open-source проектах или командной разработке.
Хотите узнать, насколько вам необходим этот курс и действительно ли вы
разобрались в теме?
Пройдите короткий тест — он поможет определить, стоит ли углубляться в эту тему,
или вы уже готовы двигаться дальше.
1. Что такое RAII и как он применяется в C++?
RAII (Resource Acquisition Is Initialization) — это идиома языка C++, позволяющая управлять ресурсами (память, файлы, сокеты и т.д.) через конструкторы и деструкторы объектов. Ресурс захватывается в конструкторе и освобождается в деструкторе, что обеспечивает безопасное управление ресурсами даже при исключениях.
2. Как работают умные указатели в C++11 и какие их виды существуют?
Умные указатели автоматизируют управление памятью. В C++11 доступны `std::unique_ptr` (уникальное владение), `std::shared_ptr` (общее владение с подсчётом ссылок) и `std::weak_ptr` (неналичие владения, используется для разрыва циклических ссылок).
3. Чем отличаются std::vector и std::list в STL?
`std::vector` — динамический массив с произвольным доступом, эффективный для последовательных операций и добавления в конец. `std::list` — двусвязный список, эффективный для вставок и удалений в любом месте, но с медленным произвольным доступом.
4. Что такое шаблоны в C++ и как они реализуются?
Шаблоны позволяют создавать обобщённые функции и классы. Они инстанцируются компилятором при вызове с конкретными типами. Шаблоны могут быть типовыми (template<typename T>) или нетиповыми (template<int N>).
5. Как работает механизм наследования и полиморфизма в C++?
Наследование позволяет создавать новые классы на основе существующих. Полиморфизм реализуется через виртуальные функции и таблицы виртуальных функций (vtable), что позволяет вызывать методы производного класса через указатель/ссылку базового класса.
6. Что такое SFINAE и где он применяется?
SFINAE (Substitution Failure Is Not An Error) — правило, позволяющее игнорировать ошибки подстановки шаблонных аргументов. Применяется в метапрограммировании для выбора перегрузок на этапе компиляции.
7. Как использовать std::thread и std::mutex для многопоточности?
`std::thread` создаёт новый поток исполнения. `std::mutex` используется для защиты общих данных от одновременного доступа. Обычно применяются вместе с `std::lock_guard` или `std::unique_lock`.
8. Что такое deadlock и как его избежать?
Deadlock — ситуация, когда два и более потока ожидают друг друга и не могут продолжить выполнение. Избегается соблюдением порядка блокировок, использованием `std::lock`, таймаутов и lock-free структур.
9. Как работают futures и promises в C++?
`std::future` представляет результат асинхронной операции. `std::promise` задаёт этот результат. Используются для обмена данными между потоками, особенно в сочетании с `std::async`.
10. В чём разница между move-семантикой и копированием?
Move-семантика перемещает ресурсы из одного объекта в другой без глубокого копирования. Реализуется через rvalue-ссылки (`T&&`). Позволяет повысить производительность.
11. Что такое noexcept и зачем он нужен?
`noexcept` указывает, что функция не выбрасывает исключений. Улучшает оптимизации и помогает писать безопасный код, особенно в move-операциях.
12. Как использовать Boost.Asio для сетевого программирования?
Boost.Asio предоставляет API для асинхронного сетевого взаимодействия. Поддерживает TCP/UDP, SSL, сериализацию, асинхронные операции через callback'и или `std::future`.
13. Что такое inline-ассемблер и как он используется в C/C++?
Inline-ассемблер позволяет вставлять ассемблерный код прямо в C/C++. Применяется для низкоуровневой оптимизации, работы с регистрами CPU и системными ресурсами.
14. Как использовать SIMD-инструкции в C++?
SIMD (Single Instruction Multiple Data) позволяет выполнять одну операцию над множеством данных. Используется через интринсики (например, `_mm_add_ps`) или специальные библиотеки.
15. Что такое memory leak и как его обнаружить?
Memory leak — утечка памяти, возникает при неосвобождении выделенной памяти. Обнаруживается с помощью Valgrind, AddressSanitizer, LeakSanitizer.
16. Как работают исключения в C++?
Исключения позволяют обрабатывать ошибки вне нормального потока программы. При возникновении ошибки вызывается `throw`, затем поиск соответствующего `catch`. Используется стек вызовов.
17. Что такое placement new и когда он используется?
Placement new позволяет создавать объект в уже выделенной области памяти. Используется в случаях, когда требуется точный контроль над размещением объектов, например, в пулах памяти.
18. Как работает механизмы сборки проекта через CMake?
CMake — система генерации скриптов сборки. Она абстрагирует платформозависимые детали и позволяет строить проекты на разных ОС с помощью Makefile, Ninja, Visual Studio и др.
19. Какие существуют категории памяти в C/C++ и как они организованы?
Память делится на стек (локальные переменные), кучу (динамически выделенная память), статическую область (глобальные и static переменные), область кода (текстовый сегмент).
20. Что такое Undefined Behavior и почему он опасен?
Undefined Behavior — поведение программы, не определённое стандартом. Может привести к непредсказуемым результатам, включая сбои, некорректную работу, падения.
21. Как использовать Google Test для unit-тестирования?
Google Test — фреймворк для тестирования C++. Тесты пишутся с помощью макросов `TEST()`, `TEST_F()`. Поддерживает параметризованные тесты, SetUp/TearDown.
22. Что такое copy elision и RVO?
Copy elision — оптимизация, позволяющая исключить лишнее копирование объектов. Return Value Optimization (RVO) — частный случай, когда временный объект, возвращаемый из функции, уничтожается.
23. Как работают пользовательские литералы в C++11?
Пользовательские литералы позволяют определять собственные суффиксы для чисел, строк и т.д. Например: `123_km`, `1.5s`.
24. Как организовать межпроцессное взаимодействие в Linux?
Межпроцессное взаимодействие (IPC) может осуществляться через pipe, FIFO, сокеты, разделяемую память (`shmget`, `mmap`), семафоры, сигналы.
25. Как работает механизм виртуального наследования в C++?
Виртуальное наследование позволяет избежать повторного наследования базового класса в иерархии наследования. Используется ключевым словом `virtual`. Требует аккуратного управления инициализацией базового класса.
26. Что такое CRTP и как он используется в C++?
CRTP (Curiously Recurring Template Pattern) — это техника, при которой шаблонный класс наследуется от производного типа. Используется для статического полиморфизма, реализации Mixin-классов и оптимизации.
27. Как работают лямбда-выражения в C++ и чем они отличаются от обычных функций?
Лямбда-выражения — это анонимные функции, которые могут захватывать переменные из окружающей области видимости. Они компилируются в объекты функторов и часто используются в STL-алгоритмах.
28. В чём разница между std::function и указателями на функции?
`std::function` — это обобщённый контейнер для вызываемых объектов (функции, лямбды, функторы). Указатель на функцию ограничен конкретной сигнатурой и не поддерживает состояние.
29. Что такое пул памяти и как его реализовать на C++?
Пул памяти — это предварительно выделенный блок памяти, используемый для эффективного управления малыми объектами. Реализуется через перегрузку `operator new/delete` или собственные аллокаторы.
30. Как использовать пользовательские аллокаторы в STL-контейнерах?
STL позволяет передавать пользовательский аллокатор в качестве шаблонного параметра. Это даёт контроль над распределением памяти, что полезно для оптимизации и интеграции с системными буферами.
31. Что такое lock-free программирование и как оно реализуется в C++?
Lock-free программирование — это подход, позволяющий организовать доступ к разделяемым данным без использования мьютексов. Реализуется через `std::atomic` и специальные примитивы памяти.
32. Как работает механизм ADL (Argument-Dependent Lookup) в C++?
ADL — это правило поиска функций, при котором компилятор учитывает пространства имён, связанные с типами аргументов. Используется, например, при перегрузке операторов.
33. Что такое tag dispatching и где он применяется?
Tag dispatching — это техника выбора перегрузки функции на основе метаданных времени компиляции. Применяется в STL для оптимизации алгоритмов в зависимости от категории итератора.
34. Как использовать constexpr и consteval в C++20?
`constexpr` разрешает вычисления во время компиляции, если возможно. `consteval` гарантирует, что функция всегда вычисляется на этапе компиляции. Оба улучшают производительность и безопасность.
35. Что такое ABI и почему важно сохранять совместимость интерфейсов?
ABI (Application Binary Interface) — это соглашение о том, как данные и функции представлены в скомпилированном коде. Нарушение ABI приводит к ошибкам при использовании динамических библиотек.
36. Как реализовать singleton в многопоточной среде?
Singleton можно реализовать с помощью `std::call_once`, статической локальной переменной (C++11) или внешнего синхронизированного менеджера. Важно обеспечить потокобезопасную инициализацию.
37. Что такое PIMPL-идиома и зачем она нужна?
PIMPL (Pointer to IMPLementation) — это способ скрытия реализации класса через указатель на внутреннюю структуру. Позволяет уменьшить зависимости и время перекомпиляции.
38. Как использовать std::optional и когда он предпочтителен?
`std::optional` представляет значение, которое может отсутствовать. Предпочтителен вместо возврата NULL или использования исключений при частичной успешности операций.
39. Что такое coroutine и как он реализован в C++20?
Coroutine — это функция, которая может приостанавливать своё выполнение и продолжать позже. Поддерживается ключевыми словами `co_await`, `co_yield`, `co_return`.
40. Как работают итераторы в STL и какие категории существуют?
Итераторы предоставляют доступ к элементам контейнеров. Категории: input, output, forward, bidirectional, random access. Каждая категория поддерживает разные операции.
41. Что такое SBO (Small Buffer Optimization) и как он реализован в std::string?
SBO — это оптимизация, при которой небольшие строки хранятся внутри самого объекта `std::string`, избегая выделения памяти в куче.
42. Как использовать std::bind и placeholders в C++?
`std::bind` создаёт функтор с частично применёнными аргументами. `std::placeholders` (`_1`, `_2`) обозначают позиции аргументов при вызове.
43. Что такое type traits и как они применяются в метапрограммировании?
Type traits — это набор шаблонов, проверяющих или модифицирующих свойства типов. Используются в SFINAE, static_assert, enable_if и других конструкциях.
44. Как работают пользовательские операторы преобразования в C++?
Операторы преобразования (`operator int()`, `operator bool()`) позволяют явно или неявно преобразовывать объекты в другие типы. Неявное преобразование может быть опасным.
45. Что такое noexcept-спецификация и как она влияет на семантику move?
Если move-конструктор помечен как `noexcept`, то STL может выбрать его вместо copy-версии при операциях, требующих безопасности исключений.
46. Как реализовать собственный allocator для STL-контейнера?
Собственный аллокатор должен реализовать минимальный интерфейс: `allocate`, `deallocate`, `construct`, `destroy`. Также желательно поддерживать `rebind`.
47. Что такое alignment и как он контролируется в C++11?
Alignment — это требование к выравниванию памяти под определённый тип. Контролируется через `alignof`, `alignas` и функции `std::aligned_alloc`.
48. Как использовать std::shared_lock и std::unique_lock для защиты данных?
`std::shared_lock` позволяет множественному чтению, но эксклюзивную запись. `std::unique_lock` — полный контроль над блокировкой с возможностью отложенного захвата.
49. Что такое memory barrier и зачем он нужен в многопоточном программировании?
Memory barrier — это инструкция, ограничивающая порядок выполнения операций с памятью. Используется для предотвращения переупорядочивания и обеспечения согласованности.
50. Как работают итераторы и ссылки после реаллокации в std::vector?
После реаллокации (например, при `push_back`) все итераторы и ссылки на элементы `std::vector` становятся недействительными, так как массив перемещается в новую область памяти.
51. Что такое decltype и как он используется в C++?
`decltype` — это ключевое слово, позволяющее получить тип выражения во время компиляции. Часто применяется в шаблонах, auto-выводе и для создания возвращаемых типов функций.
52. Как работают и чем отличаются std::enable_if и std::conjunction в метапрограммировании?
`std::enable_if` используется для условного включения перегрузок через SFINAE. `std::conjunction` — логическое И нескольких булевых типажей (traits), часто применяется с Concepts.
53. В чём разница между lvalue и rvalue в C++?
Lvalue — выражение, имеющее имя и адрес, может стоять слева от присваивания. Rvalue — временные значения, которые могут быть только справа. Rvalue-ссылки (`T&&`) используются для move-семантики.
54. Как реализовать собственный intrusive контейнер?
Intrusive контейнеры требуют, чтобы элемент сам хранил информацию о своей позиции в структуре данных (например, указатели next/prev). Реализуется через наследование или встраивание узлов.
55. Что такое POD и почему он важен в C++?
POD (Plain Old Data) — типы, совместимые по layout с C-структурами. Не имеют виртуальных функций, пользовательских конструкторов и т.д. Важны для сериализации и взаимодействия с C API.
56. Как использовать std::visit и std::variant для обработки нескольких типов?
`std::visit` позволяет применить visitor к значению `std::variant`. Это даёт безопасный способ обработки всех возможных типов, хранящихся в variant.
57. Что такое alignment и как он влияет на производительность?
Alignment — требование выравнивания памяти под конкретный тип. Неправильное выравнивание может вызвать аппаратные исключения или замедлить доступ к данным.
58. Как работает механизм noexcept-оператора и когда он используется?
`noexcept(expr)` проверяет, может ли выражение `expr` выбросить исключение. Часто используется в type traits и static_assert для анализа безопасности кода.
59. Что такое ABI-совместимость динамических библиотек и как её сохранить?
ABI-совместимость гарантирует, что библиотека будет корректно работать с клиентским кодом. Сохраняется через фиксацию сигнатур, использование extern "C", стабильные интерфейсы и versioning.
60. Как реализовать thread-safe singleton с помощью Meyers' Singleton?
Meyers' Singleton использует статическую локальную переменную внутри функции: `static MyClass& instance() { static MyClass inst; return inst; }`. В C++11 гарантирует потокобезопасную инициализацию.
61. Что такое CRTP и как он используется для статического полиморфизма?
CRTP (Curiously Recurring Template Pattern) позволяет эмулировать полиморфизм без виртуальных функций. Тип наследуется от шаблонного базового класса, обеспечивая оптимизацию и контроль над поведением.
62. Как использовать std::source_location в C++20?
`std::source_location` предоставляет информацию о месте вызова функции (файл, строка, функция). Полезен для логгирования, тестирования и диагностики ошибок.
63. Что такое memory_order_seq_cst и почему он самый строгий?
`memory_order_seq_cst` гарантирует последовательную согласованность: все потоки видят одни и те же изменения в одном порядке. Обеспечивает максимальную предсказуемость, но меньшую производительность.
64. Как реализовать lock-free очередь на двух указателях?
Lock-free очередь может быть реализована с использованием атомарных операций и CAS (Compare-and-Swap). Основные проблемы — ABA problem и управление памятью.
65. Что такое tag dispatching и как он помогает в выборе перегрузок STL-алгоритмов?
Tag dispatching — техника, при которой выбирается версия функции на основе типа тэга (например, `std::random_access_iterator_tag`). Используется в STL для оптимизации алгоритмов.
66. Как использовать std::from_chars и std::to_chars вместо atoi/atof?
`std::from_chars` и `std::to_chars` — это безопасные и быстрые аналоги `atoi`, `atof`, не зависящие от локали и не выбрасывающие исключений. Предпочтительны в high-performance коде.
67. Что такое copy assignment и move assignment и как их правильно реализовать?
Copy assignment копирует содержимое другого объекта, move assignment перемещает его. Правильно реализуются через правило трёх/пяти, с учётом self-assignment и исключений.
68. Как работает механизмы RTTI в C++ и зачем нужен dynamic_cast?
RTTI (Run-Time Type Information) предоставляет информацию о типах во время выполнения. `dynamic_cast` используется для безопасного downcasting в иерархии наследования.
69. Что такое Undefined Behavior Sanitizer и как он помогает находить баги?
UBSan — инструмент, обнаруживающий undefined behavior во время выполнения. Поддерживает проверку на integer overflow, null pointer dereference, out-of-bounds access и другие.
70. Как организовать работу с mmap в Linux и зачем она нужна?
`mmap` позволяет отображать файлы или устройства в адресное пространство процесса. Используется для эффективного чтения/записи, разделяемой памяти между процессами.
71. Что такое coroutine frame и как он управляется в C++20?
Coroutine frame — это структура, хранящая состояние корутины (локальные переменные, точка приостановки). Управление frame'ом осуществляется через promise_type и handle.
72. Как использовать std::expected из предложения C++ (и Boost)?
`std::expected<T, E>` представляет результат успешной операции (`T`) или ошибку (`E`). Альтернатива исключениям, особенно в embedded системах и low-level коде.
73. Что такое strict aliasing и как он влияет на оптимизации?
Strict aliasing — правило, запрещающее обращаться к одному и тому же объекту через указатели разных типов. Нарушение приводит к UB и мешает оптимизациям.
74. Как реализовать кастомный logger с поддержкой уровней и вывода в разные источники?
Logger можно реализовать через абстрактный интерфейс с методами log(level, message). Поддерживаются sink'и (консоль, файл, syslog), фильтрация по уровням, форматирование.
75. Что такое ownership model в Rust и как он отличается от управления памятью в C++?
Ownership model — система владения данными, обеспечивающая безопасность памяти без сборщика мусора. В C++ аналогичный контроль достигается через умные указатели и RAII, но менее строго.
1. Какой из следующих методов управления памятью НЕ является частью RAII в C++?
A) Использование умных указателей
B) Вызов delete в деструкторе
C) Прямое использование malloc и free
D) Захват ресурса в конструкторе
Правильный ответ: C
2. Что означает ключевое слово noexcept в объявлении функции?
A) Функция не может изменять состояние объекта
B) Функция не выбрасывает исключений
C) Функция не возвращает значение
D) Функция не использует стек
Правильный ответ: B
3. Какой тип указателя позволяет множественное владение ресурсом с автоматическим освобождением?
A) std::unique_ptr
B) raw pointer
C) std::shared_ptr
D) void*
Правильный ответ: C
4. Что делает оператор static_cast?
A) Преобразует между указателями на иерархию классов с проверкой времени выполнения
B) Выполняет безопасное преобразование между числами и enum
C) Приводит указатель к типу void*
D) Приводит тип без проверок
Правильный ответ: B
5. Что такое SFINAE в контексте шаблонного программирования?
A) Ошибка компиляции при неверной подстановке типа
B) Механизм исключения шаблона из перегрузки при ошибке подстановки
C) Полная специализация шаблона
D) Стандартная библиотечная функция для проверки типов
Правильный ответ: B
6. Какой из следующих классов STL реализован как двусвязный список?
A) std::vector
B) std::deque
C) std::list
D) std::array
Правильный ответ: C
7. Что гарантирует memory_order_seq_cst в std::atomic?
A) Отсутствие переупорядочивания только внутри одного потока
B) Последовательную согласованность между всеми потоками
C) Упорядочивание только для чтения
D) Упорядочивание только для записи
Правильный ответ: B
8. Какой механизм позволяет создавать функции, которые могут приостанавливаться и продолжать выполнение?
A) Лямбда-выражения
B) Корутины
C) Шаблоны
D) Виртуальные функции
Правильный ответ: B
9. Что произойдет при вызове метода на нулевом указателе (nullptr)?
A) Компилятор выдаст ошибку
B) Вызов завершится успешно
C) Возникнет неопределенное поведение
D) Вызовется обработчик исключения
Правильный ответ: C
10. Какой из следующих вариантов НЕ является способом предотвращения deadlock'а?
A) Блокировка мьютексов в фиксированном порядке
B) Использование std::lock
C) Попытка захвата с таймаутом
D) Блокировка мьютексов в случайном порядке
Правильный ответ: D
11. Какой из следующих типов поддерживает move-семантику, но не допускает копирования?
A) std::shared_ptr
B) std::weak_ptr
C) std::unique_ptr
D) raw pointer
Правильный ответ: C
12. Что означает использование ключевого слова virtual при наследовании?
A) Класс становится абстрактным
B) Разрешено множественное наследование
C) Предотвращается повторное наследование базового класса
D) Активируется виртуальное наследование
Правильный ответ: D
13. Что такое Undefined Behavior в C++?
A) Исключение, выбрасываемое во время выполнения
B) Неопределенное значение переменной
C) Поведение программы, не предусмотренное стандартом
D) Ошибка компиляции
Правильный ответ: C
14. Какой механизм используется для реализации пользовательских литералов в C++11?
A) Перегрузка оператора ()
B) Перегрузка оператора ""_suf
C) Макросы препроцессора
D) Шаблоны с нетиповыми параметрами
Правильный ответ: B
15. Какая категория итераторов поддерживает арифметические операции и произвольный доступ?
A) Input Iterator
B) Forward Iterator
C) Bidirectional Iterator
D) Random Access Iterator
Правильный ответ: D
16. Какой из следующих методов std::future блокирует выполнение до получения результата?
A) valid()
B) wait_for()
C) get()
D) notify_all()
Правильный ответ: C
17. Что такое perfect forwarding в C++?
A) Передача временного объекта по значению
B) Передача аргументов без потери категории выражения
C) Явное преобразование к rvalue
D) Объединение нескольких функций в одну
Правильный ответ: B
18. Какой из следующих инструментов используется для обнаружения утечек памяти?
A) GDB
B) Valgrind
C) Makefile
D) Clang-Tidy
Правильный ответ: B
19. Что представляет собой std::variant?
A) Тип, который всегда содержит значение
B) Объединение, которое может содержать один из нескольких типов
C) Обёртка над std::any
D) Динамический массив
Правильный ответ: B
20. Каким образом можно передать лямбда-выражение в функцию?
A) Только через auto
B) Через указатель на функцию
C) Через std::function или как шаблонный параметр
D) Через ссылку
Правильный ответ: C
21. Что такое ABI?
A) Интерфейс между компонентами ядра
B) Соглашение о взаимодействии скомпилированных модулей
C) Формат исходного кода
D) Спецификация API
Правильный ответ: B
22. Какой тип данных лучше всего подходит для хранения строки фиксированного размера?
A) std::string
B) char[]
C) std::array<char, N>
D) std::vector<char>
Правильный ответ: C
23. Какой из следующих методов применяется для создания собственных аллокаторов в STL?
A) Перегрузка operator new/delete
B) Реализация интерфейса Allocator
C) Использование placement new
D) Все вышеперечисленные
Правильный ответ: D
24. Какой из следующих механизмов позволяет выполнять операции над несколькими элементами данных одновременно?
A) RTTI
B) SIMD
C) RTOS
D) STL
Правильный ответ: B
25. Что означает использование ключевого слова consteval в C++20?
A) Функция должна быть inline
B) Функция может быть выполнена только в потоке
C) Функция всегда вычисляется на этапе компиляции
D) Функция не может иметь параметров
Правильный ответ: C
26. Какой из следующих классов предоставляет интерфейс для работы с файлами в C++?
A) std::fstream
B) std::stringstream
C) std::vector
D) std::thread
Правильный ответ: A
27. Что означает ключевое слово explicit при определении конструктора?
A) Конструктор может использоваться только внутри класса
B) Запрещено неявное преобразование типа
C) Конструктор является статическим
D) Конструктор может быть вызван только один раз
Правильный ответ: B
28. Какой тип итератора используется в std::map для обеспечения упорядоченного доступа?
A) Random Access Iterator
B) Bidirectional Iterator
C) Forward Iterator
D) Input Iterator
Правильный ответ: B
29. Какая функция STL позволяет выполнить сортировку диапазона по возрастанию?
A) std::find
B) std::copy
C) std::sort
D) std::transform
Правильный ответ: C
30. Что представляет собой std::weak_ptr?
A) Указатель, владеющий ресурсом
B) Указатель, не увеличивающий счётчик ссылок на объект
C) Сырой указатель
D) Указатель с автоматическим удалением
Правильный ответ: B
31. Какой из следующих методов std::atomic позволяет изменить значение атомарно с гарантией упорядочивания?
A) load()
B) store()
C) exchange()
D) compare_exchange_strong()
Правильный ответ: D
32. Что такое placement new?
A) Оператор, выделяющий память в куче
B) Оператор, создающий объект в уже выделенной памяти
C) Оператор, освобождающий память
D) Оператор, создающий массив
Правильный ответ: B
33. Каким образом можно получить информацию о типе во время выполнения в C++?
A) С помощью sizeof
B) С помощью typeid
C) С помощью decltype
D) С помощью static_cast
Правильный ответ: B
34. Какой из следующих вариантов НЕ является способом передачи параметров в шаблон?
A) Типовой параметр
B) Целочисленный параметр
C) Функциональный параметр
D) Шаблонный параметр-шаблона
Правильный ответ: C
35. Что делает директива #pragma once?
A) Указывает компилятору использовать оптимизацию
B) Включает предупреждения компилятора
C) Гарантирует однократное включение заголовочного файла
D) Отключает стандартные заголовки
Правильный ответ: C
36. Какой механизм используется в C++ для реализации пользовательских операторов сравнения?
A) Перегрузка оператора ==
B) Перегрузка operator<=> (three-way comparison)
C) Использование std::equal
D) Все вышеперечисленное
Правильный ответ: D
37. Какой из следующих контейнеров имеет постоянное время вставки и удаления в середине?
A) std::vector
B) std::deque
C) std::list
D) std::array
Правильный ответ: C
38. Что происходит при выбросе исключения из деструктора?
A) Исключение обрабатывается нормально
B) Программа продолжает выполнение
C) Вызывается std::terminate
D) Исключение игнорируется
Правильный ответ: C
39. Какой из следующих методов позволяет получить указатель на внутренний буфер std::string?
A) data()
B) c_str()
C) begin()
D) Все вышеперечисленное
Правильный ответ: D
40. Что означает ключевое слово mutable в объявлении члена класса?
A) Член нельзя изменять
B) Член может изменяться даже в const-методах
C) Член является статическим
D) Член может быть перегружен
Правильный ответ: B
41. Какой из следующих инструментов используется для профилирования производительности программы?
A) GDB
B) Valgrind с плагином Callgrind
C) Makefile
D) Clang-Tidy
Правильный ответ: B
42. Какой тип данных в C++ предназначен для хранения значения любого типа?
A) std::variant
B) std::any
C) void*
D) union
Правильный ответ: B
43. Что означает использование ключевого слова inline для переменной в C++17?
A) Переменная будет оптимизирована компилятором
B) Переменная может быть определена в нескольких единицах трансляции
C) Переменная будет находиться в регистре процессора
D) Переменная не занимает память
Правильный ответ: B
44. Какой из следующих методов используется для проверки, является ли итератор допустимым?
A) it != end()
B) it.valid()
C) it.is_valid()
D) Такого метода нет в стандартной библиотеке
Правильный ответ: D
45. Что представляет собой std::function?
A) Обёртка над указателем на функцию
B) Универсальный контейнер для вызываемых объектов
C) Специализация шаблона для функций
D) Макрос препроцессора
Правильный ответ: B
46. Какой из следующих элементов позволяет создать собственный аллокатор для std::vector?
A) Передача аллокатора как шаблонного параметра
B) Изменение capacity вектора
C) Вызов reserve()
D) Использование raw pointer
Правильный ответ: A
47. Какой из следующих вариантов представляет собой пример использования CRTP?
A) class Derived : public Base {}
B) template<typename T> class Mixin {}
C) class MyType : public enable_shared_from_this<MyType> {}
D) template<typename T> class Base {}; class Derived : public Base<Derived> {}
Правильный ответ: D
48. Какой из следующих флагов компиляции используется для включения поддержки C++17?
A) -std=c++11
B) -std=c++14
C) -std=c++17
D) -std=c++20
Правильный ответ: C
49. Что делает директива #include в коде на C++?
A) Компилирует указанный файл
B) Подключает заголовочный файл и включает его содержимое
C) Создаёт экземпляр класса
D) Указывает точку входа программы
Правильный ответ: B
50. Какой из следующих типов позволяет выполнять условную компиляцию на основе свойств типов?
A) std::is_integral
B) std::enable_if
C) std::conditional
D) Все вышеперечисленные
Правильный ответ: D
51. Какой из следующих механизмов позволяет реализовать статическую диспетчеризацию вызова функции?
A) Виртуальные функции
B) Шаблоны с CRTP
C) Указатели на функции
D) std::function
Правильный ответ: B
52. Что означает использование ключевого слова constexpr в объявлении функции?
A) Функция может быть выполнена только в отладочной сборке
B) Функция всегда вычисляется во время компиляции, если аргументы известны
C) Функция не может иметь возвращаемого значения
D) Функция должна быть шаблонной
Правильный ответ: B
53. Какое утверждение верно относительно std::vector при увеличении ёмкости?
A) Все итераторы остаются валидными
B) Все ссылки на элементы остаются валидными
C) При реаллокации все указатели, итераторы и ссылки становятся недействительными
D) Ёмкость никогда не увеличивается автоматически
Правильный ответ: C
54. Какой из следующих методов используется для явного указания, что класс не должен иметь определённых функций по умолчанию?
A) = default
B) = delete
C) noexcept
D) explicit
Правильный ответ: B
55. Какой тип исключений можно перехватить с помощью catch(...) без указания типа?
A) Только std::exception
B) Только пользовательские исключения
C) Все типы исключений
D) Только системные ошибки
Правильный ответ: C
56. Каким образом можно получить доступ к внутреннему буферу std::vector?
A) С помощью метода data()
B) С помощью метода buffer()
C) С помощью метода get_data()
D) Напрямую через оператор []
Правильный ответ: A
57. Какой из следующих контейнеров имеет O(1) сложность удаления из середины при наличии итератора?
A) std::vector
B) std::deque
C) std::list
D) std::array
Правильный ответ: C
58. Что такое alignment в контексте управления памятью?
A) Размер объекта в байтах
B) Требование к расположению адреса в памяти
C) Порядок хранения данных в массиве
D) Способ выделения памяти через malloc
Правильный ответ: B
59. Какой из следующих вариантов гарантирует потокобезопасную инициализацию статической переменной?
A) Использование std::mutex
B) Объявление статической локальной переменной (C++11)
C) Использование std::call_once
D) Все вышеперечисленное
Правильный ответ: D
60. Что представляет собой std::make_shared?
A) Макрос препроцессора
B) Функцию, создающую std::shared_ptr
C) Класс-фабрику
D) Оператор new с дополнительной проверкой
Правильный ответ: B
61. Какой из следующих инструментов лучше всего подходит для анализа производительности многопоточных программ?
A) GDB
B) Valgrind с Helgrind
C) Clang-Tidy
D) cppcheck
Правильный ответ: B
62. Какое утверждение неверно для std::weak_ptr?
A) Может быть преобразован в std::shared_ptr
B) Не увеличивает счётчик сильных ссылок
C) Может быть разыменован напрямую
D) Используется для предотвращения циклических ссылок
Правильный ответ: C
63. Какой из следующих методов позволяет изменить ёмкость контейнера std::vector?
A) size()
B) resize()
C) reserve()
D) shrink_to_fit()
Правильный ответ: C
64. Что означает использование ключевого слова final в объявлении класса?
A) Класс не может содержать виртуальных функций
B) Класс не может быть скопирован
C) Класс не может использоваться в выражениях
D) Класс не может быть унаследован
Правильный ответ: D
65. Какой из следующих параметров memory_order обеспечивает минимальную синхронизацию между потоками?
A) memory_order_seq_cst
B) memory_order_acquire
C) memory_order_relaxed
D) memory_order_release
Правильный ответ: C
66. Какое утверждение верно относительно std::async?
A) Всегда запускает задачу в новом потоке
B) Может использовать deferred или async политику запуска
C) Не поддерживает возврат значения через future
D) Не является частью стандартной библиотеки
Правильный ответ: B
67. Что делает директива #define в коде на C++?
A) Выполняет условную компиляцию
B) Создаёт экземпляр класса
C) Определяет макрос препроцессора
D) Указывает точку входа программы
Правильный ответ: C
68. Какой из следующих механизмов используется для проверки совместимости типов на этапе компиляции?
A) static_cast
B) dynamic_cast
C) static_assert
D) reinterpret_cast
Правильный ответ: C
69. Какой из следующих типов позволяет хранить значение одного из нескольких заданных типов?
A) std::any
B) std::tuple
C) std::variant
D) union
Правильный ответ: C
70. Какой из следующих методов используется для получения размера файла при работе с ifstream?
A) tellg()
B) seekg()
C) good()
D) is_open()
Правильный ответ: A
71. Что представляет собой std::move?
A) Функцию, перемещающую данные между контейнерами
B) Статический_cast в rvalue-ссылку
C) Функцию, освобождающую память
D) Макрос препроцессора
Правильный ответ: B
72. Какой из следующих механизмов используется для создания пользовательских литералов?
A) Перегрузка operator "" _name
B) Использование constexpr
C) Шаблоны с нетиповыми параметрами
D) Перегрузка оператора ()
Правильный ответ: A
73. Какой из следующих методов позволяет проверить, содержит ли std::future значение?
A) valid()
B) wait()
C) get()
D) notify_all()
Правильный ответ: A
74. Что означает использование ключевого слова thread_local?
A) Переменная будет общая для всех потоков
B) Переменная будет уникальной для каждого потока
C) Переменная будет статической
D) Переменная будет автоматически синхронизироваться
Правильный ответ: B
75. Какой из следующих механизмов позволяет выполнять обобщённое программирование в C++?
A) Перегрузка функций
B) Шаблоны
C) Пространства имён
D) Константные выражения
Правильный ответ: B
Экзаменационный билет №1
Теория
- Что такое RAII и как он применяется в C++?
- Какие виды умных указателей доступны в C++11 и в чём их отличие?
Практика
Напишите класс DynamicArray, который хранит динамический массив целых чисел. Реализуйте:
- Конструктор по умолчанию
- Конструктор с параметром размера
- Деструктор
- Move-семантику
- Метод push_back(int value)
- Оператор [] для доступа к элементу
Ответы:
Теория
- RAII (Resource Acquisition Is Initialization) — это идиома, при которой ресурсы захватываются в конструкторе объекта и освобождаются в деструкторе. Это позволяет автоматически управлять ресурсами и обеспечивает безопасность при исключениях.
- В C++11 доступны три типа умных указателей:
- std::unique_ptr — владение одним указателем, не допускает копирования.
- std::shared_ptr — разделяемое владение через счётчик ссылок.
- std::weak_ptr — наблюдатель за shared_ptr, не увеличивает счётчик.
Практика
#include <iostream>
class DynamicArray {
private:
int* data;
size_t capacity;
size_t size;
public:
DynamicArray() : data(nullptr), capacity(0), size(0) {}
explicit DynamicArray(size_t initialSize)
: data(new int[initialSize]), capacity(initialSize), size(0) {}
~DynamicArray() { delete[] data; }
DynamicArray(const DynamicArray&) = delete;
DynamicArray& operator=(const DynamicArray&) = delete;
DynamicArray(DynamicArray&& other) noexcept
: data(other.data), capacity(other.capacity), size(other.size) {
other.data = nullptr;
other.capacity = 0;
other.size = 0;
}
DynamicArray& operator=(DynamicArray&& other) noexcept {
if (this != &other) {
delete[] data;
data = other.data;
capacity = other.capacity;
size = other.size;
other.data = nullptr;
other.capacity = 0;
other.size = 0;
}
return *this;
}
void push_back(int value) {
if (size >= capacity) {
size_t newCapacity = capacity == 0 ? 1 : capacity * 2;
int* newData = new int[newCapacity];
for (size_t i = 0; i < size; ++i)
newData[i] = data[i];
delete[] data;
data = newData;
capacity = newCapacity;
}
data[size++] = value;
}
int& operator[](size_t index) {
return data[index];
}
size_t getSize() const { return size; }
};
Экзаменационный билет №2
Теория
- Что такое SFINAE и где он используется в шаблонном программировании?
- Как работает механизм виртуальных функций в C++?
Практика
Напишите шаблонную функцию findMax, которая принимает два значения одного типа и возвращает большее из них. Функция должна работать только с типами, поддерживающими оператор <.
Ответы:
Теория
- SFINAE (Substitution Failure Is Not An Error) — правило, позволяющее игнорировать ошибки подстановки типов в шаблонах без генерации ошибок компиляции. Используется для выбора наиболее подходящей версии шаблонной функции или класса.
- Виртуальные функции реализуются через таблицу виртуальных функций (vtable), которая хранится в каждом объекте класса с виртуальными методами. При вызове виртуальной функции происходит динамическое связывание на основе указателя на vtable.
Практика
#include <iostream>
#include <type_traits>
template <typename T>
T findMax(T a, T b) {
static_assert(std::is_less_than_comparable_v<T>, "Type must support operator <");
return (a < b) ? b : a;
}
int main() {
std::cout << findMax(5, 10) << std::endl;
std::cout << findMax(3.14, 2.71) << std::endl;
return 0;
}
Экзаменационный билет №3
Теория
- Что такое move-семантика и как она реализуется в C++?
- Чем отличаются std::vector и std::list в STL?
Практика
Напишите функцию splitString, принимающую строку и разделитель, и возвращающую вектор подстрок, разделённых этим символом. Используйте стандартные библиотеки.
Ответы:
Теория
- Move-семантика позволяет перемещать ресурсы из одного объекта в другой вместо копирования. Реализуется через rvalue-ссылки (T&&) и специальные методы move-конструктора и move-оператора присваивания.
- std::vector — динамический массив с произвольным доступом, эффективный для последовательных операций и добавления в конец. std::list — двусвязный список, эффективный для вставок и удалений в любом месте, но с медленным произвольным доступом.
Практика
#include <iostream>
#include <sstream>
#include <vector>
#include <string>
std::vector<std::string> splitString(const std::string& str, char delimiter) {
std::vector<std::string> tokens;
std::stringstream ss(str);
std::string token;
while (std::getline(ss, token, delimiter)) {
tokens.push_back(token);
}
return tokens;
}
int main() {
auto result = splitString("hello,world,test", ',');
for (const auto& s : result)
std::cout << s << std::endl;
return 0;
}
Экзаменационный билет №4
Теория
- Что такое memory_order в std::atomic и какие значения он может принимать?
- Как использовать std::thread и std::mutex для многопоточности?
Практика
Напишите простой многопоточный счетчик, использующий 4 потока. Каждый поток должен инкрементировать общую переменную 1000 раз. Используйте std::mutex для защиты данных.
Ответы:
Теория
- memory_order задаёт уровень упорядоченности доступа к памяти: memory_order_relaxed, memory_order_consume, memory_order_acquire, memory_order_release, memory_order_acq_rel, memory_order_seq_cst.
- std::thread создаёт новый поток исполнения. std::mutex используется для защиты общих данных от одновременного доступа. Обычно применяются вместе с std::lock_guard или std::unique_lock.
Практика
#include <iostream>
#include <thread>
#include <mutex>
#include <vector>
std::mutex mtx;
int counter = 0;
void increment() {
for (int i = 0; i < 1000; ++i) {
std::lock_guard<std::mutex> lock(mtx);
++counter;
}
}
int main() {
std::vector<std::thread> threads;
for (int i = 0; i < 4; ++i)
threads.emplace_back(increment);
for (auto& t : threads)
t.join();
std::cout << "Counter: " << counter << std::endl;
return 0;
}
Экзаменационный билет №5
Теория
- Что такое пользовательские литералы в C++11 и как они применяются?
- Как работают futures и promises в C++?
Практика
Напишите функцию, которая запускает асинхронное вычисление суммы двух чисел и возвращает результат через std::future.
Ответы:
Теория
- Пользовательские литералы позволяют определять собственные суффиксы для чисел, строк и т.д., например: 123_km, "hello"s. Это упрощает работу с доменными типами.
- std::future представляет результат асинхронной операции, а std::promise задаёт этот результат. Они обеспечивают обмен данными между потоками, особенно в сочетании с std::async.
Практика
#include <iostream>
#include <future>
#include <thread>
int asyncSum(int a, int b) {
return a + b;
}
int main() {
std::future<int> fut = std::async(std::launch::async, asyncSum, 10, 20);
std::cout << "Result: " << fut.get() << std::endl;
return 0;
}
Экзаменационный билет №6
Теория
- Что такое placement new и когда он используется?
- Как работают исключения в C++ и чем они отличаются от кодов возврата?
Практика
Напишите функцию, которая принимает указатель на массив целых чисел и его размер, и возвращает указатель на динамически выделенный массив, содержащий только чётные элементы исходного.
Ответы:
Теория
- Placement new позволяет создавать объект в уже выделенной области памяти. Применяется в пулах памяти, контейнерах с контролем размещения и других случаях, где требуется точный контроль.
- Исключения позволяют отделить обработку ошибок от основного потока выполнения. Они более выразительны, но могут быть дороже по производительности. Коды возврата требуют явной проверки, но дешевле по ресурсам.
Практика
#include <iostream>
int* extractEven(const int* arr, size_t size, size_t& outSize) {
outSize = 0;
for (size_t i = 0; i < size; ++i)
if (arr[i] % 2 == 0)
++outSize;
int* result = new int[outSize];
size_t idx = 0;
for (size_t i = 0; i < size; ++i)
if (arr[i] % 2 == 0)
result[idx++] = arr[i];
return result;
}
int main() {
size_t size = 8;
int arr[] = {1, 2, 3, 4, 5, 6, 7, 8};
size_t outSize;
int* evenArr = extractEven(arr, size, outSize);
for (size_t i = 0; i < outSize; ++i)
std::cout << evenArr[i] << " ";
std::cout << std::endl;
delete[] evenArr;
return 0;
}
Экзаменационный билет №7
Теория
- Что такое memory leak и как его обнаружить и исправить?
- Как организовать межпроцессное взаимодействие в Linux?
Практика
Реализуйте простой TCP-сервер на базе библиотеки Boost.Asio, который при подключении клиента отправляет ему строку "Hello from server!".
Ответы:
Теория
- Memory leak — утечка памяти, возникает при неосвобождении выделенной памяти. Обнаруживается с помощью Valgrind, AddressSanitizer, LeakSanitizer.
- Межпроцессное взаимодействие (IPC) в Linux реализуется через pipe, FIFO, сокеты, разделяемую память (shmget, mmap), семафоры и сигналы.
Практика
#include <boost/asio.hpp>
#include <iostream>
int main() {
try {
boost::asio::io_context io;
boost::asio::ip::tcp::acceptor acceptor(io, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), 12345));
std::cout << "Server is running...\n";
while (true) {
boost::asio::ip::tcp::socket socket(io);
acceptor.accept(socket);
std::cout << "Client connected.\n";
std::string message = "Hello from server!\n";
boost::asio::write(socket, boost::asio::buffer(message));
}
} catch (std::exception& e) {
std::cerr << "Error: " << e.what() << std::endl;
}
return 0;
}
Экзаменационный билет №8
Теория
- Что такое virtual inheritance и как он влияет на иерархию классов?
- Как использовать std::variant и std::any в C++17?
Практика
Напишите шаблонную функцию printType, которая принимает параметр любого типа из заданного набора (int, double, std::string) и выводит его значение на экран.
Ответы:
Теория
- Virtual inheritance используется для предотвращения множественного наследования базового класса. Позволяет иметь один экземпляр базового класса в иерархии, но усложняет инициализацию.
- std::variant представляет тип-объединение, который может содержать значение одного из заданных типов. std::any — универсальный контейнер, способный хранить любой тип.
Практика
#include <iostream>
#include <variant>
#include <string>
void printType(const std::variant<int, double, std::string>& value) {
std::visit([](const auto& v) { std::cout << v << std::endl; }, value);
}
int main() {
printType(42);
printType(3.14);
printType(std::string("Hello"));
return 0;
}
Экзаменационный билет №9
Теория
- Что такое perfect forwarding и как он реализован в C++?
- Как работают пользовательские операторы преобразования в C++?
Практика
Реализуйте простой собственный аллокатор для std::vector, который использует статический буфер фиксированного размера.
Ответы:
Теория
- Perfect forwarding позволяет передавать аргументы функции без потери информации о ссылках и категориях выражений. Реализуется с помощью шаблонов и std::forward.
- Пользовательские операторы преобразования (operator int(), operator bool()) позволяют явно или неявно преобразовывать объекты в другие типы. Неявное преобразование может быть опасным.
Практика
#include <iostream>
#include <vector>
#include <memory>
template<typename T, size_t N>
class StaticAllocator {
public:
using value_type = T;
StaticAllocator() = default;
template<typename U>
StaticAllocator(const StaticAllocator<U, N>&) noexcept {}
T* allocate(size_t n) {
if (n > N)
throw std::bad_alloc();
return reinterpret_cast<T*>(buffer);
}
void deallocate(T*, size_t) noexcept {}
private:
alignas(T) char buffer[N * sizeof(T)];
};
int main() {
StaticAllocator<int, 10> alloc;
std::vector<int, StaticAllocator<int, 10>> vec(alloc);
for (int i = 0; i < 10; ++i)
vec.push_back(i);
for (int v : vec)
std::cout << v << " ";
std::cout << std::endl;
return 0;
}
Экзаменационный билет №10
Теория
- Что такое ABI и почему важно сохранять совместимость интерфейсов?
- Как использовать std::shared_lock и std::unique_lock для защиты данных?
Практика
Напишите многопоточную программу, в которой один поток записывает данные в очередь, а другой — считывает и выводит их.
Ответы:
Теория
- ABI (Application Binary Interface) — соглашение о том, как данные и функции представлены в скомпилированном коде. Нарушение ABI приводит к ошибкам при использовании динамических библиотек.
- std::shared_lock позволяет множественному чтению, но эксклюзивную запись. std::unique_lock предоставляет больше гибкости: поддерживает отложенную блокировку, попытки захвата и передачу владения мьютексом.
Практика
#include <iostream>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>
std::queue<int> dataQueue;
std::mutex mtx;
std::condition_variable cv;
bool done = false;
void producer() {
for (int i = 0; i < 10; ++i) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
std::lock_guard<std::mutex> lock(mtx);
dataQueue.push(i);
cv.notify_one();
}
done = true;
cv.notify_all();
}
void consumer() {
while (true) {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, []{ return !dataQueue.empty() || done; });
if (dataQueue.empty() && done)
break;
while (!dataQueue.empty()) {
std::cout << "Received: " << dataQueue.front() << std::endl;
dataQueue.pop();
}
}
}
int main() {
std::thread t1(producer);
std::thread t2(consumer);
t1.join();
t2.join();
return 0;
}
Экзаменационный билет №11
Теория
- Что такое lock-free программирование и как оно реализуется в C++?
- Как использовать пользовательские аллокаторы в STL-контейнерах?
Практика
Напишите простую lock-free очередь на базе std::atomic с поддержкой одного producer и одного consumer.
Ответы:
Теория
- Lock-free программирование — это подход, позволяющий организовать доступ к разделяемым данным без использования мьютексов. Реализуется через std::atomic и специальные примитивы памяти.
- STL позволяет передавать пользовательский аллокатор в качестве шаблонного параметра. Это даёт контроль над распределением памяти, что полезно для оптимизации и интеграции с системными буферами.
Практика
#include <iostream>
#include <atomic>
#include <thread>
template<typename T, size_t Size>
class LockFreeQueue {
public:
LockFreeQueue() : head(0), tail(0) {}
bool push(const T& value) {
size_t next_tail = (tail + 1) % Size;
if (next_tail == head)
return false; // очередь заполнена
buffer[tail] = value;
tail = next_tail;
return true;
}
bool pop(T& value) {
if (head == tail)
return false; // очередь пуста
value = buffer[head];
head = (head + 1) % Size;
return true;
}
private:
T buffer[Size];
std::atomic<size_t> head;
std::atomic<size_t> tail;
};
int main() {
LockFreeQueue<int, 16> queue;
std::thread producer([&] {
for (int i = 0; i < 10; ++i)
while (!queue.push(i))
std::this_thread::yield();
});
std::thread consumer([&] {
int value;
for (int i = 0; i < 10; ++i) {
while (!queue.pop(value))
std::this_thread::yield();
std::cout << "Consumed: " << value << std::endl;
}
});
producer.join();
consumer.join();
return 0;
}
Экзаменационный билет №12
Теория
- Что такое ADL (Argument-Dependent Lookup) и где он применяется?
- Как работают пользовательские литералы в C++11?
Практика
Реализуйте собственный тип с перегруженным оператором вывода в поток и пользовательским литералом _kg.
Ответы:
Теория
- ADL — это правило поиска функций, при котором компилятор учитывает пространства имён, связанные с типами аргументов. Используется, например, при перегрузке операторов.
- Пользовательские литералы позволяют определять собственные суффиксы для чисел, строк и т.д.
Практика
#include <iostream>
#include <string>
struct Weight {
double kg;
};
std::ostream& operator<<(std::ostream& os, const Weight& w) {
return os << w.kg << " kg";
}
Weight operator"" _kg(long double value) {
return Weight{ static_cast<double>(value) };
}
int main() {
Weight w = 75.5_kg;
std::cout << w << std::endl;
return 0;
}
Экзаменационный билет №13
Теория
- Что такое PIMPL-идиома и зачем она нужна?
- Как использовать std::optional и когда он предпочтителен?
Практика
Реализуйте класс Person с использованием PIMPL-идиомы, который хранит имя и возраст.
Ответы:
Теория
- PIMPL (Pointer to IMPLementation) — это способ скрытия реализации класса через указатель на внутреннюю структуру. Позволяет уменьшить зависимости и время перекомпиляции.
- std::optional представляет значение, которое может отсутствовать. Предпочтителен вместо возврата NULL или использования исключений при частичной успешности операций.
Практика
// person.h
#pragma once
#include <string>
#include <memory>
class Person {
public:
Person(const std::string& name, int age);
~Person();
void print() const;
private:
struct Impl;
std::unique_ptr<Impl> pimpl;
};
// person.cpp
#include "person.h"
#include <iostream>
struct Person::Impl {
std::string name;
int age;
Impl(const std::string& n, int a) : name(n), age(a) {}
};
Person::Person(const std::string& name, int age)
: pimpl(std::make_unique<Impl>(name, age)) {}
Person::~Person() = default;
void Person::print() const {
std::cout << "Name: " << pimpl->name << ", Age: " << pimpl->age << std::endl;
}
// main.cpp
#include "person.h"
int main() {
Person p("Alice", 30);
p.print();
return 0;
}
Экзаменационный билет №14
Теория
- Что такое tag dispatching и где он применяется?
- Как работают итераторы в STL и какие категории существуют?
Практика
Реализуйте собственный итератор для массива фиксированного размера.
Ответы:
Теория
- Tag dispatching — техника выбора перегрузки функции на основе метаданных времени компиляции. Применяется в STL для оптимизации алгоритмов в зависимости от категории итератора.
- Итераторы предоставляют доступ к элементам контейнеров. Категории: input, output, forward, bidirectional, random access.
Практика
#include <iostream>
template<typename T, size_t N>
class FixedArray {
public:
T data[N];
class iterator {
public:
using value_type = T;
using pointer = T*;
using reference = T&;
iterator(pointer ptr) : m_ptr(ptr) {}
reference operator*() const { return *m_ptr; }
pointer operator->() { return m_ptr; }
iterator& operator++() { m_ptr++; return *this; }
iterator operator++(int) { iterator tmp = *this; ++(*this); return tmp; }
iterator& operator--() { m_ptr--; return *this; }
iterator operator--(int) { iterator tmp = *this; --(*this); return tmp; }
iterator operator+(size_t offset) const { return iterator(m_ptr + offset); }
iterator operator-(size_t offset) const { return iterator(m_ptr - offset); }
bool operator==(const iterator& other) const { return m_ptr == other.m_ptr; }
bool operator!=(const iterator& other) const { return !(*this == other); }
private:
pointer m_ptr;
};
iterator begin() { return iterator(data); }
iterator end() { return iterator(data + N); }
};
int main() {
FixedArray<int, 5> arr = {{1, 2, 3, 4, 5}};
for (auto it = arr.begin(); it != arr.end(); ++it)
std::cout << *it << " ";
std::cout << std::endl;
return 0;
}
Экзаменационный билет №15
Теория
- Что такое CRTP и как он используется в C++?
- Как работают и чем отличаются lvalue и rvalue в C++?
Практика
Реализуйте шаблонный класс Counter, который считает количество созданных, удалённых и активных объектов типа T.
Ответы:
Теория
- CRTP (Curiously Recurring Template Pattern) — это техника, при которой шаблонный класс наследуется от производного типа. Используется для статического полиморфизма, реализации Mixin-классов и оптимизации.
- Lvalue — выражение, имеющее имя и адрес, может стоять слева от присваивания. Rvalue — временные значения, которые могут быть только справа. Rvalue-ссылки (T&&) используются для move-семантики.
Практика
#include <iostream>
template<typename T>
class Counter {
public:
Counter() { ++created; ++active; }
Counter(const Counter&) { ++created; ++active; }
Counter(Counter&&) noexcept { ++created; ++active; }
~Counter() { --active; }
static size_t createdCount() { return created; }
static size_t activeCount() { return active; }
private:
static size_t created;
static size_t active;
};
template<typename T> size_t Counter<T>::created = 0;
template<typename T> size_t Counter<T>::active = 0;
struct MyType {};
using MyCounter = Counter<MyType>;
int main() {
{
MyCounter a, b;
MyCounter c = std::move(b);
}
std::cout << "Created: " << MyCounter::createdCount() << std::endl;
std::cout << "Active: " << MyCounter::activeCount() << std::endl;
return 0;
}
(1) Кейс: "Высоконагруженный TCP-сервер на C++"
Описание ситуации
Вы работаете в команде разработки, которая занимается созданием высокопроизводительного сервера обработки данных. Ваша задача — реализовать TCP-сервер на C++, способный одновременно обслуживать сотни клиентов, принимать от них данные и сохранять их в общий буфер для дальнейшей обработки.
Вам предоставлен начальный прототип кода:
#include <iostream>
#include <thread>
#include <vector>
#include <mutex>
#include <queue>
#include <boost/asio.hpp>
using boost::asio::ip::tcp;
std::queue<std::string> data_queue;
std::mutex queue_mutex;
void handle_client(tcp::socket socket) {
try {
while (true) {
boost::system::error_code ec;
char data[1024];
size_t len = socket.read_some(boost::asio::buffer(data), ec);
if (ec == boost::asio::error::eof || len == 0)
break;
std::lock_guard<std::mutex> lock(queue_mutex);
data_queue.push(std::string(data, len));
}
} catch (std::exception& e) {
std::cerr << "Error: " << e.what() << std::endl;
}
}
int main() {
try {
boost::asio::io_context io;
tcp::acceptor acceptor(io, tcp::endpoint(tcp::v4(), 8080));
std::cout << "Server started..." << std::endl;
while (true) {
tcp::socket socket(io);
acceptor.accept(socket);
std::thread(&handle_client, std::move(socket)).detach();
}
} catch (std::exception& e) {
std::cerr << "Exception: " << e.what() << std::endl;
}
return 0;
}
Скрытые проблемы
-
Несинхронизированный доступ к очереди data_queue
- Хотя очередь защищена мьютексом при добавлении, никто не читает из неё. Это может привести к переполнению памяти или утечке ресурсов.
-
Отсутствие ограничения числа потоков
- Каждое подключение создаёт новый поток (std::thread). При большом количестве клиентов это может привести к исчерпанию ресурсов системы.
-
Потенциальная утечка ресурсов сокетов
- Обработка ошибок в read_some неполная. Некоторые сценарии закрытия соединения могут оставить сокет открытым.
-
Неэффективное использование буфера
- Используется фиксированный буфер размером 1024 байта. Если клиент отправляет большие сообщения, они будут считываться частями, что может нарушить логику приложения.
-
Отсутствие механизма обработки данных
- Данные добавляются в очередь, но нет никакой логики, которая их забирает и обрабатывает.
-
Опасность использования detach()
- Вызов detach() без контроля над жизненным циклом потока может привести к неопределённому поведению, особенно если поток всё ещё работает при завершении программы.
Вопросы и задания
- Какие проблемы вы можете выявить в данном коде? Перечислите их.
- Предложите архитектурные изменения, которые позволят:
- Ограничить количество активных потоков.
- Реализовать эффективную обработку данных из очереди.
- Устранить утечки ресурсов.
- Модифицируйте код так, чтобы:
- Был добавлен worker-pool (пул обработчиков), который регулярно извлекает данные из очереди.
- Использовалась ограниченная очередь или стратегия backpressure.
- Было ограничение времени жизни потока или использовался пул потоков.
- Реализуйте корректную обработку ошибок чтения и закрытия соединений.
- Протестируйте вашу реализацию с помощью нескольких клиентских подключений, имитирующих интенсивную передачу данных.
Рекомендации по решению
- Используйте boost::asio::thread_pool или std::vector<std::thread> с задачами для обработки очереди.
- Замените detach() на join() или используйте std::shared_ptr<tcp::socket> для асинхронной работы.
- Добавьте ограничение на размер очереди и механизм уведомления обработчиков (condition_variable).
- Рассмотрите использование std::atomic<bool> для корректного завершения работы всех потоков при выходе.
- Для обработки больших сообщений можно использовать разделители (например, \n) или предварительно известный заголовок с длиной сообщения.
Возможное расширение кейса
- Добавление поддержки SSL/TLS.
- Логирование принятых данных в файл.
- Поддержка асинхронного API Boost.Asio вместо многопоточности.
- Механизм таймаута на ожидание данных от клиента.
- Использование lock-free очереди для уменьшения блокировок.
(2) Кейс: "Оптимизация производительности при обработке большого массива данных"
Описание ситуации
Вы работаете в компании, занимающейся разработкой программного обеспечения для анализа телеметрии с промышленных датчиков. Одним из ключевых компонентов системы является модуль на C++, который принимает и обрабатывает большие объёмы числовых данных (миллионы значений в секунду), поступающих из внешних систем.
Вам поручено проанализировать и оптимизировать существующий код, который стал работать слишком медленно при увеличении объёмов входных данных.
Исходный фрагмент:
#include <iostream>
#include <vector>
#include <cmath>
struct SensorData {
double timestamp;
double value;
};
std::vector<double> process_data(const std::vector<SensorData>& input) {
std::vector<double> result;
for (const auto& entry : input) {
double filtered = std::sin(entry.value) * std::exp(-0.01 * entry.timestamp);
result.push_back(filtered);
}
return result;
}
int main() {
std::vector<SensorData> data(10'000'000);
for (size_t i = 0; i < data.size(); ++i) {
data[i].timestamp = static_cast<double>(i);
data[i].value = static_cast<double>(i % 1000);
}
std::vector<double> processed = process_data(data);
std::cout << "Processed " << processed.size() << " items." << std::endl;
return 0;
}
Скрытые проблемы
-
Неэффективное использование памяти
- Вектор result выделяет память динамически, но не резервирует место заранее, что вызывает множественные перераспределения памяти.
-
Отсутствие SIMD-оптимизации
- Функция process_data не использует возможности процессора для параллельной обработки нескольких элементов (например, инструкции SSE/AVX).
-
Ненужная передача данных по значению
- Хотя данные передаются по ссылке, отсутствует возможность использования векторизации или других оптимизаций на уровне компилятора.
-
Опасность использования push_back в цикле
- Частое изменение размера вектора может замедлить выполнение. Особенно критично это при миллионах итераций.
-
Неэффективная математика
- Вызовы std::sin() и std::exp() могут быть дорогостоящими, особенно если они вызываются миллион раз без предварительных вычислений или аппроксимации.
-
Отсутствие распараллеливания
- Обработка выполняется последовательно, хотя задача легко поддаётся разделению между потоками.
Вопросы и задания
- Какие узкие места вы можете выявить в текущей реализации? Перечислите их.
- Какие инструменты можно использовать для анализа производительности этого кода?
- Предложите способы оптимизации:
- На уровне алгоритма.
- На уровне структур данных.
- На уровне архитектуры (SIMD, многопоточность).
- Реализуйте оптимизированную версию функции process_data, которая:
- Использует предварительное выделение памяти.
- Применяет SIMD-инструкции (через intrinsics или компиляторные директивы).
- Распределяет нагрузку по нескольким потокам.
- Проведите замеры времени выполнения до и после оптимизации.
Рекомендации по решению
- Используйте result.reserve(input.size()) для предотвращения лишних реаллокаций.
- Замените push_back на индексированный доступ, если известен размер выходного буфера.
- Добавьте оптимизацию через OpenMP для распараллеливания цикла.
- Используйте -O3 и -march=native при компиляции для автоматической векторизации.
- При необходимости вручную добавьте интринсики (#include <immintrin.h>).
- Рассмотрите возможность предвычисления значений sin и exp для повторяющихся входных данных.
Пример оптимизированного кода
#include <iostream>
#include <vector>
#include <cmath>
#include <omp.h>
struct SensorData {
double timestamp;
double value;
};
std::vector<double> process_data(const std::vector<SensorData>& input) {
std::vector<double> result(input.size());
#pragma omp parallel for
for (size_t i = 0; i < input.size(); ++i) {
double filtered = std::sin(input[i].value) * std::exp(-0.01 * input[i].timestamp);
result[i] = filtered;
}
return result;
}
int main() {
std::vector<SensorData> data(10'000'000);
for (size_t i = 0; i < data.size(); ++i) {
data[i].timestamp = static_cast<double>(i);
data[i].value = static_cast<double>(i % 1000);
}
double start_time = omp_get_wtime();
std::vector<double> processed = process_data(data);
double end_time = omp_get_wtime();
std::cout << "Processed " << processed.size() << " items in "
<< end_time - start_time << " seconds." << std::endl;
return 0;
}
Возможное расширение кейса
- Реализовать обработку с использованием GPU (CUDA/OpenCL).
- Сохранять результаты в файл в бинарном формате.
- Реализовать стриминговый режим обработки (без хранения всего массива в памяти).
- Добавить логирование и метрики производительности.
- Проверить работу с данными разного диапазона и точности (float vs double).
Ролевая игра №1: "Проект Хронос — Восстановление системы"
Цель игры
Погрузить обучающихся в реалистичную ситуацию разработки критически важного компонента программного обеспечения с нуля, где необходимо применять знания системного программирования на C/C++, учитывать производительность, безопасность и архитектурные ограничения.
Цель игроков — спроектировать, реализовать и протестировать модуль встраиваемой системы при ограниченных ресурсах и жёстких временных рамках.
Формат
- Тип: Командная ролевая игра (3–5 человек в команде)
- Длительность: 6–8 часов (может быть разделена на этапы)
- Форма проведения: Оффлайн или онлайн
- Инструменты: IDE, компилятор C/C++, отладчик, документация, контроль версий (например, Git), мессенджер для коммуникации
Сеттинг
В ближайшем будущем (2047 год) произошёл сбой в системе управления временными порталами — технологией, позволяющей перемещаться между эпохами для сбора исторических данных. После инцидента система частично вышла из строя. Ваша команда — группа специалистов по embedded-системам, отправленная на станцию «Хронос» для восстановления работы центрального контроллера портала.
Задача — написать новое ПО для контроллера, работающего на микроконтроллере с ограниченной памятью и без возможности динамического выделения памяти. Код должен быть оптимальным, надёжным, соответствующим техническому заданию и готовым к тестированию в условиях имитации.
Роли в команде
Каждый участник получает роль:
- Системный аналитик
- Изучает ТЗ, формирует требования, взаимодействует с заказчиком.
- Архитектор
- Проектирует структуру программы, выбирает алгоритмы и подходящие контейнеры.
- Разработчик 1
- Реализует ядро логики: обработку сигналов, управление состояниями портала.
- Разработчик 2
- Отвечает за работу с памятью, работу с датчиками, обработку ошибок.
- QA-инженер / Тестировщик
- Пишет unit-тесты, проверяет соответствие кода ТЗ, ищет утечки памяти и undefined behavior.
(Можно объединять роли при малом количестве участников)
Этапы игры
1. Получение задания
Команда получает техническое задание:
- Программа должна обрабатывать входной поток событий (таймеры, сигналы от датчиков).
- Обработка должна происходить в реальном времени.
- Использование new и malloc запрещено.
- Необходимо использовать фиксированные буферы и RAII.
- Поддержка отладочной информации через UART-интерфейс.
- Должна быть реализована защита от переполнений и некорректных данных.
2. Архитектурное проектирование
Команда обсуждает и создаёт диаграммы классов, описывает модули, выбирает контейнеры, определяет потоки выполнения.
3. Разработка
Участники пишут код, соблюдая стиль, используя современные стандарты C++ (C++17/20), шаблоны, STL-совместимые структуры.
4. Тестирование
QA-инженер проверяет:
- Соответствие ТЗ
- Наличие memory leak (Valgrind / AddressSanitizer)
- Корректность обработки крайних случаев
- Производительность
5. Защита решения
Команда представляет свою реализацию, отвечает на вопросы, демонстрирует работу программы (через тестовый фреймворк или симуляцию).
Обучающие эффекты
- Практическое применение RAII, умных указателей, move-семантики.
- Работа с ограниченными ресурсами и отсутствием динамической памяти.
- Написание безопасного и оптимизированного кода.
- Умение работать в команде, распределять роли, использовать контроль версий.
- Применение unit-тестирования и отладки в реальных условиях.
- Понимание принципов embedded-разработки и low-level программирования.
Возможные проблемы и вызовы во время игры
- Недостаточно точное понимание ТЗ — приводит к ошибкам в реализации.
- Ошибка выбора контейнеров — использование неэффективных структур в условиях ограничений.
- Проблемы с многопоточностью — race condition, deadlock, неправильная синхронизация.
- Отсутствие покрытия тестами — приводит к ненадёжному коду.
- Перегрузка одной роли — требует грамотного распределения задач.
- Недостаток времени — необходимо правильно планировать этапы.
Материалы для поддержки
- Шаблон технического задания
- Заготовка проекта с Makefile/CMake
- Симулятор аппаратной части (простейший эмулятор UART и датчиков)
- Документация по используемым библиотекам и API
- Чек-лист для QA
Возможные расширения
- Добавление поддержки нескольких типов датчиков.
- Интеграция с сетевой частью (передача данных на сервер).
- Поддержка OTA-обновлений.
- Использование SIMD-инструкций для обработки сигнала.
- Реализация пользовательского интерфейса (CLI или GUI).
- Введение элементов DevOps: CI/CD pipeline для сборки и тестирования.
Ролевая игра №2: "Система безопасности «Феникс» — восстановление контроля"
Цель игры
Погрузить обучающихся в реальную инженерную задачу по разработке и внедрению компонента безопасной системы на C/C++. Игроки должны совместно спроектировать, реализовать и протестировать модуль аутентификации для защищённого доступа к системе, учитывая требования к производительности, стабильности и безопасности.
Формат
- Тип: Командная ролевая игра (3–5 человек в команде)
- Длительность: 6–8 часов (может быть разделена на этапы)
- Форма проведения: Оффлайн или онлайн
- Инструменты: IDE, компилятор C/C++, отладчик, Git, мессенджер, unit-тест фреймворк (Google Test / Catch2), Valgrind, AddressSanitizer
Сеттинг
В ближайшем будущем произошёл масштабный сбой в центральной системе безопасности объекта «Феникс», который контролирует доступ ко всем зонам объекта. После сбоя система перешла в аварийный режим, ограничив доступ только через резервный модуль, который работает нестабильно.
Ваша команда — группа специалистов по системному программированию, отправленная для восстановления работы модуля аутентификации. От вас требуется написать надёжный и эффективный код, который сможет работать в условиях высокой нагрузки и при этом соответствовать строгим стандартам безопасности.
Роли в команде
Каждый участник получает роль:
- Аналитик безопасности
- Изучает требования по безопасности, проверяет код на наличие уязвимостей.
- Архитектор
- Разрабатывает общую структуру модуля, выбирает алгоритмы хэширования, типы данных.
- Разработчик 1
- Реализует ядро аутентификации: проверку логина/пароля, поддержку сессий.
- Разработчик 2
- Отвечает за работу с файлами, шифрование, защиту памяти, обработку ошибок.
- QA-инженер / Тестировщик
- Пишет unit-тесты, проверяет соответствие ТЗ, ищет утечки памяти, undefined behavior, уязвимости.
(Можно объединять роли при малом количестве участников)
Этапы игры
1. Получение задания
Команда получает техническое задание:
- Реализовать модуль аутентификации, принимающий логин и пароль.
- Пароли должны храниться в зашифрованном виде (например, SHA-256).
- Запрещено использование динамической памяти (malloc, new).
- Необходимо использовать RAII, умные указатели, move-семантику.
- Должна быть реализована защита от brute-force атак (например, задержка после нескольких неудачных попыток).
- Поддержка логирования событий без раскрытия чувствительной информации.
2. Архитектурное проектирование
Команда создаёт диаграммы классов, описывает модули, определяет потоки выполнения, выбирает контейнеры и подходы к шифрованию.
3. Разработка
Участники пишут код, соблюдая стиль, используя современные стандарты C++ (C++17/20), шаблоны, STL-совместимые структуры.
4. Тестирование
QA-инженер проверяет:
- Соответствие ТЗ
- Наличие memory leak (Valgrind / AddressSanitizer)
- Корректность обработки крайних случаев
- Защиту от уязвимостей (SQL-инъекции, buffer overflow и т.д.)
5. Защита решения
Команда представляет свою реализацию, отвечает на вопросы, демонстрирует работу программы (через тестовый фреймворк или эмуляцию).
Обучающие эффекты
- Применение принципов безопасного программирования на практике.
- Использование механизмов защиты памяти и обработки чувствительной информации.
- Практика использования современных возможностей C++: умные указатели, move-семантика, RAII.
- Написание защищённого, чистого и документированного кода.
- Умение работать в команде, распределять роли, использовать контроль версий.
- Понимание требований к промышленной разработке ПО.
Возможные проблемы и вызовы во время игры
- Неправильная обработка строк — утечки, переполнения, неверная работа с UTF-8.
- Ошибка выбора методов шифрования — использование устаревших или небезопасных алгоритмов.
- Проблемы с многопоточностью — race condition, deadlock, неправильная синхронизация.
- Отсутствие покрытия тестами — приводит к ненадёжному коду.
- Перегрузка одной роли — требует грамотного распределения задач.
- Недостаток времени — необходимо правильно планировать этапы.
Материалы для поддержки
- Шаблон технического задания
- Заготовка проекта с Makefile/CMake
- Базовая реализация хэширования (SHA-256)
- Документация по используемым библиотекам и API
- Чек-лист для QA
- Примеры unit-тестов
Возможные расширения
- Добавление двухфакторной аутентификации.
- Интеграция с внешними системами (API, LDAP, OAuth).
- Поддержка пользовательских прав и ролей.
- Логирование и аудит действий пользователей.
- Внедрение механизма блокировки аккаунта после множества попыток входа.
- Использование lock-free структур для работы с сессиями.
Ролевая игра №3: "Серверный кластер «Олимп» — борьба за производительность"
Цель игры
Погрузить обучающихся в реальную ситуацию разработки высокопроизводительного серверного приложения на C++, где необходимо учитывать многопоточность, сетевое взаимодействие, оптимизацию памяти и работу с большими объёмами данных.
Цель игроков — спроектировать и реализовать серверный модуль, способный обрабатывать тысячи запросов в секунду, обеспечивая минимальное время отклика и стабильную работу под нагрузкой.
Формат
- Тип: Командная ролевая игра (4–6 человек в команде)
- Длительность: 8–10 часов (может быть разделена на этапы)
- Форма проведения: Оффлайн или онлайн
- Инструменты: IDE, компилятор C/C++, отладчик, Git, Boost.Asio / socket API, unit-тест фреймворк (Google Test / Catch2), Valgrind, AddressSanitizer, профилировщик (perf / VTune)
Сеттинг
В недалёком будущем крупнейшая технологическая компания «Олимп Технолоджис» столкнулась с проблемой масштабируемости своего облачного сервера, который обслуживает миллионы пользователей по всему миру. После очередного роста трафика система начала давать сбои: увеличилось время отклика, возникают дедлоки, наблюдаются memory leak'и.
Ваша команда — группа инженеров, отправленных для полной переработки ключевых модулей сервера. От вас требуется реализовать надёжный, быстрый и отказоустойчивый сервер, способный выдерживать высокую нагрузку и корректно обрабатывать входящие запросы.
Роли в команде
Каждый участник получает роль:
- Системный аналитик
- Изучает технические требования, анализирует текущую архитектуру, формирует план улучшений.
- Архитектор
- Проектирует общую структуру сервера, выбирает модель обработки запросов (thread pool, async I/O).
- Сетевой разработчик
- Реализует сетевую часть: TCP/UDP, обработку подключений, сериализацию данных.
- Системный разработчик
- Занимается управлением памятью, многопоточностью, работой с файлами и логированием.
- QA-инженер / Тестировщик
- Пишет unit- и интеграционные тесты, проверяет производительность, ищет утечки памяти и undefined behavior.
- DevOps-специалист (по желанию)
- Настройка среды сборки, CI/CD pipeline, запуск нагрузочных тестов.
(Можно объединять роли при малом количестве участников)
Этапы игры
1. Получение задания
Команда получает техническое задание:
- Реализовать TCP-сервер, поддерживающий параллельную обработку запросов.
- Обработка запроса: JSON-вход, выполнение операции, возврат результата.
- Использование thread pool или асинхронного подхода (Boost.Asio).
- Защита от deadlocks, race conditions, утечек памяти.
- Поддержка логирования и метрик производительности.
- Оптимизация времени отклика под высокой нагрузкой.
2. Архитектурное проектирование
Команда создаёт диаграммы классов, описывает модули, выбирает контейнеры, потоковую модель, стратегию обработки запросов.
3. Разработка
Участники пишут код, соблюдая стиль, используя современные стандарты C++ (C++17/20), шаблоны, STL, boost, RAII, move-семантику.
4. Тестирование и оптимизация
QA-инженер проверяет:
- Соответствие ТЗ
- Наличие memory leak (Valgrind / AddressSanitizer)
- Корректность обработки крайних случаев
- Производительность под нагрузкой (с использованием простого клиентского тестера)
5. Защита решения
Команда представляет свою реализацию, демонстрирует работу под нагрузкой, отвечает на вопросы по архитектуре и безопасности.
Обучающие эффекты
- Глубокое понимание работы с сетью и многопоточностью в C++.
- Применение современных возможностей языка: умные указатели, move-семантика, RAII, шаблоны.
- Практика использования Boost.Asio, работы с JSON, сериализации.
- Умение оптимизировать код под высокую производительность.
- Написание чистого, документированного и тестируемого кода.
- Работа в команде, распределение задач, использование контроля версий.
Возможные проблемы и вызовы во время игры
- Неправильная организация пула потоков — приводит к низкой производительности или deadlock'ам.
- Ошибка выбора контейнеров — влияет на скорость и эффективность работы.
- Проблемы с многопоточностью — race condition, некорректная работа с shared data.
- Отсутствие покрытия тестами — приводит к ненадёжному коду.
- Перегрузка одной роли — требует грамотного распределения задач.
- Недостаток времени — необходимо правильно планировать этапы.
Материалы для поддержки
- Шаблон технического задания
- Заготовка проекта с Makefile/CMake
- Базовый пример TCP-сервера
- Документация по используемым библиотекам и API
- Чек-лист для QA
- Примеры unit-тестов
- Простейший клиент-симулятор нагрузки
Возможные расширения
- Добавление HTTPS и шифрования (OpenSSL).
- Интеграция с базой данных (SQLite, Redis).
- Поддержка WebSockets.
- Реализация механизма rate limiting.
- Использование lock-free структур для обмена данными между потоками.
- Внедрение систем мониторинга и метрик (Prometheus).
Ролевая игра №4: "Операция «Чистый код» — восстановление legacy-системы"
Цель игры
Погрузить обучающихся в реальную ситуацию, с которой часто сталкиваются профессиональные разработчики — необходимость анализа, рефакторинга и улучшения существующей кодовой базы, написанной без соблюдения современных стандартов.
Цель игроков — изучить legacy-код, выявить проблемы, выполнить его рефакторинг, покрыть тестами и адаптировать под новые требования.
Формат
- Тип: Командная ролевая игра (3–5 человек в команде)
- Длительность: 6–8 часов (может быть разделена на этапы)
- Форма проведения: Оффлайн или онлайн
- Инструменты: IDE, компилятор C/C++, отладчик, Git, unit-тест фреймворк (Google Test / Catch2), Valgrind, AddressSanitizer, статические анализаторы (Clang-Tidy, cppcheck)
Сеттинг
Крупное государственное учреждение столкнулось с кризисом: ключевая система управления инфраструктурой города («Система-97»), написанная более 20 лет назад, стала работать нестабильно. Проблемы проявляются в виде частых сбоев, падений и ошибок в логике работы.
Ваша команда — группа опытных разработчиков, нанятых для спасения проекта. Вам предстоит взять в руки запутанный, плохо документированный код и привести его в состояние, пригодное для дальнейшей поддержки и масштабирования.
Роли в команде
Каждый участник получает роль:
- Аналитик кода
- Изучает исходный код, строит диаграммы вызовов, выявляет антипаттерны.
- Архитектор
- Предлагает новую архитектуру, перепроектирует модули, устраняет дублирование.
- Разработчик 1
- Выполняет рефакторинг функциональных модулей, применяет RAII, умные указатели.
- Разработчик 2
- Реализует новые функции по ТЗ, исправляет баги, работает с памятью.
- QA-инженер / Тестировщик
- Пишет unit-тесты, проверяет соответствие ТЗ, ищет утечки памяти и undefined behavior.
(Можно объединять роли при малом количестве участников)
Этапы игры
1. Получение задания
Команда получает техническое задание:
- Улучшить читаемость и тестируемость кода.
- Использовать современные стандарты C++ (C++17/20).
- Устранить memory leak, race condition, UB.
- Добавить поддержку новых требований (например, новая команда, вывод данных в JSON).
- Написать документацию и комментарии к основным функциям.
2. Анализ legacy-кода
Команда изучает предоставленный проект:
- Поиск антипаттернов: глобальные переменные, дублирование, плохая абстракция.
- Обнаружение проблем: утечки, гонки, некорректная работа с памятью.
- Создание диаграмм классов и последовательности вызовов.
3. Архитектурное проектирование
Команда создаёт новую архитектуру, определяет интерфейсы, выбирает контейнеры, пишет план рефакторинга.
4. Рефакторинг и расширение
Участники:
- Переписывают код, используя современные практики.
- Заменяют сырые указатели на std::unique_ptr и std::shared_ptr.
- Вводят RAII, move-семантику, шаблоны.
- Добавляют новые функции согласно ТЗ.
- Пишут unit-тесты для ключевых модулей.
5. Тестирование и защита решения
QA-инженер проверяет:
- Соответствие ТЗ
- Наличие memory leak (Valgrind / AddressSanitizer)
- Корректность обработки крайних случаев
- Покрытие кода тестами
Команда представляет:
- Как изменилась архитектура
- Какие проблемы были решены
- Какие улучшения внесены
- Как добавлены новые функции
Обучающие эффекты
- Навык работы с legacy-кодом и понимания старых стилей программирования.
- Применение принципов SOLID, clean code и refactoring patterns.
- Использование современных возможностей C++ для улучшения кода.
- Практика работы с тестированием, отладкой и анализом качества кода.
- Умение работать в команде, распределять задачи, использовать контроль версий.
- Понимание важности документации и покрытия тестами.
Возможные проблемы и вызовы во время игры
- Недостаточно информации о системе — необходимо делать выводы по коду.
- Ошибка выбора приоритетов — важно сначала протестировать, потом рефакторить.
- Проблемы совместимости — изменения могут сломать существующую логику.
- Отсутствие покрытия тестами — повышает риск регресса.
- Перегрузка одной роли — требует грамотного распределения задач.
- Недостаток времени — необходимо правильно планировать этапы.
Материалы для поддержки
- Шаблон технического задания
- Исходный legacy-проект (плохо написанный, с намеренными багами)
- Заготовка проекта с Makefile/CMake
- Документация по используемым библиотекам и API
- Чек-лист для QA
- Примеры unit-тестов
- Инструкция по использованию статических анализаторов
Возможные расширения
- Добавление CI/CD pipeline для автоматической проверки изменений.
- Поддержка многопоточности в новых функциях.
- Интеграция с внешними системами (например, API).
- Графический интерфейс для пользователей.
- Автоматизация сборки через Conan/vcpkg.
- Проверка покрытия кода тестами с помощью gcov/lcov.
Интеллект-карта 1: Путь разработчика C/C++ от новичка до профессионала
Разработчик C/C++ → Профессиональный уровень
│
├── Базовые навыки
│ ├── Синтаксис C и C++
│ ├── Указатели и ссылки
│ ├── Управление памятью (malloc/free, new/delete)
│ ├── Функции, массивы, строки
│ └── Препроцессор, условная компиляция
│
├── Объектно-ориентированное программирование (ООП)
│ ├── Классы, объекты
│ ├── Наследование, полиморфизм
│ ├── Инкапсуляция
│ └── Виртуальные функции, абстрактные классы
│
├── Работа с библиотеками
│ ├── STL: контейнеры (vector, map, set)
│ ├── STL: алгоритмы (sort, find, transform)
│ ├── Boost: файлы, сети, умные указатели
│ └── Работа с шаблонами и стандартами (C++11/14/17/20)
│
├── Системное программирование
│ ├── Работа с процессами и потоками
│ ├── IPC (межпроцессное взаимодействие)
│ ├── Работа с сигналами и событиями
│ └── Разработка под Linux/Windows API
│
├── Многопоточность и параллелизм
│ ├── std::thread, std::mutex, std::atomic
│ ├── RAII для синхронизации
│ ├── Futures и Promises
│ └── Lock-free программирование
│
├── Сетевое программирование
│ ├── TCP/UDP, сокеты
│ ├── Boost.Asio
│ └── Работа с сетевыми протоколами
│
├── Архитектура и оптимизация
│ ├── Оптимизация производительности
│ ├── Inline-ассемблер, SIMD
│ ├── Отладка (GDB, Valgrind)
│ └── Профилирование и тестирование
│
└── Проектная деятельность
├── Практические задания
├── Мини-проекты
└── Итоговый проект: сервер или клиент
Интеллект-карта 2: Ключевые темы курса по уровням сложности
Уровень 1 — Введение
│
├── Язык C: базовые конструкции, типы данных, работа с памятью
├── Язык C++: ООП, перегрузка операторов, конструкторы
└── Введение в STL: vector, string, алгоритмы
Уровень 2 — Промежуточный
│
├── Шаблоны, SFINAE, Concepts
├── Умные указатели (unique_ptr, shared_ptr)
├── Исключения, RAII, move семантика
├── Многопоточность: std::thread, mutex, lock_guard
└── Базовое знание Boost
Уровень 3 — Продвинутый
│
├── Асинхронность: futures, promises, coroutines (C++20)
├── Сетевое программирование: TCP/UDP, Boost.Asio
├── Работа с архитектурой x86/x64
├── SIMD, inline-ассемблер
└── Lock-free структуры данных
Уровень 4 — Профессиональный
│
├── Архитектурные паттерны: Singleton, Factory, Observer
├── Собственные библиотеки: static/dynamic linking
├── Инструменты сборки: CMake, Makefile
├── Тестирование: Google Test, Catch2
└── CI/CD, DevOps интеграция
Интеллект-карта 3: Навыки, которые должен освоить выпускник
Выпускник курса: Разработчик C/C++ (Профессиональный уровень)
│
├── Должен знать:
│ ├── Современные стандарты языков C и C++
│ ├── Принципы системного программирования
│ ├── Архитектура ОС и памяти
│ ├── Механизмы работы компиляторов и линкеров
│ ├── Структуры данных и алгоритмы
│ └── Паттерны проектирования
│
├── Должен уметь:
│ ├── Разрабатывать высокопроизводительные приложения
│ ├── Использовать STL и Boost на профессиональном уровне
│ ├── Писать чистый и безопасный код
│ ├── Реализовывать многопоточные приложения
│ ├── Выполнять отладку и профилирование
│ ├── Проектировать архитектуру ПО
│ ├── Работать в среде Linux и Windows
│ ├── Создавать и использовать собственные библиотеки
│ └── Собирать проекты с помощью CMake
│
└── Дополнительно:
├── Участие в open-source проектах
├── Понимание принципов DevOps
├── Знание основ embedded-разработки
├── Опыт командной разработки
└── Умение работать с документацией и спецификациями
1. Бьярне Страуструп — «Язык программирования C++. Том 1–2»
- Тип: Учебник / научная литература
- Описание: Автор языка C++ подробно рассматривает устройство языка, стандартные библиотеки, механизмы ООП, шаблоны, работу с памятью и современные стандарты (включая C++17/20).
- Целевая аудитория: Разработчики уровня Middle и выше
- Применение в курсе: Базовый источник знаний по современному C++, основа для понимания устройства языка и его возможностей.
2. Скотт Мейерс — «Эффективный и более эффективный C++» (55 специальных советов)
- Тип: Учебное пособие / хрестоматия
- Описание: Два тома содержат 55 практических рекомендаций по написанию безопасного, оптимального и чистого кода на C++. Охватывает использование умных указателей, RAII, move-семантики, STL и многое другое.
- Целевая аудитория: Разработчики уровня Intermediate и выше
- Применение в курсе: Практическая основа для рефакторинга, повышения качества кода и избегания типичных ошибок.
3. Герб Саттер — «Новые испытания C++: 42 практических задачи по проектированию, стандартной библиотеке и многопоточности»
- Тип: Задачник / учебное пособие
- Описание: Книга содержит 42 задачи с решениями, охватывающими ключевые темы профессиональной разработки: управление ресурсами, шаблоны, STL, исключения, многопоточность.
- Целевая аудитория: Разработчики, углублённо изучающие язык
- Применение в курсе: Отличный источник для домашних заданий, задач на развитие мышления и проектного подхода.
4. Джонатан Диксон — «C++ Concurrency in Action: Practical Multithreading»
- Тип: Учебное пособие / научная литература
- Описание: Полное руководство по работе с многопоточностью в C++. Рассмотрены std::thread, std::atomic, std::future, lock-free структуры, синхронизация, производительность.
- Целевая аудитория: Разработчики, изучающие системное и высокопроизводительное программирование
- Применение в курсе: Вспомогательный материал для модуля по многопоточности и параллелизму.
5. Иван Бандура — «CMake. Современная сборка проектов на C/C++»
- Тип: Учебное пособие / методические рекомендации
- Описание: Современный практикум по использованию CMake для управления проектами на C/C++. Включает примеры организации проектов, работы с зависимостями, cross-platform сборки.
- Целевая аудитория: Программисты, осваивающие инструменты сборки
- Применение в курсе: Основной источник знаний по автоматизации сборки проектов, CI/CD, управлению зависимостями.
- «C++ Профессионал: от базовых знаний к высокопроизводительным решениям»
Курс для тех, кто хочет выйти за рамки базового уровня и освоить продвинутые возможности языка, шаблоны, многопоточность и оптимизацию кода под производительность.
- «Системное программирование на C/C++: разработка под Linux и Windows»
Изучение работы с API операционных систем, управление процессами и потоками, файлы, память, IPC и сетевое взаимодействие.
- «Мастер C++: современные стандарты и промышленная разработка»
Курс по применению возможностей C++11/14/17/20 в реальных проектах, включая шаблоны, метапрограммирование, работу с STL и Boost.
- «Высокопроизводительные приложения на C++: от теории до production-сервера»
Обучение разработке масштабируемых, отказоустойчивых приложений с акцентом на скорость работы, использование SIMD, lock-free структур и многопоточности.
- «Разработка на низком уровне: C++ для embedded и драйверов»
Курс по работе с памятью, регистрами, периферией, оптимизация под ограниченные ресурсы и безопасность кода в условиях жёстких требований.
- «Глубокий C++: мастерство владения стандартной библиотекой и шаблонами»
Углублённое изучение STL, Boost и современных методов обобщённого программирования. Практическая реализация собственных контейнеров и алгоритмов.
- «Многопоточность и параллелизм в C++: от std::thread до coroutines»
Курс по написанию эффективного многопоточного кода, включая futures, atomic, memory_order, coroutines и lock-free структуры данных.
- «Разработка сетевых приложений на C++: TCP/UDP, Boost.Asio и протоколы»
Практический курс по созданию клиент-серверных решений, работающих через сокеты, с использованием асинхронного API и сериализации данных.
- «Профессиональный C: продвинутые возможности и системное программирование»
Углублённое изучение языка C, включая указатели, макросы, работу с памятью, inline-ассемблер и интеграцию с ОС.
- «C++ для GameDev: внутреннее устройство движков и low-level оптимизация»
Изучение особенностей разработки игровых движков, работа с памятью, графика, физика, многопоточность и производительность.
- «Отладка и профилирование C/C++: поиск утечек, UB и повышение качества кода»
Курс по использованию GDB, Valgrind, AddressSanitizer, UBSan и другим инструментам для диагностики и улучшения кода.
- «Архитектура и паттерны на C++: от проектирования к реализации»
Изучение шаблонов проектирования (Singleton, Factory, Observer), принципов SOLID и их применение в реальных системах.
- «CMake и сборка проектов: профессиональный подход к автоматизации разработки»
Научитесь использовать CMake, Makefile, Conan и vcpkg для создания cross-platform, легко тестируемых и развиваемых проектов.
- «Реактор C++: асинхронная разработка с Boost.Asio»
Курс по созданию асинхронных приложений, работающих с сетью, файлами и событиями с использованием библиотеки Boost.Asio.
- «Оптимизация под CPU: SIMD, интринсики и inline-ассемблер»
Изучение методов ускорения вычислений за счёт использования векторных инструкций и низкоуровневого кода.
- «Безопасный C++: защита от ошибок времени выполнения и undefined behavior»
Курс по написанию кода, устойчивого к ошибкам, с применением static_assert, Concepts, SFINAE и анализаторов.
- «Командная разработка на C++: Git, CI/CD, тестирование и документация»
Обучение профессиональным практикам: версионному контролю, автоматической сборке, unit-тестированию и документированию.
- «C++ для HighLoad: серверы, пулы соединений и распределённые системы»
Курс по разработке высоконагруженных систем, включая асинхронную обработку, thread pool, работу с сокетами и балансировку нагрузки.
- «Работа с памятью в C/C++: от malloc до custom allocator'ов»
Подробное изучение управления памятью, включая RAII, умные указатели, собственные аллокаторы и оптимизацию.
- «Unit-тестирование и TDD на C++: Google Test, Catch2 и mock-объекты»
Освоение методик написания тестов, внедрения TDD и обеспечения покрытия кода в проектах на C++.
- «Рефакторинг legacy-кода на C/C++: чистый код и архитектурные изменения»
Курс по восстановлению и улучшению существующих кодовых баз, устранению антипаттернов и добавлению новых функций.
- «Шаблоны и метапрограммирование в C++: SFINAE, Concepts и constexpr»
Изучение продвинутых возможностей шаблонного программирования, включая compile-time вычисления и генерацию кода.
- «Разработка под x86/x64: ассемблер, вызовы, оптимизация»
Курс по пониманию архитектуры процессора, чтению дизассемблированного кода и применению inline-ассемблера в C++.
- «C++ в реальном времени: embedded, RTOS и hard real-time требования»
Изучение особенностей разработки систем, где время работы критично: работа с прерываниями, ограничения на динамическую память, детерминированность.
- «Финальный проект: напишите свой многопоточный сервер на C++»
Завершающий курс, в котором слушатель создаёт полноценный сервер с поддержкой TCP, многопоточности, логгирования, тестирования и деплоя.
Нет элементов для просмотра