-
Notifications
You must be signed in to change notification settings - Fork 0
/
rcref.d
145 lines (123 loc) · 3.26 KB
/
rcref.d
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
145
/* Written in the D programming language.
* Copyright (c) 2016 by the D Language Foundation
* Authors: Nick Treleaven, Walter Bright (RefCountedSlice)
* License: Boost Software License, Version 1.0. See accompanying file
* LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt.
*/
/** Memory-safe Reference Counted Slice.
* Safety is enforced using a runtime check in RCRef's destructor.
* Note: Runtime check is disabled if -noboundscheck is passed.
* 'scope' is commented out until DIP1000 support is stable. */
///
@safe struct RCSlice(T) {
private:
T[] payload;
uint* count;
public:
this(size_t initialSize) {
payload = new T[initialSize];
count = new size_t;
*count = 1;
}
this(this) {
if (count) ++*count;
}
void opAssign(RCSlice rhs) {
this.__dtor();
payload = rhs.payload;
count = rhs.count;
if (count)
++*count;
}
// Interesting fact #1: destructor can be @trusted
@trusted ~this() {
if (count && !--*count) {
delete payload;
delete count;
}
}
alias get this;
// Interesting fact #2: references to internals can be given away
//scope
private auto get() {
return RCRef!T(payload, count);
}
}
// Disable RC checking if bounds checking is disabled
version (D_NoBoundsChecks){}
else version = SafeRC;
// Ensure on destruction there's an independent RCO alive with longer lifetime
private @safe
struct RCRef(T)
{
private:
T[] payload;
version(SafeRC) uint* count;
this(T[] payload, uint* count = null)
{
this.payload = payload;
version(SafeRC)
{
this.count = count;
++*count;
}
}
public:
@disable this(this); // prevent copying
@disable void opAssign(RCRef);
version(SafeRC)
~this()
{
assert(count, "Attempting to destroy an invalid " ~ RCRef.stringof);
// Ensure it's not just our +1 keeping the memory alive
import core.exception;
if (*count <= 1)
throw new AssertError("Invalid reference: " ~ RCRef.stringof,
__FILE__, __LINE__);
--*count;
}
//scope
ref opIndex(size_t i) {
return payload[i];
}
// ...
}
private @trusted checkInvalidRef(lazy void ex)
{
import core.exception, std.exception;
assert(collectExceptionMsg!AssertError(ex) == "Invalid reference: RCRef!int");
}
///
@safe unittest
{
alias RCS = RCSlice!int;
static fun(ref RCS rc, ref int ri)
{
rc = rc.init;
ri++;
}
auto rc = RCS(1);
auto copy = rc;
assert(*rc.count == 2);
assert(rc[0] == 0);
fun(rc, rc[0]);
// refcount OK due to copy
// count checked above when rc.get temporary is destroyed
assert(!rc.count);
assert(copy[0] == 1);
// nested references
void gun(ref int ri)
{
import std.algorithm : move;
rc = copy.move;
assert(!copy.count);
ri++;
}
gun(copy[0]);
assert(*rc.count == 1);
assert(rc[0] == 2);
// call to fun is invalid, as internally rc[0] outlives rc
checkInvalidRef(fun(rc, rc[0]));
assert(!rc.count);
// Note: old rc heap memory will leak (we ignored an AssertError)
}