diff options
Diffstat (limited to 'src/gc-finalized.h')
-rw-r--r-- | src/gc-finalized.h | 144 |
1 files changed, 144 insertions, 0 deletions
diff --git a/src/gc-finalized.h b/src/gc-finalized.h new file mode 100644 index 0000000..6bd702c --- /dev/null +++ b/src/gc-finalized.h @@ -0,0 +1,144 @@ +// 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 <mental@rydia.net> + * + * 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 <new> +#include <cstddef> +#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<void *>( + reinterpret_cast<char *>(self) - reinterpret_cast<char *>(base) + ); + } + /// reconstitute 'this' given an offset-from-base-address in a void * + static Finalized *_unoffset(void *base, void *offset) { + return reinterpret_cast<Finalized *>( + reinterpret_cast<char *>(base) + + reinterpret_cast<std::ptrdiff_t>(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 : |