Skip to content

Instantly share code, notes, and snippets.

@arrieta
Created October 6, 2018 21:02
Show Gist options
  • Save arrieta/96ad0b6b46ab08b8fe03eb802f9b4378 to your computer and use it in GitHub Desktop.
Save arrieta/96ad0b6b46ab08b8fe03eb802f9b4378 to your computer and use it in GitHub Desktop.
C++ std::shared_ptr<T> Aliasing Constructor
// About std::shared_ptr<T> aliasing constructor.
// J. Arrieta, Nabla Zero Labs.
// October 06, 2018.
//
// The std::shared_ptr<T> aliasing constructor has the signature
//
// template< class Y >
// shared_ptr(const shared_ptr<Y>& r, element_type* ptr) noexcept;
//
// According to cppreference.com, it "constructs a shared_ptr which shares
// ownership information with r, but holds an unrelated and unmanaged pointer
// ptr. If this shared_ptr is the last of the group to go out of scope, it will
// call the stored deleter for the object originally managed by r. However,
// calling get() on this shared_ptr will always return a copy of ptr. It is the
// responsibility of the programmer to make sure that this ptr remains valid as
// long as this shared_ptr exists, such as in the typical use cases where ptr is
// a member of the object managed by r or is an alias (e.g., downcast) of
// r.get()".
//
// There are many use cases that can take advantage of the aliasing constructor,
// but the one that immediately comes to mind is refering to a member of a data
// structure while hiding the data management aspects.
//
// For example: a linked list can be implemented in terms of a Node<T> structure
// that contains pointers to other Node<T> elements. However, the user does not
// need to know about these pointers, as the only thing she cares about is the
// actual T.
//
// In this case, the Node<T> can be managed by shared_ptr<Node<T>>, and the
// access returns a shared_ptr<T>.
//
// The example below is NOT meant to illustrate how to implement a good linked
// list. Instead, it is meant as a quick demonstration of the concept: control
// structure and lifetime via internal nodes and shared_ptr, while providing an
// API that only exposes the data.
#include <iostream>
#include <memory>
template <typename T>
struct List {
// This is the internal control structure, an implementation detail that
// should not leak to the API.
struct Node {
T data;
std::shared_ptr<Node> next = nullptr;
};
// Initialize an empty list
List() : head(std::make_shared<Node>()) {}
// Let's imagine T is expensive and we only provide facilities to move it into
// the container.
void insert(T&& value) {
auto next = head;
while (next->next) {
next = next->next;
}
next->next = std::make_shared<Node>();
next->next->data = std::move(value);
}
// Get a handle to the data stored at `index`. Notice how the shared pointer
// references a `const T` and not a `Node<T>`. The `Node` control structure is
// not part of the API.
std::shared_ptr<const T> at(std::size_t index) const {
std::size_t current = 0;
auto next = head;
while (next->next) {
if (current++ == index) {
// Return a handle to the data contained in the appropriate node, while
// controling lifetime via the shared_pointer of the underlying node.
auto&& underlying_node = next->next;
auto&& pointer_to_data = &underlying_node->data;
// aliasing constructor
return {underlying_node, pointer_to_data};
}
next = next->next;
}
return nullptr;
}
std::shared_ptr<Node> head;
};
// An imaginary expensive object.
struct Expensive {
void report() const { std::cout << "Expensive @" << this << "\n"; }
};
int main() {
// The user can create a list of "expensive" objects.
List<Expensive> list;
list.insert(Expensive());
list.insert(Expensive());
list.insert(Expensive());
// The handle is a shared_ptr<const Expensive> whose lifetime will continue
// even if `list` goes out of scope.
auto handle = list.at(2);
// As far as the user is concerned, `handle` is not at all related to a list,
// and there is no mention of a Node<T> class or a Node<Expensive> object. It
// is simply a shared_ptr to an Expensive object.
handle->report();
// Some stupid traversal.
std::size_t k = 0;
while (true) {
auto thing = list.at(k);
if (thing) {
std::cout << "Reporting on thing " << k << "\n";
thing->report();
++k;
} else {
break;
}
}
}
@arrieta
Copy link
Author

arrieta commented Oct 6, 2018

Possible output:

$ clang++ aliasing_constructor.cpp -std=c++17
$ ./a.out 
Expensive @0x7fc269d00048
Reporting on thing 0
Expensive @0x7fc269c02848
Reporting on thing 1
Expensive @0x7fc269d00018
Reporting on thing 2
Expensive @0x7fc269d00048

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment