<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: dvuvud</title>
    <description>The latest articles on DEV Community by dvuvud (@dvuvud).</description>
    <link>https://dev.to/dvuvud</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3923569%2F3ddec9a5-99c8-45b9-900b-04de07de8ed4.jpg</url>
      <title>DEV Community: dvuvud</title>
      <link>https://dev.to/dvuvud</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dvuvud"/>
    <language>en</language>
    <item>
      <title>How I built a key-value database in a single C++ header</title>
      <dc:creator>dvuvud</dc:creator>
      <pubDate>Sun, 10 May 2026 17:40:07 +0000</pubDate>
      <link>https://dev.to/dvuvud/how-i-built-a-key-value-database-in-a-single-c-header-3f9l</link>
      <guid>https://dev.to/dvuvud/how-i-built-a-key-value-database-in-a-single-c-header-3f9l</guid>
      <description>&lt;p&gt;If you've ever needed persistent storage in a C++ project but didn't want to add a dependency, this is for you. I built &lt;a href="https://github.com/dvuvud/fluxen" rel="noopener noreferrer"&gt;fluxen&lt;/a&gt;, a key-value database that lives in one &lt;code&gt;.hpp&lt;/code&gt; file. Drop it in, include it, done.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;"fluxen.hpp"&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;
&lt;span class="n"&gt;fluxen&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;DB&lt;/span&gt; &lt;span class="nf"&gt;db&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"app.db"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"city"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Stockholm"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"score"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int32_t&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;auto&lt;/span&gt; &lt;span class="n"&gt;city&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"city"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;           &lt;span class="c1"&gt;// std::optional&amp;lt;std::string&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;auto&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;int32_t&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"score"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// std::optional&amp;lt;int32_t&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No CMake. No vcpkg. No linker flags.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why I built it
&lt;/h2&gt;

&lt;p&gt;I kept hitting the same wall on small projects. Need to save some state between runs, nothing fancy, just a handful of keys and values. Every option I looked at either required linking against a library, a build step, or had way more API surface than I needed. SQLite is great but it felt like overkill for storing a dozen keys.&lt;/p&gt;

&lt;p&gt;So I built something simpler. This post is a walkthrough of how it works internally, including the parts that took the most thought to get right.&lt;/p&gt;




&lt;h2&gt;
  
  
  Append-only log with a hash index
&lt;/h2&gt;

&lt;p&gt;Nothing is ever modified in place. Every write, including overwrites and deletes, appends to the end of the file. That one constraint is what makes crash recovery almost free.&lt;/p&gt;

&lt;p&gt;The file layout looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────┐
│  Magic header: "FLUXEN01"       │  8 bytes
├─────────────────────────────────┤
│  Entry                          │
│    flags   : 1 byte             │  0x00 = live, 0x01 = deleted
│    key_len : 1 byte             │  max 255
│    val_len : 4 bytes            │  little-endian uint32
│    key     : key_len bytes      │
│    value   : val_len bytes      │
├─────────────────────────────────┤
│  Entry                          │
│  ...                            │
└─────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In memory, each key maps to a small struct that records where its value lives in the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;IndexEntry&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;size_t&lt;/span&gt;   &lt;span class="n"&gt;val_offset&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// byte offset into the file&lt;/span&gt;
    &lt;span class="kt"&gt;uint32_t&lt;/span&gt; &lt;span class="n"&gt;val_len&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;unordered_map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IndexEntry&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;index_&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On startup, fluxen scans the log once and builds this map. Last write wins, tombstones (entries marked with the delete flag) remove entries. After that, every read is just a hash lookup and a pointer dereference with no disk access needed.&lt;/p&gt;

&lt;p&gt;This is a &lt;a href="https://riak.com/assets/bitcask-intro.pdf" rel="noopener noreferrer"&gt;Bitcask&lt;/a&gt;-style design. The main tradeoff is that all keys live in RAM, which is fine for embedded use cases but worth knowing upfront.&lt;/p&gt;




&lt;h2&gt;
  
  
  Memory-mapped I/O
&lt;/h2&gt;

&lt;p&gt;The file gets &lt;code&gt;mmap&lt;/code&gt;'d into the process's address space rather than read through &lt;code&gt;read()&lt;/code&gt; syscalls. That means there's no kernel-to-userspace copy on the read path. The OS just pages in whatever parts of the file are needed.&lt;/p&gt;

&lt;p&gt;How much copying actually happens depends on which method you call. The &lt;code&gt;each()&lt;/code&gt; and &lt;code&gt;prefix()&lt;/code&gt; iteration methods are zero-copy: the callback gets a &lt;code&gt;std::span&amp;lt;const std::byte&amp;gt;&lt;/code&gt; pointing directly into the mapped region. &lt;code&gt;get&amp;lt;T&amp;gt;()&lt;/code&gt; for structs and scalars does one &lt;code&gt;memcpy&lt;/code&gt; into the return value. &lt;code&gt;get&amp;lt;std::string&amp;gt;()&lt;/code&gt; does one allocation and copy into the string buffer.&lt;/p&gt;

&lt;p&gt;Keeping the mapping coherent after writes took some thought. Writes go through &lt;code&gt;write()&lt;/code&gt;, not through the map, so the mapping goes stale. I track this with an atomic dirty flag and a double-checked remap pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;ensure_mapped&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;file_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_dirty&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// fast path: no lock needed&lt;/span&gt;

    &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;unique_lock&lt;/span&gt; &lt;span class="n"&gt;sync_lock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sync_mutex_&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_dirty&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;         &lt;span class="c1"&gt;// still dirty? we remap&lt;/span&gt;
        &lt;span class="n"&gt;file_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;remap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;              &lt;span class="c1"&gt;// subsequent readers skip this&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first reader after a write does the remap. Other readers waiting on &lt;code&gt;sync_mutex_&lt;/code&gt; check the flag again once they acquire the lock, see it's already clear, and skip the remap.&lt;/p&gt;




&lt;h2&gt;
  
  
  Thread safety
&lt;/h2&gt;

&lt;p&gt;The main lock is a &lt;code&gt;std::shared_mutex&lt;/code&gt;. Read methods (&lt;code&gt;get&lt;/code&gt;, &lt;code&gt;has&lt;/code&gt;, &lt;code&gt;each&lt;/code&gt;, &lt;code&gt;prefix&lt;/code&gt;) take a shared lock, so any number of threads can read simultaneously. Write methods (&lt;code&gt;put&lt;/code&gt;, &lt;code&gt;remove&lt;/code&gt;, &lt;code&gt;transaction&lt;/code&gt;, &lt;code&gt;compact&lt;/code&gt;) take an exclusive lock.&lt;/p&gt;

&lt;p&gt;The secondary &lt;code&gt;sync_mutex_&lt;/code&gt; used for remapping is only ever acquired while already holding a shared lock on the main mutex. Writers can only set the dirty flag while holding an exclusive lock, so by the time any reader observes a dirty mapping, no writer is running. The two mutexes don't interact in a way that can deadlock.&lt;/p&gt;




&lt;h2&gt;
  
  
  Transactions
&lt;/h2&gt;

&lt;p&gt;Individual &lt;code&gt;put()&lt;/code&gt; calls append to the file but skip &lt;code&gt;fsync&lt;/code&gt;. Fast, but a crash could lose the last write. If you need durability guarantees, &lt;code&gt;transaction()&lt;/code&gt; batches everything into one buffer, writes it in one syscall, and calls &lt;code&gt;fsync&lt;/code&gt; once:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;([](&lt;/span&gt;&lt;span class="n"&gt;fluxen&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Tx&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"balance"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="kt"&gt;int32_t&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"USD"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"old_session"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fluxen&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// or fluxen::rollback to discard&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The in-memory index is updated only after the fsync succeeds. If fsync fails, the partial tail gets truncated before throwing, leaving the database unchanged. If truncation also fails, the DB enters a poisoned state where every subsequent call throws, because at that point consistency can't be guaranteed.&lt;/p&gt;




&lt;h2&gt;
  
  
  Crash recovery
&lt;/h2&gt;

&lt;p&gt;Since nothing is ever modified in place, a crash mid-write leaves a partial entry at the tail of the file. On the next open, &lt;code&gt;load_index()&lt;/code&gt; walks the log and stops when it finds an entry whose claimed size doesn't fit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;hdr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;key_len&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;hdr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;val_len&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;file_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The partial bytes get truncated automatically. Everything before the crash is intact.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;compact()&lt;/code&gt; reclaims space from overwritten and deleted values. It writes live entries to a &lt;code&gt;.tmp&lt;/code&gt; file, fsyncs it, then renames it over the original. On POSIX, &lt;code&gt;rename()&lt;/code&gt; is atomic, so a crash during compaction leaves one of the two files fully intact.&lt;/p&gt;




&lt;h2&gt;
  
  
  Storing typed values without a serializer
&lt;/h2&gt;

&lt;p&gt;C++ lets you &lt;code&gt;memcpy&lt;/code&gt; any trivially copyable type, so fluxen uses a concept constraint to route between strings and raw bytes automatically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="k"&gt;template&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typename&lt;/span&gt; &lt;span class="nc"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;requires&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;is_trivially_copyable_v&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
             &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;is_convertible_v&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;string_view&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;string_view&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;!is_convertible_v&amp;lt;T, std::string_view&amp;gt;&lt;/code&gt; part makes sure string literals and &lt;code&gt;std::string&lt;/code&gt; still go through the string overload. On read, if the stored size doesn't match &lt;code&gt;sizeof(T)&lt;/code&gt;, &lt;code&gt;get&amp;lt;T&amp;gt;()&lt;/code&gt; returns &lt;code&gt;nullopt&lt;/code&gt; rather than doing a bad &lt;code&gt;memcpy&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;Config&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"cfg"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;8080&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"score"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int32_t&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;auto&lt;/span&gt; &lt;span class="n"&gt;cfg&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"cfg"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;    &lt;span class="c1"&gt;// std::optional&amp;lt;Config&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;auto&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;int32_t&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"score"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// std::optional&amp;lt;int32_t&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Limitations worth knowing
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;No range queries. The index is a hash map, so there's no sorted order.&lt;/li&gt;
&lt;li&gt;One process per file. No shared access across processes.&lt;/li&gt;
&lt;li&gt;No SQL or secondary indexes.&lt;/li&gt;
&lt;li&gt;All keys live in RAM.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If any of those are dealbreakers, SQLite might be the right tool.&lt;/p&gt;




&lt;p&gt;Source and full API docs at &lt;a href="https://github.com/dvuvud/fluxen" rel="noopener noreferrer"&gt;github.com/dvuvud/fluxen&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do you usually handle small-scale persistence in your C++ tools?&lt;/strong&gt; I'm curious to hear what other "no-dependency" solutions you've used!&lt;/p&gt;

</description>
      <category>cpp</category>
      <category>database</category>
      <category>showdev</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
