forked from DlangRen/Programming-in-D
-
Notifications
You must be signed in to change notification settings - Fork 0
/
nested.d
282 lines (211 loc) · 8.38 KB
/
nested.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
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
276
277
278
279
280
281
282
Ddoc
$(DERS_BOLUMU $(IX function, nested) $(IX struct, nested) $(IX class, nested in function) $(IX nested definition) Nested Functions, Structs, and Classes)
$(P
Up to this point, we have been defining functions, structs, and classes in the outermost scopes (i.e. the module scope). They can be defined in inner scopes as well. Defining them in inner scopes helps with encapsulation by narrowing the visibility of their symbols, as well as creating $(I closures) that we have seen in $(LINK2 /ders/d.en/lambda.html, the Function Pointers, Delegates, and Lambdas chapter).
)
$(P
As an example, the following $(C outerFunc()) function contains definitions of a nested function, a nested $(C struct), and a nested $(C class):
)
---
void outerFunc(int parameter) {
int local;
$(HILITE void nestedFunc()) {
local = parameter * 2;
}
$(HILITE struct NestedStruct) {
void memberFunc() {
local /= parameter;
}
}
$(HILITE class NestedClass) {
void memberFunc() {
local += parameter;
}
}
// Using the nested definitions inside this scope:
nestedFunc();
auto s = NestedStruct();
s.memberFunc();
auto c = new NestedClass();
c.memberFunc();
}
void main() {
outerFunc(42);
}
---
$(P
Like any other variable, nested definitions can access symbols that are defined in their outer scopes. For example, all three of the nested definitions above are able to use the variables named $(C parameter) and $(C local).
)
$(P
As usual, the names of the nested definitions are valid only in the scopes that they are defined in. For example, $(C nestedFunc()), $(C NestedStruct), and $(C NestedClass) are not accessible from $(C main()):
)
---
void main() {
auto a = NestedStruct(); $(DERLEME_HATASI)
auto b = outerFunc.NestedStruct(); $(DERLEME_HATASI)
}
---
$(P
Although their names cannot be accessed, nested definitions can still be used in other scopes. For example, many Phobos algorithms handle their tasks by nested structs that are defined inside Phobos functions.
)
$(P
To see an example of this, let's design a function that consumes a slice from both ends in alternating order:
)
---
import std.stdio;
import std.array;
auto alternatingEnds(T)(T[] slice) {
bool isFromFront = true;
$(HILITE struct EndAlternatingRange) {
bool empty() @property const {
return slice.empty;
}
T front() @property const {
return isFromFront ? slice.front : slice.back;
}
void popFront() {
if (isFromFront) {
slice.popFront();
isFromFront = false;
} else {
slice.popBack();
isFromFront = true;
}
}
}
return EndAlternatingRange();
}
void main() {
auto a = alternatingEnds([ 1, 2, 3, 4, 5 ]);
writeln(a);
}
---
$(P
Even though the nested $(C struct) cannot be named inside $(C main()), it is still usable:
)
$(SHELL
[1, 5, 2, 4, 3]
)
$(P
$(IX Voldemort) $(I $(B Note:) Because their names cannot be mentioned outside of their scopes, such types are called $(I Voldemort types) due to analogy to a Harry Potter character.)
)
$(P
$(IX closure) $(IX context) Note that the nested $(C struct) that $(C alternatingEnds()) returns does not have any member variables. That $(C struct) handles its task using merely the function parameter $(C slice) and the local function variable $(C isFromFront). The fact that the returned object can safely use those variables even after leaving the context that it was created in is due to a $(I closure) that has been created automatically. We have seen closures in $(LINK2 /ders/d.en/lambda.html, the Function Pointers, Delegates, and Lambdas chapter).
)
$(H6 $(C static) when a closure is not needed)
$(P
$(IX static, nested definition) Since they keep their contexts alive, nested definitions are more expensive than their regular counterparts. Additionally, as they must include a $(I context pointer) to determine the context that they are associated with, objects of nested definitions occupy more space as well. For example, although the following two structs have exactly the same member variables, their sizes are different:
)
---
import std.stdio;
$(HILITE struct ModuleStruct) {
int i;
void memberFunc() {
}
}
void moduleFunc() {
$(HILITE struct NestedStruct) {
int i;
void memberFunc() {
}
}
writefln("OuterStruct: %s bytes, NestedStruct: %s bytes.",
ModuleStruct.sizeof, NestedStruct.sizeof);
}
void main() {
moduleFunc();
}
---
$(P
The sizes of the two structs may be different on other environments:
)
$(SHELL
OuterStruct: $(HILITE 4) bytes, NestedStruct: $(HILITE 16) bytes.
)
$(P
$(IX static class) $(IX static struct) However, some nested definitions are merely for keeping them as local as possible, with no need to access variables from the outer contexts. In such cases, the associated cost would be unnecessary. The $(C static) keyword removes the context pointer from nested definitions, making them equivalents of their module counterparts. As a result, $(C static) nested definitions cannot access their outer contexts:
)
---
void outerFunc(int parameter) {
$(HILITE static) class NestedClass {
int i;
this() {
i = parameter; $(DERLEME_HATASI)
}
}
}
---
$(P
$(IX .outer, void*) The context pointer of a nested $(C class) object is available as a $(C void*) through its $(C .outer) property. For example, because they are defined in the same scope, the context pointers of the following two objects are equal:
)
---
void foo() {
class C {
}
auto a = new C();
auto b = new C();
assert(a$(HILITE .outer) is b$(HILITE .outer));
}
---
$(P
As we will see below, for $(I classes nested inside classes), the type of the context pointer is the type of the outer class, not $(C void*).
)
$(H6 Classes nested inside classes)
$(P
$(IX class, nested in class) When a $(C class) is nested inside another one, the context that the nested object is associated with is the outer object itself.
)
$(P
$(IX .new) $(IX .outer, class type) Such nested classes are constructed by the $(C this.new) syntax. When necessary, the outer object of a nested object can be accessed by $(C this.outer):
)
---
class OuterClass {
int outerMember;
$(HILITE class NestedClass) {
int func() {
/* A nested class can access members of the outer
* class. */
return outerMember * 2;
}
OuterClass context() {
/* A nested class can access its outer object
* (i.e. its context) by '.outer'. */
return $(HILITE this.outer);
}
}
NestedClass algorithm() {
/* An outer class can construct a nested object by
* '.new'. */
return $(HILITE this.new) NestedClass();
}
}
void main() {
auto outerObject = new OuterClass();
/* A member function of an outer class is returning a
* nested object: */
auto nestedObject = outerObject.algorithm();
/* The nested object gets used in the program: */
nestedObject.func();
/* Naturally, the context of nestedObject is the same as
* outerObject: */
assert(nestedObject.context() is outerObject);
}
---
$(P
Instead of $(C this.new) and $(C this.outer), $(C .new) and $(C .outer) can be used on existing objects as well:
)
---
auto var = new OuterClass();
auto nestedObject = $(HILITE var.new) OuterClass.NestedClass();
auto var2 = $(HILITE nestedObject.outer);
---
$(H5 Summary)
$(UL
$(LI Functions, structs, and classes that are defined in inner scopes can access those scopes as their contexts.)
$(LI Nested definitions keep their contexts alive to form closures.)
$(LI Nested definitions are more costly than their module counterparts. When a nested definition does not need to access its context, this cost can be avoided by the $(C static) keyword.)
$(LI Classes can be nested inside other classes. The context of such a nested object is the outer object itself. Nested class objects are constructed by $(C this.new) or $(C variable.new) and their contexts are available by $(C this.outer) or $(C variable.outer).)
)
Macros:
SUBTITLE=Nested functions, structs, and classes
DESCRIPTION=Defining functions, structs, and classes in nested scopes.
KEYWORDS=d programming language tutorial book nested outer