summaryrefslogtreecommitdiffstats
path: root/src/util/statics.h
blob: 11ac26c812671e84b9bd044845e7052c6d8ef6d4 (plain)
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
// SPDX-License-Identifier: GPL-2.0-or-later
/** @file
 * Static objects with destruction before main() exit.
 */
#ifndef INKSCAPE_UTIL_STATICS_BIN_H
#define INKSCAPE_UTIL_STATICS_BIN_H

#include <optional>
#include <cassert>

namespace Inkscape {
namespace Util {

class StaticBase;

/**
 * The following system provides a framework for resolving gnarly issues to do with threads, statics, and libraries at program exit.
 * Instead of the function-local static initialisation idiom
 *
 *     X &get()
 *     {
 *         static X x;
 *         return x;
 *     }
 *
 * you instead write
 *
 *     X &get()
 *     {
 *         static Static<X> x;
 *         return x.get();
 *     }
 *
 * Similarities:
 *     - Both allow creation of singleton objects which are destroyed in the reverse order of constructor completion.
 *     - Both allow dependency registration and automatically prevent dependency loops.
 *
 * Differences:
 *     - The second kind are destructed before the end of main(), the first kind after.
 *     - Only the second supports on-demand destruction and re-initialisation - necessary if you want to write isolated tests.
 *     - Only the first supports thread-safe initialisation; the second must be initialised in the main thread.
 *     - Only the first supports automatic destruction; the second must be destroyed manually with StaticsBin::get().destroy().
 *
 * Rationale examples:
 *     - FontFactory depends on Harfbuzz depends on FreeType, but Harfbuzz doesn't register the dependency so FreeType fails
 *       to outlive both FontFactory and Harfbuzz. (We say X depends on Y if the lifetime of X must be contained in Y; such
 *       a situation can be guaranteed by having X::X call Y::get.) FreeType is inaccessible, so we cannot register the
 *       dependency ourselves without fragile hacks. Therefore, FontFactory must be destroyed before the end of main().
 *
 *     - Background threads can access statics of the first kind. If they run past end of main(), they may find them destructed.
 *       Therefore either the static that joins all threads must be destructed before the end of main(), or must register a
 *       dependency on every static that any background thread might use, which is infeasible and error-prone.
 */

/**
 * Maintains the list of statics that need to be destroyed,
 * destroys them, and complains if it's not asked to do so in time.
 */
class StaticsBin
{
public:
    static StaticsBin &get();
    void destroy();
    ~StaticsBin();

private:
    StaticBase *head = nullptr;

    template <typename T>
    friend class Static;
};

/// Base class for statics, allowing type-erased destruction.
class StaticBase
{
protected:
    StaticBase *next;
    ~StaticBase() = default;
    virtual void destroy() = 0;
    friend class StaticsBin;
};

/// Wrapper for a static of type T.
template <typename T>
class Static final : public StaticBase
{
public:
    T &get()
    {
        if (!opt) {
            opt.emplace();
            auto &bin = StaticsBin::get();
            next = bin.head;
            bin.head = this;
        }
        return *opt;
    }

private:
    std::optional<T> opt;
    void destroy() override { opt.reset(); }
};

} // namespace Util
} // namespace Inkscape

#endif // INKSCAPE_UTIL_STATICS_BIN_H