forked from DlangRen/Programming-in-D
-
Notifications
You must be signed in to change notification settings - Fork 0
/
special_functions.d
1234 lines (938 loc) · 34.2 KB
/
special_functions.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
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
Ddoc
$(DERS_BOLUMU Constructor and Other Special Functions)
$(P
Although this chapter focuses only on structs, the topics that are covered here apply mostly to classes as well. The differences will be explained in later chapters.
)
$(P
Four member functions of structs are special because they define the fundamental operations of that type:
)
$(UL
$(LI Constructor: $(C this()))
$(LI Destructor: $(C ~this()))
$(LI Postblit: $(C this(this)))
$(LI Assignment operator: $(C opAssign()))
)
$(P
Although these fundamental operations are handled automatically for structs, hence need not be defined by the programmer, they can be overridden to make the $(C struct) behave in special ways.
)
$(H5 $(IX constructor) $(IX this, constructor) Constructor)
$(P
The responsibility of the constructor is to prepare an object for use by assigning appropriate values to its members.
)
$(P
We have already used constructors in previous chapters. When the name of a type is used like a function, it is actually the constructor that gets called. We can see this on the right-hand side of the following line:
)
---
auto busArrival = $(HILITE TimeOfDay)(8, 30);
---
$(P
Similarly, a $(I class) object is being constructed on the right hand side of the following line:
)
---
auto variable = new $(HILITE SomeClass());
---
$(P
The arguments that are specified within parentheses correspond to the constructor parameters. For example, the values 8 and 30 above are passed to the $(C TimeOfDay) constructor as its parameters.
)
$(P
In addition to different object construction syntaxes that we have seen so far; $(C const), $(C immutable), and $(C shared) objects can be constructed with the $(I type constructor) syntax as well (e.g. as $(C immutable(S)(2))). (We will see the $(C shared) keyword in $(LINK2 /ders/d.en/concurrency_shared.html, a later chapter).)
)
$(P
For example, although all three variables below are $(C immutable), the construction of variable $(C a) is semantically different from the constructions of variables $(C b) and $(C c):
)
---
/* More familiar syntax; immutable variable of a mutable
* type: */
$(HILITE immutable) a = S(1);
/* Type constructor syntax; a variable of an immutable
* type: */
auto b = $(HILITE immutable(S))(2);
/* Same meaning as 'b' */
immutable c = $(HILITE immutable(S))(3);
---
$(H6 Constructor syntax)
$(P
Different from other functions, constructors do not have return values. The name of the constructor is always $(C this):
)
---
struct SomeStruct {
// ...
this(/* constructor parameters */) {
// ... operations that prepare the object for use ...
}
}
---
$(P
The constructor parameters include information that is needed to make a useful and consistent object.
)
$(H6 $(IX automatic constructor) $(IX default constructor) Compiler-generated automatic constructor)
$(P
All of the structs that we have seen so far have been taking advantage of a constructor that has been generated automatically by the compiler. The automatic constructor assigns the parameter values to the members in the order that they are specified.
)
$(P
As you will remember from $(LINK2 /ders/d.en/struct.html, the Structs chapter), the initial values for the trailing members need not be specified. The members that are not specified get initialized by the $(C .init) value of their respective types. The $(C .init) values of a member could be provided during the definition of that member after the $(C =) operator:
)
---
struct Test {
int member $(HILITE = 42);
}
---
$(P
Also considering the $(I default parameter values) feature from $(LINK2 /ders/d.en/parameter_flexibility.html, the Variable Number of Parameters chapter), we can imagine that the automatic constructor for the following $(C struct) would be the equivalent of the following $(C this()):
)
---
struct Test {
char c;
int i;
double d;
/* The equivalent of the compiler-generated automatic
* constructor (Note: This is only for demonstration; the
* following constructor would not actually be called
* when default-constructing the object as Test().) */
this(in char c_parameter = char.init,
in int i_parameter = int.init,
in double d_parameter = double.init) {
c = c_parameter;
i = i_parameter;
d = d_parameter;
}
}
---
$(P
For most structs, the compiler-generated constructor is sufficient: Usually, providing appropriate values for each member is all that is needed for objects to be constructed.
)
$(H6 $(IX this, member access) Accessing the members by $(C this.))
$(P
To avoid mixing the parameters with the members, the parameter names above had $(C _parameter) appended to their names. There would be compilation errors without doing that:
)
---
struct Test {
char c;
int i;
double d;
this(in char c = char.init,
in int i = int.init,
in double d = double.init) {
// An attempt to assign an 'in' parameter to itself!
c = c; $(DERLEME_HATASI)
i = i;
d = d;
}
}
---
$(P
The reason is; $(C c) alone would mean the parameter, not the member, and as the parameters above are defined as $(C in), they cannot be modified:
)
$(SHELL
Error: variable deneme.Test.this.c $(HILITE cannot modify const)
)
$(P
A solution is to prepend the member names with $(C this.). Inside member functions, $(C this) means "this object", making $(C this.c) mean "the c member of this object":
)
---
this(in char c = char.init,
in int i = int.init,
in double d = double.init) {
$(HILITE this.)c = c;
$(HILITE this.)i = i;
$(HILITE this.)d = d;
}
---
$(P
Now $(C c) alone means the parameter and $(C this.c) means the member, and the code compiles and works as expected: The member $(C c) gets initialized by the value of the parameter $(C c).
)
$(H6 $(IX user defined constructor) User-defined constructors)
$(P
I have described the behavior of the compiler-generated constructor. Since that constructor is suitable for most cases, there is no need to define a constructor by hand.
)
$(P
Still, there are cases where constructing an object involves more complicated operations than assigning values to each member in order. As an example, let's consider $(C Duration) from the earlier chapters:
)
---
struct Duration {
int minute;
}
---
$(P
The compiler-generated constructor is sufficient for this single-member struct:
)
---
time.decrement(Duration(12));
---
$(P
Since that constructor takes the duration in minutes, the programmers would sometimes need to make calculations:
)
---
// 23 hours and 18 minutes earlier
time.decrement(Duration(23 * 60 + 18));
// 22 hours and 20 minutes later
time.increment(Duration(22 * 60 + 20));
---
$(P
To eliminate the need for these calculations, we can design a $(C Duration) constructor that takes two parameters and makes the calculation automatically:
)
---
struct Duration {
int minute;
this(int hour, int minute) {
this.minute = hour * 60 + minute;
}
}
---
$(P
Since hour and minute are now separate parameters, the users simply provide their values without needing to make the calculation themselves:
)
---
// 23 hours and 18 minutes earlier
time.decrement(Duration($(HILITE 23, 18)));
// 22 hours and 20 minutes later
time.increment(Duration($(HILITE 22, 20)));
---
$(H6 User-defined constructor disables compiler-generated constructor)
$(P
A constructor that is defined by the programmer makes some uses of the compiler-generated constructor invalid: Objects cannot be constructed by $(I default parameter values) anymore. For example, trying to construct $(C Duration) by a single parameter is a compilation error:
)
---
time.decrement(Duration(12)); $(DERLEME_HATASI)
---
$(P
Calling the constructor with a single parameter does not match the programmer's constructor and the compiler-generated constructor is disabled.
)
$(P
One solution is to $(I overload) the constructor by providing another constructor that takes just one parameter:
)
---
struct Duration {
int minute;
this(int hour, int minute) {
this.minute = hour * 60 + minute;
}
this(int minute) {
this.minute = minute;
}
}
---
$(P
A user-defined constructor disables constructing objects by the $(C { }) syntax as well:
)
---
Duration duration = { 5 }; $(DERLEME_HATASI)
---
$(P
Initializing without providing any parameter is still valid:
)
---
auto d = Duration(); // compiles
---
$(P
The reason is, in D, the $(C .init) value of every type must be known at compile time. The value of $(C d) above is equal to the initial value of $(C Duration):
)
---
assert(d == Duration.init);
---
$(H6 $(IX static opCall) $(IX opCall, static) $(C static opCall) instead of the default constructor)
$(P
Because the initial value of every type must be known at compile time, it is impossible to define the default constructor explicitly.
)
$(P
Let's consider the following constructor that tries to print some information every time an object of that type is constructed:
)
---
struct Test {
this() { $(DERLEME_HATASI)
writeln("A Test object is being constructed.");
}
}
---
$(P
The compiler output:
)
$(SHELL
Error: constructor deneme.Deneme.this default constructor for
structs only allowed with @disable and no body
)
$(P $(I $(B Note:) We will see in later chapters that it is possible to define the default constructor for classes.
))
$(P
As a workaround, a no-parameter $(C static opCall()) can be used for constructing objects without providing any parameters. Note that this has no effect on the $(C .init) value of the type.
)
$(P
For this to work, $(C static opCall()) must construct and return an object of that struct type:
)
---
import std.stdio;
struct Test {
$(HILITE static) Test $(HILITE opCall)() {
writeln("A Test object is being constructed.");
Test test;
return test;
}
}
void main() {
auto test = $(HILITE Test());
}
---
$(P
The $(C Test()) call in $(C main()) executes $(C static opCall()):
)
$(SHELL
A Test object is being constructed.
)
$(P
Note that it is not possible to type $(C Test()) inside $(C static opCall()). That syntax would execute $(C static opCall()) as well and cause an infinite recursion:
)
---
static Test opCall() {
writeln("A Test object is being constructed.");
return $(HILITE Test()); // ← Calls 'static opCall()' again
}
---
$(P
The output:
)
$(SHELL
A Test object is being constructed.
A Test object is being constructed.
A Test object is being constructed.
... $(SHELL_NOTE repeats the same message)
)
$(H6 Calling other constructors)
$(P
Constructors can call other constructors to avoid code duplication. Although $(C Duration) is too simple to demonstrate how useful this feature is, the following single-parameter constructor takes advantage of the two-parameter constructor:
)
---
this(int hour, int minute) {
this.minute = hour * 60 + minute;
}
this(int minute) {
this(0, minute); // calls the other constructor
}
---
$(P
The constructor that only takes the minute value calls the other constructor by passing 0 as the value of hour.
)
$(P
$(I $(B Warning:) There is a design flaw in the $(C Duration) constructors above because the intention is not clear when the objects are constructed by a single parameter):
)
---
// 10 hours or 10 minutes?
auto travelDuration = Duration(10);
---
$(P
Although it is possible to determine by reading the documentation or the code of the struct that the parameter actually means "10 minutes," it is an inconsistency as the first parameter of the two-parameter constructor is $(I hours).
)
$(P
Such design mistakes are causes of bugs and must be avoided.
)
$(H6 $(IX constructor qualifier) $(IX qualifier, constructor) Constructor qualifiers)
$(P
Normally, the same constructor is used for $(I mutable), $(C const), $(C immutable), and $(C shared) objects:
)
---
import std.stdio;
struct S {
this(int i) {
writeln("Constructing an object");
}
}
void main() {
auto m = S(1);
const c = S(2);
immutable i = S(3);
shared s = S(4);
}
---
$(P
Semantically, the objects that are constructed on the right-hand sides of those expressions are all mutable but the variables have different type qualifiers. The same constructor is used for all of them:
)
$(SHELL
Constructing an object
Constructing an object
Constructing an object
Constructing an object
)
$(P
Depending on the qualifier of the resulting object, sometimes some members may need to be initialized differently or need not be initialized at all. For example, since no member of an $(C immutable) object can be mutated throughout the lifetime of that object, leaving its mutable members uninitialized can improve program performance.
)
$(P
$(I Qualified constructors) can be defined differently for objects with different qualifiers:
)
---
import std.stdio;
struct S {
this(int i) {
writeln("Constructing an object");
}
this(int i) $(HILITE const) {
writeln("Constructing a const object");
}
this(int i) $(HILITE immutable) {
writeln("Constructing an immutable object");
}
// We will see the 'shared' keyword in a later chapter.
this(int i) $(HILITE shared) {
writeln("Constructing a shared object");
}
}
void main() {
auto m = S(1);
const c = S(2);
immutable i = S(3);
shared s = S(4);
}
---
$(P
However, as indicated above, as the right-hand side expressions are all semantically mutable, those objects are still constructed with the $(I mutable) object contructor:
)
$(SHELL
Constructing an object
Constructing an object $(SHELL_NOTE_WRONG NOT the const constructor)
Constructing an object $(SHELL_NOTE_WRONG NOT the immutable constructor)
Constructing an object $(SHELL_NOTE_WRONG NOT the shared constructor)
)
$(P
$(IX type constructor) To take advantage of qualified constructors, one must use the $(I type constructor) syntax. (The term $(I type constructor) should not be confused with object constructors; type constructor is related to types, not objects.) This syntax $(I makes) a different type by combining a qualifier with an existing type. For example, $(C immutable(S)) is a qualified type made from $(C immutable) and $(C S):
)
---
auto m = S(1);
auto c = $(HILITE const(S))(2);
auto i = $(HILITE immutable(S))(3);
auto s = $(HILITE shared(S))(4);
---
$(P
This time, the objects that are in the right-hand expressions are different: $(I mutable), $(C const), $(C immutable), and $(C shared), respectively. As a result, each object is constructed with its matching constructor:
)
$(SHELL
Constructing an object
Constructing a $(HILITE const) object
Constructing an $(HILITE immutable) object
Constructing a $(HILITE shared) object
)
$(P
Note that, since all of the variables above are defined with the $(C auto) keyword, they are correctly inferred to be $(I mutable), $(C const), $(C immutable), and $(C shared), respectively.
)
$(H6 Immutability of constructor parameters)
$(P
In the $(LINK2 /ders/d.en/const_and_immutable.html, Immutability chapter) we have seen that it is not easy to decide whether parameters of reference types should be defined as $(C const) or $(C immutable). Although the same considerations apply for constructor parameters as well, $(C immutable) is usually a better choice for constructor parameters.
)
$(P
The reason is, it is common to assign the parameters to members to be used at a later time. When a parameter is not $(C immutable), there is no guarantee that the original variable will not change by the time the member gets used.
)
$(P
Let's consider a constructor that takes a file name as a parameter. The file name will be used later on when writing student grades. According to the guidelines in the $(LINK2 /ders/d.en/const_and_immutable.html, Immutability chapter), to be more useful, let's assume that the constructor parameter is defined as $(C const char[]):
)
---
import std.stdio;
struct Student {
$(HILITE const char[]) fileName;
int[] grades;
this($(HILITE const char[]) fileName) {
this.fileName = fileName;
}
void save() {
auto file = File(fileName.idup, "w");
file.writeln("The grades of the student:");
file.writeln(grades);
}
// ...
}
void main() {
char[] fileName;
fileName ~= "student_grades";
auto student = Student(fileName);
// ...
/* Assume the fileName variable is modified later on
* perhaps unintentionally (all of its characters are
* being set to 'A' here): */
$(HILITE fileName[] = 'A');
// ...
/* The grades would be written to the wrong file: */
student.save();
}
---
$(P
The program above saves the grades of the student under a file name that consists of A characters, not to $(STRING "student_grades"). For that reason, sometimes it is more suitable to define constructor parameters and members of reference types as $(C immutable). We know that this is easy for strings by using aliases like $(C string). The following code shows the parts of the struct that would need to be modified:
)
---
struct Student {
$(HILITE string) fileName;
// ...
this($(HILITE string) fileName) {
// ...
}
// ...
}
---
$(P
Now the users of the struct must provide $(C immutable) strings and as a result the confusion about the name of the file would be prevented.
)
$(H6 $(IX type conversion, constructor) Type conversions through single-parameter constructors)
$(P
Single-parameter constructors can be thought of as providing a sort of type conversion: They produce an object of the particular struct type starting from a constructor parameter. For example, the following constructor produces a $(C Student) object from a $(C string):
)
---
struct Student {
string name;
this(string name) {
this.name = name;
}
}
---
$(P
$(C to()) and $(C cast) observe this behavior as a $(I conversion) as well. To see examples of this, let's consider the following $(C salute()) function. Sending a $(C string) parameter when it expects a $(C Student) would naturally cause a compilation error:
)
---
void salute(Student student) {
writeln("Hello ", student.name);
}
// ...
salute("Jane"); $(DERLEME_HATASI)
---
$(P
On the other hand, all of the following lines ensure that a $(C Student) object is constructed before calling the function:
)
---
import std.conv;
// ...
salute(Student("Jane"));
salute(to!Student("Jean"));
salute(cast(Student)"Jim");
---
$(P
$(C to) and $(C cast) take advantage of the single-parameter constructor by constructing a temporary $(C Student) object and calling $(C salute()) with that object.
)
$(H6 $(IX @disable, constructor) Disabling the default constructor)
$(P
Functions that are declared as $(C @disable) cannot be called.
)
$(P
Sometimes there are no sensible default values for the members of a type. For example, it may be illegal for the following type to have an empty file name:
)
---
struct Archive {
string fileName;
}
---
$(P
Unfortunately, the compiler-generated default constructor would initialize $(C fileName) as empty:
)
---
auto archive = Archive(); // ← fileName member is empty
---
$(P
The default constructor can explicitly be disabled by declaring it as $(C @disable) so that objects must be constructed by one of the other constructors. There is no need to provide a body for a disabled function:
)
---
struct Archive {
string fileName;
$(HILITE @disable this();) $(CODE_NOTE cannot be called)
this(string fileName) { $(CODE_NOTE can be called)
// ...
}
}
// ...
auto archive = Archive(); $(DERLEME_HATASI)
---
$(P
This time the compiler does not allow calling $(C this()):
)
$(SHELL
Error: constructor deneme.Archive.this is $(HILITE not callable) because
it is annotated with @disable
)
$(P
Objects of $(C Archive) must be constructed either with one of the other constructors or explicitly with its $(C .init) value:
)
---
auto a = Archive("records"); $(CODE_NOTE compiles)
auto b = Archive.init; $(CODE_NOTE compiles)
---
$(H5 $(IX destructor) $(IX ~this) Destructor)
$(P
The destructor includes the operations that must be executed when the lifetime of an object ends.
)
$(P
The compiler-generated automatic destructor executes the destructors of all of the members in order. For that reason, as it is with the constructor, there is no need to define a destructor for most structs.
)
$(P
However, sometimes some special operations may need to be executed when an object's lifetime ends. For example, an operating system resource that the object owns may need to be returned to the system; a member function of another object may need to be called; a server running somewhere on the network may need to be notified that a connection to it is about to be terminated; etc.
)
$(P
The name of the destructor is $(C ~this) and just like constructors, it has no return type.
)
$(H6 Destructor is executed automatically)
$(P
The destructor is executed as soon as the lifetime of the struct object ends. (This is not the case for objects that are constructed with the $(C new) keyword.)
)
$(P
As we have seen in the $(LINK2 /ders/d.en/lifetimes.html, Lifetimes and Fundamental Operations chapter,) the lifetime of an object ends when leaving the scope that it is defined in. The following are times when the lifetime of a struct ends:
)
$(UL
$(LI When leaving the scope of the object either normally or due to a thrown exception:
---
if (aCondition) {
auto duration = Duration(7);
// ...
} // ← The destructor is executed for 'duration'
// at this point
---
)
$(LI Anonymous objects are destroyed at the end of the whole expression that they are constructed in:
---
time.increment(Duration(5)); // ← The Duration(5) object
// gets destroyed at the end
// of the whole expression.
---
)
$(LI All of the struct members of a struct object get destroyed when the outer object is destroyed.
)
)
$(H6 Destructor example)
$(P
Let's design a type for generating simple XML documents. XML elements are defined by angle brackets. They contain data and other XML elements. XML elements can have attributes as well; we will ignore them here.
)
$(P
Our aim will be to ensure that an element that has been $(I opened) by a $(C <name>) tag will always be $(I closed) by a matching $(C </name>) tag:
)
$(MONO
<class1> ← opening the outer XML element
<grade> ← opening the inner XML element
57 ← the data
</grade> ← closing the inner element
</class1> ← closing the outer element
)
$(P
A struct that can produce the output above can be designed by two members that store the tag for the XML element and the indentation to use when printing it:
)
---
struct XmlElement {
string name;
string indentation;
}
---
$(P
If the responsibilities of opening and closing the XML element are given to the constructor and the destructor, respectively, the desired output can be produced by managing the lifetimes of XmlElement objects. For example, the constructor can print $(C <tag>) and the destructor can print $(C </tag>).
)
$(P
The following definition of the constructor produces the opening tag:
)
---
this(in string name, in int level) {
this.name = name;
this.indentation = indentationString(level);
writeln(indentation, '<', name, '>');
}
---
$(P
$(C indentationString()) is the following function:
)
---
import std.array;
// ...
string indentationString(in int level) {
return replicate(" ", level * 2);
}
---
$(P
The function calls $(C replicate()) from the $(C std.array) module, which makes and returns a new string made up of the specified value repeated the specified number of times.
)
$(P
The destructor can be defined similar to the constructor to produce the closing tag:
)
---
~this() {
writeln(indentation, "</", name, '>');
}
---
$(P
Here is a test code to demonstrate the effects of the automatic constructor and destructor calls:
)
---
import std.conv;
import std.random;
import std.array;
string indentationString(in int level) {
return replicate(" ", level * 2);
}
struct XmlElement {
string name;
string indentation;
this(in string name, in int level) {
this.name = name;
this.indentation = indentationString(level);
writeln(indentation, '<', name, '>');
}
~this() {
writeln(indentation, "</", name, '>');
}
}
void main() {
immutable classes = XmlElement("classes", 0);
foreach (classId; 0 .. 2) {
immutable classTag = "class" ~ to!string(classId);
immutable classElement = XmlElement(classTag, 1);
foreach (i; 0 .. 3) {
immutable gradeElement = XmlElement("grade", 2);
immutable randomGrade = uniform(50, 101);
writeln(indentationString(3), randomGrade);
}
}
}
---
$(P
Note that the $(C XmlElement) objects are created in three separate scopes in the program above. The opening and closing tags of the XML elements in the output are produced solely by the constructor and the destructor of $(C XmlElement).
)
$(SHELL
<classes>
<class0>
<grade>
72
</grade>
<grade>
97
</grade>
<grade>
90
</grade>
</class0>
<class1>
<grade>
77
</grade>
<grade>
87
</grade>
<grade>
56
</grade>
</class1>
</classes>
)
$(P
The $(C <classes>) element is produced by the $(C classesElement) variable. Because that variable is constructed first in $(C main()), the output contains the output of its construction first. Since it is also the variable that is destroyed last, upon leaving $(C main()), the output contains the output of the destructor call for its destruction last.
)
$(H5 $(IX postblit) $(IX this(this)) Postblit)
$(P
$(I Copying) is constructing a new object from an existing one. Copying involves two steps:
)
$(OL
$(LI Copying the members of the existing object to the new object bit-by-bit. This step is called $(I blit), short for $(I block transfer).
)
$(LI Making further adjustments to the new object. This step is called $(I postblit).
)
)
$(P
The first step is handled automatically by the compiler: It copies the members of the existing object to the members of the new object:
)
---
auto returnTripDuration = tripDuration; // copying
---
$(P
Do not confuse copying with $(I assignment). The $(C auto) keyword above is an indication that a new object is being defined. (The actual type name could have been spelled out instead of $(C auto).)
)
$(P
For an operation to be assignment, the object on the left-hand side must be an existing object. For example, assuming that $(C returnTripDuration) has already been defined:
)
---
returnTripDuration = tripDuration; // assignment (see below)
---
$(P
Sometimes it is necessary to make adjustments to the members of the new object after the automatic blit. These operations are defined in the postblit function of the struct.
)
$(P
Since it is about object construction, the name of the postblit is $(C this) as well. To separate it from the other constructors, its parameter list contains the keyword $(C this):
)
---
this(this) {
// ...
}
---
$(P
We have defined a $(C Student) type in the $(LINK2 /ders/d.en/struct.html, Structs chapter), which had a problem about copying objects of that type:
)
---
struct Student {
int number;
int[] grades;
}
---
$(P
Being a slice, the $(C grades) member of that $(C struct) is a reference type. The consequence of copying a $(C Student) object is that the $(C grades) members of both the original and the copy provide access to the same actual array elements of type $(C int). As a result, the effect of modifying a grade through one of these objects is seen through the other object as well:
)
---
auto student1 = Student(1, [ 70, 90, 85 ]);
auto student2 = student1; // copying
student2.number = 2;
student1.grades[0] += 5; // this changes the grade of the
// second student as well:
assert($(HILITE student2).grades[0] == $(HILITE 75));
---
$(P
To avoid such a confusion, the elements of the $(C grades) member of the second object must be separate and belong only to that object. Such $(I adjustments) are done in the postblit:
)