Structs & Unions
Whereas classes are reference types, structs are value types. Any C struct can be exactly represented as a D struct. In C++ parlance, a D struct is a POD (Plain Old Data) type, with a trivial constructors and destructors. Structs and unions are meant as simple aggregations of data, or as a way to paint a data structure over hardware or an external type. External types can be defined by the operating system API, or by a file format. Object oriented features are provided with the class data type.
A struct is defined to not have an identity; that is, the implementation is free to make bit copies of the struct as convenient.
Feature | struct | class | C struct | C++ struct | C++ class |
---|---|---|---|---|---|
value type | X | X | X | X | |
reference type | X | ||||
data members | X | X | X | X | X |
hidden members | X | X | X | ||
static members | X | X | X | X | |
default member initializers | X | X | |||
bit fields | X | X | X | ||
non-virtual member functions | X | X | X | X | |
virtual member functions | X | X | X | ||
constructors | X | X | X | ||
postblit/copy constructors | X | X | X | ||
destructors | X | X | X | X | |
RAII | X | X | X | X | |
assign overload | X | X | X | ||
literals | X | ||||
operator overloading | X | X | X | X | |
inheritance | X | X | X | ||
invariants | X | X | |||
unit tests | X | X | |||
synchronizable | X | ||||
parameterizable | X | X | X | X | |
alignment control | X | X | |||
member protection | X | X | X | X | |
default public | X | X | X | X | |
tag name space | X | X | X | ||
anonymous | X | X | X | X | |
static constructor | X | X | |||
static destructor | X | X | |||
const/invariant | X | X |
AggregateDeclaration: Tag Identifier StructBody Tag Identifier ; Tag: struct union StructBody: { } { StructBodyDeclarations } StructBodyDeclarations: StructBodyDeclaration StructBodyDeclaration StructBodyDeclarations StructBodyDeclaration: Declaration StaticConstructor StaticDestructor Invariant UnitTest StructAllocator StructDeallocator StructPostblit StructDestructor StructAllocator: ClassAllocator StructDeallocator: ClassDeallocator
They work like they do in C, with the following exceptions:
- no bit fields
- alignment can be explicitly specified
- no separate tag name space - tag names go into the current scope
- declarations like:
struct ABC x;
are not allowed, replace with:ABC x;
- anonymous structs/unions are allowed as members of other structs/unions
- Default initializers for members can be supplied.
- Member functions and static members are allowed.
Static Initialization of Structs
Static struct members are by default initialized to whatever the default initializer for the member is, and if none supplied, to the default initializer for the member's type. If a static initializer is supplied, the members are initialized by the member name, colon, expression syntax. The members may be initialized in any order. Members not specified in the initializer list are default initialized.struct X { int a; int b; int c; int d = 7;} static X x = { a:1, b:2}; // c is set to 0, d to 7 static X z = { c:4, b:5, a:2 , d:5}; // z.a = 2, z.b = 5, z.c = 4, z.d = 5C-style initialization, based on the order of the members in the struct declaration, is also supported:
static X q = { 1, 2 }; // q.a = 1, q.b = 2, q.c = 0, q.d = 7
Static Initialization of Unions
Unions are initialized explicitly.union U { int a; double b; } static U u = { b : 5.0 }; // u.b = 5.0Other members of the union that overlay the initializer, but occupy more storage, have the extra storage initialized to zero.
Dynamic Initialization of Structs
Structs can be dynamically initialized from another value of the same type:
struct S { int a; } S t; // default initialized t.a = 3; S s = t; // s.a is set to 3
If opCall is overridden for the struct, and the struct is initialized with a value that is of a different type, then the opCall operator is called:
struct S { int a; static S opCall(int v) { S s; s.a = v; return s; } static S opCall(S v) { S s; s.a = v.a + 1; return s; } } S s = 3; // sets s.a to 3 S t = s; // sets t.a to 3, S.opCall(s) is not called
Struct Literals
Struct literals consist of the name of the struct followed by a parenthesized argument list:
struct S { int x; float y; } int foo(S s) { return s.x; } foo( S(1, 2) ); // set field x to 1, field y to 2
Struct literals are syntactically like function calls. If a struct has a member function named opCall, then struct literals for that struct are not possible. It is an error if there are more arguments than fields of the struct. If there are fewer arguments than fields, the remaining fields are initialized with their respective default initializers. If there are anonymous unions in the struct, only the first member of the anonymous union can be initialized with a struct literal, and all subsequent non-overlapping fields are default initialized.
Struct Properties
.sizeof | Size in bytes of struct |
.alignof | Size boundary struct needs to be aligned on |
.tupleof | Gets type tuple of fields |
Struct Field Properties
.offsetof | Offset in bytes of field from beginning of struct |
Const and Invariant Structs
A struct declaration can have a storage class of const or invariant. It has an equivalent effect as declaring each member of the struct as const or invariant.
const struct S { int a; int b = 2; } void main() { S s = S(3); // initializes s.a to 3 S t; // initializes t.a to 0 t = s; // ok, t.a is now 3 t.a = 4; // error, t.a is const }
Struct Postblits
StructPostblit: this(this) FunctionBody
Copy construction is defined as initializing a struct instance from another struct of the same type. Copy construction is divided into two parts:
- blitting the fields, i.e. copying the bits
- running postblit on the result
The first part is done automatically by the language, the second part is done if a postblit function is defined for the struct. The postblit has access only to the destination struct object, not the source. Its job is to 'fix up' the destination as necessary, such as making copies of referenced data, incrementing reference counts, etc. For example:
struct S { int[] a; // array is privately owned by this instance this(this) { a = a.dup; } ~this() { delete a; } }
Struct Destructors
StructDestructor: ~this() FunctionBody
Destructors are called when an object goes out of scope. Their purpose is to free up resources owned by the struct object.
Assignment Overload
While copy construction takes care of initializing an object from another object of the same type, assignment is defined as copying the contents of one object over another, already initialized, type:
struct S { ... } S s; // default construction of s S t = s; // t is copy-constructed from s t = s; // t is assigned from s
Struct assignment t=s is defined to be semantically equivalent to:
t = S.opAssign(s);
where opAssign is a member function of S:
S* opAssign(S s) { ... bitcopy *this into tmp ... ... bitcopy s into *this ... ... call destructor on tmp ... return this; }
While the compiler will generate a default opAssign as needed, a user-defined one can be supplied. The user-defined one must still implement the equivalent semantics, but can be more efficient.
One reason a custom opAssign might be more efficient is if the struct has a reference to a local buffer:
struct S { int[] buf; int a; S* opAssign(ref const S s) { a = s.a; return this; } this(this) { buf = buf.dup; } ~this() { delete buf; } }
Here, S has a temporary workspace buf[]. The normal postblit will pointlessly free and reallocate it. The custom opAssign will reuse the existing storage.