Darren New wrote:
Christopher Smith wrote:
The third one clearly carries the day.
Sure, until you need the lifetime to not match the scope.
Eh, you simply transfer the ownership to whatever else it is that needs
it to live past the scope.
Which means you can have a resource that leaks until the GC gets it's
job done (if ever).
... which is easy to fix, and really wouldn't even need a change in
the source if the runtime invoked the GC whenever you caught an
exception.
Once again: invoking GC() here and there provides no guarantee that an
object will get finalized and therefore that any resource leaks will get
closed.
Well, you have one of two idioms you can go with in C#. You can
either a) have your constructor not throw exceptions, and instead it
returns an "invalid" object, so you then have to invoke some test
method to determine if you actually got something valid or b) let the
constructor throw exceptions, and then the caller has to figure out
where the exception occurred and what resources need to be freed up
(lovely encapsulation either way).
b) isn't any worse than C++. If a C++ constructor throws an exception,
you have to clean it up.
Again, not like C++. In C++, if your constructor throws an exception,
any member variable that has been initialized will have it's destructor
invoked. C# unfortunately fails to do this.
However, if you follow the RAII paradigm, you shouldn't have either
problem unless you have an object whose lifecycle isn't deterministic
by design.
Excactly. I.e., RAII only is guaranteed to clean up resources promptly
if you only allocate such objects on the stack. My programs tend to be
a bit more complicated than that.
Again: NO! RAII only requires that *something* be on the stack (which is
effectively a requirement for GC as well ;-), not the object containing
the sources that need to be cleaned up. Consider this trivial case:
struct A {
A::A(const std::string& someString, const std::string& someOtherString)
: b(new B(someString), b2(new B(someOtherString) {
if (mrand48() < 0) {
throw "just for scuzz";
}
}
std::auto_ptr<B> b;
std::auto_ptr<B> b2;
};
Now, assuming a correct implementation of B, you can have exceptions
thrown during the construction of A::b, A::b2, or in the "just for
scuzz" case without generating a memory leak or a resource leak for any
memory/resources used by B. b and b2 are on the heap, but no catch
clauses needed. You can also see how one could heap allocate A and still
employ RAII to ensure both A and it's member b's get cleaned up. C# will
avoid the memory leak for you, which is great. However, if there is a
resource managed through B, you'll have to write a fair bit of code to
ensure b and b2 are disposed in a timely fashion.
It is also a *very* expensive operation as compared to a method
invocation. I'm kind of surprised that anyone would use it
primarily as a way to trigger finalizers.
One wouldn't. I'd use it to trigger finalizers in the event that I
forgot to use "using" or I otherwise failed to explicitly invoke a
disposal. Something one can't do in C++.
Lots of ways to do that in C++ actually.
Tell me how you ensure the destructor runs on an object you
dynamically allocated and forgot to call destroy on?
Object pools, allocators, garbage collectors, etc.
now you can construct Foo and have exceptions thrown while
initializing any of those members or in something_that_might_throw(),
and all the resources are cleaned up by the time the exception
bubbles up to the caller, all without Foo having to be aware of the
particular workings of its member variables.
Yes. And you can do essentially the same thing in C# - put all the
resource allocation in objects whose constructors won't throw an
exception.
In C++ it's fine if they throw exceptions. *That* is the difference.
And since 95%+ of "resources" in C++ are memory allocations which
*are* handled automatically in C#, it's really not that big a deal.
95% of the "resources" in C++ might be memory allocations, but far less
that 95% of the resource contention problems in C++ involve the free
store. It might give you an idea of the relative difficulty of managing
memory vs. other resources.
Granted, people don't tend to do this, opting to instead just catch
any exception in the constructor and clean up, but you see it all
through the professional libraries.
Ugh. I hope you aren't paying much for them! It's very rare that one
might need a catch block in a constructor in C++.
When I said "generally you know this from the spec", I meant that
generally you know that your logging won't throw an exception, which
is why I didn't bother to smother it. Some would argue that if it
does fail than the best behavior is the ensuing core dump anyway. ;-)
Well, uh, *you* might think so. I write servers that are expected to
run indefinitely in the face of failures of all kinds of underlying
failures of service providers.
So, when a process can no longer log successfully, you'd rather keep it
running than let it die, so it's parent can report the problem and spawn
a new child?!?! Granted, sometimes that isn't the right thing to do, but
the vast majority of the time that's the right thing to do.
and why you get compiler errors when a IDisposable is created
outside of a using statement. ;-)
You do? I never encountered that.
Obviously I forgot to insert my <sarcasm></sarcasm> tags.
Oh! No, you don't. But just because it has an IDisposable interface
doesn't mean you want LIFO semantics on the lifetime.
A *good* compiler should be able to do escape analysis and realize that
perhaps the object isn't even reachable (which is often the case)
outside of the block.
But let's take your RPC example. Let's say that I'm using an RPC
service, and it is holding on to 400 petabytes of information for me.
Perhaps it'd be good to tell it to free that up before I ask it to
allocate a new 400 petabyte dataset for me, yes?
No, why? If it knows you've stopped using it when you stop having a
reference to it, why do you care when it gets freed?
Sigh. That's my point. The RPC service *doesn't* yet know that I've
stopped referencing it unless I explicitly tell it to. If you have
distributed GC, it will often take it a while to automatically notify
the RPC service about the unreferencing (particularly if there isn't
much memory pressure on the client side).
I'm not 100% sure how you go about freeing a MAC address. ;-)
Pretty easy really. Usually a few calls to the device driver a few ARP
packets. Really helpful for HA systems.
--Chris
--
[email protected]
http://www.kernel-panic.org/cgi-bin/mailman/listinfo/kplug-lpsg