C++ - язык системного программирования
Play ITЗагрузка интерактивного демо…
Play ITЗагрузка интерактивного демо…
Основы C++
Что такое C++?
C++ — это язык программирования со следующими особенностями:
- Типизация — статическая, сильная; вывод типов есть (
auto,decltype, шаблоны). - Парадигма — мультипарадигменный — процедурный (наследие C), объектно-ориентированный, обобщённое программирование (шаблоны), функциональные элементы (лямбды, алгоритмы STL).
- Уровень — низкоуровневый с высокоуровневыми абстракциями: прямой доступ к памяти и железу при классах, шаблонах и RAII.
- Выполнение — компилируемый в нативный машинный код (AOT); компиляторы MSVC, GCC, Clang.
- Память — в первую очередь ручное (
new/delete,malloc/free); идиоматично — RAII и владение (деструкторы, умные указатели, move semantics); GC и подсчёт ссылок в стандарте нет. - Платформа — кроссплатформенный (стандарт ISO C++); нативный код под целевую ОС и архитектуру; управляемого runtime нет.
- Формат разработки — требует структуры проекта — единицы трансляции, заголовки, линковка, системы сборки (CMake, Meson, Make, Bazel).
- Направление — системное программирование, встроенные системы, игры, HPC, финансы, драйверы и компоненты с жёсткими требованиями к производительности.
- REPL — в стандарте нет; интерактивно — Cling (интерпретатор на базе LLVM), online-компиляторы (Compiler Explorer, Wandbox), фрагменты в IDE.
- Поколение — классический (с 1983 года), активно развивающийся (цикл стандартов C++11, 14, 17, 20, 23, 26).
- Параллелизм и асинхронность — нативные потоки (
std::thread,std::async,std::future,std::atomic); корутины с C++20 (co_await); стандартного async/await как в JavaScript или C# нет; OpenMP и MPI — через библиотеки. - Безопасность — "опасный" — арифметика указателей, переполнения буферов, use-after-free, data races и неопределённое поведение (UB); гарантий memory safety как у Rust нет.
Если какой-то пункт из списка непонятен — подробные определения и примеры в Язык программирования.
Добро пожаловать в мир C++
Если вы читаете эту статью, значит вы решили познакомиться с одним из самых влиятельных языков программирования в истории компьютеров. Это отличный выбор!
Название C++ читается как "си плюс плюс". Символ ++ — это оператор постинкремента в C.
C++ — это язык, который даёт полный контроль над памятью и производительностью, позволяя писать код как на высоком уровне (с классами и шаблонами), так и на низком (с прямыми указателями и битовыми операциями), но требует ручного управления ресурсами.
Исторически C++ задумывался как C с классами (идеи Simula, эффективность C). Типичный курс по ООП на C++ идёт от сложности ПО и объектной декомпозиции к инкапсуляции, наследованию и полиморфизму уже в синтаксисе языка — см. маршрут ООП и ООП в C++.
С++ - очень сложный язык. Он очень требователен к подготовке. У него высокий порог входа. Готовьтесь.
Код ITЗагрузка примера кода…
Суть в том, что в одной программе сочетаются классы и методы (ООП), RAII (ресурс живёт вместе с объектом), умные указатели (unique_ptr — владение без ручного delete), шаблоны (std::string, make_unique) — при этом код остаётся близким по производительности к низкоуровневому, но читается на более высоком уровне. Именно эта многопарадигмальность и контроль отличают C++.
Пример управления памятью:
int a = 10; // на стеке (автоудаление)
int* b = new int(20); // в куче (ручное удаление)
delete b; // обязательно!
std::unique_ptr<int> c = std::make_unique<int>(30); // умный указатель (сам удалит)
АЛГОРИТМ СобратьПрограммуCpp(исходники)
для каждого файл в исходники
объект := компилятор.скомпилировать(файл) // .o / .obj
конец
исполняемый := линковщик.связать(объекты, библиотеки)
вернуть исполняемый
КОНЕЦ
| Этап | Результат |
|---|---|
| Препроцессор | Подстановка #include, макросов |
| Компиляция | Объектный файл на одну единицу трансляции |
| Линковка | Один .exe / бинарник с адресами символов |
Основные концепты C++
- Компиляция — исходный код переводится в машинный, что даёт высокую производительность.
- Управление памятью — ручное через
new/deleteили умные указатели (unique_ptr,shared_ptr). - ООП — классы, наследование, полиморфизм, инкапсуляция.
- Шаблоны — обобщённое программирование, generics.
- STL — стандартная библиотека контейнеров и алгоритмов.
- Многопоточность —
std::thread, мьютексы, атомарные операции. - Безопасность — строгая типизация, RAII, исключения; проверки границ — через
at(), GSL, санитайзеры (не "из коробки" для каждого доступа). - Производительность — близко к C, минимальные накладные расходы, работа с памятью напрямую.
Всё это в одном языке.
Сложности в работе с C++
Помимо синтаксиса, в C++ важны сборка, ABI и единый toolchain: шаблоны, потоки и ручная память должны линковаться с одной runtime и одним стандартом.
При работе в C++ нужно учитывать:
- работать с файлами в проекте;
- обеспечивать совместимость компонентов (из-за разных поколений C++, многие решения и библиотеки могут не работать в новых версиях, вызывать ошибки и требовать настроек;
- настраивать конфигурацию - дополнительные каталоги, оптимизация, предпроцессор, библиотеки времени выполнения, и прочее;
- учитывать стандарт языка C++.
Важно не путать версию стандарта языка и бинарную совместимость (ABI). Исходный код C++14 обычно компилируется и с -std=c++20, а вот готовая .dll/.so, собранная другим компилятором, другой версией runtime или в другом режиме Debug/Release, может не слинковаться или упасть в рантайме.
// Библиотека, скомпилированная с C++14
class OldLibrary {
std::string name; // несовместимость чаще из-за компилятора, runtime и Debug/Release, не только -std=
};
// Ваш проект на C++20 подключает её — получаем:
// "undefined reference" или странные краши
Все компоненты проекта должны быть скомпилированы одним компилятором с одинаковыми настройками. Могут быть сложности с линковкой, разными компиляторами (MSVC, GCC, Clang), ABI (Application Binary Interface).
Настройка конфигурации - это лабиринт из тысяч опций. Откройте свойства любого С++ проекта в Visual Studio:
C/C++ → General → Additional Include Directories (где искать .h)
C/C++ → Preprocessor → Preprocessor Definitions (макросы: _DEBUG, _UNICODE)
C/C++ → Code Generation → Runtime Library (/MT, /MD, /MTd, /MDd)
C/C++ → Language → C++ Language Standard (C++14/17/20/23)
Linker → General → Additional Library Directories (где искать .lib)
Linker → Input → Additional Dependencies (какие .lib подключать)
Linker → Система → SubSystem (Console, Windows)
В реальности без правильных макросов библиотека не соберётся, порой в каждом заголовке приходится писать всякое, например:
// Без правильных макросов библиотека не соберётся
#ifdef _WIN32
#ifdef BUILD_DLL
#define API __declspec(dllexport)
#else
#define API __declspec(dllimport)
#endif
#else
#define API
#endif
// И это в каждом заголовке!
Эволюция стандарта (ориентир на 2026 год):
| Стандарт | Год ISO | Практика |
|---|---|---|
| C++11/14 | 2011/2014 | legacy-код, embedded, долгоживущие проекты |
| C++17 | 2017 | широко в продакшене |
| C++20 | 2020 | дефолт для новых проектов (GCC 11+, Clang 14+, MSVC 19.29+) |
| C++23 | 2023 | актуальный "текущий" стандарт в свежих toolchain |
| C++26 | в работе | черновик комитета |
В корпоративной среде часто фиксируют минимальную версию стандарта в CI (например, C++17). Код с std::ranges (C++20) на проекте с -std=c++14 не соберётся — это ограничение toolchain, а не "несовместимость языков" как таковая. Генераторы кода без указания стандарта часто подставляют C++20 — проверяйте CMakeLists.txt / флаги компилятора.
Простое Hello World превращается в конфигурацию. Для выполнения кода:
#include <iostream>
int main() { std::cout << "Hello\n"; }
Нужно за кулисами:
- Выбрать компилятор (
MSVC?Clang?MinGW?) — см. Компиляторы и toolchain - Выбрать стандарт (
/std:c++20или-std=c++17) - Выбрать Runtime Library (динамическую или статическую?)
- Настроить пути (если используем внешние библиотеки)
- Решить, как распространять (свои DLL или один
.exe) - Если на Linux — учесть другой ABI
Один неправильный флаг компиляции вызывает 50 неразрешённых внешних символов. Разные версии стандарта, по факту разные языки. А библиотека 2015 года может не слинковаться с проектом 2026 года.
И это мы ещё не говорили про разницу между Debug и Release. Собранная в Debug библиотека прекрасно работает с Debug-проектом, но при переключении на Release — пачка ошибок линковки. Потому что имена функций (mangling) различаются, а Runtime Library другая (/MTd и /MT).
Вот почему инженеры C++ ценятся так дорого, потому что они управляют такой сложностью.
Это язык для тех, кто готов взять на себя ответственность за каждый бит, каждый флаг компилятора и каждый стандарт за последние 40 лет. Такова плата за один из самых мощных языков в мире.
C with Classes
Бьёрн Страуструп, разрабатывая язык в Bell Labs в 1979–1985 годах, начал с эксперимента под названием C with Classes — надстройки над C, добавляющей поддержку классов, наследования и конструкторов/деструкторов. Совместимость с C изначально была условием выживания. Нельзя было требовать от инженеров переписывать ядра, драйвера и низкоуровневые библиотеки — следовало позволить им наращивать новую функциональность поверх уже написанного кода. Отсюда и фундаментальная особенность — большая часть кода на C компилируется как C++ при компиляции с -std=c++ (оговорки — ключевые слова C++ вроде class/new, более строгие правила приведения типов, отличия void* и перегрузки). Полная двусторонняя совместимость обеспечивается на уровне линковки через extern "C" и общий ABI платформы.
Но если C++ начался как C с классами, то к середине 1990-х он уже вышел далеко за пределы этой идеи. Шаблоны (введённые в 1991 г.), исключения, пространства имён, STL (Standard Template Library, принятая в стандарт в 1998 г.) — всё это самостоятельные парадигмы, вплетённые в язык. Важно понимать: C++ — это набор взаимосовместимых подъязыков, укладываемых в одну систему типов и одну модель компиляции. Программист может писать в стиле чистого процедурного C (что нередко делается в embedded-разработке), использовать объектно-ориентированную модель (как в большинстве фреймворков), применять обобщённое программирование через шаблоны (как в современных библиотеках вроде Eigen или Boost.Hana), или даже встраивать функциональные паттерны (через лямбда-выражения, появившиеся в C++11).
Почему C++ до сих пор актуален, несмотря на "более удобные" языки?
Чтобы ответить на этот вопрос, необходимо отделить удобство разработки от требований к исполнению. Языки вроде C#, Java, Python или Go решают одну ключевую задачу: повысить продуктивность программиста за счёт ограничений, абстракций и автоматизации. Сборка мусора, строгая типизация времени выполнения, управляемая среда — всё это снижает когнитивную нагрузку и уменьшает количество классов ошибок. Но цена за это — предсказуемость производительности.
C++ не гарантирует, что вы напишете код быстрее. Он гарантирует, что если вы знаете, что делаете, то получите максимально близкое к аппаратным возможностям время выполнения. Это достигается за счёт нескольких фундаментальных принципов:
-
Отсутствие накладных расходов по умолчанию (zero-cost abstractions).
Любая абстракция — будь то шаблонный контейнерstd::vector, итератор или умный указатель — должна компилироваться в код, не уступающий по эффективности ручному управлению. Например,std::vector<T>::operator[]сводится к простой арифметике указателей — никаких проверок границ (если не включены отладочные режимы), никаких вызовов виртуальных функций. Это архитектурное требование — если абстракция не может быть реализована без overhead’а, она либо не вводится, либо предоставляется альтернатива без него. -
Прямой контроль над моделью памяти.
C++ не навязывает кучу, стек и статическую память как единственно возможные области. Он предоставляет инструменты для работы с ними —new/delete,malloc/free, размещение объектов на стеке, placement new — и позволяет создавать собственные аллокаторы, менеджеры памяти, пулы. В играх, например, критически важно избегать фрагментации и пауз от сборки мусора; в реальном времени — гарантировать, что выделение памяти не приведёт к блокировке. В C++ это ожидаемо. -
Прозрачность компиляции и линковки.
В отличие от JIT-компилируемых или интерпретируемых языков, C++ превращается в машинный код до запуска. Это позволяет профилировать, оптимизировать, анализировать ассемблерный вывод на этапе разработки. Для высоконагруженных систем (биржевые трейдинговые платформы, телекоммуникационные стеки, ядра ОС) критична скорость и предсказуемость: нельзя допустить, чтобы сборка мусора в Java-приложении внезапно вызвала задержку в 50 мс — на фондовом рынке это эквивалентно потере сделки. C++ позволяет доказать отсутствие таких пауз аналитически. -
Платформенная независимость без посредника.
C++ не требует виртуальной машины. Исполняемый файл — это нативный код для целевой архитектуры (x86-64, ARM, RISC-V и др.). Это означает:- отсутствие зависимости от runtime’а (кроме стандартной библиотеки, которую можно статически линковать);
- минимальный размер образа (в embedded-системах — килобайты);
- возможность прямого доступа к регистрам, портам ввода-вывода, страницам памяти;
- совместимость с legacy-кодом и бинарными интерфейсами (ABI), написанными на C и ассемблере.
Именно эти свойства делают C++ не заменяемым в таких областях, как:
- Ядра операционных систем (Windows NT, Linux, macOS XNU — многие подсистемы написаны на C++ или сочетают C и C++);
- Графические и игровые движки (Unreal Engine, Frostbite, Source 2 — полностью или частично на C++);
- Браузерные движки (Chromium/V8, WebKit — C++ как основа);
- СУБД (MySQL, MongoDB — движки на C++; PostgreSQL — ядро в основном на C, клиенты и расширения часто на C++);
- Системы реального времени и embedded (автомобильные контроллеры, авиаэлектроника, робототехника);
- Высокочастотный трейдинг (где каждая наносекунда — деньги).
Да, C# (а также Rust, Zig, Go) успешно конкурируют в смежных нишах — например, в серверной разработке или инструментарии. Но в тех случаях, где требуется абсолютный контроль над выполнением — C++ остаётся одним из промышленно апробированных вариантов с 40-летней историей развития и огромной экосистемой (компиляторы, отладчики, профайлеры, статические анализаторы). ABI не универсален: совместимость бинарников зависит от компилятора, toolset (MSVC v142/v143 и т.д.), стандартной библиотеки и флагов вроде _GLIBCXX_USE_CXX11_ABI; между GCC, Clang и MSVC единого "C++ ABI" нет — для стабильных границ DLL часто используют C-интерфейс (extern "C").
Архитектурные основы
Прежде чем говорить о синтаксисе, важно понять модель компиляции — ту "машину", внутри которой живёт C++-код. В отличие от языков с единым загрузчиком (например, JVM), C++ опирается на многоступенчатую, децентрализованную схему:
1. Единица трансляции (translation unit)
Программа на C++ состоит из множества единиц трансляции. Каждая такая единица — это один .cpp-файл после предварительной обработки (#include, #define, условная компиляция). То есть:
// main.cpp
#include <iostream>
#include "utils.h"
int main() { /* ... */ }
после #include превращается в один большой текст, содержащий всё содержимое <iostream>, utils.h и собственного кода. Этот текст и есть единица трансляции.
Ключевая особенность: каждая единица трансляции компилируется независимо. Нет глобального анализа всей программы на этапе компиляции. Это накладывает ограничения (например, невозможность оптимизировать вызовы между единицами без LTO), но даёт гигантский выигрыш в скорости сборки и масштабируемости — в проектах с миллионами строк можно перекомпилировать только изменившиеся .cpp-файлы.
2. Заголовочные файлы
.h- или .hpp-файлы в C++ — это спецификации интерфейсов, передаваемые между единицами трансляции. Они содержат:
- объявления функций и классов (не определения);
inline-функции и шаблоны (которые должны быть видны во всех единицах, где используются);constexpr-константы;- директивы
#pragma onceили include guards (защита от множественного включения).
Заголовок — это договор — "если ты мне подключишь этот файл, я гарантирую, что в линковке будет найдена реализация этих символов". Нарушение этого договора (например, определение глобальной переменной в заголовке без inline) приводит к ошибкам линковки.
3. Компиляция → Линковка → Выполнение
Этапы:
- Предобработка (
cpp): раскрытие макросов, включение файлов. - Компиляция (
g++ -c main.cpp): превращение единицы трансляции в объектный файл (.o/.obj), содержащий машинный код и таблицу символов (имена функций, глобальных переменных). - Линковка (
ld,link.exe): объединение всех объектных файлов и библиотек в исполняемый файл или библиотеку. На этом этапе разрешаются внешние ссылки (например, вызовprintfсвязывается с реализацией изlibc). - Запуск — загрузка кода в память, инициализация глобальных объектов (в порядке определения внутри единицы, но неопределённом между ними!), вызов
main().
Важно: глобальные объекты инициализируются до main(), и порядок их инициализации между разными единицами трансляции не определён. Это одна из самых тонких и опасных особенностей C++ (т.н. "static initialization order fiasco").
RAII и стандартная библиотека
RAII — ресурс привязан к времени жизни объекта. Подробнее: Управление памятью.
STL — контейнеры, итераторы и алгоритмы. Подробнее: Библиотеки.
В рунет-дискурсе
C++ в рунетских форумах часто рисуют как "язык гуру" и бесконечной сложности — указатели, шаблоны, segfault. Мем отражает реальную цену контроля над памятью и ABI, но перенос на любого пользователя C++ несправедлив — тот же язык держит игры, браузеры, БД и embedded.
Шутки про "настоящий программист на C++" — социальный маркер элитарности; в команде важнее читаемый код, тесты и стандарт C++ под проект. Указатель — Неолурк (C++); смежный низкий уровень — язык C.
Что читать дальше
| Тема | Статья |
|---|---|
| Типы, операторы, циклы | Типы данных в C++, Операторы и выражения в C++, Циклы и управляющие конструкции в C++ |
| ООП и память | Объектно-ориентированное программирование в C++, Управление памятью в C++ |
| Функции, системное | Функции и лямбда-выражения в C++, Системное программирование на C++ |
| Сборка, потоки, сеть | Конфигурация и сборка в C++, Многопоточность и асинхронное выполнение в C++, Сетевое взаимодействие в C++ |
| Углубление | C++ — углублённые темы |
| Идиомы, ranges | Идиомы современного C++, Диапазоны и представления в C++20 |
| Vulkan, Qt Quick | Vulkan и низкоуровневая графика на C++, Qt Quick — первая программа на QML |
| Практика | Практические задания по C++ |
| Справочник | Справочник по C++ |