-
Notifications
You must be signed in to change notification settings - Fork 24
/
value_semantics.cpp
275 lines (213 loc) · 6.99 KB
/
value_semantics.cpp
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
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
/*
Copyright 2012 Adobe Systems Incorporated
Distributed under the MIT License (see license at
http://stlab.adobe.com/licenses.html)
This file is intended as example code and is not production quality.
*/
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
#include <cassert>
using namespace std;
/******************************************************************************/
// copy_on_write
/*
Disclaimer:
This is a simplified version of copy_on_write from the Adobe Source
Libraries. It has not been well tested, thread safety has not been
tested at all as my compiler doesn't support std::atomic<> yet.
The version in ASL uses Intel TBB's atomic type and has been well
tested.
*/
#if __has_feature(cxx_atomic)
#include <atomic>
using counter_t = std::atomic<size_t>;
#else
using counter_t = size_t;
#endif
/*
A copy-on-write wrapper for any model of a regular type.
Requirements:
T models regular.
Copy-on-write sematics allow for object to be lazily copied - only creating a
copy when the value is modified and there is more than one reference to the
value.
Note:
copy_on_write is thread safe when C++11 atomics are supported.
*/
template <typename T> // models Regular
class copy_on_write
{
public:
// The type of value stored.
using value_type = T;
/*
The first call to the default constructor will construct a default
instance of value_type which will be used for subsequent calls to the
default constructor. The default instance will be released at exit.
*/
copy_on_write()
{
/*
NOTE : for thread safety this assumes static initization is
thread safe - required in C++11. In non-compliant compilers
use a once-init library.
*/
static implementation_t default_s;
object_m = default_s;
++object_m->count_m;
}
/*
Constructs a new copy_on_write object with a value x.
Paramter: x A default value to assign to this object
*/
copy_on_write(T x) :
object_m(new implementation_t(std::move(x)))
{ }
/*
Copy construction is a non-throwing operation and simply increments
the reference count on the stored object.
*/
copy_on_write(const copy_on_write& x) noexcept :
object_m(x.object_m)
{
if (object_m) ++object_m->count_m;
}
copy_on_write(copy_on_write&& x) noexcept :
object_m(x.object_m)
{
x.object_m = 0;
}
~copy_on_write()
{
release();
}
/*
As with copy construction, assignment is a non-throwing operation which
releases the old value and increments the reference count of the item
being assigned to.
*/
copy_on_write& operator=(copy_on_write x) noexcept
{ *this = move(x); return *this; }
copy_on_write& operator=(T x)
{
if (!object_m) object_m = new implementation_t(move(x));
else if (object_m->count_m == size_t(1)) object_m->value_m = move(x);
else reset(new implementation_t(move(x)));
return *this;
}
/*
Obtain a reference to the value the object is referencing. This will copy
the underlying value (if necessary) so changes to the value do not affect
other copy_on_write objects.
Note that write() does not have the same preconditions as operator=().
write() returns a reference to the underlying object's value, thus requiring
that an underlying object exist. operator=() on the other hand will perform
an allocation if one is necessary.
Return: A reference to the underlying object
*/
value_type& write()
{
assert(object_m && "FATAL : using a moved copy_on_write object");
if (!(object_m->count_m == size_t(1)))
reset(new implementation_t(object_m->value_m));
return object_m->value_m;
}
/*!
Obtain a const reference to the underlying object.
Return: A const reference to the underlying object
*/
const value_type& read() const noexcept
{
assert(object_m && "FATAL : using a moved copy_on_write object");
return object_m->value_m;
}
private:
struct implementation_t
{
explicit implementation_t(T x) :
value_m(std::move(x))
{ }
counter_t count_m = 1;
value_type value_m;
};
void release()
{
if (!object_m || --object_m->count_m) return;
delete object_m;
}
void reset(implementation_t* to)
{
release();
object_m = to;
}
implementation_t* object_m;
};
/******************************************************************************/
// Library
template <typename T>
void draw(const T& x, ostream& out, size_t position)
{ out << string(position, ' ') << x << endl; }
class object_t {
public:
template <typename T>
object_t(const T& x) : object_(new model<T>(x))
{ }
object_t(const object_t& x) : object_(x.object_->copy_())
{ cout << "copy" << endl; }
object_t(object_t&& x) = default;
object_t& operator=(object_t x)
{ object_ = move(x.object_); return *this; }
friend void draw(const object_t& x, ostream& out, size_t position)
{ x.object_->draw_(out, position); }
private:
struct concept_t {
virtual ~concept_t() = default;
virtual concept_t* copy_() = 0;
virtual void draw_(ostream&, size_t) const = 0;
};
template <typename T>
struct model : concept_t {
model(const T& x) : data_(x) { }
concept_t* copy_() { return new model(*this); }
void draw_(ostream& out, size_t position) const
{ draw(data_, out, position); }
T data_;
};
unique_ptr<concept_t> object_;
};
using document_t = vector<copy_on_write<object_t>>;
void draw(const document_t& x, ostream& out, size_t position)
{
out << string(position, ' ') << "<document>" << endl;
for (auto& e : x) draw(e.read(), out, position + 2);
out << string(position, ' ') << "</document>" << endl;
}
using history_t = vector<document_t>;
void commit(history_t& x) { assert(x.size()); x.push_back(x.back()); }
void undo(history_t& x) { assert(x.size()); x.pop_back(); }
document_t& current(history_t& x) { assert(x.size()); return x.back(); }
/******************************************************************************/
// Client
class my_class_t {
/* ... */
};
void draw(const my_class_t&, ostream& out, size_t position)
{ out << string(position, ' ') << "my_class_t" << endl; }
int main()
{
history_t h(1);
current(h).emplace_back(0);
current(h).emplace_back(string("Hello!"));
draw(current(h), cout, 0);
cout << "--------------------------" << endl;
commit(h);
current(h).emplace_back(current(h));
current(h).emplace_back(my_class_t());
current(h)[1] = string("World");
draw(current(h), cout, 0);
cout << "--------------------------" << endl;
undo(h);
draw(current(h), cout, 0);
}