stlab::copy_on_write 1.0.3
Copy-on-write wrapper for any type
Loading...
Searching...
No Matches
stlab::copy_on_write

Source Code

Introduction

The stlab::copy_on_write<T> class provides a copy-on-write wrapper for any type that models Regular. This implementation allows multiple instances to share the same underlying data until one of them needs to modify it, at which point a copy is made. The class is not intended to be exposed in a class interface, but rather used as a utility to construct a type where some or all of the members are copy_on_write.

The stlab::copy_on_write<T>::write() operation optionally takes a transform and in-place transform function. For many mutable operations, there is a transform operation that can be applied to perform the copy with the mutation in a more efficient manner.

Copy-on-write is most useful for types between 4K and 1M in size and expected to be copied frequently, such as part of a transaction system, i.e., implementing undo/redo. Larger objects can be decomposed into smaller copy-on-write objects or use a data structure such as a rope that has an internal copy-on-write structure. Smaller objects can be more efficient by avoiding heap allocations (such as with small object optimization) and always copying; however, if they always heap allocate, they may be more efficient by using copy-on-write.

Key Features

  • Thread-safe: Uses atomic reference counting for safe concurrent access
  • Header-only: No compilation required, just include the header
  • C++17: Leverages modern C++ features for clean, efficient implementation

Basic Usage

This example demonstrates the core functionality including efficient copying, copy-on-write semantics, identity checking, and swap operations.

#include <cassert>
#include <cstddef>
#include <iostream>
#include <string>
#include <vector>
using namespace std;
using namespace stlab;
namespace {
// This is a simple document class that uses copy-on-write to manage its lines.
class document {
public:
// We can default all the special member functions and still have value semantics.
document() = default;
// Copy operations are noexcept and only cost an atomic increment.
document(const document& other) noexcept = default;
document& operator=(const document& other) noexcept = default;
// Move operations are noexcept and minimal cost.
document(document&& other) noexcept = default;
document& operator=(document&& other) noexcept = default;
// We only expose const iterators - exposing non-const iterators may require a copy and
// would invalidate any const iterators. This is unusual for a container and may catch
// developers off guard.
using iterator = std::vector<std::string>::const_iterator;
iterator begin() const { return _lines.read().begin(); }
iterator end() const { return _lines.read().end(); }
size_t size() const { return _lines.read().size(); }
bool empty() const { return _lines.read().empty(); }
bool identity(const document& other) const { return _lines.identity(other._lines); }
void insert(std::string&& line, size_t index) {
assert(index <= size() && "index out of bounds");
_lines.write(
[&](const std::vector<std::string>& lines) {
std::vector<std::string> new_lines = lines;
new_lines.insert(new_lines.begin() + index, std::move(line));
return new_lines;
},
[&](std::vector<std::string>& lines) {
// If the object is unique, we can modify the underlying data in place
lines.insert(lines.begin() + index, std::move(line));
});
}
void erase(size_t index) {
assert(index <= size() && "index out of bounds");
_lines.write(
[&](const std::vector<std::string>& lines) {
std::vector<std::string> new_lines = lines;
if (index < lines.size()) {
new_lines.erase(new_lines.begin() + index);
}
return new_lines;
},
[&](std::vector<std::string>& lines) {
// If the object is unique, we can modify the underlying data in place
lines.erase(lines.begin() + index);
});
}
};
} // namespace
int main() {
cerr << "--- Test starting ---" << endl;
document d0;
d0.insert("Hello, world!", 0);
d0.insert("After Hello", 1);
document d1(d0);
assert(d0.identity(d1));
cerr << "main: calling d1.insert..." << endl;
d1.insert("Start of d1", 0);
cerr << "main: d1.insert returned." << endl;
assert(!d0.identity(d1));
cout << "d0:" << endl;
for (const auto& line : d0) {
cout << line << endl;
}
cout << "d1:" << endl;
for (const auto& line : d1) {
cout << line << endl;
}
cerr << "--- Test finished ---" << endl;
return 0;
}
auto identity(const copy_on_write &x) const noexcept -> bool
auto read() const noexcept -> const element_type &
auto write() -> element_type &
Copy-on-write wrapper implementation.

License

Distributed under the Boost Software License, Version 1.0.