stable_reference_buffer

Overview

stable_reference_buffer manages dynamic memory with a stable, cross-shared-library reference count. Traditional std::shared_ptr can fail when different shared libraries create their own copies of the reference count. By contrast, stable_reference_buffer places the reference count in a consistently accessible location (an interprocess map), ensuring correct usage counts across multiple libraries within the same process.

Main Goal
Prevent accidental double-deletion or mismatched reference counts when a memory buffer is passed around among different shared libraries.

Key Features
- Cross-Library Safe: A single, unified reference count for each memory block, even if the block is shared across library boundaries.
- Stable Buffer Lifecycle: Each stable_reference_buffer instance increments the shared ref-count; resetting or destructing decrements it. Deallocation occurs only when the last reference is gone.
- Delay Deallocation: A specialized scope guard delay_deallocation can prevent memory from being freed while a library or plugin is still actively referencing the buffer.

Typical Usage

1. Allocate and Share Memory

#include <cbeam/container/stable_reference_buffer.hpp>

int main() {
    // Allocate a buffer that can store 10 integers
    cbeam::container::stable_reference_buffer buf(10, sizeof(int));

    // 'get()' returns a raw pointer for direct access
    auto* data = static_cast<int*>(buf.get());
    for (int i = 0; i < 10; ++i) {
        data[i] = i;
    }

    // Now pass 'buf' to another library or function. 
    // The reference count is tracked so deallocation won't happen prematurely.
    return 0;
}

Here, buf is “known,” meaning the system has a global reference count for the memory block. Another library receiving buf can copy it or store it, and the usage count remains consistent.

2. Copy and Reference Counting

// Suppose we have an existing stable_reference_buffer `buf`
cbeam::container::stable_reference_buffer copy(buf);

// Both now refer to the same underlying memory
assert(copy.get() == buf.get());
assert(copy.use_count() == 2);

When copy is destroyed or reset, the reference count decrements. The memory remains valid if another reference is still active.

3. Append Data

int additional[3] = {42, 43, 44};
buf.append(additional, sizeof(additional));

// The buffer grows, incorporating new content.

append supports dynamic resizing. If the memory location changes, stable_reference_buffer updates the global reference-tracking structures accordingly.

4. Delay Deallocation

{
    cbeam::container::stable_reference_buffer::delay_deallocation guard;

    // Within this scope, memory blocks created or copied won't be freed 
    // even if their ref-count reaches zero, until after we exit the scope.
    cbeam::container::stable_reference_buffer temp(10, sizeof(int));
    // ... do work ...
    temp.reset(); // Normally frees memory if no references remain,
                  // but 'delay_deallocation' defers it until scope exit.
}
// Exiting the scope now potentially frees memory whose ref-count was zeroed.

This is especially helpful if a plugin (which allocated the buffer) is about to unload but another library is still referencing the raw pointer. Keeping the memory alive until the end of delay_deallocation scope prevents crashes or undefined behavior.

Motivation

When multiple shared libraries each see a memory block with a shared pointer, they may use different static runtime data, resulting in mismatched reference counts and double-frees. By using a stable, process-wide map for reference counts, stable_reference_buffer solves:

  • Potential Double-Free: Eliminates the risk that each library tries to free the block.
  • Consistent Lifecycle: Ensures all references remain valid as long as at least one library holds the buffer.

When to Use

  • Inter-Library Data Sharing: If you pass dynamic memory across library boundaries, stable_reference_buffer ensures a single reference count.
  • Delayed Destruction: If you’re unsure when a library might unload, use delay_deallocation to ensure memory remains accessible.
  • Complex Systems: Plugin architectures or multi-DLL setups benefit greatly from robust, stable memory references.