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