Chapter 3. Programming Objects the D Way

In this chapter, we're going to build upon the foundation established in the previous chapter by looking at D's user-defined types, its support for object-oriented programming, and some peripherally related features. By the end of the chapter, we'll be finished with the basics of D and ready for more advanced topics. Here's our checklist:

  • User-defined types: enumerations, unions, structs, and classes
  • Working with objects: protection attributes, constructors, and destructors
  • Contract programming: contracts and invariants
  • Error handling: exceptions and asserts
  • MovieMan: adding menus via user-defined types

User-defined types

This section shows how to define custom types using enum, union, struct, and class. The latter two will be the focus for most of the remainder of the chapter.

Enumerations

The anonymous enumeration in D declares a set of immutable values. A major difference from C is that it's possible to specify the underlying type. When no fields are explicitly assigned a value, the underlying type of an enum defaults to int. Note that user-defined type declarations in D do not require a semicolon at the end:

enum {top, bottom, left, right}         // type is int
enum : ubyte {red, green, blue, alpha}  // type is ubyte

The members of each will be initialized with sequential values starting at 0. In the second declaration, the underlying type is explicitly set to ubyte by appending a colon and the type name to the enum keyword. enum values aren't restricted to integrals, or even just the basic types. Any type that D supports, be it one of the derived data types or even a user-defined type, can back an enum. Where possible, the compiler will infer the type:

enum {one = "One", two = "Two"}  // type is immutable(char)[]

An anonymous enum with only one member is eligible for some special treatment:

enum {author = "Mike Parker"}
enum author = "Mike Parker";
enum string author = "Mike Parker";

As shown in the second line, the braces can be dropped. The third line explicitly specifies a type, but in this form there's no colon. An enum declared without braces is called a manifest constant.

An anonymous enum does not create a new type, but a named enumeration does:

enum Side {top, bottom, left, right}
enum Color : ubyte {red, green, blue, alpha}

The name is used as a namespace and the members are accessed via the dot operator. Printing the typeid of one of these produces a fully-qualified name that includes the enum name (Side or Color, in this case). All user-defined types get this treatment.

Named enums have properties. .init equates to the value of the first member of the enum; its .sizeof is that of the underlying type. The type-specific properties .min and .max return the lowest and highest member values respectively.

One special feature designed to work specifically with enums is the final switch statement. For example, to switch on a value of the Side type:

auto s = Side.bottom;
final switch(s) {
    case Side.top: writeln("On top"); break;
    case Side.bottom: writeln("On the bottom"); break;
    case Side.left: writeln("On the left"); break;
    case Side.right: writeln("On the right"); break;
}

A big benefit is that the compiler will give you an error if you forget to add a case for one of the enum members. It also adds a default case that asserts if a non-member value somehow slips through, making it an error to add a default case. If two or more members have the same value, only one need appear in a final switch.

Unions

For the most part, unions in D work as they do in C:

union One {
  int a = 10;
  double b;
}

The members of a union share the same memory, which is large enough to hold the biggest type. In this declaration, the biggest type is double, so an instance of One takes up 8 bytes. D diverges from C in terms of initialization. Every variable in D is by default initialized and a union instance is no exception. By default, the first member is initialized to its .init value if not explicitly initialized, as in the previous example. It is an error to initialize other members.

To explicitly initialize an instance of a union, you can use the name:value syntax with braces. Given the nature of unions, it's an error to initialize more than one field:

One o2 = { b:22.0 };

Note

Unions are great for compatibility with C, but there is an alternative in the standard library that offers better type safety. The module std.variant exposes the Variant type. See http://dlang.org/phobos/std_variant.html for details.

Structs and classes

D's implementations of the struct and class types are a major source of misunderstanding for C-family programmers new to D. Here are declarations of each:

struct MyStruct {
  int a, b;
  int calculate() { return a + b; }
}
class MyClass {
  int a, b;
  int calculate() { return a + b; }
}

The declarations have the same syntax. The biggest difference is hidden here:

MyStruct ms;
MyClass mc;

In D, a struct is a value type. It's enough to declare an instance for it to be created on the stack and ready to go. At this point, a call to ms.calculate will successfully compile. The same cannot be said for mc. A class is a reference type. It isn't enough to declare an instance, as that only produces an uninitialized reference, or handle. It is a common error for new D programmers coming from C++ to try using uninitialized class references. Before mc.calculate can be called, an instance must be allocated:

mc = new MyClass;
auto mc2 = new MyClass;

This has implications for how instances are passed to and returned from functions. Since a struct is a value type, instances are copied. ms.sizeof is 8 bytes, the size of two ints, which means passing ms to a function will cause 8 bytes to be copied:

void modMS(MyStruct ms1, ref MyStruct ms2, MyStruct* ms3) {
  ms1.a = 1;  // Modifies local copy
  ms2.a = 2;  // Modifies original
  ms3.a = 3;  // Ditto.
}

Since the first argument is passed by value, any modifications to ms1 only affect the function's copy, but modifications to ms2 and ms3 will be reflected in the original variable. It's different with mc:

void modMC(MyClass mc) {
  mc.a = 1;  // Modifies original.
}

As MyClass is a reference type, there's no need to declare a pointer or ref parameter to modify the original variable. mc.sizeof is 4 on 32-bit architectures and 8 on 64-bit. This is the size of the reference, not the size of the instance itself.

Tip

Struct pointers

A struct pointer can be obtained by taking the address of an instance or allocating an instance on the heap. C and C++ programmers take note: in D, there's no -> for any type of pointer. There is only the dot operator.

When a class or struct instance is instantiated, it gets its own copy of any member variables. If a member variable is declared as static, only one thread-local copy of it exists. Each member is by default initialized to its .init value unless an initialization value is specified. Member functions are normal functions that accept a hidden this argument as the first parameter (accessible inside the function scope), representing the instance. Taking the address of a member function produces a delegate. Static member functions have no this parameter and taking the address of one yields a function pointer:

struct MembersOnly {
    static int x;
    int y;
    int z = 10; // Initialized to 10 for all instances
    static void printX() {
        writeln(x);
    }
    void printYZ() {
        writefln("%s, %s", this.y, z); // this.y is the same as y
    }
}

Non-static members may only be accessed using an instance name as a namespace:

MembersOnly mo;
writeln(mo.z);
mo.printYZ();

Static members must be accessed using the type name as a namespace:

MembersOnly.x += 1;
MembersOnly.printX();

Tip

The with Statement

Any time a namespace is required, it can be temporarily bypassed via the with statement. It works with all of the types described here, as well as with static imports:

MembersOnly mo;
with(mo) {
    printYZ(); // Same as mo.printYZ().
}
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.116.118.229