Code

Replace GC::Managed<>::clearOnceInaccessible with GC::soft_ptr<>
[inkscape.git] / src / gc-finalized.h
1 /*
2  * Inkscape::GC::Finalized - mixin for GC-managed objects with non-trivial
3  *                           destructors
4  *
5  * Copyright 2004 MenTaLguY <mental@rydia.net>
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * See the file COPYING for details.
13  *
14  */
16 #ifndef SEEN_INKSCAPE_GC_FINALIZED_H
17 #define SEEN_INKSCAPE_GC_FINALIZED_H
19 #include <new>
20 #include "gc-core.h"
22 namespace Inkscape {
24 namespace GC {
26 /* @brief a mix-in ensuring that a object's destructor will get called before
27  *        the garbage collector destroys it
28  *
29  * Normally, the garbage collector does not call destructors before destroying
30  * an object.  On construction, this "mix-in" will register a finalizer
31  * function to call destructors before derived objects are destroyed.
32  * 
33  * This works pretty well, with the following caveats:
34  *
35  *   1. The garbage collector uses strictly topologically-ordered
36  *      finalization; if objects with finalizers reference each other
37  *      directly or indirectly, the collector will refuse to finalize (and
38  *      therefor free) them.  You'll see a warning on the console if this
39  *      happens.
40  *
41  *      The best way to limit this effect is to only make "leaf" objects
42  *      (i.e. those that don't point to other finalizaable objects)
43  *      finalizable, and otherwise use GC::soft_ptr<> instead of a regular
44  *      pointer for "backreferences" (e.g. parent pointers in a tree
45  *      structure), so that those references can be cleared to break any
46  *      finalization cycles.
47  *
48  *      @see Inkscape::GC::soft_ptr<>
49  *
50  *   2. Because there is no guarantee when the collector will destroy
51  *      objects, there is no guarantee when the destructor will get called.
52  *
53  *      It may not get called until the very end of the program, or ever.
54  *
55  *   3. If allocated in arrays, only the first object in the array will
56  *      have its destructor called, unless you make other arrangements by
57  *      registering your own finalizer instead.
58  *
59  *   4. Similarly, making multiple GC::Finalized-derived objects members
60  *      of a non-finalized but garbage-collected object generally won't
61  *      work unless you take care of registering finalizers yourself.
62  *
63  * [n.b., by "member", I mean an actual by-value-member of a type that
64  *  derives from GC::Finalized, not simply a member that's a pointer or a
65  *  reference to such a type]
66  *
67  */
68 class Finalized {
69 public:
70     Finalized() {
71         void *base=Core::base(this);
72         if (base) { // only if we are managed by the collector
73             CleanupFunc old_cleanup;
74             void *old_data;
76             // the finalization callback needs to know the value of 'this'
77             // to call the destructor, but registering a real pointer to
78             // ourselves would pin us forever and prevent us from being
79             // finalized; instead we use an offset-from-base-address
81             Core::register_finalizer_ignore_self(base, _invoke_dtor,
82                                                        _offset(base, this),
83                                                        &old_cleanup, &old_data);
85             if (old_cleanup) {
86                 // If there was already a finalizer registered for our
87                 // base address, there are two main possibilities:
88                 //
89                 // 1. one of our members is also a GC::Finalized and had
90                 //    already registered a finalizer -- keep ours, since
91                 //    it will call that member's destructor, too
92                 //
93                 // 2. someone else registered a finalizer and we will have
94                 //    to trust that they will call the destructor -- put
95                 //    the existing finalizer back
96                 //
97                 // It's also possible that a member's constructor was called
98                 // after ours (e.g. via placement new).  Don't do that.
100                 if ( old_cleanup != _invoke_dtor ) {
101                     Core::register_finalizer_ignore_self(base,
102                                                          old_cleanup, old_data,
103                                                          NULL, NULL);
104                 }
105             }
106         }
107     }
109     virtual ~Finalized() {
110         // make sure the destructor won't get invoked twice
111         Core::register_finalizer_ignore_self(Core::base(this),
112                                              NULL, NULL, NULL, NULL);
113     }
115 private:
116     /// invoke the destructor for an object given a base and offset pair
117     static void _invoke_dtor(void *base, void *offset) {
118         _unoffset(base, offset)->~Finalized();
119     }
121     /// turn 'this' pointer into an offset-from-base-address (stored as void *)
122     static void *_offset(void *base, Finalized *self) {
123         return reinterpret_cast<void *>(
124             reinterpret_cast<char *>(self) - reinterpret_cast<char *>(base)
125         );
126     }
127     /// reconstitute 'this' given an offset-from-base-address in a void *
128     static Finalized *_unoffset(void *base, void *offset) {
129         return reinterpret_cast<Finalized *>(
130             reinterpret_cast<char *>(base) +
131             reinterpret_cast<std::ptrdiff_t>(offset)
132         );
133     }
134 };
140 #endif
141 /*
142   Local Variables:
143   mode:c++
144   c-file-style:"stroustrup"
145   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
146   indent-tabs-mode:nil
147   fill-column:99
148   End:
149 */
150 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :