Manually managing class memory

The new operator in D uses the garbage collector, but it isn't the only option. When interfacing with other languages' code or working in a constrained-resource environment (possibly including tight loops on fast PCs!), it is useful to avoid the new operator. Let's see how.

How to do it…

In order to manage class memory manually, perform the following steps:

  1. Get the size of the memory block you need to use: __traits(classInstanceSize, ClassName).
  2. Allocate the memory, slicing it to get a sized array.
  3. Use std.conv.emplace to construct the class in place and cast the memory to the new type.
  4. Stop using the untyped memory block. Instead, use only the class reference.
  5. When you are finished, use destroy() to call the object's destructor, then free the memory.
  6. Be careful not to store the reference where it might be used after the memory is freed. You may want to create a reference counting struct to help manage the memory.

How it works…

The __traits function retrieves information about a type that uses compile-time reflection. The first argument is the data you want and the subsequent arguments are specific to each query. Typically, the second argument is an identifier or a type and the third argument, if necessary, is a member name string to drill down to more details. The __traits function returns data in the same way as a function call.

Here, we use __traits(classInstanceSize), which takes a class type as an argument and returns the number of bytes needed by the instantiated object. We cannot use ClassName.sizeof directly because objects are always reference types in D; ClassName.sizeof returns the size of a pointer to the data (a constant), not the size of the data itself.

Once we get the size of the object, we can allocate the memory. This can be done by any method. We could use the C standard libraries: import core.stdc.stdlib; and void* buffer = malloc(size). We could also define a static array ubyte[size] buffer;. Here, size would need to be available at compile time for the static array method. To achieve that, use enum: enum size = __traits(classInstanceSize, ClassName); or we could use any other method.

After getting the memory block, we need to slice it to the right size. Slicing it ties a length to the pointer, which used by the emplace function to ensure the buffer is large enough. Consider the following code snippet:

auto bufferSlice = buffer[0 .. size];

Finally, we pass the slice to std.conv.emplace. Consider the following code snippet:

import std.conv;
auto classReference = emplace!ClassName(bufferSlice, args_to_constructor…);

This casts the memory block to the correct type (that is why it is in std.conv—it converts untyped memory into typed memory via in-place construction), copies the initial memory image to the block, and calls the appropriate constructor to initialize the object.

Note

The implementation of std.conv.emplace performs two of the three steps of the new operator. The new operator allocates memory of the class size, copies the initial contents (typeid(Class).init) to the newly allocated memory, and calls the requested class constructor on the new object. The emplace function does the latter two steps, but not the first, thereby giving you control over the memory allocation strategy.

Once the object is created, accessing its memory directly is undefined behavior. Thus, you shouldn't use the buffer variables again, except to free them (if necessary) once you are sure no more references will be used. You can use it normally through the reference to call methods, perform dynamic casts, or do anything else that you could do through any other reference.

The destroy function, which is located in the automatically imported object.d module, calls the class' destructor but does not attempt to free the memory. After destroying the object and making sure that no more references to it exist, you may free the memory using the pair function with your allocator (for example, free if you allocated with malloc). If necessary, you may cast the reference back to void*:

MyClass c = myMallocAllocator!MyClass(); // allocate with malloc
free(cast(void*) c); // cast the reference back to a pointer

See also

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

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