// SPDX-License-Identifier: GPL-2.0-or-later /** @file * Inkscape::GC::Finalized - mixin for GC-managed objects with non-trivial * destructors *//* * Authors: * see git history * MenTaLguY * * Copyright (C) 2004-2018 Authors * Released under GNU GPL v2+, read the file 'COPYING' for more information. */ #ifndef SEEN_INKSCAPE_GC_FINALIZED_H #define SEEN_INKSCAPE_GC_FINALIZED_H #include #include #include "inkgc/gc-core.h" namespace Inkscape { namespace GC { /* @brief A mix-in ensuring that an object's destructor will get called before * the garbage collector destroys it * * Normally, the garbage collector does not call destructors before destroying * an object. On construction, this "mix-in" will register a finalizer * function to call destructors before derived objects are destroyed. * * This works pretty well, with the following caveats: * * 1. The garbage collector uses strictly topologically-ordered * finalization; if objects with finalizers reference each other * directly or indirectly, the collector will refuse to finalize (and * therefore free) them. You'll see a warning on the console if this * happens. * * The best way to limit this effect is to only make "leaf" objects * (i.e. those that don't point to other finalizable objects) * finalizable, and otherwise use GC::soft_ptr<> instead of a regular * pointer for "backreferences" (e.g. parent pointers in a tree * structure), so that those references can be cleared to break any * finalization cycles. * * @see Inkscape::GC::soft_ptr<> * * 2. Because there is no guarantee when the collector will destroy * objects, it is impossible to tell in advance when the destructor * will get called. It may not get called until the very end * of the program, or ever. * * 3. If allocated in arrays, only the first object in the array will * have its destructor called, unless you make other arrangements by * registering your own finalizer instead. * * 4. Similarly, putting a finalized object as a member in another * garbage collected but non-finalized object will cause the member * object's destructor not to be called when the parent object is * collected, unless you register the finalizer yourself (by "member" * we mean an actual by-value member, not a reference or a pointer). */ class Finalized { public: Finalized() { void *base=Core::base(this); if (base) { // only if we are managed by the collector CleanupFunc old_cleanup; void *old_data; // the finalization callback needs to know the value of 'this' // to call the destructor, but registering a real pointer to // ourselves would pin us forever and prevent us from being // finalized; instead we use an offset-from-base-address Core::register_finalizer_ignore_self(base, _invoke_dtor, _offset(base, this), &old_cleanup, &old_data); if (old_cleanup) { // If there was already a finalizer registered for our // base address, there are two main possibilities: // // 1. one of our members is also a GC::Finalized and had // already registered a finalizer -- keep ours, since // it will call that member's destructor, too // // 2. someone else registered a finalizer and we will have // to trust that they will call the destructor -- put // the existing finalizer back // // It's also possible that a member's constructor was called // after ours (e.g. via placement new). Don't do that. if ( old_cleanup != _invoke_dtor ) { Core::register_finalizer_ignore_self(base, old_cleanup, old_data, nullptr, nullptr); } } } } virtual ~Finalized() { // make sure the destructor won't get invoked twice Core::register_finalizer_ignore_self(Core::base(this), nullptr, nullptr, nullptr, nullptr); } private: /// invoke the destructor for an object given a base and offset pair static void _invoke_dtor(void *base, void *offset); /// turn 'this' pointer into an offset-from-base-address (stored as void *) static void *_offset(void *base, Finalized *self) { return reinterpret_cast( reinterpret_cast(self) - reinterpret_cast(base) ); } /// reconstitute 'this' given an offset-from-base-address in a void * static Finalized *_unoffset(void *base, void *offset) { return reinterpret_cast( reinterpret_cast(base) + reinterpret_cast(offset) ); } }; } } #endif /* Local Variables: mode:c++ c-file-style:"stroustrup" c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) indent-tabs-mode:nil fill-column:99 End: */ // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :