ORM for C++: Comparison and Usage Examples
Unlike dynamically typed languages (Python, PHP), C++ does not have built-in support for reflection and metaprogramming sufficient to create a "magical" ORM. Nevertheless, there are several powerful solutions that automate working with databases. In this article, we will compare three popular libraries: ODB, SOCI, and SQLiteCpp, and provide examples of their use.
Comparison Criteria
When choosing an ORM for C++, it is worth paying attention to:
- Code Generation Type — external generator or C++ templates.
- DBMS Support — SQLite, PostgreSQL, MySQL, Oracle.
- Performance — overhead of mapping.
- Integration Complexity — whether a separate build step is required.
1. ODB — Industrial ORM for C++
ODB is a commercial (with a free version for open-source) ORM based on code generation from annotated header files. It supports relational databases (SQLite, PostgreSQL, MySQL, Oracle) and even NoSQL (MongoDB).
Example with ODB (SQLite)
First, declare the entity class:
#include <odb/core.hxx>
#pragma db objectclass Person {public: Person(const std::string& name, int age) : name_(name), age_(age) {}
unsigned long id() const { return id_; } const std::string& name() const { return name_; } int age() const { return age_; }
private: friend class odb::access; #pragma db id auto unsigned long id_; std::string name_; int age_;};Usage:
#include <odb/database.hxx>#include <odb/sqlite/database.hxx>#include <odb/transaction.hxx>#include "person.hxx"#include "person-odb.hxx"
int main() { auto db = std::make_shared<odb::sqlite::database>("test.db"); odb::transaction t(db->begin());
Person p("Alice", 30); db->persist(p); // INSERT
auto result = db->query<Person>( odb::query<Person>::age > 25); for (const auto& person : result) { std::cout << person.name() << std::endl; } t.commit();}Pros: full object model, relationship support, lazy loading. Cons: requires a code generator (build step), commercial license.
2. SOCI — Lightweight "Data Access Layer"
SOCI is not a full-fledged ORM, but a library for working with SQL from C++ that provides a convenient syntax for executing queries and mapping results. It works with SQLite, PostgreSQL, MySQL, Oracle, and ODBC.
Example with SOCI
#include <soci/soci.h>#include <soci/sqlite3/soci-sqlite3.h>#include <iostream>
struct Person { int id; std::string name; int age;};
int main() { soci::session sql(soci::sqlite3, "test.db"); sql << "CREATE TABLE IF NOT EXISTS person (" "id INTEGER PRIMARY KEY, " "name TEXT, age INTEGER)";
// Insert sql << "INSERT INTO person (name, age) VALUES (:name, :age)", soci::use("Bob"), soci::use(25);
// Query with mapping to struct Person p; soci::indicator ind; sql << "SELECT id, name, age FROM person WHERE id = :id", soci::use(1), soci::into(p.id), soci::into(p.name, ind), soci::into(p.age);
if (ind == soci::i_ok) { std::cout << p.name << " " << p.age << std::endl; }}Pros: easy integration, no code generation, support for prepared statements. Cons: no automatic object mapping