C++ Academy
16.5K subscribers
629 photos
127 videos
1 file
587 links
По всем вопросам- @haarrp

@itchannels_telegram - 🔥 best it channels

РКН: clck.ru/3FmxJF
Download Telegram
🖥 Некоторые тонкости генерации случайных чисел в C++

C и C++ имеют свои собственные встроенные генераторы случайных чисел. Они реализованы в двух отдельных функциях, которые находятся в заголовочном файле cstdlib:

srand() устанавливает передаваемое пользователем значение в качестве стартового. srand() следует вызывать только один раз — в начале программы (обычно в верхней части функции main())

rand() генерирует следующее случайное число в последовательности. Оно будет находиться в диапазоне от 0 до RAND_MAX (константа в cstdlib, значением которой является 32767).

Вот пример программы, в которой используются обе эти функции:
#include <iostream>
#include <cstdlib> // для функций rand() и srand()

int main()
{
srand(4541); // устанавливаем стартовое значение - 4 541

// Выводим 100 случайных чисел
for (int count=0; count < 100; ++count)
{
std::cout << rand() << "\t";

// Если вывели 5 чисел, то вставляем символ новой строки
if ((count+1) % 5 == 0)
std::cout << "\n";
}
}

// 14867 24680 8872 25432 21865
// ...



Рандомные числа в C++ 11

В C++ 11 добавили тонну нового функционала для генерации случайных чисел, включая алгоритм Вихрь Мерсенна, а также разные виды генераторов случайных чисел (например, равномерные, генератор Poisson и пр.). Доступ к ним осуществляется через подключение заголовочного файла random.

Вот пример генерации случайных чисел в C++11 с использованием Вихря Мерсенна на картинке.

Вихрь Мерсенна генерирует случайные 32-битные целые числа unsigned (а не 15-битные целые числа, как в случае с rand()), что позволяет использовать гораздо больший диапазон значений.
Существует также версия (std::mt19937_64) для генерации 64-битных целых чисел unsigned

@cpluspluc
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1731🔥1💘1
🖥 Некоторые тонкости генерации случайных чисел в C++

C и C++ имеют свои собственные встроенные генераторы случайных чисел. Они реализованы в двух отдельных функциях, которые находятся в заголовочном файле cstdlib:

srand() устанавливает передаваемое пользователем значение в качестве стартового. srand() следует вызывать только один раз — в начале программы (обычно в верхней части функции main())

rand() генерирует следующее случайное число в последовательности. Оно будет находиться в диапазоне от 0 до RAND_MAX (константа в cstdlib, значением которой является 32767).

Вот пример программы, в которой используются обе эти функции:
#include <iostream>
#include <cstdlib> // для функций rand() и srand()

int main()
{
srand(4541); // устанавливаем стартовое значение - 4 541

// Выводим 100 случайных чисел
for (int count=0; count < 100; ++count)
{
std::cout << rand() << "\t";

// Если вывели 5 чисел, то вставляем символ новой строки
if ((count+1) % 5 == 0)
std::cout << "\n";
}
}

// 14867 24680 8872 25432 21865
// ...



Рандомные числа в C++ 11

В C++ 11 добавили тонну нового функционала для генерации случайных чисел, включая алгоритм Вихрь Мерсенна, а также разные виды генераторов случайных чисел (например, равномерные, генератор Poisson и пр.). Доступ к ним осуществляется через подключение заголовочного файла random.

Вот пример генерации случайных чисел в C++11 с использованием Вихря Мерсенна на картинке.

Вихрь Мерсенна генерирует случайные 32-битные целые числа unsigned (а не 15-битные целые числа, как в случае с rand()), что позволяет использовать гораздо больший диапазон значений.
Существует также версия (std::mt19937_64) для генерации 64-битных целых чисел unsigned

@cpluspluc
Please open Telegram to view this post
VIEW IN TELEGRAM
👍931🔥1
🖥 Интеграция HTML/CSS UI в разработку C++ приложений

▶️Итак, если вам требуется быстро интегрировать веб-интерфейс в приложение, написанное на C++, используйте Chromium Embedded Framework (CEF). Подключив экземпляр CEF к вашему проекту, загрузите HTML-документ и запустите цикл обработки событий CEF. Эта гибридная схема разработки позволит вам реализовать интерфейс на HTML/CSS, оставив при этом бекенд на C++. Выглядеть это может как-то так:
#include "include/cef_app.h"

int main(int argc, char* argv[]) {
CefMainArgs main_args(argc, argv);
CefSettings settings;

// выполняем инициализацию
CefInitialize(main_args, settings, nullptr, nullptr);

// создаем окно с UI
CefWindowInfo window_info;
CefBrowserHost::CreateBrowser(window_info, new SimpleHandler(), "file:///your_ui.html", CefBrowserSettings(), nullptr);

// цикл обработки сообщений
CefRunMessageLoop();

// Cef для окончания работы
CefShutdown();
}

Замените "file:///your_ui.html" на путь к вашему HTML-файлу. Вуаля! Ваш интерфейс на HTML/CSS уже встроен в приложение на C++.


▶️CEF зарекомендовал себя как надёжное решение для интеграции Chromium в приложения на C++, однако есть и другие варианты, которые стоит рассмотреть:

Electron: Объединяет Chromium и Node.js, позволяя использовать JavaScript для построения настольных приложений.

Ultralight: Идеально подходит для приложений на OpenGL или DirectX, где важна эффективность рендеринга HTML.

Qt WebEngine: Дает доступ к возможностям Qt с поддержкой интеграции веб-контента.

Sciter: Имеет компактный движок для работы с HTML/CSS, поддерживающий такие возможности, как SVG и WebSocket.

Awesomium, движки на базе WebKit или Gecko: Предлагают функциональность, схожую с CEF, однако с некими уникальными отличиями.


▶️При выборе фреймворка обратите внимание на его уровень зрелости, поддержку со стороны сообщества и доступность документации.

📎 Читать подробнее

@cpluspluc
Please open Telegram to view this post
VIEW IN TELEGRAM
9👍6🔥21
🖥 Чем пустой конструктор по умолчанию отличается от =default;?

▶️Вопрос: чем пустое тело отличается от того, что получается с =default (для конструктора по умолчанию)
X::X() {}
// и
X::X() = default;



▶️Что ж, наличие конструктора по умолчанию с пустым телом автоматически делает класс нетривиальным. Со всеми вытекающими отсюда особенностями. Например делает тип не POD. А также исключает возможность использовать агрегатную инициализацию:
#include <iostream>
#include <type_traits>

struct X {
//X() {}
X() = default;
int a;
int b;
};

int main( ) {
X x { 1, 2 }; // ошибка, если X - не тривиальный класс.
std::cout << std::boolalpha <<
std::is_trivial<X>::value << "\n";
}


При определении конструктора как = default тривиальность класса сохраняется, если она была до этого. В данном случае, это равносильно отсутствию явного упоминания конструктора в определении класса.

Если конструктор по умолчанию определен как = default вне определения класса, всё равно будет считаться, что конструктор предоставлен пользователем, и это тоже делает класс нетривиальным.

@cpluspluc
Please open Telegram to view this post
VIEW IN TELEGRAM
👌8👍751🔥1
🖥 cpr — curl здорового человека

C++ Requests — это простая обертка вокруг libcurl, вдохновленная Python Requests.

Вот так можно отправить GET-запрос при помощи cpr:
#include <cpr/cpr.h>

int main(int argc, char** argv) {
cpr::Response r = cpr::Get(cpr::Url{"https://api.github.com/repos/libcpr/cpr/contributors"},
cpr::Authentication{"user", "pass", cpr::AuthMode::BASIC},
cpr::Parameters{{"anon", "true"}, {"key", "value"}});
r.status_code; // 200
r.header["content-type"]; // application/json; charset=utf-8
r.text; // JSON text string
}


🖥 GitHub
🟡 Доки

@cpluspluc
Please open Telegram to view this post
VIEW IN TELEGRAM
👍262
🔓Что выведет следующий код?


#include <iostream>
using namespace std;

class Base {
public:
Base() {
cout << "Base constructor: ";
call();
}
virtual void call() { cout << "Base::call\n"; }
};

class Derived : public Base {
int x = init();

int init() {
cout << "Derived::init\n";
return 42;
}

public:
Derived() {
cout << "Derived constructor\n";
}

void call() override {
cout << "Derived::call, x = " << x << "\n";
}
};

int main() {
Derived d;
return 0;
}


🔢Варианты ответа:

A)

Derived::init
Derived constructor


B)

Derived::init
Derived constructor


C)

Base constructor: Derived::call, x = 42
Derived constructor


D)

Derived::call, x = <undefined>
Derived::init
Derived constructor


Правильный ответ: B

💡Почему?
В момент вызова конструктора Base, объект ещё не стал Derived. Виртуальная функция вызывается в контексте Base.
👍22🔥4🥰1
✔️ ЗАДАЧА: Что выведет код?



#include <iostream>
#include <vector>

struct Tracer {
Tracer(const char* name) : name(name) {
std::cout << "Constructing " << name << "\n";
}
~Tracer() {
std::cout << "Destructing " << name << "\n";
}
const char* name;
};

struct Example {
Tracer t1{"t1"};
std::vector<Tracer> list;
Tracer t2{"t2"};

Example() : list{Tracer("temp1"), Tracer("temp2")} {
std::cout << "Inside constructor\n";
}
};

int main() {
Example e;
std::cout << "End of main\n";
return 0;
}


---

ОТВЕТ:

Constructing t1
Constructing temp1
Constructing temp2
Inside constructor
Constructing t2
End of main
Destructing t2
Destructing temp2
Destructing temp1
Destructing t1

---

Почему так:

• Поля инициализируются в порядке объявления в структуре, а не в списке инициализации
• std::initializer_list создаёт временные объекты, которые копируются в вектор
• t2 создаётся после строки Inside constructor
• Деструкторы вызываются в обратном порядке: t2 → temp2 → temp1 → t1

Хитрость — в порядке инициализации, временных объектах и destructuring-порядке!


@cpluspluc
Please open Telegram to view this post
VIEW IN TELEGRAM
👍204🔥2🎄1
🖥 Задача для собеседования: "Быстрая очередь с удалением элемента за O(1)"

🔖 Условие:

Реализуйте структуру данных — очередь (`queue`), поддерживающую три операции:

- enqueue(x) — добавить элемент в конец очереди (должно работать за O(1));
- dequeue() — удалить элемент из начала очереди и вернуть его (должно работать за O(1));
- remove(x) — удалить первое вхождение элемента x из очереди за O(1).

Ограничения:
- Элементы могут повторяться.
- Если элемент отсутствует, remove(x) не делает ничего.
- Операции должны оставаться эффективными на больших объемах данных (миллионы элементов).
- Можно использовать стандартные структуры данных STL (`list`, unordered_map, unordered_set и т.д.).

▪️ Дополнительные вопросы к кандидату:

- Как обеспечить O(1) удаление произвольного элемента из очереди?
- Как избежать утечек памяти или "битых" итераторов после удаления элементов?
- Что будет, если очередь будет содержать множество одинаковых элементов?
- Как бы вы изменили решение, если бы нужно было удалять все вхождения элемента, а не только первое?

---

▪️ Разбор возможного решения:


Основная архитектура:

- Используем std::list<int> data для хранения реальной очереди.
- Почему list? Потому что позволяет за O(1) удалять элементы по итератору.
- Используем std::unordered_map<int, std::list<std::list<int>::iterator>> index.
- Для каждого значения храним список итераторов на его вхождения в data.

Операции:

- enqueue(x):
- Добавляем x в конец data.
- Сохраняем итератор на него в index[x].
- Все действия — за O(1).

- dequeue():
- Удаляем элемент из начала data.
- Находим соответствующий итератор в index, удаляем его.
- Если в списке итераторов для этого значения больше нет элементов, удаляем ключ из index.

- remove(x):
- Если в index[x] есть итераторы:
- Берем первый итератор.
- Удаляем его из data и из списка итераторов.
- Если список стал пустым — удаляем запись x из index.
- Все действия — за O(1).

---

▪️ Возможные подводные камни:

- Не проверять, пустой ли index[x], перед удалением итератора.
- Не удалять ключи из unordered_map, что приводит к накоплению "пустых" списков в памяти.
- Использовать vector вместо list — тогда удаление из середины будет занимать O(n).

---

▪️ Мини-пример интерфейса класса:


#include <list>
#include <unordered_map>

class FastQueue {
private:
std::list<int> data;
std::unordered_map<int, std::list<std::list<int>::iterator>> index;

public:
void enqueue(int x) {
auto it = data.insert(data.end(), x);
index[x].push_back(it);
}

int dequeue() {
if (data.empty()) throw std::out_of_range("Queue is empty");
int val = data.front();
auto it = index[val].front();
index[val].pop_front();
if (index[val].empty()) {
index.erase(val);
}
data.pop_front();
return val;
}

void remove(int x) {
if (index.count(x) == 0) return;
auto it = index[x].front();
data.erase(it);
index[x].pop_front();
if (index[x].empty()) {
index.erase(x);
}
}
};


▪️ Дополнительные вопросы на усложнение:


- Что если нужно сделать removeAll(x) — удаление всех вхождений элемента?
- Как изменится решение, если в очереди будут сложные объекты вместо int?
- Как минимизировать использование памяти, если очередь очень большая?
- Как обеспечить безопасность потоков (thread-safety) для многопоточного варианта очереди?

@cpluspluc
Please open Telegram to view this post
VIEW IN TELEGRAM
👍195🔥3
🎯 Задача: "Числа-близнецы с кастомным компаратором" (C++ для продвинутых)

У вас есть массив N целых чисел. Нужно найти все пары чисел (A, B), таких что:

1. |A - B| минимально среди всех пар в массиве
2. Aчетное, а Bнечетное
3. Если таких пар несколько, вернуть все, отсортированные по A, затем по B

Ограничения:
- 1 <= N <= 10^5
- -10^9 <= A[i] <= 10^9

Пример:

Вход:

arr = [4, 7, 9, 2, 8, 3]


Вывод:


(2,3)
(4,3)
(4,7)
(8,7)
(8,9)


Минимальная разница = 1

🧠 Уловки задачи:

Наивное сравнение O(N²) слишком медленно

Нужно использовать сортировку + два указателя или бинарный поиск

✍️ Решение:

Идея решения — сначала разделить все числа на четные и нечетные, отсортировать их, а затем с помощью двух указателей найти пары с минимальной разницей. После этого собрать все такие пары и отсортировать результат по условию.

Код:
```cpp
#include <iostream>
#include <vector>
#include <algorithm>
#include <cmath>

using namespace std;

int main() {
int N;
cin >> N;
vector<int> even, odd;

for (int i = 0; i < N; ++i) {
int x;
cin >> x;
if (x % 2 == 0) even.push_back(x);
else odd.push_back(x);
}

sort(even.begin(), even.end());
sort(odd.begin(), odd.end());

vector<pair<int, int>> result;
int min_diff = INT_MAX;

int i = 0, j = 0;
while (i < even.size() && j < odd.size()) {
int a = even[i];
int b = odd[j];
int diff = abs(a - b);

if (diff < min_diff) {
min_diff = diff;
result.clear();
result.emplace_back(a, b);
} else if (diff == min_diff) {
result.emplace_back(a, b);
}

if (a < b) i++;
else j++;
}

sort(result.begin(), result.end());

for (auto& p : result) {
cout << "(" << p.first << "," << p.second << ")\n";
}

return 0;
}```

🔍 Пошаговый разбор:

Считываем входные данные

Разделяем числа на четные и нечетные

Сортируем оба массива

Используем два указателя, чтобы найти минимальную разницу за O(N)

Если нашли пару с меньшей разницей — сбрасываем результат и добавляем её

Если нашли пару с такой же разницей — просто добавляем

Финально сортируем все найденные пары по A, затем по B

Почему задача хитрая: Нужно оптимальное решение вместо лобового перебора
Включает тонкую работу с индексами
Требует правильно обрабатывать несколько минимальных пар
Объединяет знания сортировки, двух указателей и поиска пар

💪 Challenge для профи: 👉 Попробуйте реализовать эту задачу с помощью multiset + lower_bound / upper_bound, чтобы избежать сортировки массивов и упростить логику поиска ближайших чисел.
👍9👀73🔥3🌭1
🦾 Задача с подвохом: Виртуальные функции и конструкторы

Условие:

Что выведет следующий код и почему?


#include <iostream>

class Base {
public:
Base() {
std::cout << "Base constructor\n";
foo();
}
virtual void foo() {
std::cout << "Base foo\n";
}
};

class Derived : public Base {
public:
Derived() {
std::cout << "Derived constructor\n";
}
void foo() override {
std::cout << "Derived foo\n";
}
};

int main() {
Derived d;
return 0;
}


Вопрос:
Что будет выведено? Почему результат может удивить даже опытных C++ разработчиков?

🔍 Разбор:

1️⃣ Мы создаём объект
Derived d;.
Это вызывает конструктор
Derived, но сначала выполняется конструктор `Base` (по правилам иерархии).

2️⃣ В конструкторе
Base есть вызов foo();.
Может показаться, что поскольку объект на самом деле
Derived, вызовется Derived::foo().

Но! Вот главный подвох:

➡️ В C++, когда вы вызываете виртуальную функцию из конструктора (или деструктора), она не виртуальна для объекта, который ещё не полностью сконструирован.

На момент вызова
foo() объект всё ещё только Base, потому что Derived ещё не инициализирован.

Пошаговое выполнение:

- Вызов конструктора
Base:
```
Base constructor
```
- Вызов
foo() внутри конструктора Base:
Это вызовет Base::foo(), а не
Derived::foo():
```
Base foo
```
- После завершения конструктора
Base, вызывается конструктор Derived:
```
Derived constructor
```

Итоговый вывод:

```
Base constructor
Base foo
Derived constructor
```

💥 Подвох:

• Многие ожидают, что виртуальные функции работают «магически» всегда.
• Но при вызове из конструктора (или деструктора) виртуальные функции не полиморфны, потому что объект ещё не «стал» Derived полностью.

🛡️ Что нужно помнить:

Никогда не полагайтесь на вызовы виртуальных функций в конструкторах/деструкторах для вызова методов производных классов. Это источник трудноуловимых багов.

Вывод:

C++ строго следует правилам объектной модели: пока объект конструируется (или разрушается), он считается экземпляром того класса, конструктор которого выполняется в данный момент. Это поведение важно помнить при проектировании иерархий классов!


@cpluspluc
👍257🔥6🖕1🎄1
🧠 Задача с подвохом для продвинутых C++ разработчиков

🔹 Уровень: Advanced
🔹 Темы: std::vector, управление памятью, конструкторы/деструкторы, reserve() vs resize()

📌 Условие

Рассмотрим следующий код:


#include <iostream>
#include <vector>

struct Foo {
Foo() { std::cout << "Ctor\n"; }
~Foo() { std::cout << "Dtor\n"; }
Foo(const Foo&) { std::cout << "Copy\n"; }
Foo(Foo&&) noexcept { std::cout << "Move\n"; }
};

int main() {
std::vector<Foo> v;
v.reserve(3); // Резервируем место под 3 элемента

std::cout << "--- Pushing ---\n";
for (int i = 0; i < 3; ++i) {
v.push_back(Foo());
}

std::cout << "--- Done ---\n";
}


Вопросы

1. Что будет выведено на экран?
2. Почему reserve() не предотвращает конструкторы копирования/перемещения?
3. Что изменится, если заменить reserve(3) на resize(3)?

🔍 Разбор

Ожидаемый вывод:

--- Pushing ---
Ctor
Move
Dtor
Ctor
Move
Dtor
Ctor
Move
Dtor
--- Done ---
Dtor
Dtor
Dtor


🔧 Почему так происходит

- Foo() создаёт временный объект.
- push_back() вызывает перемещающий конструктор Move.
- Временный объект уничтожается (вызывается `Dtor`).
- reserve(3) выделяет память, но не создаёт объектов.

🔄 Если заменить `reserve(3)` на `resize(3)`

- resize(3) создаст 3 объекта Foo через конструктор по умолчанию.
- push_back(Foo()) добавит четвёртый, возможно вызовет realocation.
- Это может привести к копированию или перемещению уже созданных элементов.

⚠️ Подвох

Многие ошибочно считают, что reserve(n) создаёт n объектов. Но это не такreserve() только выделяет память, не вызывая конструкторы. Именно поэтому внутри push_back всё равно происходит перемещение или копирование.

🧠 Вывод

- reserve() — экономия на реаллокациях, без создания объектов.
- resize() — создаёт n объектов, вызывает конструкторы.
- Не путай эти методы — от этого зависит и производительность, и семантика.


// Резервирует память, не создаёт объекты
v.reserve(10);

// Создаёт 10 объектов Foo
v.resize(10);


📌 Совет:
Если не хочешь лишнего перемещения, используй emplace_back():


v.emplace_back(); // Вызывает конструктор Foo напрямую внутри вектора



@cpluspluc
👍164🔥1
🧠 Хитрая задача на C++: "Исчезающее число среди строк"

Условие
Дан список строк, представляющих числа от 1 до 100 включительно. Одного числа нет.
Твоя задача — найти, какого именно числа не хватает.
Нельзя использовать сортировку, std::accumulate, std::unordered_map, std::stoi можно.

Пример:
Массив: ["1", "2", "3", ..., "98", "99"] (без "100")
Ответ: 100

Формат:


int findMissingNumber(const std::vector<std::string>& data);


Решение с XOR


#include <iostream>
#include <vector>
#include <string>

int findMissingNumber(const std::vector<std::string>& data) {
int xor_full = 0;
int xor_data = 0;

for (int i = 1; i <= 100; ++i) {
xor_full ^= i;
}

for (const auto& s : data) {
xor_data ^= std::stoi(s);
}

return xor_full ^ xor_data;
}


Пример использования:


int main() {
std::vector<std::string> data;
for (int i = 1; i <= 100; ++i) {
if (i != 57) { // Удалим 57
data.push_back(std::to_string(i));
}
}

std::cout << "Пропущено: " << findMissingNumber(data) << std::endl;
return 0;
}


Почему работает:
XOR — идеальное решение, когда нужно найти одну потерянную величину среди уникальных значений.
a ^ a = 0, 0 ^ b = b. Поэтому:
(XOR всех от 1 до 100) ^ (XOR из данных) = отсутствующее число.

@cpluspluc
👍16🔥43🤔2
🔍 C++ Задача для профи: кто вызовется?

У тебя есть следующий код:


#include <iostream>
#include <type_traits>

template<typename T>
typename std::enable_if<std::is_integral<T>::value, void>::type
process(T value) {
std::cout << "Integral: " << value << std::endl;
}

template<typename T>
typename std::enable_if<std::is_floating_point<T>::value, void>::type
process(T value) {
std::cout << "Floating-point: " << value << std::endl;
}

int main() {
process(42); // (1)
process(3.14); // (2)
process('A'); // (3)
}


Вопросы:
1. Что напечатает программа?
2. Почему вызов process('A') может удивить?
3. Что произойдёт, если передать true?

🧠 Подвох:

- char — это integral type, а не string и не отдельный класс
- true — это тоже int`-подобный тип: `std::is_integral<bool>::value == true
- У тебя может возникнуть разный вывод в зависимости от перегрузки

Разбор:

- `process(42)` → `Integral: 42`
- `process(3.14)` → `Floating-point: 3.14`
- `process('A')` → `Integral: 65` (ASCII код символа!) ⚠️
- `process(true)` → `Integral: 1` (да, это `bool`)

🎯 Что проверяет задача:

- Понимание `SFINAE` и `enable_if`
- Типовую систему C++ (что именно считается integral)
- Разницу между `char`, `bool`, `int`, `float` в шаблонах
- Предсказание поведения перегрузки на уровне типов

@cpluspluc
👍122🔥2
🔍 C++ Задача для профи: кто вызовется?

У тебя есть следующий код:


#include <iostream>
#include <type_traits>

template<typename T>
typename std::enable_if<std::is_integral<T>::value, void>::type
process(T value) {
std::cout << "Integral: " << value << std::endl;
}

template<typename T>
typename std::enable_if<std::is_floating_point<T>::value, void>::type
process(T value) {
std::cout << "Floating-point: " << value << std::endl;
}

int main() {
process(42); // (1)
process(3.14); // (2)
process('A'); // (3)
}

Вопросы:
1. Что напечатает программа?
2. Почему вызов process('A') может удивить?
3. Что произойдёт, если передать true?

🧠 Подвох:

- char — это integral type, а не string и не отдельный класс
- true — это тоже int`-подобный тип: `std::is_integral<bool>::value == true
- У тебя может возникнуть разный вывод в зависимости от перегрузки

Разбор:

- process(42)Integral: 42
- process(3.14)Floating-point: 3.14
- process('A')Integral: 65 (ASCII код символа!) ⚠️
- process(true)Integral: 1 (да, это `bool`)

🎯 Что проверяет задача:

- Понимание SFINAE и enable_if
- Типовую систему C++ (что именно считается integral)
- Разницу между char, bool, int, float в шаблонах
- Предсказание поведения перегрузки на уровне типов

@cpluspluc
12👍6🔥3
⚙️ Задача для C++ разработчиков: «Непонятная ошибка, которая портит данные»


🎯 Цель: Найти и объяснить причину скрытого неопределённого поведения, которое проявляется не сразу

📍 Ситуация:

Ты разрабатываешь кроссплатформенное приложение на C++17, которое обрабатывает массивы бинарных данных.
На тестах — всё работает. Но у части пользователей (особенно на Linux) возникают:

- Повреждённые файлы после сериализации
- Непредсказуемые вылеты при больших объёмах данных
- Валидация данных случайно "съезжает" (байты путаются)

Вот фрагмент кода:


#include <vector>
#include <cstring>

struct Packet {
uint32_t id;
char data[64];
};

std::vector<uint8_t> serialize(const Packet& p) {
std::vector<uint8_t> buffer(sizeof(Packet));
std::memcpy(buffer.data(), &p, sizeof(Packet));
return buffer;
}


🔍 Визуально всё нормально. В unit-тестах — ок. На CI — ок.
Но на проде данные иногда повреждены, и никто не может воспроизвести баг стабильно.

🧩 Задача:

1. Почему memcpy здесь небезопасен, хотя кажется логичным?
2. Что может отличаться на разных платформах и влиять на поведение?
3. Как бы ты безопасно сериализовал структуру в std::vector<uint8_t>?
4. Как это можно поймать с помощью valgrind / asan / -fsanitize=undefined?
5. Как написать cross-platform-safe сериализацию?

💡 Подсказка:
В C++ `struct Packet` может иметь **padding** и **alignment**, которые отличаются на архитектурах. `memcpy` по `sizeof(Packet)` может захватить лишние или мусорные байты.

🛠 Решение:

1. `struct Packet` не является POD-структурой с гарантированным layout — в ней может быть **неинициализированный padding**, который `memcpy` тоже копирует.

2. Проблема усиливается на системах с разным выравниванием: x86 vs ARM, GCC vs MSVC.

3. Более безопасный способ — сериализовать поля по отдельности:

std::vector<uint8_t> serialize(const Packet& p) {
std::vector<uint8_t> buffer;
buffer.insert(buffer.end(), reinterpret_cast<const uint8_t*>(&
p.id),
reinterpret_cast<const uint8_t*>(&
p.id) + sizeof(p.id));
buffer.insert(buffer.end(),
p.data, p.data + sizeof(p.data));
return buffer;
}



4. Или использовать `std::ostringstream` / `std::span` / `protobuf` / `flatbuffers`.

5. Проверка с `-fsanitize=undefined` даст warning:
```
memcpy: reading padding bytes from stack frame
```

📌 **Вывод:**
В C++ `memcpy` на структуру — это **ловушка**, если ты не контролируешь padding. Никогда не сериализуй структуры напрямую через память, если это не `
#pragma pack` и не строго определённый layout.

💬 Это вопрос для собеседования на позицию C++ системного разработчика с уклоном в безопасность и низкоуровневую разработку.

@cpluspluc
👍297🔥7😁1
🧠 C++ Задача для продвинутых: загадка с `std::move` и `const`

Задача: что выведет этот код и почему?


#include <iostream>
#include <string>
#include <utility>

std::string identity(std::string&& s) {
std::cout << "rvalue reference\n";
return std::move(s);
}

std::string identity(const std::string& s) {
std::cout << "const lvalue reference\n";
return s;
}

int main() {
const std::string a = "hello";
std::string b = identity(std::move(a));
std::cout << "Result: " << b << std::endl;
}


🔍 Варианты ответов:
• a) rvalue reference
• b) const lvalue reference
• c) Ошибка компиляции
• d) Поведение зависит от компилятора

💡 Разбор:

a объявлена как const std::string. Даже после std::move(a) она остаётся const, потому что std::move не снимает константность, он лишь преобразует к rvalue:


std::move(const std::string&) → const std::string&&


Смотрим на перегрузки identity:

identity(std::string&&) — принимает неконстантный rvalue
identity(const std::string&) — принимает константный lvalue

const std::string&& не подходит к std::string&&, потому что нельзя привязать rvalue-ссылку к `const`-объекту без соответствия типов.

👉 Вызовется вторая функция, с const std::string&

Ответ: b) const lvalue reference

🧠 Вывод: std::move не делает объект мутабельным. Если объект const, он остаётся const, и rvalue-перегрузки не срабатывают.

📌 Совет: перед использованием std::move всегда учитывайте `const`-квалификатор. Он может "сломать" ожидаемую семантику перемещения.
👍185🔥3
🚀 Хотите мониторить загрузку вашей NVIDIA GPU прямо из C++?

Вот минимальный пример на C++, который использует nvidia-smi через системный вызов для получения текущей загрузки GPU и использованной памяти.

🛠 Требования:
• Установленный драйвер NVIDIA и утилита nvidia-smi
• C++17 или выше

📄 Код:


#include <iostream>
#include <cstdlib>
#include <memory>
#include <array>

void get_gpu_utilization() {
std::array<char, 128> buffer;
std::string result;

std::unique_ptr<FILE, decltype(&pclose)> pipe(
popen("nvidia-smi --query-gpu=utilization.gpu,memory.used,memory.total --format=csv,nounits,noheader", "r"),
pclose
);

if (!pipe) {
std::cerr << " Ошибка при вызове nvidia-smi" << std::endl;
return;
}

int gpu_id = 0;
while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) {
std::string line(buffer.data());
size_t pos1 = line.find(',');
size_t pos2 = line.rfind(',');

std::string util = line.substr(0, pos1);
std::string mem_used = line.substr(pos1 + 1, pos2 - pos1 - 1);
std::string mem_total = line.substr(pos2 + 1);

std::cout << "🖥 GPU " << gpu_id++ << ": " << util << "% load | "
<< mem_used << " MiB / " << mem_total << " MiB";
}
}

int main() {
get_gpu_utilization();
return 0;
}



📦 Компиляция:

bash
Копировать
Редактировать
g++ gpu_monitor.cpp -o gpu_monitor -std=c++17
📌 Вывод:

🖥 GPU 0: 17% load | 512 MiB / 8192 MiB

🧠 Подходит для:
• Мониторинга GPU в реальном времени
• Интеграции в бэкенды, боты, системы логгинга
• Лёгкой отладки ML/AI-приложений на C++
🥴15🔥14🤣5🥰32😁2👾2👻1🙉1
🔥 Совет дня: быстро посчитать частоты элементов в C++ с `std::unordered_map`

Когда нужно подсчитать, сколько раз каждый элемент встречается в векторе — не пиши вручную поиск. Просто используй unordered_map:


#include <iostream>
#include <vector>
#include <unordered_map>

int main() {
std::vector<std::string> items = {"apple", "banana", "apple", "orange", "banana", "apple"};
std::unordered_map<std::string, int> freq;

for (const auto& item : items) {
++freq[item];
}

for (const auto& [key, count] : freq) {
std::cout << key << ": " << count << "\n";
}
// 👉 apple: 3, banana: 2, orange: 1
}


📌 Удобно для:
— анализа строк
— парсинга логов
— простых хэш-агрегаций


@cpluspluc
18😴4🙈1🗿1
🧠 C++ Задача для продвинутых: безопасный счётчик с автоматическим сбросом

Условие:
Реализуйте потокобезопасный счётчик, который:
1. Увеличивается при вызове increment()
2. Возвращает текущее значение при get()
3. Автоматически сбрасывается в 0 через N миллисекунд после последнего increment()
4. Не сбрасывается, если за это время пришёл новый increment()

🔧 Условия:
- Нельзя использовать сторонние библиотеки (только стандарт C++17+)
- Нельзя вручную управлять потоками (используйте std::thread, std::mutex, `std::condition_variable`)
- Нужно обеспечить корректный RAII (автоматическое завершение потоков при разрушении объекта)

🔍 Пример:

SafeCounter counter(1000); // сброс через 1000 мс
counter.increment(); // value = 1
std::this_thread::sleep_for(500ms);
counter.increment(); // value = 2, таймер сброса перезапускается
std::this_thread::sleep_for(1500ms);
std::cout << counter.get(); // value = 0


💡 Подсказка:
Нужно реализовать "отложенный сброс", который перезапускается при каждом increment(). Используйте фоновый поток и condition_variable.

Ожидаемое решение (упрощённый скелет):

#include <iostream>
#include <thread>
#include <atomic>
#include <mutex>
#include <condition_variable>
#include <chrono>

class SafeCounter {
public:
SafeCounter(int timeout_ms) : timeout(timeout_ms), value(0), stop(false) {
worker = std::thread([this] { this->watch(); });
}

~SafeCounter() {
{
std::lock_guard<std::mutex> lock(mtx);
stop = true;
}
cv.notify_all();
worker.join();
}

void increment() {
{
std::lock_guard<std::mutex> lock(mtx);
++value;
last_action = std::chrono::steady_clock::now();
}
cv.notify_all();
}

int get() const {
return value.load();
}

private:
void watch() {
std::unique_lock<std::mutex> lock(mtx);
while (!stop) {
cv.wait_for(lock, timeout, [this] {
return stop || std::chrono::steady_clock::now() - last_action >= timeout;
});

if (stop) break;

if (std::chrono::steady_clock::now() - last_action >= timeout) {
value = 0;
}
}
}

std::chrono::milliseconds timeout;
std::chrono::steady_clock::time_point last_action = std::chrono::steady_clock::now();
std::atomic<int> value;
std::mutex mtx;
std::condition_variable cv;
std::thread worker;
bool stop;
};



🎯 Отличная задача, чтобы потренировать:

понимание RAII

работу с condition_variable

атомарные переменные

и правильную остановку фоновых потоков

Можешь доработать под конкретные кейсы — например, логирование событий сброса или работу с несколькими счётчиками.
17👍5🔥2
🧠 Задача: Реализация `TypeList` с поддержкой операций на этапе компиляции

📌 Описание

Реализуйте шаблонный класс TypeList, который представляет собой список типов на этапе компиляции (compile-time type list). Он должен поддерживать следующие операции:

1. Получение длины списка (`length`)
2. Получение типа по индексу (`at<N>`)
3. Добавление типа в начало списка (`push_front<T>`)
4. Удаление первого типа (`pop_front`)
5. Проверка наличия типа в списке (`contains<T>`)
6. Фильтрация по условию (например, только целочисленные типы) (`filter<Predicate>`)

Всё это должно работать на этапе компиляции, без использования std::tuple или других runtime-контейнеров.

🧩 Пример использования


#include <type_traits>
#include <iostream>

// Пример предиката
template<typename T>
struct is_integral : std::is_integral<T> {};

int main() {
using MyList = TypeList<int, char, float, double, short>;

static_assert(MyList::length == 5);
static_assert(std::is_same_v<MyList::at<0>, int>);
static_assert(std::is_same_v<MyList::at<2>, float>);

using WithBool = MyList::push_front<bool>;
static_assert(WithBool::length == 6);
static_assert(std::is_same_v<WithBool::at<0>, bool>);

using Popped = WithBool::pop_front;
static_assert(std::is_same_v<Popped, MyList>);

static_assert(MyList::contains<int>);
static_assert(!MyList::contains<bool>);

using OnlyIntegral = MyList::filter<is_integral>;
static_assert(std::is_same_v<OnlyIntegral, TypeList<int, char, short>>);

return 0;
}


🛠 Требования к реализации
Используйте только возможности шаблонов и constexpr.
Не используйте std::tuple, std::array, if constexpr (если хотите усложнить — можно).
Предпочтительно использование C++17 или выше.
Код должен компилироваться и проходить все static_assert.

🧪 Бонусное задание
Реализуйте print_types() — функцию, которая выводит все типы из списка в std::cout (можно использовать typeid, PRETTY_FUNCTION или другие хаки).

@cpluspluc
8🔥4👍2