why allocators are not discussed here

Adam D. Ruppe destructionator at gmail.com
Tue Jun 25 16:16:31 PDT 2013


On Tuesday, 25 June 2013 at 22:50:55 UTC, H. S. Teoh wrote:
> And maybe (b) can be implemented by making gc_alloc / gc_free
> overridable function pointers? Then we can override their 
> values and use scope guards to revert them back to the values 
> they were before.

Yea, I was thinking this might be a way to go. You'd have a 
global (well, thread-local) allocator instance that can be set 
and reset through stack calls.

You'd want it to be RAII or delegate based, so the scope is clear.

with_allocator(my_alloc, {
      do whatever here
});


or

{
    ChangeAllocator!my_alloc dummy;

    do whatever here
} // dummy's destructor ends the allocator scope


I think the former is a bit nicer, since the dummy variable is a 
bit silly. We'd hope that delegate can be inlined.



But, the template still has a big advantage: you can change the 
type. And I think that is potentially enormously useful.



Another question is how to tie into output ranges. Take 
std.conv.to.

auto s = to!string(10); // currently, this hits the gc

What if I want it to go on a stack buffer? One option would be to 
rewrite it to use an output range, and then call it like:

char[20] buffer;
auto s = to!string(10, buffer); // it returns the slice of the 
buffer it actually used

(and we can do overloads so to!string(10, radix) still works, as 
well as to!string(10, radix, buffer). Hassle, I know...)

Naturally, the default argument is to use the 'global' allocator, 
whatever that is, which does nothing special.



The fun part is the output range works for that, and could also 
work for something like this:

struct malloced_string {
     char* ptr;
     size_t length;
     size_t capacity;
     void put(char c) {
         if(length >= capacity)
            ptr = realloc(ptr, capacity*2);
         ptr[length++] = c;
     }

     char[] slice() { return ptr[0 .. length]; }
     alias slice this;
     mixin RefCounted!this; // pretend this works
}


{
    malloced_string str;
    auto got = to!string(10, str);
} // str is out of scope, so it gets free()'d. unsafe though: if 
you stored a copy of got somewhere, it is now a pointer to freed 
memory. I'd kinda like language support of some sort to help 
mitigate that though, like being a borrowed pointer that isn't 
allowed to be stored, but that's another discussion.


And that should work. So then what we might do is provide these 
little output range wrappers for various allocators, and use them 
on many functions.

So we'd write:

import std.allocators;
import std.range;

// mallocator is provided in std.allocators and offers the goods
OutputRange!(char, mallocator) str;

auto got = to!string(10, str);



What's nice here is the output range is useful for more than just 
allocators. You could also to!string(10, my_file) or a delegate, 
blah blah blah. So it isn't too much of a burden, it is something 
you might naturally use anyway.

> Also, we may have the problem of the wrong allocator
> being used to free the object.

Another reason why encoding the allocator into the type is so 
nice. For the minimal D I've been playing with, the idea I'm 
running with is all allocated memory has some kind of special 
type, and then naked pointers are always assumed to be borrowed, 
so you should never store or free them.

auto foo = HeapArray!char(capacity);

void bar(char[] lol){}

bar(foo); // allowed, foo has an alias this on slice

// but....

struct A {
    char[] lol; // not allowed, because you don't know when lol is 
going to be freed
}


foo frees itself with refcounting.


More information about the Digitalmars-d mailing list