Перейти к основному содержимому

Примеры решений в С++

1. Базовые структуры управления и ввод-вывод

1.1. Безопасное чтение строки с std::getline и cin.ignore()
#include <iostream>
#include <string>

std::string safe_getline() {
std::string line;
std::getline(std::cin, line);
return line;
}

int main() {
std::cout << "Имя: ";
std::string name = safe_getline();
std::cout << "Фамилия: ";
std::string surname = safe_getline();
std::cout << "Привет, " << name << ' ' << surname << "!\n";
}

⚠️ Важно: после cin >> остаётся символ \n, который захватывается getline. Используйте cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n'), если смешиваете операторы.


1.2. Чтение чисел с валидацией
#include <iostream>
#include <limits>

template<typename T>
T input_number(const std::string& prompt) {
T value;
while (true) {
std::cout << prompt;
if (std::cin >> value) {
break;
}
std::cin.clear();
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
std::cout << "Некорректный ввод. Повторите.\n";
}
// Сброс остатка строки (на случай, если было что-то вроде "123 abc")
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
return value;
}

// Использование:
// int n = input_number<int>("Введите целое: ");
// double x = input_number<double>("Введите дробное: ");

2. RAII и управление ресурсами

2.1. Кастомный RAII-объект для временного отключения std::cout
#include <iostream>
#include <streambuf>

class CoutGuard {
std::streambuf* old_buf;
std::ostringstream silent_stream;

public:
CoutGuard() : old_buf(std::cout.rdbuf(silent_stream.rdbuf())) {}
~CoutGuard() { std::cout.rdbuf(old_buf); }

std::string captured() const { return silent_stream.str(); }
};

// Пример:
/*
{
CoutGuard guard;
std::cout << "Это не будет выведено\n";
std::string captured = guard.captured(); // "Это не будет выведено\n"
}
*/

2.2. Умный указатель с пользовательским делетером (для C-библиотек)
#include <memory>
#include <cstdio>

auto make_file(const char* path, const char* mode) {
return std::unique_ptr<FILE, decltype(&std::fclose)>(
std::fopen(path, mode),
&std::fclose
);
}

// Использование:
/*
auto f = make_file("log.txt", "w");
if (f) {
std::fprintf(f.get(), "Hello, RAII!\n");
}
// fclose вызовется автоматически
*/

3. STL: контейнеры, алгоритмы, итераторы

3.1. Удаление по условию (erase-remove idiom)
#include <vector>
#include <algorithm>

std::vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

// Удалить чётные
v.erase(
std::remove_if(v.begin(), v.end(), [](int x) { return x % 2 == 0; }),
v.end()
);

🔁 Для std::list предпочтительно использовать list::remove_if (O(n), без перемещения).


3.2. Разделение строки по разделителю (C++17)
#include <string>
#include <vector>
#include <string_view>

std::vector<std::string_view> split(std::string_view str, char delim = ' ') {
std::vector<std::string_view> result;
size_t start = 0;
size_t end = 0;

while ((end = str.find(delim, start)) != std::string_view::npos) {
if (end != start) { // избегаем пустых частей
result.emplace_back(str.substr(start, end - start));
}
start = end + 1;
}
if (start < str.size()) {
result.emplace_back(str.substr(start));
}
return result;
}

// Пример:
// auto parts = split("a,b,c", ','); // {"a", "b", "c"}

💡 Возвращает string_view — эффективно для readonly-обработки без аллокаций.


3.3. Группировка элементов (аналог GROUP BY)
#include <vector>
#include <map>
#include <string>
#include <algorithm>

struct Person {
std::string name;
std::string city;
};

std::map<std::string, std::vector<Person>> group_by_city(const std::vector<Person>& people) {
std::map<std::string, std::vector<Person>> groups;
for (const auto& p : people) {
groups[p.city].push_back(p);
}
return groups;
}

// C++20: можно использовать std::ranges::views::group_by (если доступен)

4. Шаблоны и метапрограммирование

4.1. Статический assert для enum-типов (проверка exhaustiveness)
enum class Color { Red, Green, Blue };

constexpr const char* to_string(Color c) {
switch (c) {
case Color::Red: return "Red";
case Color::Green: return "Green";
case Color::Blue: return "Blue";
default: static_assert(false, "Non-exhaustive switch"); // ❌ не сработает (false всегда)
}
}

Исправленный вариант (C++17):

template<Color C>
constexpr const char* color_name() {
if constexpr (C == Color::Red) return "Red";
else if constexpr (C == Color::Green) return "Green";
else if constexpr (C == Color::Blue) return "Blue";
else static_assert(false, "Unhandled Color variant");
}

// Использование: color_name<Color::Red>()

✅ Альтернатива: использовать std::to_underlying + switch + default: __builtin_unreachable() (GCC/Clang) + -Wswitch-enum.


4.2. CRTP: статический полиморфизм
template<typename Derived>
struct Drawable {
void draw() {
static_cast<Derived*>(this)->drawImpl();
}
};

struct Circle : Drawable<Circle> {
void drawImpl() { std::cout << "Drawing Circle\n"; }
};

struct Square : Drawable<Square> {
void drawImpl() { std::cout << "Drawing Square\n"; }
};

// Использование:
// Circle c; c.draw(); // → "Drawing Circle"

Подходит для виртуализации без виртуальной таблицы — нулевые накладные расходы.


5. Move semantics и эффективное владение

5.1. Move-only класс (например, для уникального ресурса)
#include <memory>
#include <utility>

class UniqueResource {
std::unique_ptr<int> data_;

public:
explicit UniqueResource(int v) : data_(std::make_unique<int>(v)) {}

// Запрет копирования
UniqueResource(const UniqueResource&) = delete;
UniqueResource& operator=(const UniqueResource&) = delete;

// Разрешение перемещения
UniqueResource(UniqueResource&&) noexcept = default;
UniqueResource& operator=(UniqueResource&&) noexcept = default;

int value() const { return *data_; }
};

5.2. Передача временных объектов через std::move в функции
#include <string>
#include <vector>

class Repository {
std::vector<std::string> items_;

public:
// Приём rvalue — перемещаем
void add(std::string&& item) {
items_.push_back(std::move(item)); // избегаем копирования строки
}

// Приём lvalue — копируем
void add(const std::string& item) {
items_.emplace_back(item);
}
};

// Использование:
// repo.add("temp"); // вызывает add(string&&)
// std::string s = "val"; repo.add(s); // вызывает add(const string&)

💡 Лучше: использовать универсальную ссылку + perfect forwarding (см. ниже).


5.3. Perfect forwarding и factory-функция
#include <memory>
#include <utility>

template<typename T, typename... Args>
std::unique_ptr<T> make_unique_resource(Args&&... args) {
return std::make_unique<T>(std::forward<Args>(args)...);
}

// Пример:
// auto ptr = make_unique_resource<std::vector<int>>(10, 42); // 10 элементов по 42

std::make_unique — стандартное решение с C++14. Пример показывает принцип.


6. Работа с временем (C++17 chrono)

6.1. Измерение времени выполнения
#include <chrono>
#include <iostream>

template<typename Func>
auto time_it(Func&& f) {
auto start = std::chrono::steady_clock::now();
std::invoke(std::forward<Func>(f));
auto end = std::chrono::steady_clock::now();
return std::chrono::duration_cast<std::chrono::microseconds>(end - start);
}

// Пример:
/*
auto dur = time_it([]{ heavy_computation(); });
std::cout << "Выполнено за " << dur.count() << " мкс\n";
*/

6.2. Таймер с обратным вызовом (на основе std::thread + std::this_thread::sleep_for)
#include <thread>
#include <chrono>
#include <functional>

class Timer {
std::thread thread_;

public:
Timer(std::chrono::milliseconds delay, std::function<void()> cb)
: thread_([delay, cb = std::move(cb)]() mutable {
std::this_thread::sleep_for(delay);
cb();
}) {}

~Timer() {
if (thread_.joinable()) {
thread_.join();
}
}

Timer(const Timer&) = delete;
Timer& operator=(const Timer&) = delete;
Timer(Timer&&) = default;
Timer& operator=(Timer&&) = default;
};

// Использование:
// Timer t(2000ms, []{ std::cout << "Прошло 2 сек!\n"; });
// (блокирует деструктор до завершения)

⚠️ Это упрощённый пример. Для продакшена — лучше использовать asio::steady_timer или std::jthread (C++20).


7. Многопоточность и синхронизация

7.1. Безопасный потокобезопасный контейнер (обёртка через std::mutex)
#include <mutex>
#include <vector>
#include <shared_mutex> // C++17

template<typename T>
class ThreadSafeVector {
mutable std::shared_mutex mutex_;
std::vector<T> data_;

public:
void push_back(const T& value) {
std::lock_guard lock(mutex_);
data_.push_back(value);
}

void push_back(T&& value) {
std::lock_guard lock(mutex_);
data_.push_back(std::move(value));
}

size_t size() const {
std::shared_lock lock(mutex_);
return data_.size();
}

std::vector<T> snapshot() const {
std::shared_lock lock(mutex_);
return data_; // копия — caller получает консистентный снимок
}

// Доступ по индексу с проверкой границ
std::optional<T> at(size_t idx) const {
std::shared_lock lock(mutex_);
if (idx < data_.size()) {
return data_[idx];
}
return std::nullopt;
}
};

✅ Используется shared_mutex для разделения чтения/записи: несколько читателей — один писатель.
🔁 Альтернатива: std::mutex, если читателей мало и overhead shared_mutex не оправдан.


7.2. Асинхронный вызов с std::async и обработка исключений
#include <future>
#include <stdexcept>
#include <iostream>

int risky_computation(int n) {
if (n < 0) throw std::invalid_argument("n must be non-negative");
return n * n;
}

int main() {
auto fut = std::async(std::launch::async, risky_computation, -5);

try {
int result = fut.get(); // get() бросает исключение, если оно было в потоке
std::cout << "Результат: " << result << "\n";
} catch (const std::exception& e) {
std::cerr << "Ошибка: " << e.what() << "\n";
}
}

std::async с std::launch::async гарантирует запуск в отдельном потоке (в отличие от deferred).
⚠️ Если не вызвать .get() или .wait(), деструктор future заблокируется.


7.3. std::atomic для флагов и счётчиков
#include <atomic>
#include <thread>
#include <vector>

std::atomic<bool> stop_flag{false};
std::atomic<int> counter{0};

void worker(int id) {
while (!stop_flag.load(std::memory_order_relaxed)) {
++counter; // memory_order по умолчанию — seq_cst (безопасно)
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
std::cout << "Поток " << id << " завершён\n";
}

// Запуск:
/*
std::vector<std::thread> threads;
for (int i = 0; i < 4; ++i) {
threads.emplace_back(worker, i);
}

std::this_thread::sleep_for(1s);
stop_flag.store(true);
for (auto& t : threads) t.join();

std::cout << "Счётчик: " << counter.load() << "\n";
*/

🔧 Для инкремента в высоконагруженных сценариях лучше fetch_add(1, std::memory_order_relaxed).


8. Работа с файловой системой (filesystem, C++17)

8.1. Рекурсивный обход каталога и фильтрация по расширению
#include <filesystem>
#include <vector>
#include <string>

namespace fs = std::filesystem;

std::vector<fs::path> find_files_by_extension(
const fs::path& root,
const std::string& ext // например, ".cpp"
) {
std::vector<fs::path> matches;
for (const auto& entry : fs::recursive_directory_iterator(root)) {
if (entry.is_regular_file() && entry.path().extension() == ext) {
matches.push_back(entry.path());
}
}
return matches;
}

// Использование:
// auto cpp_files = find_files_by_extension("/src", ".cpp");

⚠️ recursive_directory_iterator может выбросить filesystem_error при недоступных правах.


8.2. Создание временного каталога с гарантией удаления (RAII)
#include <filesystem>
#include <random>
#include <stdexcept>

class TempDirectory {
fs::path path_;

static fs::path generate_unique_path() {
static std::random_device rd;
static std::mt19937 gen(rd());
static std::uniform_int_distribution<uint64_t> dis;

auto parent = fs::temp_directory_path();
for (int attempts = 0; attempts < 100; ++attempts) {
auto name = "tmp_" + std::to_string(dis(gen));
auto candidate = parent / name;
if (!fs::exists(candidate)) {
fs::create_directory(candidate);
return candidate;
}
}
throw std::runtime_error("Failed to create unique temp dir");
}

public:
TempDirectory() : path_(generate_unique_path()) {}
~TempDirectory() { fs::remove_all(path_); }

const fs::path& path() const noexcept { return path_; }

TempDirectory(const TempDirectory&) = delete;
TempDirectory& operator=(const TempDirectory&) = delete;
TempDirectory(TempDirectory&&) = default;
TempDirectory& operator=(TempDirectory&&) = default;
};

✅ Используется remove_all — рекурсивное удаление. Гарантируется даже при исключении.


9. Обработка ошибок без исключений (до C++23)

9.1. Простой expected-подобный контейнер (до std::expected в C++23)
#include <variant>
#include <optional>
#include <system_error>

template<typename T, typename E = std::error_code>
class Expected {
std::variant<T, E> data_;

public:
// Конструктор успеха
Expected(T&& value) : data_(std::move(value)) {}
Expected(const T& value) : data_(value) {}

// Конструктор ошибки
Expected(E&& error) : data_(std::move(error)) {}
Expected(const E& error) : data_(error) {}

bool has_value() const { return std::holds_alternative<T>(data_); }
explicit operator bool() const { return has_value(); }

const T& value() const & {
if (!has_value()) throw std::bad_variant_access{};
return std::get<T>(data_);
}

T&& value() && {
if (!has_value()) throw std::bad_variant_access{};
return std::move(std::get<T>(data_));
}

const E& error() const {
if (has_value()) throw std::bad_variant_access{};
return std::get<E>(data_);
}
};

🔁 В C++23 — используйте #include <expected> и std::expected<T, E>.
🔧 Альтернатива: std::optional<T> + отдельный код ошибки или std::pair<bool, T> — но это менее выразительно.


9.2. Функция с возвратом кода ошибки (POSIX-стиль)
#include <cerrno>
#include <cstring>
#include <string>

struct ReadResult {
std::string data;
int error_code = 0; // 0 — успех
};

ReadResult read_file(const std::string& path) {
ReadResult res;
FILE* f = std::fopen(path.c_str(), "rb");
if (!f) {
res.error_code = errno;
return res;
}

if (std::fseek(f, 0, SEEK_END) != 0) {
res.error_code = errno;
std::fclose(f);
return res;
}

long size = std::ftell(f);
if (size < 0) {
res.error_code = errno;
std::fclose(f);
return res;
}

std::rewind(f);
res.data.resize(static_cast<size_t>(size));
size_t nread = std::fread(res.data.data(), 1, static_cast<size_t>(size), f);
std::fclose(f);

if (nread != static_cast<size_t>(size)) {
res.error_code = errno ? errno : EIO;
res.data.clear();
}
return res;
}

// Использование:
/*
auto r = read_file("test.txt");
if (r.error_code) {
std::cerr << "Ошибка: " << std::strerror(r.error_code) << "\n";
} else {
std::cout << "Прочитано " << r.data.size() << " байт\n";
}
*/

✅ Подходит для системных утилит, embedded, или когда исключения отключены (-fno-exceptions).


10. Тестирование и отладка

10.1. Минималистичный compile-time unit-test через static_assert
constexpr bool test_factorial() {
auto fact = [](int n) -> long long {
long long res = 1;
for (int i = 2; i <= n; ++i) res *= i;
return res;
};
return fact(0) == 1 &&
fact(1) == 1 &&
fact(5) == 120 &&
fact(10) == 3628800;
}

static_assert(test_factorial(), "factorial implementation is broken");

✅ Выполняется на этапе компиляции. Не создаёт runtime overhead.


10.2. Простой runtime-assert с выводом в stderr
#include <iostream>
#include <source_location> // C++20

#define CHECK(cond) do { \
if (!(cond)) { \
std::cerr << "[ASSERT FAILED] " << #cond \
<< " at " << std::source_location::current().file_name() \
<< ":" << std::source_location::current().line() << "\n"; \
std::abort(); \
} \
} while (false)

// Использование:
// int x = 5; CHECK(x > 0);

🔁 Для C++17 — замените source_location на __FILE__, __LINE__ и макрос:

#define CHECK_IMPL(cond, file, line) do { \
if (!(cond)) { \
std::cerr << "[ASSERT FAILED] " << #cond \
<< " at " << (file) << ":" << (line) << "\n"; \
std::abort(); \
} \
} while (false)

#define CHECK(cond) CHECK_IMPL(cond, __FILE__, __LINE__)

11. Сериализация и парсинг

11.1. Простой бинарный протокол (фиксированная структура)
#include <cstdint>
#include <vector>
#include <cstring>

struct MessageHeader {
uint32_t magic = 0xABCDEF01;
uint16_t version = 1;
uint16_t payload_size = 0;
};

struct Message {
MessageHeader header;
std::vector<std::byte> payload;

bool is_valid() const {
return header.magic == 0xABCDEF01 && header.version == 1;
}

// Сериализация в буфер
std::vector<std::byte> serialize() const {
std::vector<std::byte> buf;
buf.resize(sizeof(MessageHeader) + header.payload_size);
std::memcpy(buf.data(), &header, sizeof(header));
std::memcpy(buf.data() + sizeof(header), payload.data(), payload.size());
return buf;
}

// Десериализация из span (C++20) или const byte*
static std::optional<Message> deserialize(std::span<const std::byte> data) {
if (data.size() < sizeof(MessageHeader)) return std::nullopt;
Message msg;
std::memcpy(&msg.header, data.data(), sizeof(msg.header));
if (!msg.is_valid()) return std::nullopt;
if (data.size() != sizeof(MessageHeader) + msg.header.payload_size) return std::nullopt;

msg.payload.assign(
data.begin() + sizeof(MessageHeader),
data.begin() + sizeof(MessageHeader) + msg.header.payload_size
);
return msg;
}
};

✅ Подходит для внутренних IPC, embedded, когда не требуется совместимость с big-endian (иначе — htonl и т.д.).


11.2. CSV-парсер (минималистичный, без экранирования)
#include <string>
#include <vector>
#include <sstream>

std::vector<std::vector<std::string>> parse_csv(std::string_view csv) {
std::vector<std::vector<std::string>> rows;
std::istringstream ss(std::string(csv)); // string_view → string для stringstream
std::string line;

while (std::getline(ss, line)) {
if (line.empty()) continue;
std::vector<std::string> row;
std::istringstream line_ss(line);
std::string field;
while (std::getline(line_ss, field, ',')) {
// trim (простой вариант)
auto start = field.find_first_not_of(" \t");
auto end = field.find_last_not_of(" \t");
if (start == std::string::npos) {
row.emplace_back("");
} else {
row.emplace_back(field.substr(start, end - start + 1));
}
}
rows.push_back(std::move(row));
}
return rows;
}

// Пример:
// auto data = parse_csv("a, b, c\n1, 2, 3");
// data[0] → {"a", "b", "c"}

⚠️ Не поддерживает запятые внутри кавычек ("a,b",c). Для production — используйте библиотеку (например, rapidcsv).


12. C++20 и новейшие практики

12.1. std::format (C++20) — безопасная и производительная замена printf/stringstream
#include <format>
#include <iostream>

int main() {
std::string s = std::format("Hello, {}! You have {} new messages.",
"Timur", 5);
std::cout << s << "\n"; // → "Hello, Timur! You have 5 new messages."

// Локализация, ширина, точность:
double pi = 3.1415926535;
auto s2 = std::format("π ≈ {:.3f}", pi); // → "π ≈ 3.142"
}

✅ Безопаснее printf (проверка типов на этапе компиляции при consteval-расширениях).
📦 В GCC 12+, Clang 14+ (с -std=c++20 и -lstdc++fs не нужен).


12.2. Concepts (C++20) — ограничения на шаблоны
#include <concepts>
#include <vector>
#include <list>

template<typename T>
concept Container = requires(T t) {
typename T::value_type;
{ t.begin() } -> std::input_iterator;
{ t.end() } -> std::sentinel_for<decltype(t.begin())>;
};

template<Container C>
void print_size(const C& c) {
std::cout << "Size: " << c.size() << "\n"; // ошибка, если size() нет → SFINAE-friendly
}

// Или более строго:
template<typename C>
requires requires(C c) { c.size(); }
void print_size_strict(const C& c) {
std::cout << "Size: " << c.size() << "\n";
}

// Использование:
// std::vector<int> v; print_size(v); // OK
// std::forward_list<int> fl; // print_size(fl); — ошибка (нет size())

✅ Concepts улучшают сообщения об ошибках и позволяют делать requires в if constexpr.


12.3. std::ranges (C++20) — ленивые трансформации
#include <ranges>
#include <vector>
#include <iostream>

int main() {
std::vector<int> nums = {1, 2, 3, 4, 5};

auto squares = nums
| std::views::filter([](int x) { return x % 2 == 1; })
| std::views::transform([](int x) { return x * x; });

for (int x : squares) {
std::cout << x << ' '; // → 1 9 25
}
}

⚡ Вычисления происходят лениво — без временного вектора.


12.4. std::expected (C++23) — официальная замена optional + ошибка
#include <expected>
#include <string>
#include <iostream>

std::expected<int, std::string> safe_divide(int a, int b) {
if (b == 0) {
return std::unexpected("Division by zero");
}
return a / b;
}

// Использование:
/*
auto res = safe_divide(10, 2);
if (res) {
std::cout << "Result: " << *res << "\n";
} else {
std::cerr << "Error: " << res.error() << "\n";
}
*/

📌 Требует C++23 и компилятор с поддержкой (GCC 12+, Clang 16+ с -std=c++23).


13. Интерфейсы и дизайн API

13.1. Pimpl (Pointer to implementation) — скрытие реализации и ускорение компиляции
// widget.h
#pragma once
#include <memory>

class Widget {
public:
Widget();
~Widget(); // определён в .cpp!
Widget(const Widget&);
Widget& operator=(const Widget&);
Widget(Widget&&) noexcept;
Widget& operator=(Widget&&) noexcept;

void do_work();

private:
class Impl; // объявлен, но не определён
std::unique_ptr<Impl> p_;
};
// widget.cpp
#include "widget.h"
#include <iostream>

class Widget::Impl {
int internal_state_ = 42;
public:
void do_impl() {
std::cout << "Impl: " << internal_state_ << "\n";
}
};

Widget::Widget() : p_(std::make_unique<Impl>()) {}
Widget::~Widget() = default;

Widget::Widget(const Widget& other) : p_(std::make_unique<Impl>(*other.p_)) {}
Widget& Widget::operator=(const Widget& other) {
if (this != &other) *p_ = *other.p_;
return *this;
}

Widget::Widget(Widget&&) noexcept = default;
Widget& Widget::operator=(Widget&&) noexcept = default;

void Widget::do_work() {
p_->do_impl();
}

✅ Преимущества:
– Изменение Impl не требует перекомпиляции клиентского кода.
– Уменьшение времени сборки (особенно при изменении деталей реализации).
– Сокрытие зависимостей (например, #include <heavy_lib.h> остаётся в .cpp).
⚠️ Накладные расходы: один дополнительный аллокатор (unique_ptr) и косвенный вызов.


13.2. NVI (Non-Virtual Interface) — инверсия вызова для контроля контракта
class Logger {
public:
// Публичный не-виртуальный интерфейс
void log(const std::string& msg) {
if (msg.empty()) return; // пре-условие
do_log(timestamp(), msg); // делегирование
++counter_; // пост-условие / учёт
}

int messages_logged() const { return counter_; }

protected:
// Виртуальная заглушка для наследников
virtual void do_log(const std::string& ts, const std::string& msg) = 0;

private:
std::string timestamp() const {
auto now = std::chrono::system_clock::now();
auto time_t = std::chrono::system_clock::to_time_t(now);
return std::ctime(&time_t); // упрощённо
}

int counter_ = 0;
};

class FileLogger : public Logger {
protected:
void do_log(const std::string& ts, const std::string& msg) override {
std::ofstream f("log.txt", std::ios::app);
f << "[" << ts << "] " << msg << "\n";
}
};

✅ Позволяет централизовать:
– Валидацию аргументов
– Логирование вызовов
– Метрики (счётчики, тайминги)
– Обработку исключений
без дублирования в каждом наследнике.


13.3. Fluent interface (цепочка вызовов)
class QueryBuilder {
std::string table_;
std::vector<std::string> columns_;
std::string where_clause_;
int limit_ = -1;

public:
QueryBuilder& select(std::initializer_list<std::string> cols) {
columns_.insert(columns_.end(), cols);
return *this;
}

QueryBuilder& from(std::string table) {
table_ = std::move(table);
return *this;
}

QueryBuilder& where(std::string cond) {
where_clause_ = std::move(cond);
return *this;
}

QueryBuilder& limit(int n) {
limit_ = n;
return *this;
}

std::string build() const {
std::string sql = "SELECT ";
if (columns_.empty()) {
sql += "*";
} else {
for (size_t i = 0; i < columns_.size(); ++i) {
if (i > 0) sql += ", ";
sql += columns_[i];
}
}
sql += " FROM " + table_;
if (!where_clause_.empty()) {
sql += " WHERE " + where_clause_;
}
if (limit_ >= 0) {
sql += " LIMIT " + std::to_string(limit_);
}
return sql;
}
};

// Использование:
/*
auto query = QueryBuilder{}
.select({"id", "name", "email"})
.from("users")
.where("age > 18")
.limit(100)
.build();
// → "SELECT id, name, email FROM users WHERE age > 18 LIMIT 100"
*/

✅ Удобно для DSL-подобных API. Все методы возвращают *this по ссылке (не по значению!).


14. Интеграция с C

14.1. Экспорт C++ API в C (стабильный ABI)
// api.h (чистый C — заголовок для клиентов на C)
#pragma once

#ifdef __cplusplus
extern "C" {
#endif

typedef struct my_handle my_handle;

my_handle* my_create(int config);
void my_destroy(my_handle* h);

int my_process(my_handle* h, const char* input, char** output);

#ifdef __cplusplus
}
#endif
// api.cpp
#include "api.h"
#include <string>
#include <memory>

struct my_handle { // opaque type
std::unique_ptr<class Impl> impl;
};

class Impl {
int config_;
public:
Impl(int c) : config_(c) {}
std::string process(std::string_view input) {
return "Config " + std::to_string(config_) + ": processed '" + std::string(input) + "'";
}
};

my_handle* my_create(int config) {
try {
auto h = new my_handle{};
h->impl = std::make_unique<Impl>(config);
return h;
} catch (...) {
return nullptr;
}
}

void my_destroy(my_handle* h) {
delete h; // вызовёт ~unique_ptr → ~Impl
}

int my_process(my_handle* h, const char* input, char** output) {
if (!h || !h->impl || !input || !output) return -1;
try {
std::string result = h->impl->process(input);
*output = static_cast<char*>(std::malloc(result.size() + 1));
if (!*output) return -2;
std::memcpy(*output, result.c_str(), result.size() + 1);
return 0;
} catch (...) {
return -1;
}
}

✅ Правила:
– В заголовке — только struct, typedef, extern "C".
– В C++ — RAII внутри, но управление памятью — malloc/free на границе.
– Все исключения ловятся — C не умеет их обрабатывать.
opaque struct скрывает детали реализации.


14.2. Callback с void* user_data (C-style обратный вызов)
// callback.h
typedef void (*progress_callback)(int percent, void* user_data);

void long_operation(progress_callback cb, void* user_data);
// callback.cpp
#include "callback.h"
#include <thread>
#include <chrono>

void long_operation(progress_callback cb, void* user_data) {
for (int i = 0; i <= 100; ++i) {
std::this_thread::sleep_for(std::chrono::milliseconds(20));
if (cb) cb(i, user_data);
}
}
// usage.cpp (C++ wrapper)
#include <functional>
#include <iostream>

extern "C" void c_style_callback(int percent, void* user_data) {
auto* fn = static_cast<std::function<void(int)>*>(user_data);
(*fn)(percent);
}

void cpp_usage() {
std::function<void(int)> cb = [](int p) {
std::cout << "Progress: " << p << "%\n";
};
long_operation(c_style_callback, &cb);
}

⚠️ Внимание: user_data должен жить дольше, чем вызов. Никаких &local_lambda без обёртки.


15. Профилирование и отладка

15.1. Интеграция sanitizers (пример использования)
# Сборка с sanitizers (GCC/Clang)
g++ -g -fsanitize=address,undefined -fno-omit-frame-pointer -O1 main.cpp -o app
./app # при ошибке — подробный стек, адрес, тип
// Пример: use-after-free (детектируется ASan)
int* p = new int(42);
delete p;
*p = 100; // 🚨 AddressSanitizer: heap-use-after-free
// Пример: undefined behavior (UBSan)
int x = 1 << 32; // 🚨 UBSan: shift exponent too large

✅ Рекомендуется:
asan + ubsan — для CI и локальной отладки
tsan — для data race detection (многопоточка)
lsan — утечки памяти (часто входит в asan)


15.2. Статический таймер с RAII (для profiling отдельных блоков)
#include <chrono>
#include <iostream>

class ScopedTimer {
const char* label_;
std::chrono::steady_clock::time_point start_;

public:
explicit ScopedTimer(const char* label) : label_(label), start_(std::chrono::steady_clock::now()) {}
~ScopedTimer() {
auto end = std::chrono::steady_clock::now();
auto dur = std::chrono::duration_cast<std::chrono::microseconds>(end - start_);
std::cerr << "[TIMER] " << label_ << ": " << dur.count() << " µs\n";
}
};

// Использование:
/*
{
ScopedTimer t("heavy_computation");
heavy_computation();
} // → [TIMER] heavy_computation: 12345 µs
*/

🔧 Расширения:
– Передача std::ostream& для вывода
– Сбор статистики (min/max/avg) через статические поля
– Условная активация через #ifdef PROFILE


16. Embedded-специфика (без исключений, RTTI, STL-аллокаций)

16.1. Fixed-size arena allocator (стековая аллокация)
#include <array>
#include <cstddef>
#include <cstdint>

class StackAllocator {
alignas(std::max_align_t) std::array<std::byte, 1024> buffer_;
size_t offset_ = 0;

public:
void* allocate(size_t n, size_t align = alignof(std::max_align_t)) {
auto addr = reinterpret_cast<std::uintptr_t>(buffer_.data()) + offset_;
auto aligned = (addr + align - 1) & ~(align - 1);
size_t delta = aligned - addr;
if (offset_ + delta + n > buffer_.size()) return nullptr;
offset_ = delta + n;
return reinterpret_cast<void*>(aligned);
}

void reset() { offset_ = 0; } // ⚠️ Не вызывает деструкторы!

template<typename T, typename... Args>
T* make(Args&&... args) {
void* mem = allocate(sizeof(T), alignof(T));
if (!mem) return nullptr;
return new (mem) T(std::forward<Args>(args)...);
}
};

// Использование:
/*
StackAllocator alloc;
auto p = alloc.make<int>(42);
alloc.reset(); // повторное использование буфера
*/

✅ Подходит для realtime-систем, где malloc/new недопустимы.
⚠️ Нет поддержки delete — только reset() (RAII не нарушается, если объекты уничтожаются до reset).


16.2. Memory-mapped register access (volatile + reinterpret_cast)
#include <cstdint>

struct Register {
volatile uint32_t value;
void set_bit(int n) { value |= (1U << n); }
void clear_bit(int n) { value &= ~(1U << n); }
bool get_bit(int n) const { return (value & (1U << n)) != 0; }
};

// Предположим, что регистр GPIO находится по адресу 0x4000'0000
inline Register& GPIO() {
return *reinterpret_cast<Register*>(0x4000'0000);
}

// Использование:
/*
GPIO().set_bit(5); // установить 5-й бит
bool state = GPIO().get_bit(5);
*/

volatile предотвращает оптимизацию чтения/записи компилятором.
⚠️ Адреса зависят от SoC — выносите в #define или constexpr.


17. Паттерны проектирования в C++

17.1. Visitor с std::variant (без виртуальных функций, C++17+)
#include <variant>
#include <iostream>
#include <string>

struct Circle {
double radius;
};

struct Rectangle {
double width, height;
};

struct Triangle {
double a, b, c;
};

using Shape = std::variant<Circle, Rectangle, Triangle>;

// Перегрузка для std::visit
template<typename... Ts>
struct overloaded : Ts... {
using Ts::operator()...;
};
template<typename... Ts>
overloaded(Ts...) -> overloaded<Ts...>;

double area(const Shape& s) {
return std::visit(overloaded{
[](const Circle& c) -> double { return 3.14159 * c.radius * c.radius; },
[](const Rectangle& r) -> double { return r.width * r.height; },
[](const Triangle& t) -> double {
double p = (t.a + t.b + t.c) / 2.0;
return std::sqrt(p * (p - t.a) * (p - t.b) * (p - t.c));
}
}, s);
}

// Использование:
/*
Shape s = Rectangle{3.0, 4.0};
std::cout << "Area: " << area(s) << "\n"; // → 12
*/

✅ Преимущества перед классическим Visitor:
– Нет наследования, виртуальных вызовов, RTTI
– Контейнеры std::vector<Shape> компактны (нет указателей vtable)
– Compile-time проверка покрытия всех типов (если variant не std::monostate)
⚠️ std::visit использует switch по внутреннему индексу — O(1), но без инлайна.


17.2. State-машина
#include <memory>
#include <iostream>

class Context; // forward declaration

class State {
public:
virtual ~State() = default;
virtual void handle(Context& ctx) = 0;
};

class Context {
std::unique_ptr<State> state_;

public:
explicit Context(std::unique_ptr<State> initial) : state_(std::move(initial)) {}

void set_state(std::unique_ptr<State> new_state) {
state_ = std::move(new_state);
}

void request() {
if (state_) state_->handle(*this);
}
};

class ConcreteStateA : public State {
public:
void handle(Context& ctx) override {
std::cout << "State A → switching to B\n";
ctx.set_state(std::make_unique<ConcreteStateB>());
}
};

class ConcreteStateB : public State {
public:
void handle(Context& ctx) override {
std::cout << "State B → switching to A\n";
ctx.set_state(std::make_unique<ConcreteStateA>());
}
};

// Использование:
/*
Context ctx(std::make_unique<ConcreteStateA>());
ctx.request(); // A → B
ctx.request(); // B → A
*/

✅ Чистый RAII: при замене состояния старое удаляется автоматически.
🔁 Альтернатива: enum class StateId + switch, но теряется инкапсуляция логики.


17.3. Strategy через std::function (лёгкая замена наследованию)
#include <functional>
#include <iostream>
#include <vector>
#include <algorithm>

class Sorter {
std::function<bool(int, int)> comparator_;

public:
explicit Sorter(std::function<bool(int, int)> comp)
: comparator_(std::move(comp)) {}

void sort(std::vector<int>& data) const {
std::sort(data.begin(), data.end(), comparator_);
}
};

// Использование:
/*
Sorter asc([](int a, int b) { return a < b; });
Sorter desc([](int a, int b) { return a > b; });

std::vector<int> v = {3, 1, 4, 1, 5};
asc.sort(v); // → {1,1,3,4,5}
desc.sort(v); // → {5,4,3,1,1}
*/

✅ Гибкость: стратегия может быть лямбдой с захватом, std::bind, или std::less<int>{}.
⚠️ Накладные расходы: std::function — виртуальный вызов внутри (но инлайнится при простом захвате в современных компиляторах).


18. CMake: современные практики

18.1. Минималистичный CMakeLists.txt для библиотеки (C++17)
cmake_minimum_required(VERSION 3.14)
project(MyLib VERSION 1.0.0 LANGUAGES CXX)

# Требуем C++17
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF) # строгий стандарт — без GNU-расширений

# Библиотека
add_library(MyLib
src/core.cpp
include/mylib/core.h
)

# Публичные заголовки
target_include_directories(MyLib
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/src
)

# Проверка компилятора
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
target_compile_options(MyLib PRIVATE -Wall -Wextra -Wpedantic)
endif()

# Установка (для CPack или `make install`)
install(TARGETS MyLib
EXPORT MyLibTargets
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
RUNTIME DESTINATION bin
INCLUDES DESTINATION include
)

install(DIRECTORY include/ DESTINATION include)
install(EXPORT MyLibTargets
FILE MyLibTargets.cmake
NAMESPACE MyLib::
DESTINATION lib/cmake/MyLib
)

18.2. FetchContent: встраивание зависимостей
include(FetchContent)

# Пример: GoogleTest
FetchContent_Declare(
googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG release-1.14.0
)

FetchContent_MakeAvailable(googletest)

# Библиотека с тестами
add_library(MyLib ...)
add_executable(MyLibTest tests/main.cpp tests/core_test.cpp)
target_link_libraries(MyLibTest PRIVATE MyLib GTest::gtest GTest::gtest_main)

✅ Преимущества:
– Нет внешних зависимостей при клонировании
– Версионирование через GIT_TAG
– Изоляция: зависимости собираются в подкаталоге _deps
⚠️ Избегайте add_subdirectory для чужих проектов — нарушает инкапсуляцию.


18.3. CMakePresets.json (для унификации сборки)
{
"version": 3,
"configurePresets": [
{
"name": "dev",
"displayName": "Development",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build-dev",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
}
},
{
"name": "release",
"displayName": "Release",
"inherits": "dev",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release"
}
},
{
"name": "asan",
"displayName": "AddressSanitizer",
"inherits": "dev",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug"
},
"environment": {
"CC": "clang",
"CXX": "clang++"
}
}
],
"buildPresets": [
{
"name": "dev",
"configurePreset": "dev"
},
{
"name": "asan",
"configurePreset": "asan",
"jobs": 4
}
]
}

Использование:

cmake --preset dev
cmake --build --preset dev

cmake --preset asan -DSANITIZE=ON
cmake --build --preset asan

✅ Позволяет стандартизировать сборку в команде без shell-скриптов.


19. Тестирование: GoogleTest и mocking

19.1. Базовый тест с параметризацией
#include <gtest/gtest.h>

int factorial(int n) {
if (n < 0) throw std::domain_error("n < 0");
int res = 1;
for (int i = 2; i <= n; ++i) res *= i;
return res;
}

TEST(FactorialTest, Positive) {
EXPECT_EQ(factorial(0), 1);
EXPECT_EQ(factorial(1), 1);
EXPECT_EQ(factorial(5), 120);
}

TEST(FactorialTest, ThrowsOnNegative) {
EXPECT_THROW(factorial(-1), std::domain_error);
}

// Параметризованный тест
class FactorialParamTest : public ::testing::TestWithParam<std::pair<int, int>> {};

TEST_P(FactorialParamTest, Values) {
auto [input, expected] = GetParam();
EXPECT_EQ(factorial(input), expected);
}

INSTANTIATE_TEST_SUITE_P(
Factorial,
FactorialParamTest,
::testing::Values(
std::make_pair(0, 1),
std::make_pair(1, 1),
std::make_pair(3, 6),
std::make_pair(6, 720)
)
);

TEST_P + INSTANTIATE_TEST_SUITE_P — избегает дублирования EXPECT_EQ.


19.2. Mock-объект через MOCK_METHOD
#include <gmock/gmock.h>

class Database {
public:
virtual ~Database() = default;
virtual int query(const std::string& sql) = 0;
};

class MockDatabase : public Database {
public:
MOCK_METHOD(int, query, (const std::string& sql), (override));
};

class Service {
Database& db_;
public:
explicit Service(Database& db) : db_(db) {}
int get_user_count() {
return db_.query("SELECT COUNT(*) FROM users");
}
};

TEST(ServiceTest, CallsCorrectQuery) {
MockDatabase mock_db;
EXPECT_CALL(mock_db, query("SELECT COUNT(*) FROM users"))
.Times(1)
.WillOnce(::testing::Return(42));

Service srv(mock_db);
EXPECT_EQ(srv.get_user_count(), 42);
}

EXPECT_CALL проверяет:
– Количество вызовов
– Аргументы (можно ::testing::StartsWith, ::testing::_)
– Возвращаемое значение / side effects
⚠️ Mock должен жить дольше, чем тестируемый объект.


19.3. Тестирование смерти (death test)
TEST(DeathTest, FactorialNegativeDies) {
EXPECT_DEATH({ factorial(-5); }, "n < 0");
}

// Для многопоточных death-тестов:
TEST(MultiThreadDeathTest, ConcurrentAccessDies) {
GTEST_FLAG_SET(death_test_style, "threadsafe");
SomeUnsafeClass obj;
std::thread t([&] { obj.use_after_free(); });
EXPECT_DEATH({ obj.use_after_free(); }, "");
t.join();
}

⚠️ EXPECT_DEATH запускает код в отдельном процессе — не подходит для std::cout/файлов без осторожности.


20. Производительность: cache-friendly структуры

20.1. AoS (Array of Structures) vs SoA (Structure of Arrays)
// AoS — плохо для SIMD и cache line utilisation
struct ParticleAoS {
float x, y, z;
float vx, vy, vz;
int id;
};

std::vector<ParticleAoS> particles_aos(1'000'000);

// SoA — cache-friendly при работе с подмножеством полей
struct ParticlesSoA {
std::vector<float> x, y, z;
std::vector<float> vx, vy, vz;
std::vector<int> id;

void resize(size_t n) {
x.resize(n); y.resize(n); z.resize(n);
vx.resize(n); vy.resize(n); vz.resize(n);
id.resize(n);
}
};

ParticlesSoA particles_soa;
particles_soa.resize(1'000'000);

// Обновление скоростей — только `vx, vy, vz` читаются/пишутся
for (size_t i = 0; i < particles_soa.vx.size(); ++i) {
particles_soa.vx[i] += 0.01f;
particles_soa.vy[i] += 0.01f;
particles_soa.vz[i] += 0.01f;
}

📊 Эффект:
– AoS: 1 cache miss на частицу (при 64-байтной строке — ~8 частиц/строка)
– SoA: 1 cache miss на ~16 значений float (64 / 4 = 16) → 16× меньше промахов при обработке только скоростей
✅ Используйте SoA, если:
– Доступ идёт к подмножеству полей
– Размер структуры > L1 cache line
– Возможна векторизация (#pragma omp simd / std::transform)


20.2. Alignment и padding: alignas, [[no_unique_address]] (C++20)
struct Bad {
bool flag; // 1 байт + 3 padding
int count; // 4 байта
double value; // 8 байт
}; // sizeof = 16 (но полезных — 13)

struct Better {
double value; // 8
int count; // 4
bool flag; // 1 + 3 padding → total 16
}; // то же, но порядок улучшает доступ

// C++20: экономия на пустых базах
struct EmptyBase {};

struct [[no_unique_address]] Optimized : EmptyBase {
int data;
};

static_assert(sizeof(Optimized) == sizeof(int)); // true

✅ Проверяйте sizeof и alignof в тестах производительности.
🔧 alignas(64) для false sharing prevention в многопотоке:

struct alignas(64) Counter {
std::atomic<int> value{0};
};
// каждый Counter — в отдельной cache line

20.3. Избегаем std::vectorbool
// ❌ Антипаттерн: std::vector<bool> — не контейнер, proxy-итераторы, медленный доступ
std::vector<bool> flags(1000000); // bitset-like, but non-STL-compliant

// ✅ Альтернативы:
std::vector<char> flags_char(1000000); // 1 байт/флаг — просто и быстро
std::vector<int8_t> flags_i8(1000000); // явный signed char
std::bitset<1000000> flags_static; // если размер известен на этапе компиляции
boost::dynamic_bitset<> flags_boost(n); // динамический, но корректный

std::vector<bool> не имеет .data(), &v[0] — UB, итераторы не T*.
✅ Используйте только если критична память и вы точно знаете ограничения.