From 0f99488236d891d4c92494fc581bf314bf292dbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20P=C3=B6schel?= Date: Tue, 18 Jun 2024 13:31:26 +0200 Subject: [PATCH] Document new internal function --- include/openPMD/backend/Attributable.hpp | 12 +++++++++ src/backend/Attributable.cpp | 31 ++++++++++++++++++++++-- 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/include/openPMD/backend/Attributable.hpp b/include/openPMD/backend/Attributable.hpp index 9580b60989..0f7b722ae5 100644 --- a/include/openPMD/backend/Attributable.hpp +++ b/include/openPMD/backend/Attributable.hpp @@ -125,6 +125,18 @@ namespace internal class RecordComponentData; + /* + * Internal function to turn a handle into an owning handle that will keep + * not only itself, but the entire Series alive. Works by hiding a copy of + * the Series into the destructor lambda of the internal shared pointer. The + * returned handle is entirely safe to use in just the same ways as a normal + * handle, just the surrounding Series needs not be kept alive any more + * since it is stored within the handle. By storing the Series in the + * handle, not in the actual data, reference cycles are avoided. + * + * Instantiations for T exist for types RecordComponent, + * MeshRecordComponent, Mesh, Record, ParticleSpecies, Iteration. + */ template T &makeOwning(T &self, Series); } // namespace internal diff --git a/src/backend/Attributable.cpp b/src/backend/Attributable.cpp index a4d993b9f9..d5ff005389 100644 --- a/src/backend/Attributable.cpp +++ b/src/backend/Attributable.cpp @@ -514,12 +514,39 @@ namespace internal template T &makeOwning(T &self, Series s) { + /* + * `self` is a handle object such as RecordComponent or Mesh (see + * instantiations below). + * These objects don't normally keep alive the Series, i.e. as soon as + * the Series is destroyed, the handle becomes invalid. + * This function modifies the handle such that it actually keeps the + * Series alive and behaves otherwise identically. + * First, get the internal shared pointer of the handle. + */ std::shared_ptr data_ptr = self.T::getShared(); auto raw_ptr = data_ptr.get(); + /* + * Now, create a new shared pointer pointing to the same address as the + * actual pointer and replace the old internal shared pointer by the new + * one. + */ self.setData(std::shared_ptr{ raw_ptr, - [s_lambda = std::move(s), data_ptr_lambda = std::move(data_ptr)]( - auto const *) { /* no-op */ }}); + /* + * Here comes the main trick. + * The new shared pointer stores (and thus keeps alive) two items + * via lambda capture in its destructor: + * 1. The old shared pointer. + * 2. The Series. + * It's important to notice that these two items are only stored + * within the newly created handle, and not internally within the + * actual openPMD object model. This means that no reference cycles + * can occur. + */ + [s_lambda = std::move(s), + data_ptr_lambda = std::move(data_ptr)](auto const *) { + /* no-op, the lambda captures simply go out of scope */ + }}); return self; }