Markus Gerwinski wrote:
Frank Heckenbach wrote (in reply to da Silva, Joe):
Obviously, you need pointers to represent such structures. Creating them is not a problem, but how to clean up properly? You can traverse all pointers in the object and clean up the objects they point to recursively. But that's wrong because those objects might be referred to from elsewhere. Also, with cyclic references, you'd run into an endless loop.
This is not to say it's impossible (I mentioned reference counting, and there are ways to avoid the endless loop), but is this much simpler than GC? In fact, if you consider the whole memory as a large tree(*), the resulting algorithm wouldn't look much differently from a GC algorithm ...
If this inter-dependence with GCC also means that garbage collection is a requirement, then "so be it" ...
Practically, it does. But if you know a better solution, even if only in theory, I'd be interested, just for curiosity.
I'm currently working on a framework including an object type named "tReferenceManager". It is intended to exactly solve this problem, at least for synchronization of collections (lists, hashtables etc.): For every pointer, you can subscribe _one_ collection it is stored in, plus an arbitrary number of reference collections. When the pointer is removed from the store collection, it is automatically disposed, and all of the references are removed from the reference collections.
I hope to complete the respective unit of this framework within a few weeks. If anyone thinks he/she can use it, I'll announce it after this unit is ready. (Frank: The framework contains also the exception handling unit we already talked about.)
Personally, I _really_ dislike garbage collection since I had to deal with it in an optimization problem in Java. ;-/ IMO, providing a language with GC is just a way to encourage programmers to give up control. An alternative feature I'd like to see in a language would be to split pointers into two separate data types: "store" pointers and "reference" pointers. Allocating and deallocating may only be done on a store pointer; setting a pointer to an already existing address may only be done with a reference pointer. When disposing of a store pointer, itself and all of its references are automatically set to nil, so you can always check whether the pointer is pointing to valid data. (It's the same principle I'm using in my tReferenceManager, and up to now I didn't find any disadvantages in it.
What about the following situation:
- Create an object Foo (store pointer) and an object Bar that points to Foo (i.e., contains the store pointer).
- Create an object Baz that also points to Foo (reference pointer).
- Destroy Bar. IIUIC, Foo will be disposed of and the reference pointer in Baz invalidated. That's nice so I get an error message rather than a crash when I try to access Baz.Foo, but I'd prefer to actually access it. ;-)
I guess I must be missing something, since that's only a very simple example of what happens all the time in GCC/GPC.
Besides, when you really want to evaluate things, you have to describe all the hidden costs first. AFAICS, for every object you need an implicit list containing all the reference pointers, so they can be invalidated later.
When copying a pointer, you have to add the destination to the list, and remove its previous value (if any) from its list. If not done properly, this can easily be O(n) which is unacceptable. So you need at least a doubly-linked list or something like this.
These are memory and runtime issues, and given that copying pointers is quite common in GPC, these effects must be analyzed precisely.
Except that you are forced to think before you code.;-)
As I said in another mail, polemics probably won't get us any further. (I, e.g., have some scepticism about over-use of OOP (though I use a limited amount of OOP in my own programs) and could go on about people talking about "collections" and "subscribing" which I have to translate into the terminology I'm familiar with to understand what's really going on, and who sometimes simply hide their inefficiencies behind a cloud of classes (I've seen such code in C++ and Java), but I wouldn't talk about this in such a discussion. ;-)
Not "giving up control" can also mean not delegating work. There are certainly situations where you want exact control about when and how some block of memory is disposed of, and I also don't view GC as a panacea.
But the question is whether it's appropriate for this particular program that GCC is. GCC is rather complex (I don't think anyone will disagree ;-), and having one more thing to worry about can be really annoying (speaking from experience). So *if* the GC works silently in the background (I haven't seen it in actual use yet), it would be a great relief for the programmer in the first place.
As I said, the memory overhead of GC seems to be rather small. I haven't analyzed the runtime overhead, but given that GC is done rather seldom (as compared to something that needs extra effort for each pointer copy, such as your suggestion IIUIC), I'm rather optimistic there, too. Some other conditions that GC is more or less hostile to (like real-time issues) don't apply either.
Frank