In the spirit of accepting the decision of removing delete, I would like to
present a proposal for semantics/library features that would give any
programmer the choice between managing dinamically allocated memory by himself
and trusting the garbage collector to efficiently handle everything. This
would be similar to what happens in C# (minus the extra unsafe and fixed
keywords), and can be considered safe.
1. As decided by the D designers, the "delete" keyword is to be removed. If the
"new" keyword is kept, it is reserved to allocating memory managed by the
garbage collector for objects.
2. Two distinct heaps are available by default: the managed (garbage collected)
heap and the unmanaged heap. Objects declared on one heap cannot be moved
to the other heap (Except by virtue of copying an object from the unmanaged
heap to the garbage collected heap and then destroying the original, which is
the closest to a safe operation.) The garbage collector monitors the state of
both, but only reads/writes the managed heap.
3. Manual memory (de)allocation, and construction/destruction of objects are
separated, each is given its own function/operator. If a function is used, it
should be a function template that infers the required memory amount needed by
a initialized object according to the contained data (i.e., calling
cmalloc()/cfree() without needing anything other than the object type).
The functions to take care of memory allocation/deallocation could simply be
called alloc()/dealloc(). Example:
class Foo
{
this() {}
~this() {} // no object may be declared on the unmanaged heap unless there
are explicit constructors/destructors.
}
Foo f1; // Declared, not initialized.
alloc!Foo(f1); // Memory is allocated for the object. Constructor not yet
called. Reference to f1 is returned.
f1 = Foo(); // Proposed syntax for construction on unmanaged heap. Only legal
*after* allocating with alloc() (i.e. , the .init state is tested.)
destroy!Foo(f1); // Possible syntax for a function template that calls the
destructor of Foo and reverts to .init. No deallocation occurs yet.
dealloc!Foo(f1); // Memory may only be deallocated *after* destroying the
object (i.e., reverting it to initial state.)
Foo f1 = new Foo(); // The familiar syntax would be kept for creating on the
managed heap, without any further intervention from the programmer.
4. Needless to say, in many cases the correct type would be inferred without
any explicit template parameters (i.e., one could just write alloc(f1) instead
of alloc!Foo(f1).) Attempts to call either alloc(null), destroy(null) or
dealloc(null) would result in exceptions.
5. Additional functions could be available to simplify these tasks. For example,
class Boo : Foo
{
this() {}
~this() {}
}
Foo f2 = create!Foo(f2); // Calls alloc!Foo(f2) and then calls the
constructor. Equivalent to new in C++.
Foo b = create!Boo(b); // Casts the pointer to the correct type before
allocating, then calls the constructor. Again, same behavior as new.
delete(f2); // Calls the destructor, and then deallocates. Would replace
delete, except it wouldn't touch the managed heap.
I think this could be a reasonable way to keep manual memory management, at
least on a superficial analysis.