C++ geek
3.58K subscribers
254 photos
3 videos
18 links
Учим C/C++ на примерах
Download Telegram
Как включить все стандартные библиотеки одной командой

Чтобы разом включить в проект все стандартные библиотеки, используйте #include <bits/stdc++.h>. Это особенно полезно в условиях дефицита времени на соревнованиях по программированию.

Но помните, что:

#include <bits/stdc++.h>
содержит множество заголовочных файлов, которые, возможно, и не понадобятся в конкретном проекте. А это может привести к увеличению времени компиляции.

#include <bits/stdc++.h> не является стандартным заголовочным файлом библиотеки GNU C++. Таким образом, не относящиеся к типу GCC (GNU Compiler Collection) компиляторы могут испытывать затруднения в процессе исполнения. Однако так бывает не часто!
➡️ @cpp_geek
Как включить все стандартные библиотеки одной командой

Чтобы разом включить в проект все стандартные библиотеки, используйте #include <bits/stdc++.h>. Это особенно полезно в условиях дефицита времени на соревнованиях по программированию.

Но помните, что:

#include <bits/stdc++.h>
содержит множество заголовочных файлов, которые, возможно, и не понадобятся в конкретном проекте. А это может привести к увеличению времени компиляции.

#include <bits/stdc++.h> не является стандартным заголовочным файлом библиотеки GNU C++. Таким образом, не относящиеся к типу GCC (GNU Compiler Collection) компиляторы могут испытывать затруднения в процессе исполнения. Однако так бывает не часто!

➡️ @cpp_geek
👍5
📌 Оптимизация кода с std::optional в C++

Привет, друзья! Сегодня поговорим про std::optional — мощный инструмент, который делает код чище и безопаснее.

💡 Зачем нужен std::optional?
Обычно, если функция не может вернуть корректное значение, приходится использовать:
Возвращаемое значение с ошибочным кодом (неудобно, особенно если 0 или -1 могут быть валидными).
Выброс исключения (дорого по ресурсам).
Указатели (nullptr, но требует дополнительных проверок).

🔥 Альтернатива? Используем std::optional!


#include <iostream>
#include <optional>
#include <string>

std::optional<std::string> findUser(int id) {
if (id == 42) return "John Doe";
return std::nullopt;
}

int main() {
auto user = findUser(42);

if (user) {
std::cout << "User found: " << *user << std::endl;
} else {
std::cout << "User not found!" << std::endl;
}
}

Код стал чище: нет лишних проверок nullptr, исключений или специальных значений.

🎯 Когда использовать?
🔹 Когда функция может вернуть "ничего", но исключения и специальные значения не подходят.
🔹 Для более понятного API (например, парсинг строки в число).
🔹 Когда важно избежать неопределенного состояния (например, с переменной внутри класса).

А ты уже используешь std::optional в своем коде? Делись опытом в комментариях! 🔍

➡️ @cpp_geek
👍17
std::span — мощная альтернатива std::vector и std::array

Сегодня поговорим о std::span — контейнере, который делает работу с массивами и векторами в C++ более удобной и эффективной. 🚀

Проблема: Лишние копии данных
Представьте, что у нас есть функция, принимающая массив чисел:


void processArray(const std::vector<int>& arr) {
for (int num : arr) {
std::cout << num << " ";
}
std::cout << std::endl;
}


С одной стороны, передача const std::vector<int>& предотвращает копирование, но что, если у нас массив std::array или сырой int[]?
Придётся перегружать функцию или копировать данные в std::vector.

Решение: Используем std::span
std::span позволяет передавать любой диапазон (`std::vector`, std::array, сырые массивы) без копирования:


#include <iostream>
#include <span>
#include <vector>
#include <array>

void processArray(std::span<int> arr) {
for (int num : arr) {
std::cout << num << " ";
}
std::cout << std::endl;
}

int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
std::array<int, 5> arr = {6, 7, 8, 9, 10};
int rawArr[] = {11, 12, 13, 14, 15};

processArray(vec); // Работает
processArray(arr); // Работает
processArray(rawArr); // Работает
}


🚀 Преимущества std::span
Не копирует данные — передаётся как ссылка
Работает с любыми последовательностями
Гибкость — можно создавать срезы без копий

Пример использования .subspan(), чтобы передавать часть массива:


std::vector<int> vec = {1, 2, 3, 4, 5};
std::span<int> sp = vec;
processArray(sp.subspan(2)); // Выведет: 3 4 5


⚠️ Важно
- std::span не владеет данными. Убедитесь, что исходные данные живут дольше span.
- Не поддерживает автоматическое изменение размера, как std::vector.

📌 Итог
Если ваша функция принимает std::vector<int>&, std::array<int, N>&, int[] или даже std::initializer_list<int>, замените их на std::span<int>! Это сделает код более гибким и эффективным. 🔥

➡️ @cpp_geek
👍202🔥1
Как упростить дебаг через std::format и std::source_location

Когда вы отлаживаете сложный баг, бывает сложно быстро понять, где и почему произошла ошибка. С C++20 стало гораздо проще автоматизировать логирование и сделать его по-настоящему информативным.

Вот пример:


#include <iostream>
#include <format>
#include <source_location>

void log(const std::string& message,
const std::source_location location = std::source_location::current())
{
std::cout << std::format("[{}:{} - {}] {}\n",
location.file_name(),
location.line(),
location.function_name(),
message);
}

int divide(int a, int b)
{
if (b == 0) {
log("Попытка деления на ноль");
return 0;
}
return a / b;
}


📌 Этот код выведет:


[main.cpp:16 - divide] Попытка деления на ноль


Вы больше не пишете руками __FILE__, __LINE__ и __func__. Всё это делает std::source_location. А с std::format — красиво и читаемо.

➡️ @cpp_geek
👍133🔥1
🧵 RAII — главный секрет устойчивого к утечкам C++ кода

Привет! Сегодня хочу напомнить о технике, без которой невозможно писать безопасный и устойчивый C++ код — это RAII (Resource Acquisition Is Initialization).

RAII — это идиома, в которой захват ресурса (файл, сокет, память, мьютекс) происходит в конструкторе объекта, а освобождение — в деструкторе. Благодаря этому ресурсы освобождаются автоматически, даже при исключениях.

Пример:


#include <fstream>

void saveData(const std::string& filename) {
std::ofstream file(filename); // открытие файла
if (!file.is_open())
throw std::runtime_error("Cannot open file");

file << "some data"; // файл закроется автоматически
}


RAII делает твой код:
Безопасным к утечкам
Устойчивым к исключениям
Лёгким для чтения и сопровождения

💡 Совет: всегда оборачивай "ручные" ресурсы в обёртки — std::unique_ptr, std::lock_guard, std::ofstream, std::thread и т.д.


➡️ @cpp_geek
👍141💯1
🔥 Ловим баги в C++ на лету с помощью AddressSanitizer (ASan)

Если valgrind — это медленный, но подробный детектив, то ASan — это охрана, которая ловит баги прямо во время исполнения. Быстро, точно, удобно.


💡 Что такое ASan?
Это часть компилятора (clang или gcc), которая вставляет дополнительные проверки в бинарник. Работает во время запуска, ловит:

- выход за границы массива,
- use-after-free,
- double free,
- утечки памяти (с флагом LeakSanitizer).


👨‍💻 Пример:


// asan_example.cpp
#include <iostream>

int main() {
int* arr = new int[5];
arr[10] = 42; // выход за границу
delete[] arr;
return 0;
}


⚙️ Компиляция с ASan:


g++ -fsanitize=address -g asan_example.cpp -o app


🚀 Запуск:


./app


📄 Результат:


==12345==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x602000000050
READ of size 4 at 0x602000000050 thread T0
#0 0x... in main asan_example.cpp:6



📌 Плюсы ASan:
- Мгновенная обратная связь;
- Прост в использовании;
- Отлично работает с CI (GitHub Actions, GitLab CI и т.д.);
- Поддерживает LeakSanitizer (-fsanitize=leak).

📉 Минусы:
- Увеличивает размер бинарника;
- Иногда мешает оптимизациям;
- Не ловит всё (например, утечки в сторонних lib без debug info).


🔧 Совет:
Запускай тесты с -fsanitize=address в debug-сборках. Это бесплатно и спасает от кучи головной боли в будущем.


🧵 Используешь ли ты ASan в своих проектах? Или только valgrind? Пиши в комментах👇

➡️ @cpp_geek
👍71
🚀 Сегодня я покажу вам простой, но очень полезный приём в C++: как элегантно управлять временем жизни ресурса с помощью std::unique_ptr и кастомного deleter'а.

📌 Задача: у нас есть не-C++ ресурс, например, FILE* из stdio.h. Мы хотим, чтобы он автоматически закрывался, как только выходит из области видимости.

Вместо ручного вызова fclose, используем std::unique_ptr с кастомным deleter'ом:


#include <memory>
#include <cstdio>

int main() {
// Кастомный deleter для FILE*
auto fileDeleter = [](FILE* f) {
if (f) {
std::puts("Файл закрывается автоматически!");
std::fclose(f);
}
};

// Умный указатель с кастомным deleter'ом
std::unique_ptr<FILE, decltype(fileDeleter)> file(std::fopen("data.txt", "r"), fileDeleter);

if (!file) {
std::perror("Не удалось открыть файл");
return 1;
}

// Файл будет автоматически закрыт в конце блока main()
}


💡 Такой подход безопаснее, чем fopen/fclose, особенно в реальных проектах с множеством return'ов и исключениями. А главное — код остаётся чистым и идиоматичным.

🔥 А вы используете unique_ptr с кастомным deleter’ом в своём коде? Поделитесь, для чего вы его применяли!

➡️ @cpp_geek
👍8🔥5🥱21
👨‍💻 Сегодня покажу вам удобный способ, как избавиться от болей с #include в больших C++ проектах.

Когда проект растёт, количество инклудов становится пугающим. Компиляция тормозит, зависимости запутаны, порядок подключения начинает влиять на поведение программы… Знакомо?

📌 Решение — Precompiled Headers (PCH).

Это не магия, а вполне рабочая практика. Всё просто:

1. Создаём файл pch.h, в котором собираем самые часто используемые инклюды:

// pch.h
#pragma once
#include <iostream>
#include <vector>
#include <map>
// и т.д.


2. Добавляем его в компиляцию с флагом:

g++ -x c++-header pch.h -o pch.h.gch


3. Теперь любой другой файл, который первым инклудит pch.h, компилируется быстрее.

⚡️ Бонус: современные сборочные системы, вроде CMake, умеют работать с PCH почти автоматически. Достаточно:

target_precompile_headers(my_target PRIVATE pch.h)


🧠 Маленький совет: следите, чтобы в pch.h не попадали редко используемые или изменяющиеся файлы — иначе получите обратный эффект.

Пользовались ли вы PCH в своих проектах? Какой прирост производительности заметили?

➡️ @cpp_geek
👍20
How to: убираем типы с помощью std::decay_t

std::decay_t — один из самых полезных type traits в C++. Он имитирует процесс передачи параметра по значению, «разрушая» исходный тип.

🔄 Что именно делает decay_t?

• Убирает cv-квалификаторы
• Превращает ссылки в соответствующие типы без ссылок
• Преобразует массивы в указатели
• Преобразует функции в указатели на функции

💻 Пример:

#include <type_traits>
#include <iostream>

int main() {
// const int& -> int
static_assert(std::is_same_v<std::decay_t<const int&>, int>);

// int[10] -> int*
static_assert(std::is_same_v<std::decay_t<int[10]>, int*>);

// void(int) -> void(*)(int)
static_assert(std::is_same_v<std::decay_t<void(int)>, void(*)(int)>);

std::cout << "All assertions passed!" << std::endl;
}


🚀 Где это используется?

• В шаблонном программировании для упрощения работы с типами
• В std::make_shared и std::make_unique для определения типа создаваемого объекта
• При написании обобщенного кода, где нужна правильная дедукция типов

🔍 И да, название «decay» («разрушение») действительно отражает суть — тип «разрушается» до базового представления!

➡️ @cpp_geek
👍7👎1
Сейчас покажу вам простой, но очень полезный приём, как аккуратно и безопасно управлять ресурсами в C++ с помощью RAII (Resource Acquisition Is Initialization).

Когда вы работаете с ресурсами (файлы, сокеты, мьютексы и т.д.), важно не забывать освобождать их. Особенно если программа может завершиться по исключению. И вот тут RAII — наш лучший друг.

Рассмотрим пример:


#include <fstream>
#include <string>

void writeToFile(const std::string& filename, const std::string& data) {
std::ofstream file(filename);
if (!file) {
throw std::runtime_error("Unable to open file");
}
file << data;
} // файл автоматически закрывается здесь


Мы открыли файл — и не закрыли его вручную! Почему? Потому что std::ofstream сам закроет его в своём деструкторе. Это и есть RAII в действии.

И теперь представьте: вы можете создавать свои классы с таким же поведением! Например, класс-обёртку над pthread_mutex_t или системным дескриптором.

RAII — это стиль. И это стиль надёжного кода.


➡️ @cpp_geek
👍71💅1
🧵 Сегодня покажу вам простой способ логгировать вызовы функций в C++ — пригодится для отладки и анализа кода.

Часто бывает нужно понять, какие функции вызываются, в каком порядке и с какими параметрами. Вручную вставлять std::cout — неудобно. Вместо этого используем RAII-макрос с выводом в консоль:


#include <iostream>
#include <string>

struct FunctionLogger {
std::string func_name;
FunctionLogger(const std::string& name) : func_name(name) {
std::cout << ">> Entering: " << func_name << '\n';
}
~FunctionLogger() {
std::cout << "<< Exiting: " << func_name << '\n';
}
};

#define LOG_FUNCTION() FunctionLogger logger(__FUNCTION__)


Теперь в любой функции достаточно просто написать LOG_FUNCTION();, и вы получите автоматический лог при входе и выходе:


void do_work() {
LOG_FUNCTION();
// Работаем...
}


Это особенно удобно в больших проектах, когда нужно быстро локализовать ошибку или понять структуру вызовов.

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

➡️ @cpp_geek
👍161👎1🤔1