On Wednesday, 21 December 2011 at 05:08:27 UTC, Jonathan M Davis
wrote:
On Monday, December 19, 2011 08:54:00 Jacob Carlborg wrote:
The Tango runtime has added a new method in Object, "dispose".
This
method is called when "scope" is used or "delete" is
explicitly called.
I don't know if that would help.
That sounds a lot like using clear, though clear doesn't free
memory unless the finalizer does (and I'm not sure that
managing memory with finalizers actually works right now; there
were issues with that previously - but it might have only been
GC memory, so perhaps malloc and free would still work). The
issue with them is escaping references. You can easily end up
with references to data which has been destroyed. It also makes
them harder to pass around unless you essentially wrap the
container in ref-counted struct.
The lifetime of the container must be managed if you really
want to be able to use custom allocators or have the container
be as efficient as possible with regards to its memory usage in
the general case. With a struct, it manages itself. It can do
whatever it wants to with memory internally, and the
combination of its constructor, postblit constructor, and
destructor allows it to take care of it itself and clean up its
memory when it's destroyed. You also don't have issues of stuff
referring to a container which doesn't exist anymore (unless
you add pointers to the container into the mix and then move or
destroy the container).
With classes, if they're owned by the GC, then it's the GC that
manages their lifetime. It destroys them when they're no longer
referenced and it runs a collection cycle. You have no control
over its lifetime, and even if it tries to manage its memory
internally on some level, that memory won't be cleaned up until
the GC collects the container itself. So, if you want to manage
the lifetime of the class, you can't use a naked reference
anymore. You need a way to get the GC to collect it, or you
need to have it somewhere else other than the GC heap.
If you use something like Scoped, then its lifetime will be
tied to the scope that it's in, but you risk references to it
escaping, and limiting the container to a particular scope
could be far too limiting anyway. That being the case, if you
want to use an allocator other than the GC for the class
itself, then you're probably going to have to stick it in a
struct. That struct could then manage the container's lifetime
on some level. It would probably have to use ref-counting and
then call clear on the container when the ref-count hits zero.
That should then allow the container to at least clean up its
internal memory, assuming that that's not on the GC heap, but
unless you use emplace on non-GC heap memory, the container
itself still can't be collected until the GC collects it (since
clear destroys it but doesn't free its memory). Not to mention,
if you're using a ref-counted struct to hold a class in order
to manage that class' lifetime, why on earth did you make it a
class in the first place?
If what you're suggesting with dispose is that it would act
like clear except that it would actually free the container,
well that pretty much goes against the direction that we've
been trying to go with the GC, which is to not have the
programmer explicitly freeing anything on the GC heap (which is
why delete is being removed from the language).
I really think that if we want deterministic destruction of
containers, they need to be structs. And if we want to use
custom allocators with containers, I don't see how we could
reasonably expect them to be of much use unless we're expecting
that programmers will explicitly call clear on them when
they're done with them. So, while the fact that containers need
to be reference types does imply that classes are the way to
go, I think that we're going to have to go with structs if we
want their memory management to be more efficient than simply
letting the GC handle it.
- Jonathan M Davis
I disagree with the above conclusion. you conflate two issues
that are orthogonal:
a. value vs. ref semantics which is already seemed to be decided
in favor of the latter and hence classes. b. memory and lifetime
management
The containers should allow for (disregard the specifics of the
syntax):
Container a = new(SharedMemAllocator) LinkedList();
Container b = new(MallocAllocator) LinkedList();
Container c = new(GC) LinkedList();
When adding an item to the above containers the relevant
allocator will enact its policy about intermixing with other
allocators - by default the item will be copied if it comes from
a separate allocator. I don't see anything here that forces the
use of structs instead of classes.