Creating a struct with reference semantics

D structs are typically a value type (different variables are always different objects), as opposed to D's classes, which are always a reference type (different variables may refer to the same object). Sometimes, we want the struct that has reference semantics to ensure that assignment is cheap, for example.

How to do it…

To create a struct with reference semantics, we will execute the following steps:

  1. Create a struct with a single data member that is itself a reference type, such as a pointer.
  2. You may choose to use a private nested struct to hold the implementation, using alias this to forward methods automatically. The reference counted object in the previous chapter is a reference-type struct that uses this technique.
  3. Use the struct normally—you should not use the ref storage class when passing it to functions.

How it works…

Structs are a compile-time collection of their contents. If a struct has only one member, it works almost exactly the same way as that member; if a struct's only member is a reference type, the struct works like a reference type too. Assigning a struct to another variable of the same type performs a simple memory copy of the contents:

struct Foo {  char[] c;  }
Foo a, b;
a = b; // conceptually performs memcpy(&a, &b, Foo.sizeof);

This is a shallow copy, and this has exactly the same semantics as assigning the members individually. This property builds the flexible foundation on which we can build all kinds of wrapped types with structs. Using structs with only one data member incurs little to no runtime cost compared to using a variable of the member's type directly.

There's more…

There is one major exception to the general rule that structs with one member are the same as the value they wrap: when you are returning a struct from a function. Consider the following functions:

int* returnPointer();
struct Ptr { int* p; }
Ptr returnWrapperPointer();

These two functions are not compatible on the ABI level despite the fact that both types have the same binary layout, because the struct wrapper may be subject to return value optimization (though it isn't actually an optimization in this case, which is why the D calling convention on 32-bit doesn't use it). This is a common compiler optimization to avoid the excess copying of object values by taking a pointer to the recipient object as a function argument, allowing the function to fill in the data directly instead of copying it through the return value to the destination.

This is permitted by the C++ spec even when the optimization may change the behavior of the function and is implemented across most C, C++, and D compilers for any struct passed with the C calling conventions and many structs passed by other conventions.

Since return value optimization may be performed by a C compiler, use caution when trying to substitute wrapped types for basic types in the extern(C) functions. It is likely that they will not work the way you expect when returned from a function. In these cases, stick to what you did in the C declarations and use wrappers once you are fully into D code.

..................Content has been hidden....................

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