1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
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 :
|