TGViewer
Channel Public Channel
C/C++ | Вопросы собесов

C/C++ | Вопросы собесов

@easy_c_plus

Сайт: https://easyoffer.ru/
Все каналы: t.me/+xGeAw6ckJ4liYzQy

Контакт для рекламы: @sendme_ads
Subscribers
4.19K
Photos
37
Videos
1
Links
1.6K

Showing posts older than #2069 · Back to latest

Older Posts 20 shown
Post #2068 806
🤔 Что будет если для беззнаковой переменной равной 0 сделать декремент?

Если вы сделаете декремент (--) для беззнаковой переменной, значение которой равно 0, это приведет к переполнению, и переменная примет максимальное значение, которое может быть представлено этим типом.

Рассмотрим, что произойдет с переменной типа unsigned int, если её значение равно 0 и мы применим к ней декремент:
#include <iostream>

int main() {
unsigned int x = 0;
std::cout << "x before decrement: " << x << std::endl;
x--; // Декрементирование беззнаковой переменной 0
std::cout << "x after decrement: " << x << std::endl; // Переполнение
return 0;
}


Для беззнаковой переменной unsigned int: Диапазон значений: от 0 до 4294967295 (для 32-битной реализации).

Когда переменная x равна 0 и мы выполняем x--, происходит следующее:
🟠Значение 0 уменьшается на 1.
🟠Поскольку unsigned int не может представлять отрицательные значения, происходит переполнение.
🟠Переполнение для беззнакового типа приводит к "оборачиванию" значения через максимальное значение типа.
🟠Максимальное значение для unsigned int (при 32-битной реализации) равно 4294967295.

Таким образом, после выполнения x--, значение x становится 4294967295.
Результат выполнения приведенного выше кода будет следующим:
x before decrement: 0
x after decrement: 4294967295


Для беззнаковых типов в C++ (например, unsigned char, unsigned short, unsigned int, unsigned long): Если значение переменной равно 0 и к ней применяется декремент, результатом будет максимальное значение этого типа.

Ставь 👍 и забирай 📚 Базу знаний
Post #2067 699
🤔 Коллизия в хеш-таблицах

Коллизия в хеш-таблицах возникает, когда два разных ключа имеют одинаковое хэш-значение.
Методы разрешения коллизий:
1. Цепочки (chaining): элементы с одинаковым хэш-значением хранятся в связанном списке или другой структуре.
2. Открытая адресация (open addressing): ищется следующая доступная ячейка для хранения элемента.
Коллизии снижают производительность, поэтому важно выбирать хорошие хэш-функции.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний
  • 👍 3
Post #2066 749
🤔 В каком порядке конструируется объект и в каком разрушается?

При создании и удалении объекта в C++ выполняется несколько этапов:
Конструирование: от базового класса к производному.
Деструкторы вызываются в обратном порядке: от производного к базовому.

🚩Порядок вызова конструкторов

При создании объекта сначала создаются его базовые классы, затем члены класса, потом сам объект.
#include <iostream>

class Base {
public:
Base() { std::cout << "Конструктор Base\n"; }
~Base() { std::cout << "Деструктор Base\n"; }
};

class Derived : public Base {
public:
Derived() { std::cout << "Конструктор Derived\n"; }
~Derived() { std::cout << "Деструктор Derived\n"; }
};

int main() {
Derived obj;
}


Вывод
Конструктор Base
Конструктор Derived
Деструктор Derived
Деструктор Base


🚩Порядок вызова конструкторов для членов класса

Члены класса инициализируются в порядке их объявления в классе (а не в порядке в списке инициализации).
class A {
public:
A() { std::cout << "A()\n"; }
~A() { std::cout << "~A()\n"; }
};

class B {
public:
B() { std::cout << "B()\n"; }
~B() { std::cout << "~B()\n"; }
};

class C {
A a;
B b;
public:
C() { std::cout << "C()\n"; }
~C() { std::cout << "~C()\n"; }
};

int main() {
C obj;
}


Вывод
A()
B()
C()
~C()
~B()
~A()


Ставь 👍 и забирай 📚 Базу знаний
Post #2065 731
🤔 Если std::move() не перемещает данные, то что их перемещает?

Это просто явное преобразование объекта в rvalue-ссылку, что позволяет использовать семантику перемещения.
Реальное перемещение выполняется методами, поддерживающими rvalue-ссылки, например, конструктором перемещения или оператором присваивания.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний
Post #2064 814
🤔 В чем разница map и unordered_map?

std::map и std::unordered_map – оба являются ассоциативными контейнерами в C++, но они устроены по-разному и имеют разные скорости работы.

🚩Как устроены контейнеры?

🟠`std::map` – красно-чёрное дерево (сбалансированное BST)
Все ключи хранятся упорядоченно.
Операции insert, erase, find выполняются за O(log n).
Упорядоченность важна, если требуется поиск в диапазоне (lower_bound, upper_bound).
#include <map>
#include <iostream>

int main() {
std::map<int, std::string> m;
m[2] = "B";
m[1] = "A";
m[3] = "C";

for (const auto& [key, value] : m) {
std::cout << key << " -> " << value << '\n';
}
}


Вывод (отсортирован!)
1 -> A  
2 -> B
3 -> C


🟠`std::unordered_map` – хеш-таблица
Нет сортировки, но поиск выполняется быстро – O(1) (в среднем).
Использует хеш-функцию, поэтому доступ к элементам зависит от хеширования.
Может быть медленным при коллизиях (O(n) в худшем случае).
#include <unordered_map>
#include <iostream>

int main() {
std::unordered_map<int, std::string> um;
um[2] = "B";
um[1] = "A";
um[3] = "C";

for (const auto& [key, value] : um) {
std::cout << key << " -> " << value << '\n';
}
}


Вывод (порядок непредсказуем!)
2 -> B  
1 -> A
3 -> C


🚩Когда использовать `map`, а когда `unordered_map`?

🟠Используйте `std::map`, если:
Нужно сортированное хранение ключей.
Требуется поиск по диапазону (lower_bound, upper_bound).
Хеш-функция для ключей сложна или недоступна (например, std::pair без кастомного хешера).

Ставь 👍 и забирай 📚 Базу знаний
  • 👍 1
Post #2063 719
🤔 Что можешь рассказать о little-endian и big-endian?

Little-endian и big-endian — это порядки байтов в мультибайтовых данных, используемые в компьютерной архитектуре для хранения данных. В little-endian младший байт сохраняется по низкому адресу, а старший байт по высокому. В big-endian старший байт сохраняется по низкому адресу, а младший по высокому. Выбор порядка байтов влияет на совместимость и производительность системы в различных сценариях использования.

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний
  • 👍 2
  • 🔥 2
Post #2062 682
🤔 Почему по идее в конструкторе нужны исключения?

Необходимы для того, чтобы сигнализировать о неудачной инициализации объекта. Если инициализация объекта не может быть завершена успешно, конструктор должен сообщить об этом, чтобы предотвратить дальнейшее использование объекта в некорректном состоянии.

🟠Гарантия корректного состояния объекта
Конструктор должен гарантировать, что объект находится в корректном состоянии после его создания. Если возникает ошибка при инициализации, использование объекта в некорректном состоянии может привести к непредсказуемым результатам и трудноотлаживаемым ошибкам. Если подключение к базе данных не удалось, исключение сигнализирует о неудачной инициализации.
class DatabaseConnection {
public:
DatabaseConnection(const std::string& connectionString) {
if (!connect(connectionString)) {
throw std::runtime_error("Connection failed");
}
}
private:
bool connect(const std::string& connectionString) {
// Логика подключения
return false; // Имитация ошибки подключения
}
};


🟠Безопасность ресурсов
Конструкторы часто выделяют ресурсы, такие как память, файлы или сетевые соединения. Если инициализация не удалась, необходимо убедиться, что все выделенные ресурсы будут корректно освобождены. Исключения помогают автоматизировать этот процесс, используя RAII (Resource Acquisition Is Initialization). Если файл не удалось открыть, исключение будет выброшено, и деструктор корректно освободит ресурсы.
class FileHandler {
public:
FileHandler(const std::string& filename) {
file = fopen(filename.c_str(), "r");
if (!file) {
throw std::runtime_error("Failed to open file");
}
}
~FileHandler() {
if (file) {
fclose(file);
}
}
private:
FILE* file;
};


🟠Целостность программы
Исключения в конструкторах позволяют предотвратить использование некорректно инициализированных объектов. Это важно для поддержания целостности программы и избежания непредсказуемых ошибок. Если загрузка конфигурации не удалась, исключение предотвращает создание некорректного объекта ConfigLoader.
class ConfigLoader {
public:
ConfigLoader(const std::string& configPath) {
if (!loadConfig(configPath)) {
throw std::runtime_error("Failed to load config");
}
}
private:
bool loadConfig(const std::string& configPath) {
// Логика загрузки конфигурации
return false; // Имитация ошибки загрузки
}
};


🚩Обработка исключений в конструкторах

Исключения в конструкторах должны быть обработаны на более высоком уровне программы, чтобы гарантировать корректное завершение программы или повторение попытки инициализации. В этом примере ошибка инициализации обрабатывается в main, что позволяет программе корректно завершить работу или предпринять другие действия.
int main() {
try {
DatabaseConnection db("connection_string");
} catch (const std::exception& e) {
std::cerr << "Initialization failed: " << e.what() << std::endl;
// Дополнительные действия по обработке ошибки
}
return 0;
}


Ставь 👍 и забирай 📚 Базу знаний
  • 👍 1
Post #2060 649
🤔 Сколько будет таблиц виртуальных функций у двух классов, участвующих в наследовании?

Если у базового класса есть хотя бы одна виртуальная функция, то у него создается одна таблица виртуальных функций (vtable). У производного класса также создается своя vtable, если он переопределяет виртуальные методы или добавляет новые.
- Если производный класс не добавляет новых виртуальных функций, он использует vtable родительского класса.
- Если переопределяет методы, создается отдельная vtable для производного класса.
Таким образом, в общем случае будет две таблицы vtable – по одной для каждого класса.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний
  • 🔥 1
Post #2059 662
🤔 Что такое функции обратного вызова?

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

🚩Основные характеристики

🟠Передача как параметр
Функции обратного вызова передаются как параметры другим функциям.
🟠Вызов в определенный момент
Они вызываются внутри функции, которая их получила, в определенный момент времени или по определенному условию.
🟠Гибкость и расширяемость
Использование функций обратного вызова позволяет легко изменять поведение программы без необходимости изменения основной функции.

🟠Пример на С
В языке C функции обратного вызова обычно реализуются с использованием указателей на функции.
#include <stdio.h>

// Объявление функции обратного вызова
void callback_function(int value) {
printf("Callback called with value: %d\n", value);
}

// Функция, принимающая указатель на функцию в качестве параметра
void process_value(int value, void (*callback)(int)) {
// Некоторая обработка
value *= 2;

// Вызов функции обратного вызова
callback(value);
}

int main() {
int x = 5;
// Передача функции обратного вызова
process_value(x, callback_function);
return 0;
}


🟠Пример на C++
В C++ можно использовать указатели на функции, объекты-функции (functors) и лямбда-функции в качестве функций обратного вызова.
#include <iostream>
#include <functional>

// Функция обратного вызова
void callback_function(int value) {
std::cout << "Callback called with value: " << value << std::endl;
}

// Функция, принимающая std::function в качестве параметра
void process_value(int value, std::function<void(int)> callback) {
// Некоторая обработка
value *= 2;

// Вызов функции обратного вызова
callback(value);
}

int main() {
int x = 5;

// Использование указателя на функцию
process_value(x, callback_function);

// Использование лямбда-функции
process_value(x, [](int val) {
std::cout << "Lambda callback called with value: " << val << std::endl;
});

// Использование объекта-функции
struct Functor {
void operator()(int val) {
std::cout << "Functor callback called with value: " << val << std::endl;
}
};

Functor functor;
process_value(x, functor);

return 0;
}


🚩Применение в асинхронном программировании

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

Пример с асинхронным вызовом
#include <iostream>
#include <thread>

// Функция обратного вызова
void callback_function() {
std::cout << "Async operation completed" << std::endl;
}

// Асинхронная функция
void async_operation(std::function<void()> callback) {
std::thread([callback]() {
// Имитация длительной операции
std::this_thread::sleep_for(std::chrono::seconds(2));

// Вызов функции обратного вызова
callback();
}).detach();
}

int main() {
std::cout << "Starting async operation..." << std::endl;
async_operation(callback_function);

// Основной поток продолжает выполнять свою работу
std::cout << "Main thread continues..." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(3));

return 0;
}


Ставь 👍 и забирай 📚 Базу знаний
Post #2058 687
🤔 О чем следует помнить при вызове исключений в конструкторе?

- Исключения из конструктора предотвращают создание объекта.
- Деструкторы членов класса или базового класса должны корректно освобождать ресурсы.
- Лучше избегать сложной логики, которая может выбрасывать исключения.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний
  • 👍 2
Post #2057 783
🤔 Зачем нам нужна move семантика?

Move семантика введена с целью повышения эффективности работы с ресурсами, такими как память, файлы, сокеты и другие объекты, которые занимают значительные ресурсы. Она позволяет избежать ненужного копирования объектов, что может быть дорогостоящим как по времени, так и по памяти.

🚩Зачем она нужна?

🟠Эффективность работы с ресурсами
Копирование больших объектов может быть очень затратным. Move семантика позволяет перенести ресурсы от одного объекта к другому без дорогостоящего копирования.

🟠Улучшение производительности
Перемещение (move) быстрее копирования, поскольку оно всего лишь переназначает указатели на ресурсы, вместо создания их копий. Это особенно важно в приложениях с высокой производительностью, таких как игры, обработка видео, базы данных.

🚩Как это используется?

Move семантика реализуется с помощью rvalue ссылок (ссылок на временные объекты) и специальных методов — move конструктора и move оператора присваивания.
#include <iostream>
#include <vector>

class MyClass {
public:
int* data;
size_t size;

// Конструктор
MyClass(size_t s) : size(s), data(new int[s]) {
std::cout << "Constructing MyClass\n";
}

// Деструктор
~MyClass() {
delete[] data;
std::cout << "Destructing MyClass\n";
}

// Move конструктор
MyClass(MyClass&& other) noexcept : data(other.data), size(other.size) {
other.data = nullptr; // Обнуляем указатель у "старого" объекта
other.size = 0;
std::cout << "Move constructing MyClass\n";
}

// Move оператор присваивания
MyClass& operator=(MyClass&& other) noexcept {
if (this != &other) {
delete[] data; // Освобождаем старый ресурс
data = other.data;
size = other.size;
other.data = nullptr; // Обнуляем указатель у "старого" объекта
other.size = 0;
std::cout << "Move assigning MyClass\n";
}
return *this;
}
};

int main() {
MyClass a(10); // Создаем объект a
MyClass b = std::move(a); // Перемещаем ресурсы от a к b

return 0;
}


Ставь 👍 и забирай 📚 Базу знаний
  • 👍 1
Post #2055 743
🤔 В каком порядке элементы списка инициализируются в конструкторе после двоеточия?

Элементы инициализируются в порядке их объявления в классе, а не в том порядке, в котором они указаны в списке инициализации.
Несовпадение может вызвать предупреждение компилятора.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний
  • 👍 2
Post #2054 816
🤔 Как пофиксить проблему, когда mutex является локальной переменной?

Использование std::mutex в качестве локальной переменной может привести к различным проблемам, особенно если он используется для синхронизации доступа к общим данным. Локальный std::mutex будет уничтожен при выходе из области видимости, что нарушит работу других потоков, ожидающих блокировку или разблокировку.

🚩Проблемы с локальным `std::mutex`

🟠Жизненный цикл локального `std::mutex`
Локальная переменная уничтожается при выходе из функции или блока, в котором она объявлена. Это может привести к неопределенному поведению, если другие потоки все еще используют этот мьютекс.

🟠Неопределенное поведение
Уничтожение мьютекса, который все еще заблокирован, может привести к неопределенному поведению программы и потенциальным сбоям.

🚩Решение проблемы

Чтобы исправить проблему, нужно гарантировать, что std::mutex имеет более длительный срок жизни и доступен всем потокам, которые его используют.

🚩Правильные подходы

🟠Использование глобального или статического мьютекса
Если мьютекс используется для защиты глобальных или статических данных, сделайте его также глобальным или статическим.
#include <iostream>
#include <thread>
#include <mutex>

std::mutex mtx;

void sharedFunction() {
std::lock_guard<std::mutex> lock(mtx);
// Доступ к общим данным
std::cout << "Thread " << std::this_thread::get_id() << " is running\n";
}

int main() {
std::thread t1(sharedFunction);
std::thread t2(sharedFunction);

t1.join();
t2.join();

return 0;
}


🟠Член класса
Если мьютекс используется для защиты данных объекта, объявите его членом класса.
#include <iostream>
#include <thread>
#include <mutex>

class SharedResource {
private:
std::mutex mtx;
int data;

public:
void increment() {
std::lock_guard<std::mutex> lock(mtx);
++data;
std::cout << "Data: " << data << " from thread " << std::this_thread::get_id() << "\n";
}
};

int main() {
SharedResource resource;

std::thread t1(&SharedResource::increment, &resource);
std::thread t2(&SharedResource::increment, &resource);

t1.join();
t2.join();

return 0;
}


🟠Использование умных указателей
Если мьютекс должен иметь динамическую продолжительность жизни, используйте умные указатели, такие как std::shared_ptr или std::unique_ptr.
#include <iostream>
#include <thread>
#include <mutex>
#include <memory>

void sharedFunction(std::shared_ptr<std::mutex> mtx) {
std::lock_guard<std::mutex> lock(*mtx);
// Доступ к общим данным
std::cout << "Thread " << std::this_thread::get_id() << " is running\n";
}

int main() {
auto mtx = std::make_shared<std::mutex>();

std::thread t1(sharedFunction, mtx);
std::thread t2(sharedFunction, mtx);

t1.join();
t2.join();

return 0;
}


Ставь 👍 и забирай 📚 Базу знаний
Post #2052 721
🤔 Double except привести примеры, когда может полететь повторное исключение?

Повторное исключение (double exception) в C++ может возникнуть, когда исключение генерируется внутри блока обработчика другого исключения. Например, если в конструкторе копирования объекта, который копируется для передачи в catch-блок, происходит исключение, это приведет к повторному исключению. Также повторное исключение может возникнуть, если в блоке catch происходит вызов функции, генерирующей исключение, в то время как первое исключение еще не обработано полностью.

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний
Post #2050 760
🤔 Какой подсчет ссылок имеется в shared_ptr?

В умном указателе shared_ptr используется два основных счётчика: счётчик сильных ссылок (strong reference count) и счётчик слабых ссылок (weak reference count). Эти счётчики управляют жизненным циклом объекта и связанных с ним ресурсов различными способами.

🚩Счётчик сильных ссылок

Увеличивается каждый раз, когда новый shared_ptr создаётся как копия другого shared_ptr или когда объект присваивается shared_ptr. Этот счётчик уменьшается, когда shared_ptr уничтожается или когда его значение присваивается другому объекту. Когда счётчик достигает нуля, это означает, что больше нет shared_ptr, управляющих этим объектом, и объект удаляется. Это гарантирует, что ресурсы, связанные с объектом, будут освобождены только тогда, когда не останется ни одной "сильной" ссылки.

🚩Счётчик слабых ссылок

Используется вместе с weak_ptr, другим типом умных указателей, который может ссылаться на объект, управляемый shared_ptr, но не увеличивает счётчик сильных ссылок. Слабые ссылки не предотвращают удаление объекта, к которому они имеют доступ, так как не участвуют в владении объектом. Счётчик слабых ссылок увеличивается каждый раз, когда создаётся weak_ptr, указывающий на объект, и уменьшается, когда такой weak_ptr уничтожается. Когда счётчик сильных ссылок достигает нуля и объект удаляется, память, выделенная под сам объект, освобождается, но "control block" (блок управления), содержащий счётчики, сохраняется до тех пор, пока счётчик слабых ссылок также не обнулится.

#include <iostream>
#include <memory>

int main() {
std::shared_ptr<int> sp1 = std::make_shared<int>(10);
std::cout << "sp1 use_count: " << sp1.use_count() << '\n'; // Вывод: 1

{
std::shared_ptr<int> sp2 = sp1; // Копирование shared_ptr
std::cout << "sp1 use_count after copy: " << sp1.use_count() << '\n'; // Вывод: 2

std::weak_ptr<int> wp1 = sp1; // Создание weak_ptr
std::cout << "wp1 use_count: " << wp1.use_count() << '\n'; // Вывод: 2
} // sp2 выходит из области видимости, use_count уменьшается до 1

std::cout << "sp1 use_count after sp2 destruction: " << sp1.use_count() << '\n'; // Вывод: 1
return 0;
}


Ставь 👍 и забирай 📚 Базу знаний
Post #2048 744
🤔 Как работает priority_queue?

priority_queue управляет элементами на основе их приоритетов, используя бинарную кучу: на вершине всегда элемент с наивысшим приоритетом, а операции добавления и удаления имеют сложность O(log n).

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний
  • 👍 1
Post #2047 853
🤔 Каким свойством должен обладать объект, чтобы его можно было добавить в ассоциативные контейнеры в качестве ключа?

Чтобы объект можно было использовать в качестве ключа в ассоциативных контейнерах (std::set, std::map, std::unordered_set, std::unordered_map), он должен обладать определёнными свойствами, которые зависят от типа контейнера.

🚩Требования к объекту-ключу

🟠Для `std::map` и `std::set` (красно-чёрное дерево)
Класс или структура, используемая в качестве ключа, должна поддерживать операцию < (меньше).
#include <iostream>
#include <map>

struct Person {
std::string name;
int age;

// Оператор сравнения, необходимый для std::map и std::set
bool operator<(const Person& other) const {
return age < other.age; // Ключи будут упорядочены по возрасту
}
};

int main() {
std::map<Person, std::string> people;
people[{ "Alice", 30 }] = "Doctor";
people[{ "Bob", 25 }] = "Engineer";

for (const auto& [key, value] : people) {
std::cout << key.name << " (" << key.age << "): " << value << '\n';
}
}


🟠Для `std::unordered_map` и `std::unordered_set` (хеш-таблица)
Объект-ключ должен поддерживать операции:
Оператор == (для проверки равенства)
Функция-хешер (по умолчанию std::hash<T>)
#include <iostream>
#include <unordered_map>

struct Person {
std::string name;
int age;

// Оператор равенства нужен для сравнения ключей
bool operator==(const Person& other) const {
return name == other.name && age == other.age;
}
};

// Специализация std::hash для структуры Person
namespace std {
template <>
struct hash<Person> {
std::size_t operator()(const Person& p) const {
return std::hash<std::string>()(p.name) ^ (std::hash<int>()(p.age) << 1);
}
};
}

int main() {
std::unordered_map<Person, std::string> people;
people[{ "Alice", 30 }] = "Doctor";
people[{ "Bob", 25 }] = "Engineer";

for (const auto& [key, value] : people) {
std::cout << key.name << " (" << key.age << "): " << value << '\n';
}
}


Ставь 👍 и забирай 📚 Базу знаний
Post #2045 742
🤔 В чем разница vector и list?

Vector в C++ представляет собой динамический массив с последовательным размещением элементов в памяти, что обеспечивает быстрый доступ по индексу. List — это двусвязный список, где каждый элемент хранит указатели на соседние элементы, что обеспечивает быструю вставку и удаление. Вставка и удаление в vector могут быть медленными из-за необходимости сдвига элементов, тогда как в list такие операции происходят быстрее. Однако доступ по индексу в list требует обхода элементов, что делает его медленнее для случайного доступа.

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний
  • 👍 1
Post #2044 726
🤔 Что знаешь о умных указателях?

Умные указатели (smart pointers) в C++ – это классы-обёртки для обычных указателей, которые автоматически управляют временем жизни объектов в динамической памяти. Они помогают избежать утечек памяти и делают код более безопасным, минимизируя вероятность ошибок с указателями.

🚩Зачем нужны умные указатели?

🟠Управление ресурсами
Автоматически освобождают память, когда объект больше не нужен.
🟠Исключение утечек памяти
С их помощью можно избежать забывания вызова delete.
🟠Улучшение безопасности
Умные указатели предотвращают доступ к освобождённой памяти (dangling pointers).
🟠Снижение сложности
RAII (Resource Acquisition Is Initialization) позволяет делегировать управление ресурсами объектам.

🚩Типы умных указателей

🟠`std::unique_ptr`
Уникальный указатель (unique pointer) обладает единственным владельцем объекта. После передачи владения объектом другому std::unique_ptr, исходный указатель становится недействительным.
Обеспечивает строгую семантику владения.
Лёгкий, так как не использует счётчики ссылок.
Не копируемый, но перемещаемый.

#include <iostream>
#include <memory>

class MyClass {
public:
MyClass() { std::cout << "Создан объект\n"; }
~MyClass() { std::cout << "Удалён объект\n"; }
};

int main() {
std::unique_ptr<MyClass> ptr1 = std::make_unique<MyClass>(); // Владеет объектом
std::unique_ptr<MyClass> ptr2 = std::move(ptr1); // Передача владения
// ptr1 теперь nullptr, ptr2 владеет объектом
return 0; // Объект автоматически удаляется
}


🟠`std::shared_ptr`
Разделяемый указатель (shared pointer) поддерживает разделённое владение объектом. Указатель освобождает память только тогда, когда все std::shared_ptr, ссылающиеся на объект, будут уничтожены.
Использует счётчик ссылок (reference count).
Обеспечивает совместное использование ресурса несколькими владельцами.

#include <iostream>
#include <memory>

class MyClass {
public:
MyClass() { std::cout << "Создан объект\n"; }
~MyClass() { std::cout << "Удалён объект\n"; }
};

int main() {
std::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>();
std::shared_ptr<MyClass> ptr2 = ptr1; // Разделение владения
// Указатель ptr1 и ptr2 указывают на один и тот же объект
return 0; // Объект удаляется после уничтожения последнего shared_ptr
}


🟠`std::weak_ptr`
Слабый указатель (weak pointer) не увеличивает счётчик ссылок. Используется для предотвращения циклических зависимостей между std::shared_ptr.
Ссылается на объект, которым управляет std::shared_ptr.
Не владеет объектом.
Может проверять существование объекта с помощью expired().

#include <iostream>
#include <memory>

class MyClass {
public:
MyClass() { std::cout << "Создан объект\n"; }
~MyClass() { std::cout << "Удалён объект\n"; }
};

int main() {
std::shared_ptr<MyClass> shared = std::make_shared<MyClass>();
std::weak_ptr<MyClass> weak = shared; // Слабая ссылка
if (auto ptr = weak.lock()) { // Проверка на существование
std::cout << "Объект существует\n";
}
shared.reset(); // Освобождаем shared_ptr
if (weak.expired()) {
std::cout << "Объект удалён\n";
}
return 0;
}


Ставь 👍 и забирай 📚 Базу знаний
Post #2043 707
🤔 В unordered_set поиск — константа?

Поиск в unordered_set имеет среднюю сложность O(1), но в худшем случае при хеш-коллизиях может быть O(n).

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний
  • 🔥 1
Older posts →
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →