Какие знаешь примеры представляющие RAII помимо умных указателей?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Примеры RAII помимо умных указателей
RAII (Resource Acquisition Is Initialization) — это один из самых мощных паттернов в C++, гарантирующий автоматическое управление ресурсами. Рассмотрим разнообразные практические примеры за пределами умных указателей.
1. Lock Guards для синхронизации потоков
Самый популярный пример после умных указателей:
#include <mutex>
#include <thread>
std::mutex mtx;
void safe_function() {
std::lock_guard<std::mutex> lock(mtx); // Захватываем мьютекс в конструкторе
// Критическая секция
std::cout << "Безопасный доступ\n";
} // Деструктор автоматически освобождает мьютекс
Более новый и универсальный вариант:
#include <shared_mutex>
std::shared_mutex rw_mtx;
void read_safe() {
std::shared_lock<std::shared_mutex> lock(rw_mtx); // Read lock
// Несколько потоков могут читать одновременно
}
void write_safe() {
std::unique_lock<std::shared_mutex> lock(rw_mtx); // Write lock
// Эксклюзивный доступ
// Можно временно разблокировать
lock.unlock();
// ... какой-то код
lock.lock(); // И снова заблокировать
}
Преимущество: Даже если выкинуть исключение, мьютекс будет освобождён.
2. File I/O RAII обёртки
Управление файлами — классический случай для RAII:
#include <fstream>
class FileHandler {
private:
std::ofstream file;
public:
FileHandler(const std::string& filename) {
file.open(filename);
if (!file.is_open()) {
throw std::runtime_error("Failed to open file");
}
std::cout << "File opened: " << filename << "\n";
}
~FileHandler() {
if (file.is_open()) {
file.close();
std::cout << "File closed\n";
}
}
void write(const std::string& data) {
file << data;
}
// Запретить копирование
FileHandler(const FileHandler&) = delete;
FileHandler& operator=(const FileHandler&) = delete;
};
int main() {
try {
FileHandler fh("output.txt");
fh.write("Hello, RAII!");
} // Файл автоматически закроется здесь
return 0;
}
Стандартная библиотека уже имеет RAII файлы:
{
std::ifstream file("data.txt"); // Открытие в конструкторе
std::string line;
std::getline(file, line);
} // Закрытие в деструкторе
3. Database Connection Pool
Управление подключениями к БД:
#include <memory>
class DatabaseConnection {
private:
std::string connection_string;
bool is_connected = false;
public:
DatabaseConnection(const std::string& cs) : connection_string(cs) {
// Логирование и инициализация подключения
std::cout << "Connecting to: " << cs << "\n";
is_connected = true;
}
~DatabaseConnection() {
if (is_connected) {
std::cout << "Disconnecting from database\n";
is_connected = false;
}
}
void execute(const std::string& query) {
if (!is_connected) throw std::runtime_error("Not connected");
std::cout << "Executing: " << query << "\n";
}
DatabaseConnection(const DatabaseConnection&) = delete;
DatabaseConnection& operator=(const DatabaseConnection&) = delete;
};
class ConnectionGuard {
private:
std::unique_ptr<DatabaseConnection> conn;
public:
ConnectionGuard(const std::string& connection_string)
: conn(std::make_unique<DatabaseConnection>(connection_string)) {}
DatabaseConnection* operator->() { return conn.get(); }
DatabaseConnection& operator*() { return *conn; }
// Деструктор автоматически удалит подключение
};
int main() {
ConnectionGuard db("postgresql://localhost/mydb");
db->execute("SELECT * FROM users");
} // Подключение автоматически закроется
4. Scope Guards для деинициализации
Подобно defer в Go:
#include <functional>
class ScopeGuard {
private:
std::function<void()> exit_func;
public:
ScopeGuard(std::function<void()> f) : exit_func(f) {}
~ScopeGuard() {
if (exit_func) exit_func();
}
// Отменить выполнение при выходе
void release() { exit_func = nullptr; }
ScopeGuard(const ScopeGuard&) = delete;
ScopeGuard& operator=(const ScopeGuard&) = delete;
};
int main() {
ScopeGuard cleanup([] {
std::cout << "Cleanup resources\n";
});
std::cout << "Doing work\n";
} // Cleanup выполнится здесь
Практический пример:
void process_data() {
void* memory = malloc(1024);
ScopeGuard mem_cleanup([memory] {
free(memory);
std::cout << "Memory freed\n";
});
// Используем memory
// Даже если выкинуть исключение, память освободится
} // Автоматическое освобождение памяти
5. Timer и Performance Monitoring
Подсчёт времени выполнения:
#include <chrono>
class Timer {
private:
std::chrono::high_resolution_clock::time_point start;
std::string operation_name;
public:
Timer(const std::string& name) : operation_name(name) {
start = std::chrono::high_resolution_clock::now();
std::cout << "Timer started for: " << name << "\n";
}
~Timer() {
auto end = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(
end - start
).count();
std::cout << operation_name << " took: " << duration << "ms\n";
}
Timer(const Timer&) = delete;
Timer& operator=(const Timer&) = delete;
};
void expensive_operation() {
Timer t("database_query");
// Тяжёлая работа
std::this_thread::sleep_for(std::chrono::milliseconds(100));
} // Автоматический вывод времени выполнения
6. Memory Pool с автоматическое очисткой
class MemoryBlock {
private:
char* data;
size_t size;
public:
MemoryBlock(size_t sz) : size(sz) {
data = new char[sz];
std::cout << "Allocated " << sz << " bytes\n";
}
~MemoryBlock() {
delete[] data;
std::cout << "Deallocated " << size << " bytes\n";
}
char* get() { return data; }
size_t get_size() const { return size; }
MemoryBlock(const MemoryBlock&) = delete;
MemoryBlock& operator=(const MemoryBlock&) = delete;
};
std::vector<MemoryBlock> pool;
void allocate_and_use() {
pool.emplace_back(1024); // Конструктор вызывается
pool.emplace_back(2048);
// Используем
std::cout << "Using memory pools\n";
} // Деструкторы вызываются автоматически
7. Resource Wrapper для API
class APIClient {
private:
void* api_handle;
std::string api_key;
public:
APIClient(const std::string& key) : api_key(key) {
// Инициализация API в конструкторе
std::cout << "API initialized with key: " << key.substr(0, 4) << "...\n";
api_handle = reinterpret_cast<void*>(this);
}
~APIClient() {
// Очистка в деструкторе
std::cout << "API client destroyed\n";
}
std::string make_request(const std::string& endpoint) {
std::cout << "Making request to: " << endpoint << "\n";
return "response_data";
}
APIClient(const APIClient&) = delete;
APIClient& operator=(const APIClient&) = delete;
};
void fetch_user_data() {
APIClient api("secret_key_12345");
auto data = api.make_request("/users/123");
std::cout << "Received: " << data << "\n";
} // Очистка API автоматически
Ключевые принципы RAII
| Принцип | Описание |
|---|---|
| Конструктор | Захватывает ресурс (файл, мьютекс, память) |
| Деструктор | Гарантированно освобождает ресурс |
| Исключения | Работает даже при выбросе исключения |
| Scope-based | Ресурс привязан к времени жизни объекта |
Почему RAII лучше try-finally
// Плохо: что если выкинуть исключение между lock и unlock?
mtx.lock();
try {
do_something();
mtx.unlock();
} catch (...) {
mtx.unlock(); // Легко забыть!
throw;
}
// Хорошо: RAII гарантирует освобождение
{
std::lock_guard<std::mutex> lock(mtx);
do_something();
} // Деструктор ВСЕГДА вызывается
Итог: RAII — это не просто паттерн для умных указателей, это философия управления ресурсами в C++. Применяй RAII для файлов, мьютексов, таймеров, подключений и любых других ресурсов, которые требуют явной очистки.