Ответ
В C++ приложениях работа с БД происходит на нескольких уровнях:
- Уровень драйвера/низкоуровневого API: Прямые вызовы нативного API СУБД (например,
libpqдля PostgreSQL) или использование стандартизированных интерфейсов вроде ODBC. - Уровень обёртки/библиотеки: Использование библиотек, таких как SQLite C API, MySQL Connector/C++ или SOCI, которые предоставляют более удобный C++ интерфейс для выполнения запросов и управления соединениями.
- Уровень ORM/высокоуровневой абстракции (опционально): Применение ORM вроде ODB или sqlpp11 для отображения таблиц на C++ классы.
Пример работы на уровне библиотеки SQLite:
#include <sqlite3.h>
#include <iostream>
int main() {
sqlite3* db;
char* errMsg = nullptr;
// 1. Открытие соединения (уровень управления подключением)
if (sqlite3_open("test.db", &db) != SQLITE_OK) {
std::cerr << "Cannot open database: " << sqlite3_errmsg(db) << std::endl;
return 1;
}
// 2. Выполнение SQL-запроса (уровень запроса)
const char* sql = "SELECT id, name FROM users WHERE active = 1;";
sqlite3_stmt* stmt;
if (sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr) == SQLITE_OK) {
// 3. Чтение результата (уровень обработки данных)
while (sqlite3_step(stmt) == SQLITE_ROW) {
int id = sqlite3_column_int(stmt, 0);
const unsigned char* name = sqlite3_column_text(stmt, 1);
std::cout << id << ": " << name << std::endl;
}
sqlite3_finalize(stmt);
}
sqlite3_close(db); // 4. Закрытие соединения
return 0;
}
На практике я проектирую слой доступа к данным, который инкапсулирует эту логику, разделяя ответственность между выполнением сырых SQL-запросов, маппингом результатов в объекты предметной области и управлением транзакциями.
Ответ 18+ 🔞
Слушай, ну вот реально, как будто на дворе 2002-й год, когда каждый второй писал свою обёртку над базой. В C++ с базами данных — это отдельная песня, там слоёв, как в хорошем торте, овердохуища.
Вот смотри, обычно всё начинается с самого низа, с драйверов. Это как общаться с базой на её родном языке, через нативное API. Типа libpq для Постгреса или ODBC, который везде суёт свой нос. Работать на этом уровне — это как вручную собирать двигатель, нихуя не понятно, но зато контроль полный. Доверия ебать ноль к этим абстракциям, пока сам не покрутишь.
Потом идёт уровень библиотек-обёрток. Вот это уже поинтереснее. Берешь ту же SQLite C API или SOCI — и у тебя уже не голые сишные структуры, а какие-никакие плюсовые классы. Удобнее, конечно, но всё равно иногда чувствуешь себя полупидором, потому что половина — это С++, а половина — это всё ещё ручное управление ресурсами, как в твоём примере.
#include <sqlite3.h>
#include <iostream>
int main() {
sqlite3* db;
char* errMsg = nullptr;
// 1. Открытие соединения (уровень управления подключением)
if (sqlite3_open("test.db", &db) != SQLITE_OK) {
std::cerr << "Cannot open database: " << sqlite3_errmsg(db) << std::endl;
return 1;
}
// 2. Выполнение SQL-запроса (уровень запроса)
const char* sql = "SELECT id, name FROM users WHERE active = 1;";
sqlite3_stmt* stmt;
if (sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr) == SQLITE_OK) {
// 3. Чтение результата (уровень обработки данных)
while (sqlite3_step(stmt) == SQLITE_ROW) {
int id = sqlite3_column_int(stmt, 0);
const unsigned char* name = sqlite3_column_text(stmt, 1);
std::cout << id << ": " << name << std::endl;
}
sqlite3_finalize(stmt);
}
sqlite3_close(db); // 4. Закрытие соединения
return 0;
}
Видишь этот код? Классика жанра. Открыл, подготовил запрос, прошагал по строкам, почистил за собой. Пропустишь один sqlite3_finalize — и всё, накрылся медным тазом твой процесс, утечка памяти. Терпения ноль ебать на отладку такого.
А самый верх — это уже ORM, типа ODB или sqlpp11. Вот тут уже почти красота: пишешь класс на C++, а ORM сам генерит SQL и маппит результаты обратно. Но, бля, это палка о двух концах. С одной стороны, удобно, с другой — когда запрос тормозит, начинаешь рыться в сгенерированном SQL, а там такая пиздопроебибна, что сам от себя охуел. И чувствуешь себя обманутым, будто тебе дали вилкой в глаз или в жопу раз.
Поэтому на практике я всегда делаю свой слой доступа к данным. Не какой-то монстр, а просто аккуратную прослойку, которая прячет всю эту возню с хендлами, запросами и маппингом. Одна сущность отвечает за соединения, другая — за выполнение запросов, третья — за то, чтобы превратить эти сырые данные в нормальные объекты. И транзакции, бля, отдельная история, чтобы не получилось, что половина операции прошла, а вторая — хуй с горы. Главное — разделить ответственность, а то получится одна хитрая жопа на тысячу строк, которую потом ни понять, ни исправить.