C++0x shared_ptr Object Creation support (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2351.htm#creation ), voted into the Working Paper, will provide the efficiency advantages of intrusive reference counting without the usability penalties, by allocating a single chunk of memory for the object and its reference count. Doing this properly requires variadic templates and rvalue references to solve the forwarding problem.Cool! Good to know.
> It is often quite expensive to track items thatOk, sure, most resources don't have to go into shared_ptr. But that wasn't what I was saying. The fact remains that tracking object lifetime is not free. shared_ptr makes it easier in some cases, and potentially even makes it more efficient, but with or without shared_ptr, tracking object lifetime is still a non-trivial cost, and manually tracking it (even with shared_ptr) is hard to get right. To repeat myself, several studies have found that 30-60% of a typical non-trivial program's CPU and memory usage is spent tracking and managing object lifetime (the kind of thing that goes away with GC), and that doesn't count the time spent in delete. You cited a particular paper as an argument that GC has lousy performance, but that paper ignored the costs of manual lifetime tracking (it assumed they were 0), so the paper's conclusions need to be adjusted appropriately before they are used to make any decisions. Even automatics, members, container cleanup and destructors have a cost, and the IF statements used to control flow and do cleanup at function exit also have a cost. This cost has to be factored into any comparison with GC's performance, and once that cost has been factored in, manual lifetime management and GC end up being fairly close in runtime cost.
> need to be deleted
Remember: most objects in most C++ programs will NOT be immediately held by shared_ptr. Automatics, members, and elements of containers incur no such expenses. shared_ptr is the icing, not the cake.
(I find most managed-talk incomprehensible, and I've found it hard to explain the native ways of doing things to managed programmers).I find it very frustrating when people who only have experience with one side of things criticize the other way without understanding it (and trying it for a while) first.
It may seem easier to be able to ignore whether something is owning or not - but the price is being unable to handle non-memory resources properly. That price is absolutely unacceptable to me.If that were actually the price, it would be unacceptable to me (and everybody else) too. Fortunately for managed runtimes, that isn't actually the case. There is a price, but it isn't nearly as dramatic as you make it out to be. With the current GC-based runtimes, critical resources need to be tracked separately from object lifetime. In C++, the release of critical resources is conflated with object destruction. That has some advantages, but it is not the only way to do it.
The "managed way" is to let the runtime handle object lifetime and let the developer handle the release of critical resources, with finalization as a backstop in case the developer screws up. This assumes that there are a lot more objects than critical resources, and that seems to be true in my experience. There is certainly some room for improvement in the "managed way", but it certainly isn't an abomination or a catastrophe. If it were, nobody would be using it.
My primary argument against GC is that it doesn't do anything for non-memory resources (finalizers are an abomination).Ah, now we're getting somewhere. You're right -- GC itself doesn't do much for non-memory resources (though it does do something -- it gives you a chance to do something at some point after the object becomes unreachable). GC solves an object lifetime issue and is mostly agnostic about critical resources. (Note that "non-memory" is not the best description here, since there are some memory-based resources that must be freed deterministically, and there are some non-memory resources that don't need deterministic release.)
So basically, I think it boils down to this: with GC, you get automatic object lifetime management, but there is no support for deterministic critical resource management (finalizers are the backstop so that critical resources do eventually get freed, but they are tricky and should be used as little as possible). With C++, you have to manage object lifetime yourself, but you get a lot of help from the language and the runtime, and critical resource management is tied to the object lifetime. Neither system is perfect. Everybody will have a preference. But neither system is fatally flawed.
Another thing I'd like to point out is that the real issue here is provable type safety, which actually can't be acheived in the general case with manual or RAII-based resource management. If there's any way for a program to access a type that has been released, type safety goes out the window. That's the real limitation here. If this were just an issue of garbage collection, it could be resolved by various clever schemes that allow both GC and non-GC resources to co-exist. However, since the actual goal is type safety (which is a very nice thing!), those clever schemes don't work very well.
There is still some room for other clever schemes -- using, finally, and IDisposable are a start, but I would love to see more support from the language, runtime, and code analysis tools.
Calling finalizers an abomination doesn't make it so. They are best avoided as much as possible, but they aren't inherently evil, and there are (rare) cases when letting the finalizer take care of things is really the best thing to do. If abused, they can certainly cause you no end of trouble. But the same goes for a lot of other things in software development. I'm not sure what makes them deserve the label "abomination". Is it the sharp edges? shared_ptr has a few sharp edges of its own -- accidentally turning a raw pointer into a shared_ptr twice leads to double-free, use of shared_ptr as a temporary leads to trouble, etc.