August 26, 2002 Re: RAII | ||||
---|---|---|---|---|
| ||||
Posted in reply to Pavel Minayev | Yeah, that might be a good approach. "Pavel Minayev" <evilone@omen.ru> wrote in message news:CFN374949701187963@news.digitalmars.com... Alternatively, it could new it by defaullt, and where you don't want it, you can initialize it to null: auto File a; // default = new File(); auto File b = null; // no object created. |
August 26, 2002 Re: RAII | ||||
---|---|---|---|---|
| ||||
Posted in reply to Walter | "Walter" <walter@digitalmars.com> wrote in news:ake6ln$79r$2 @digitaldaemon.com:
>>
>> auto Foo a; // same as a = new Foo();
>
> That would preclude initialization via out parameter:
>
True. I was trying to sneek stack based storage in somehow.
|
August 26, 2002 Re: RAII | ||||
---|---|---|---|---|
| ||||
Posted in reply to Mac Reiter | "Mac Reiter" <Mac_member@pathlink.com> wrote in message news:ake227$2go$1@digitaldaemon.com... > >Making it a property of the class, rather than the instance, leads to much > >implementation grief. For example, pulling on that thread a bit <g>, it seems to lead to needing to implement two versions of each class - one counted, one not. > I cannot offhand think of any classes that would need both a dof'ed and normal > implementation. Most standard library classes certainly don't need dof behavior. Of the dof'able classes I can consider (Locks, Files, Ports), they > should always be dof'ed. If I am finished with a Lock, it needs to be released > now, or it may deadlock the program. If I am finished with a File or Port, it > should be released now so that other applications can work with it. You would need two versions of, say a File class, if you'd like to store File references inside some other class. > If someone really does need both versions of a class, it isn't all that difficult to do: > > class Foo{} > dof class DofFoo : Foo{} > > (I am going to go slightly off my primary point here because I want to head off > any comments about having to remember which version of the class to use...) > This is no better or worse than the instance property approach for this case, > but for the more common case of a dof'ed-only class, it saves the user having to > remember the extra keyword each time they use the class. The number of polymorphic C++ designs that have blown up because a member functions somewhere > in the class hierarchy left off the "virtual" keyword should be a clear enough > example of why it would be preferable to make dof a class property rather than > requiring a keyword at each instance. Yes, a good point. > The quick point is that I personally cannot imagine a class that needs both > dof'ed and normal implementations, but even if such a class arises it can be > handled with inheritance with no more effort cost than the instance property > form would force on all dof'ed instances. I can see it happening also with handles to windows resources. Sometimes you deal with it completely in function scope, other times you attach it as a member to some other gc'd class. > I think this comment is suggesting that dof'ing is an exception, so it's OK to > require a keyword at each instance. Yes. > While a dof'ed instance may be an exception > compared to the broader scope of programming (see below for my doubts), for > classes that need dof the dof'ed instance is definitely the rule rather than the > exception. Consider the all-too-common Lock. Lock would be dof'ed at least > 99.9% of the time. Any non-dof usage of Lock would almost certainly be better > handled by performing the lock/unlock function on the underlying Mutex/Semaphore/CriticalSection, rather than bundling it up in a useless Lock. > Lock's very existence is to provide dof services. Lock doesn't even need to > have any member functions -- its constructor locks the sync object, and its > destructor/finalizer unlocks it. It has no other user interface. If such services can be disabled by forgetting to add a keyword, then they might as well > not exist. 99% of the classes I use do not hold resources other than memory. These do not need to be dof'd in a gc environment - but in C++ they all would need carefully crafted destructors. > Slightly off topic, since I know that you recognize the importance of dof (otherwise you would not have offered up an implementation, right?). Yes. > The > statement that the use of dof is the exceptional case bothers me. I suspect > that that is not the case. How many people have stayed away from Java, C#, and > D specifically because of the lack of dof? I have no idea. I have it heard as a common complaint. > Once a programmer becomes familiar > with RAII-style programming, relying on some form of dof, it can very quickly > permeate designs. This is not simply syntactic sugar, but is a fundamental > design practice that helps make the resulting implementation stabler, more correct, and more robust. A programmer that is used to RAII would never even > consider attempting to make a multithreaded program without RAII. I realize > that D's synchronized keyword and inherent multithreading simplify this process, > but there are always other resources that are commonly used, and misused, that > can benefit from dof/RAII behaviors. Yes, but D already handles memory and synchronization, leaving the resource issue for a relatively small percentage of classes. > >At least in D the resource would eventually get cleaned up on the GC pass, > >whereas in C++ it is a memory leak that will never get cleaned up. > It would eventually get cleaned up as long as some thread remained active to > pump the GC. The GC is also called on program exit specifically to run all remaining finalizers. > Back to Locks and threads: if I do not write a separate thread > whose sole job is to run this code: > > while (!done) > { > gc.Collect(); > } > > then it is possible to get in a state where all threads are locked on a Lock > instance that has been lost. Yes, but that would happen only if you "leaked" reference to the Lock outside of the auto instance. > This is why I suggested that 'counted' classes not be allowed to participate in > polymorphic actions (conversion to Object, in your example). But then you cannot take advantage of things like Object.print. > I certainly don't > want everything in D to be refcounted. I have mentioned several times that > refcounting adds a very noticeable performance penalty, and thus should be limited only to those things that need it. Ok. > I also agree that "Ignoring the > possibility of bugs" is never a wise choice of action. That is why I went for > disabling type conversions for counted classes. I actually went further than > your 3), because I suggested disabling _any_ conversion, up or down, to any > level of the class hierarchy. The compiler simply disallows casting a counted > class, implicitly or explicitly, to anything else. Maybe someday, when more > experience is gained, some method of safely handling conversions may be found. > But I don't think a moratorium on 'counted' conversions would cause any problems. Let me explain why: > > (I will continue to use thread synchronization primitives and the Lock class, > because it is my primary experience with the RAII idiom that does not work just > as well with GC) > > Say you have some system that needs to be able to manage a collection of Locks, > some of which will lock Mutexes, others lock Semaphores, etc. You might think > that you need polymorphism here so that you can treat all the different types of > Locks as the same type. But that is a flawed design. There is only _one_ Lock > type. What is changing is not the type of lock, but the type of object being > locked. Lock has a private data member, and that member will be a polymorphic > base class pointer/reference to the base class of all of the synchronization > objects: > > class SyncObject{} > class Mutex : SyncObject{} > class Semaphore : SyncObject{} > counted class Lock > { > SyncObject* lockableObject; > } > Lock[] MyLocks; > bool HandleLock(Lock TheLock); > > Note that Lock does not derive from anything, and nothing derives from Lock, and > yet you can still maintain a collection of Locks that are each internally polymorphizing SyncObjects. You don't need any implicit or explicit conversions, so the 'counted' on Lock doesn't cause any problems. > > I already talked about the "requires creation of two versions of most things in > the library, one to handle counted and one for non-counted" issue above, and why > I don't think it is an issue. I see your point, but the D design relies on the existence of Object's member functions. If the object cannot be cast to Object, then those are inaccessible. > 1. arrays of counted objects > Pragmatically, this means that the array is counted. Yes - making for bugs in the implementation and hidden complexity. I speak from experience in trying to get arrays of destructed objects in C++ working right. There are all kinds of issues, such as having an exception thrown halfway through destructing the array, etc. > 2. counted objects as members of structs > I assume this is referring to the lack of destructors for structs, which would > mean that there was nowhere to perform the refcount decrement. Yes. It means auto-generating constructors, destructors, and assignment ops for them. I was trying to avoid this C++ messiness in D. > 3. counted objects as members of non-counted objects > Similar to the "Lock isn't polymorphic, it just contains a polymorphic member" > example above, I suspect that it is a bad design to have a counted object as a > member of a non-counted object, whether it is a struct or a normal class. I > think that the composite class should also be counted. But I also believe that > you shouldn't walk around with pointers to members of objects, so maybe I'm a > little too strict. My simple answer is to disallow it. Compiler error. With that, you wind up with two parallel implementations of classes. > 4. assignment overloading > I think that this is addressed by the "disallow conversions" and "store a flag > in the symbol table to know that you need additional prolog/epilog code for > assignment" topics. It would not surprise me, however, to discover that I am > simply overlooking something. It took years for the people designing C++ and the people implementing it to discover all the cases that got overlooked in making this work right. I am reluctant to start off down that path. > Reference counting _will_ eventually be implemented. It's been done several > times in C++, and C++ already had dof. No matter how hard it is to do in the > compiler, it is even harder to do correctly from source-code-land. Sometimes it > is even impossible to do correctly from out here, due to optimizations that can > be performed by the compiler, which can result is out-of-order operation that > makes the classes think that the last reference was removed before another reference was created. I agree it's hard to get right. > If I had to pick someone I trust to implement difficult things correctly, I > would rather trust you (Walter) than someone who decides to provide an add-on > library that "does reference counting, mostly, as long as you don't ever do > <list of not entirely uncommon things>". Yes, it does add to the complexity of > the compiler. But I suspect that once you did it you would find some simplifications and tricks that made it considerably easier. The nice thing about the auto method is it is easy to explain, so the problems with misuse are more obvious. I never liked the hidden magic going on in C++ to try and make these things work right, I find code much easier to understand if the machinations are more out in the open. I understand that many programmers find the opposite more appealing <g>. > Having rambled on for so long, I will close with this. I would _really_ prefer > the dof mechanism to be reference counting, but if scoping is all anyone else > needs, then that is still almost infinitely better than no dof at all. Most dof > usage will be fine with scoping. I suspected that was true. If by using a much simpler mechanism most all cases can be covered, and there are workarounds for the remainder, I think that is eminently pragmatic. > The problem of "losing" instances when their > controlling reference switches to a new instance (A1 and A2 example) is a little > disturbing, but probably not common in practice. C++ avoids it by removing the > reference indirection -- RAII in C++ only applies to local objects, not to local > pointers/references to heap objects. I suppose you could try to do something > similar in D, but only for dof'ed instances. This doesn't mean it has to come > from the stack -- SmallEiffel knows the difference between objects and references to objects, but all of them come from the heap. It's just that objects can't ever be attached to anything except the particular instance that > was created for them at scope entry. Or you could do what was suggested in one > of the earlier posts, and when an assignment happens to a dof'ed variable, you > finalize the previous occupant (after making sure that they haven't done something foolish like "a = a;"). I am trying to avoid complicated rules - thinking it is better to have a simple rule with a few caveats than a complex rule that few will really understand. > Hoping that I'm not being too annoying, Not at all. I value your thoughts on this. |
August 27, 2002 Re: RAII | ||||
---|---|---|---|---|
| ||||
Posted in reply to Walter | On Mon, 26 Aug 2002 13:17:01 -0700 "Walter" <walter@digitalmars.com> wrote:
> Wouldn't it be better to make it clear upon each use that this is an auto-destruct instance?
Sometimes making it non-auto (simply by forgetting to put "auto" in
declaration) is an _error_.
For example, a Lock is supposed to be always auto.
|
August 27, 2002 Re: RAII | ||||
---|---|---|---|---|
| ||||
Posted in reply to Mac Reiter | On Mon, 26 Aug 2002 20:11:51 +0000 (UTC) Mac Reiter <Mac_member@pathlink.com> wrote:
> your 3), because I suggested disabling _any_ conversion, up or down, to any level of the class hierarchy. The compiler simply disallows casting a counted class, implicitly or explicitly, to anything else. Maybe someday, when more experience is gained, some method of safely handling conversions may be found.
I have to disagree here. Clearly, File should be a counted class. On the other
hand, it derives
from Stream (which is not counted). Currently, you can write code like this:
uint crc32(Stream s) { ... }
File f = new File("foo.bar");
crc = crc32(f);
If casting is forbidden, you couldn't treat File as if it was Stream - which
is the main feature of
the current streams implementation.
|
August 27, 2002 Re: RAII | ||||
---|---|---|---|---|
| ||||
Posted in reply to Mac Reiter | Just poking my head into this thread to mention that if you think having people maintain pointers to internal class data is a problem for reference counting, simply disallow taking the address of data members of reference counted classes or at very least prevent storing them anywhere but a local variable inside a member function of the class. I'd just completely disallow it, myself. There are workarounds to avoid the need for taking addresses. Sean |
August 27, 2002 Re: RAII | ||||
---|---|---|---|---|
| ||||
Posted in reply to Walter | "Walter" <walter@digitalmars.com> wrote in message news:akecsk$e6a$1@digitaldaemon.com... > > "Mac Reiter" <Mac_member@pathlink.com> wrote in message news:ake227$2go$1@digitaldaemon.com... > > >Making it a property of the class, rather than the instance, leads to > much > > >implementation grief. For example, pulling on that thread a bit <g>, it seems to lead to needing to implement two versions of each class - one counted, one not. > > I cannot offhand think of any classes that would need both a dof'ed and > normal > > implementation. Most standard library classes certainly don't need dof behavior. Of the dof'able classes I can consider (Locks, Files, Ports), > they > > should always be dof'ed. If I am finished with a Lock, it needs to be > released > > now, or it may deadlock the program. If I am finished with a File or > Port, it > > should be released now so that other applications can work with it. > > You would need two versions of, say a File class, if you'd like to store File references inside some other class. I don't know what you're talking about. A file reference in a non-dof class would just get its refcount decremented when the containing class *is* finalized. Before then, it's not "done" with the file. > > The quick point is that I personally cannot imagine a class that needs > both > > dof'ed and normal implementations, but even if such a class arises it can > be > > handled with inheritance with no more effort cost than the instance > property > > form would force on all dof'ed instances. > > I can see it happening also with handles to windows resources. Sometimes you > deal with it completely in function scope, other times you attach it as a member to some other gc'd class. If it's dealt with entirely in function scope, the compiler can potentially figure out that it can optimize the ref counting completely away. This would be a Very Good Thing. If stored in a member, I still don't see the big problem. I would not have a class inherit refcounting semantics just because a member has them. The refcounting semantics are only for references anyway. You wouldn't want to embed the actual class as a member because then the containing object would always have an implicit ref on it and it could never be freed until the containing class is freed, at which point the memory it lives in is freed and it can't exist anymore so all remaining references are invalid. So embedding an actual refcounted object in a class would be a no-no. Embedding a ref to one would be what you'd want to do. > 99% of the classes I use do not hold resources other than memory. These do not need to be dof'd in a gc environment - but in C++ they all would need carefully crafted destructors. You write compilers, not servers, not games, not web browsers, not military software. > Yes, but D already handles memory and synchronization, leaving the resource > issue for a relatively small percentage of classes. Resources are a very important fundamental concept. Just because you have 2 common instances handled doesn't mean you have the entire spectrum handled. You speak as though resources such as threads, files, window handles, etc are all trivial. Resource leaks are very real problems. > > 2. counted objects as members of structs > > I assume this is referring to the lack of destructors for structs, which > would > > mean that there was nowhere to perform the refcount decrement. > > Yes. It means auto-generating constructors, destructors, and assignment ops > for them. I was trying to avoid this C++ messiness in D. I don't see it as messiness, I see it as having the compiler do useful work for me automatically. That's a Good Thing. > > 3. counted objects as members of non-counted objects > > Similar to the "Lock isn't polymorphic, it just contains a polymorphic > member" > > example above, I suspect that it is a bad design to have a counted object > as a > > member of a non-counted object, whether it is a struct or a normal class. > I > > think that the composite class should also be counted. But I also believe > that > > you shouldn't walk around with pointers to members of objects, so maybe > I'm a > > little too strict. My simple answer is to disallow it. Compiler error. > > With that, you wind up with two parallel implementations of classes. Just disallow addresses of members in general. You can always pass the address of a temporary and use assignment. Wouldn't it simplify the GC as well? > The nice thing about the auto method is it is easy to explain, so the problems with misuse are more obvious. I never liked the hidden magic going > on in C++ to try and make these things work right, I find code much easier to understand if the machinations are more out in the open. I understand that many programmers find the opposite more appealing <g>. My ears are burning. :) > I am trying to avoid complicated rules - thinking it is better to have a simple rule with a few caveats than a complex rule that few will really understand. That sounds like a rule that should trump most other rules. Simpler is almost universally better. Sean |
August 27, 2002 Re: RAII | ||||
---|---|---|---|---|
| ||||
Posted in reply to Pavel Minayev | "Pavel Minayev" <evilone@omen.ru> wrote in message news:CFN374955344668171@news.digitalmars.com... > On Mon, 26 Aug 2002 13:17:01 -0700 "Walter" <walter@digitalmars.com> wrote: > > > Wouldn't it be better to make it clear upon each use that this is an auto-destruct instance? > > Sometimes making it non-auto (simply by forgetting to put "auto" in > declaration) is an _error_. > For example, a Lock is supposed to be always auto. I've been thinking about that. Some classes should never be 'auto' (because they might squirrel a reference to themselves away remotely), and some should always be 'auto'. |
August 27, 2002 Re: RAII | ||||
---|---|---|---|---|
| ||||
Posted in reply to Walter | Walter wrote:
> "Pavel Minayev" <evilone@omen.ru> wrote in message
> news:CFN374955344668171@news.digitalmars.com...
>
>>On Mon, 26 Aug 2002 13:17:01 -0700 "Walter" <walter@digitalmars.com>
>
> wrote:
>
>>>Wouldn't it be better to make it clear upon each use that this is an
>>>auto-destruct instance?
>>
>>Sometimes making it non-auto (simply by forgetting to put "auto" in
>>declaration) is an _error_.
>>For example, a Lock is supposed to be always auto.
>
>
> I've been thinking about that. Some classes should never be 'auto' (because
> they might squirrel a reference to themselves away remotely), and some
> should always be 'auto'.
>
>
How about being able to check "auto" in the invariant of the class?
|
August 27, 2002 Re: RAII | ||||
---|---|---|---|---|
| ||||
Posted in reply to Walter | On Tue, 27 Aug 2002 10:38:41 -0700 "Walter" <walter@digitalmars.com> wrote:
> I've been thinking about that. Some classes should never be 'auto' (because they might squirrel a reference to themselves away remotely), and some should always be 'auto'.
I think that the best approach would be to allow both auto classes and auto
objects.
Every instance of an auto class _must_ be declared as auto, otherwise it is an
error. Non-auto classes can still have auto instances.
Why require auto for classes if they are auto already? It'd make clear that
class
is actually auto.
|
Copyright © 1999-2021 by the D Language Foundation