value_ptr — The Missing C++ Smart-Pointer

Use value_ptr to get value semantics on a heap resource. At the cost of some extra copying, your code will be simpler and easier to reason about.

·5 min read

TL;DR

Use the value_ptr smart-pointer to get value semantics on a heap resource. At the cost of some extra copying, your code will be simpler and easier to reason about.

Nameunique_ptrshared_ptrweak_ptrvalue_ptr
OwnershipUniqueSharedUnique
Copyable
Movable
SharingReferenceValue
LifetimeLexicalReference-countedNon-extendingLexical
SemanticsReferenceReferenceOptional-referenceValue

An implementation of value_ptr can be found on GitHub.

Introduction

With smart-pointers, encoding ownership semantics and managing resources has never been easier. We can find smart-pointers in the standard library for the most common use-cases, however none of these provides value semantics. This article introduces value_ptr, alongside some motivating examples.

(Dumb) Raw Pointers

Raw pointers do not convey any information about a resource's ownership model. Allocation and deallocation must be managed by the programmer, which may lead to bugs like double-delete or memory-leaks.

void* x = createInstance();
int* y = new int();
foo.bar(y);

Three lines raise many unanswered questions: Can x be nullptr? Is it managed? Must I delete it? If I delete y before foo, is foo still valid?

unique_ptr

unique_ptr manages the lifetime of a resource by taking sole ownership and binding that to its lexical scope. Copying is not possible, but ownership can be transferred via std::move.

unique_ptr<Widget> createWindow() {
  return make_unique<Widget>();
}
 
int main() {
  auto w = createWindow();
 
  // auto w2 = w; // error: unique_ptr has no copy-constructor
 
  auto w3 = move(w);
  // w3 now owns the widget, w is empty
}

shared_ptr

shared_ptr allows multiple owners by counting references. The copy-constructor increments the counter; the destructor decrements it. When the counter hits zero the resource is disposed.

shared_ptr<Texture> tex = Texture::load("textures/my_texture.png");
 
thread(doSomethingWithTexture, tex).detach();
thread(doSomethingMoreWithTexture, tex).detach();

weak_ptr

If shared_ptr is used in a cycle, the reference counter never hits zero and memory leaks. weak_ptr does not increment the reference counter, breaking the cycle.

struct Node {
  string name;
  weak_ptr<Node> parent;
  vector<shared_ptr<Node>> children;
};

Introducing value_ptr

Value semantics make your code easier to reason about because ownership is strictly hierarchical and exclusive. value_ptr enforces those semantics on a copyable heap resource.

How it Works

  • value_ptr has exclusive ownership of a resource on the heap.
  • Assigning one value_ptr to another creates a new value_ptr pointing to its own copy of the resource.
  • The resource is destroyed when the value_ptr leaves its lexical scope.
  • No memory is shared, so value_ptr is inherently thread-safe.
  • A modern compiler removes most redundant copies.

Example 1 — Recursive Data Types

Recursive types like trees must use pointers in C++ so the memory layout can be computed at compile-time. With value_ptr we keep the simplicity of value semantics:

struct Tree {
  string const name;
  value_ptr<Tree> left;
  value_ptr<Tree> right;
 
  Tree(
    string const& name,
    value_ptr<Tree> const& left = value_ptr<Tree>{},
    value_ptr<Tree> const& right = value_ptr<Tree>{})
    : name{name}, left{left}, right{right}
  {}
};
 
int main() {
  Tree root = Tree{
    "root",
    Tree{"l0"},
    Tree{"r0"}
  };
 
  root.left = root;  // copy of root assigned to left
  root.right = root; // no cyclic references!
}

Example 2 — The PImpl Pattern

value_ptr is a natural fit for PImpl. It gives value semantics for free, so you don't need to hand-write copy constructors:

struct Foo {
  int bar() { return ptr->bar(); }
 
  Foo(int x);
 
  // value_ptr gives us value semantics for free
  Foo(Foo const&) = default;
  Foo& operator=(Foo const&) = default;
  ~Foo() = default;
 
  struct Pimpl;
  value_ptr<Pimpl> ptr;
};
foo.cpp
struct Foo::Pimpl {
  int x;
  int bar() { return ++x; }
};
 
Foo::Foo(int x) : ptr{Foo::Pimpl{x}} {}

Implementing value_ptr

value_ptr is similar to unique_ptr but with a different copy-constructor. Whilst copying a unique_ptr is forbidden, copying a value_ptr creates a copy of the resource. We implement it by leveraging unique_ptr and a copy function.

Take a look at the source on GitHub.

Summary

Value semantics are easy to reason about, and are often useful even for heap objects. The C++ standard library does not provide a smart-pointer with value semantics, but C++ has the features to allow us to roll our own.

Nameunique_ptrshared_ptrweak_ptrvalue_ptr
OwnershipUniqueSharedUnique
Copyable
Movable
SharingReferenceValue
LifetimeLexicalReference-countedNon-extendingLexical
SemanticsReferenceReferenceOptional-referenceValue

Related Articles