C++SeniorExperience
Какие production-риски чаще всего возникают в проектах на C++: производительность, зависимости, конкурентность, деплой или observability?
Главные production-риски в C++: утечки памяти и UB (undefined behaviour), гонки данных при многопоточности, длинное время сборки крупных проектов, трудная диагностика крэшей без символов и фрагментация зависимостей.
Production-риски в C++ проектах
1. Undefined Behaviour и утечки памяти
UB — главная причина production-инцидентов. Компилятор оптимизирует код на основе предположения об отсутствии UB, что может дать неожиданные результаты.
#include <sanitizer/asan_interface.h>
// Обнаружение на этапе разработки — AddressSanitizer
// Сборка: g++ -fsanitize=address,undefined -g my_app.cpp
// Или в CMake:
// target_compile_options(my_app PRIVATE -fsanitize=address,undefined)
// target_link_options(my_app PRIVATE -fsanitize=address,undefined)
// ThreadSanitizer для гонок данных:
// g++ -fsanitize=thread -g my_app.cpp
// Valgrind в CI:
// valgrind --leak-check=full --error-exitcode=1 ./my_app
2. Гонки данных и дедлоки
#include <shared_mutex>
#include <atomic>
#include <thread>
// Риск: несинхронизированный доступ к shared state
class SafeCounter {
mutable std::shared_mutex mtx_;
int count_ = 0;
public:
void increment() {
std::unique_lock lock(mtx_); // эксклюзивный доступ
++count_;
}
int get() const {
std::shared_lock lock(mtx_); // совместное чтение
return count_;
}
};
// Для простых счётчиков — std::atomic
std::atomic<int> request_count{0};
request_count.fetch_add(1, std::memory_order_relaxed);
3. Производительность: heap allocations в hot path
#include <string_view>
#include <array>
// Риск: лишние аллокации в цикле обработки
void process_request(const std::string& raw) { // копирование!
// ...
}
// Лучше — string_view для read-only доступа
void process_request(std::string_view raw) { // нет копирования
// ...
}
// Small buffer optimization для локальных буферов
std::array<char, 4096> buf; // на стеке, нет heap
4. Сборка и деплой
Крупные C++ проекты собираются 10–60 минут — это CI-риск. Митигация:
# Ускорение сборки
apt-get install ccache
export CCACHE_DIR=/tmp/ccache
cmake -B build -G Ninja \
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
-DCMAKE_BUILD_TYPE=Release
ninja -C build -j$(nproc)
# Разделение на shared libs для incremental linking
5. Observability: символы и стектрейсы
# Release-сборка без символов = нечитаемые крэши
# Решение: split debug info
cmake -B build \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_CXX_FLAGS="-g -gsplit-dwarf"
# Или strip + отдельные .dbg файлы:
objcopy --only-keep-debug my_app my_app.dbg
strip my_app
objcopy --add-gnu-debuglink=my_app.dbg my_app
# Sentry/Crashpad для production крэш-репортов
6. Управление зависимостями
# vcpkg — современный менеджер пакетов
vcpkg install openssl:x64-linux fmt:x64-linux
cmake -B build -DCMAKE_TOOLCHAIN_FILE=/opt/vcpkg/scripts/buildsystems/vcpkg.cmake
# Conan — альтернатива
conan install . --output-folder=build --build=missing
Подводные камни
- UB не всегда проявляется на dev-машине — компилятор с
-O2может убрать код, который «работал» при-O0. Всегда запускайте sanitizers в CI. - Дедлок между двумя мьютексами, захватываемыми в разном порядке, воспроизводится только под нагрузкой. Используйте
std::lock(m1, m2)илиstd::scoped_lock. - Тонкие проблемы с памятью (use-after-free, double-free) в production без ASAN обнаруживаются только по крэшу с непонятным стектрейсом — держите символы в отдельных артефактах.
- Версионирование ABI разделяемых библиотек: изменение структур данных без смены major-версии ломает бинарную совместимость. Используйте
symbol versioningили PIMPL. - Инициализация статических объектов (SIOF — Static Initialization Order Fiasco) между translation units не определена по стандарту — используйте Meyers Singleton или lazy initialization.
- Стоимость исключений: хотя
throwв happy path бесплатен, раскрутка стека при исключении в tight loop критична. В latency-sensitive коде предпочитайте error codes илиstd::expected. - Фрагментация экосистемы: нет единого стандарта для пакетов — vcpkg, Conan, системные пакеты и FetchContent конкурируют, что усложняет репродуцируемые сборки в команде.
What hurts your answer
- Говорить только о запуске C++, но не об эксплуатации
- Не упоминать observability, обновления, безопасность и rollback
- Описывать риски абстрактно, без способов их снижать
What they're listening for
- Видит production-риски C++
- Говорит про monitoring, rollout, rollback и безопасность
- Умеет ранжировать риски по вероятности и влиянию