summaryrefslogtreecommitdiffstats
path: root/src/gc-finalized.h
diff options
context:
space:
mode:
Diffstat (limited to 'src/gc-finalized.h')
-rw-r--r--src/gc-finalized.h155
1 files changed, 155 insertions, 0 deletions
diff --git a/src/gc-finalized.h b/src/gc-finalized.h
new file mode 100644
index 000000000..a31155653
--- /dev/null
+++ b/src/gc-finalized.h
@@ -0,0 +1,155 @@
+/*
+ * Inkscape::GC::Finalized - mixin for GC-managed objects with non-trivial
+ * destructors
+ *
+ * Copyright 2004 MenTaLguY <mental@rydia.net>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * See the file COPYING for details.
+ *
+ */
+
+#ifndef SEEN_INKSCAPE_GC_FINALIZED_H
+#define SEEN_INKSCAPE_GC_FINALIZED_H
+
+#include <new>
+#include "gc-core.h"
+
+namespace Inkscape {
+
+namespace GC {
+
+/* @brief a mix-in ensuring that a 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
+ * therefor 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 finalizaable objects)
+ * finalizable, or if the object also derives from GC::Managed<>,
+ * use GC::Managed<>::clearOnceInaccessible to register those links
+ * to be cleared once the object is made inacecssible (and before it's
+ * finalized).
+ *
+ * In a tree structure that has parent links and finalized nodes,
+ * you will almost always want to do this with the parent links
+ * if you can't avoid having them.
+ *
+ * @see Inkscape::GC::Managed<>::clearOnceInaccessible
+ * @see Inkscape::GC::Managed<>::cancelClearOnceInacessible
+ *
+ * 2. Because there is no guarantee when the collector will destroy
+ * objects, there is no guarantee when the destructor will get called.
+ *
+ * It may not get called until the very end of the program, or never.
+ *
+ * 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, making multiple GC::Finalized-derived objects members
+ * of a non-finalized but garbage-collected object generally won't
+ * work unless you take care of registering finalizers yourself.
+ *
+ * [n.b., by "member", I mean an actual by-value-member of a type that
+ * derives from GC::Finalized, not simply a member that's a pointer or a
+ * reference to such a type]
+ *
+ */
+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,
+ NULL, NULL);
+ }
+ }
+ }
+ }
+
+ virtual ~Finalized() {
+ // make sure the destructor won't get invoked twice
+ Core::register_finalizer_ignore_self(Core::base(this),
+ NULL, NULL, NULL, NULL);
+ }
+
+private:
+ /// invoke the destructor for an object given a base and offset pair
+ static void _invoke_dtor(void *base, void *offset) {
+ _unoffset(base, offset)->~Finalized();
+ }
+
+ /// 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:encoding=utf-8:textwidth=99 :