Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI21 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
# RAII: Resource Acquisition Is Initialization
RAII — это фундаментальный паттерн в C++, который привязывает управление ресурсами к жизненному циклу объектов. Это одна из самых мощных инноваций языка, обеспечивающая безопасность памяти и надёжность.
Основная идея
Ресурсы захватываются в конструкторе, освобождаются в деструкторе.
Этот паттерн гарантирует:
- Утечки памяти невозможны (деструктор вызовется ВСЕГДА)
- Исключения обработаны правильно
- Не нужно вручную отслеживать освобождение ресурсов
Базовый пример: File RAII
// ❌ БЕЗ RAII (C-style, опасно)
void process_file_bad() {
FILE* f = fopen("data.txt", "r");
// Если здесь исключение — файл не закроется!
if (some_error_condition) {
throw std::runtime_error("Oops!");
// fclose(f) НИКОГДА не вызовется
}
// А что если раньше возвращаемся?
if (file_empty) {
return; // утечка файлового дескриптора!
}
fclose(f); // может быть, может не быть
}
// ✅ С RAII (безопасно)
class FileWrapper {
FILE* handle;
public:
FileWrapper(const char* path) {
handle = fopen(path, "r");
if (!handle) {
throw std::runtime_error("Cannot open file");
}
}
~FileWrapper() {
if (handle) {
fclose(handle);
}
}
// Запрещаем копирование (ресурс не может быть дублирован)
FileWrapper(const FileWrapper&) = delete;
FileWrapper& operator=(const FileWrapper&) = delete;
// Разрешаем перемещение (transfer ownership)
FileWrapper(FileWrapper&& other) noexcept
: handle(other.handle) {
other.handle = nullptr;
}
};
void process_file_good() {
FileWrapper f("data.txt"); // RAII: acquire resource
// Исключение? Деструктор f всё равно вызовется
if (some_error) throw std::runtime_error("Error");
// Ранний return? Деструктор вызовется
if (file_empty) return;
// Конец функции? Деструктор вызовется
} // Здесь f.~FileWrapper() вызывается автоматически
Примеры RAII в стандартной библиотеке
1. std::lock_guard / std::unique_lock
void critical_section() {
std::mutex mu;
// RAII для мьютекса!
{
std::lock_guard<std::mutex> lock(mu); // acquire
// критичная секция
} // lock.~lock_guard() вызывается, мьютекс разблокируется
}
// Эквивалент БЕЗ RAII (manual):
void critical_section_bad() {
mu.lock();
try {
// критичная секция
// Если здесь исключение — unlock не вызовется!
} catch (...) {
mu.unlock();
throw;
}
mu.unlock();
}
2. std::unique_ptr
void foo() {
auto ptr = std::make_unique<MyClass>(); // acquire
ptr->do_work();
// Если исключение? Деструктор уникального указателя удаляет объект
} // delete автоматически
3. std::string
void process() {
std::string data = "hello"; // RAII: выделяет память
// Исключение? Строка всё равно очистится (деструктор вызовется)
if (error) throw std::exception();
} // data.~string() освобождает память
Правило нуля и правило пяти
Правило нуля (Rule of Zero)
Если класс использует только RAII типы, не пиши деструктор, копирование, перемещение.
// ✅ ХОРОШО: Rule of Zero
class DataProcessor {
std::unique_ptr<Buffer> buffer; // RAII
std::vector<std::string> log; // RAII
std::lock_guard<std::mutex> lock; // RAII
// Дефолтные конструктор/деструктор/copy/move
// Компилятор сгенерирует правильно!
};
Правило пяти (Rule of Five)
Если пишешь деструктор, пиши и копирование + перемещение.
// ❌ ОПАСНО: Rule of Five violation
class CustomBuffer {
char* data;
size_t size;
public:
CustomBuffer(size_t sz) : size(sz) {
data = new char[sz]; // manual allocation
}
~CustomBuffer() {
delete[] data; // manual deallocation
}
// ЗАБЫЛИ copy constructor + assignment!
// Компилятор генерирует default: shallow copy → double delete!
};
// ✅ ПРАВИЛЬНО: Rule of Five
class CustomBuffer {
char* data;
size_t size;
public:
// 1. Constructor
CustomBuffer(size_t sz) : size(sz), data(new char[sz]) {}
// 2. Destructor
~CustomBuffer() { delete[] data; }
// 3. Copy constructor
CustomBuffer(const CustomBuffer& other)
: size(other.size), data(new char[other.size]) {
std::copy(other.data, other.data + size, data);
}
// 4. Copy assignment
CustomBuffer& operator=(const CustomBuffer& other) {
if (this != &other) {
delete[] data; // старые данные
size = other.size;
data = new char[other.size];
std::copy(other.data, other.data + size, data);
}
return *this;
}
// 5. Move constructor
CustomBuffer(CustomBuffer&& other) noexcept
: size(other.size), data(other.data) {
other.data = nullptr;
other.size = 0;
}
// 6. Move assignment
CustomBuffer& operator=(CustomBuffer&& other) noexcept {
delete[] data;
size = other.size;
data = other.data;
other.data = nullptr;
return *this;
}
};
RAII в многопоточности
class TransactionGuard {
Database& db;
TransactionId tx_id;
public:
TransactionGuard(Database& d) : db(d) {
tx_id = db.begin_transaction(); // acquire
}
~TransactionGuard() {
// Если исключение — автоматический rollback!
if (std::uncaught_exceptions() > 0) {
db.rollback(tx_id);
} else {
db.commit(tx_id);
}
}
};
void transfer_money(int from_id, int to_id, int amount) {
TransactionGuard tx(database);
Account& from = db.get_account(from_id);
Account& to = db.get_account(to_id);
if (from.balance < amount) {
throw std::invalid_argument("Insufficient funds");
// Автоматический rollback благодаря RAII!
}
from.balance -= amount;
to.balance += amount;
} // tx.~TransactionGuard() вызовется, commit() будет выполнен
RAII в network programming
class TcpConnection {
int socket_fd;
public:
TcpConnection(const std::string& host, int port) {
socket_fd = ::socket(AF_INET, SOCK_STREAM, 0);
if (socket_fd < 0) throw std::runtime_error("socket failed");
struct sockaddr_in addr = {};
// ... setup addr
if (::connect(socket_fd, ...) < 0) {
::close(socket_fd);
throw std::runtime_error("connect failed");
}
}
~TcpConnection() {
if (socket_fd >= 0) {
::close(socket_fd);
}
}
TcpConnection(const TcpConnection&) = delete;
TcpConnection& operator=(const TcpConnection&) = delete;
};
void send_request() {
TcpConnection conn("api.example.com", 443);
// Если исключение — сокет закроется автоматически
conn.send_data(request);
auto response = conn.receive_data();
} // сокет закроется гарантированно
Преимущества RAII
- Exception safety — ресурсы освобождаются даже при исключениях
- No memory leaks — невозможны утечки (за редкими исключениями)
- Clear ownership — видно, кто владит ресурсом
- Performance — нет overhead, управление во время компиляции
- Readability — меньше boilerplate, больше смысла
Резюме
RAII = ресурсы привязаны к объектам, которые их держат.
- Захват в конструкторе, освобождение в деструкторе
- Автоматически работает при исключениях
- Используй
unique_ptrвместоnew/delete - Используй
std::lock_guardвместо manual lock/unlock - Это не просто паттерн — это философия безопасного C++
RAII — одна из причин, почему современный C++ считается более безопасным, чем Java или Python (по управлению ресурсами) — нет сборщика мусора, нет задержек, нет сюрпризов.