August 30, 2002 Re: RAII take 2 | ||||
---|---|---|---|---|
| ||||
Posted in reply to Russ Lewis | If I had a choice, I'd *much* rather have operator overloading for structs than for classes. I guess for both wouldn't hurt. And I'd prefer if the stack allocation policy be explicit in the spec. Although I suppose arrays of structs and very large structs can come from the heap if necessary. I don't know what everybody's problem with implicit code is. Every function ever compiled comes with hidden entry and exit code. A lot of stuff goes on behind the scenes that most people never know about, even in C. The chance to contribute to that general madness can make alot of programming tasks a lot easier. If I can't get the compiler to write my program for me, at least I should be able to get it to do a good portion of the redundant grunt work. ;) Sean "Russ Lewis" <spamhole-2001-07-16@deming-os.org> wrote in message news:3D6E5614.E9ACCEE9@deming-os.org... > Walter wrote: > > > They are: > > 1) struct > > 2) class > > 3) struct with destructor > > > > The distinction between 1 and 3 should be comfortable for anyone used to destructors in C++. > > I hear you, but (from my perspective), the key difference between a struct and > a class is not the stack/heap question, but the active/inactive and binary layout issues. > > As I think of them, a struct is a primarily a binary layout mechanism - its > purpose is to give a portable, definable, C-interoperable design for the data. > That it can be laid out on the stack is very much secondary in that view. Since it is primarily a binary layout mechanism, it is an "inactive" mechanism > - it doesn't contain any virtualization or implicit code (constructors and destructors are implicit code, since they run without us explicitly calling > them). Member functions of structs, IMHO, are just (good) syntax sugar - they > simply serve to encapsulate common operations on the structure. > > A class, on the other hand, is fundamentally an active mechanism. It has implicit code (constructors, destructors, operator overloading, etc.) and does > a lot of virtualization. It explicitly does NOT allow you to define binary > layout. > > In this view, an active property of the object (raii) fits far better with a > class than a struct. > > -- > The Villagers are Online! villagersonline.com > > .[ (the fox.(quick,brown)) jumped.over(the dog.lazy) ] > .[ (a version.of(English).(precise.more)) is(possible) ] > ?[ you want.to(help(develop(it))) ] > > |
September 02, 2002 Re: RAII take 2 | ||||
---|---|---|---|---|
| ||||
Posted in reply to Walter | "Walter" <walter@digitalmars.com> wrote in message news:akjm07$urk$1@digitaldaemon.com... > > "Russ Lewis" <spamhole-2001-07-16@deming-os.org> wrote in message news:3D6D4905.4BAF1C5C@deming-os.org... > > I think that that is an argument FOR the other style. As I see it, there > are > > (at least) three types of usage: > > * struct (pure data map) > > * garbage collected class (arbitrary lifespan) > > * raii class (d.o.f.) > > > > Those 3 should be well distinguished. > > They are: > 1) struct > 2) class > 3) struct with destructor > > The distinction between 1 and 3 should be comfortable for anyone used to destructors in C++. I have used C++ destructors for a few years now. I usually think of (1) as a struct with an empty destructor. So the list becomes: 1) struct with empty destructor 2) class 3) struct with non-empty destructor Now that distinction between raii and non-raii is not clear enough for me. The problem is that you want to pass raii objects into other functions before releasing them. With structs that is quite uncomfortable. I think we should pick the example of a File, find a solution, and that solution will be OK for every other raii object. Struct destructors, (and constructors) are usefull on their own right, and they should be implemented, but using them for raii, means or requires workarounds. Please do not force workarounds in or with the language. As I have already written raii is the property of the class not of the instance. BUT: It doesn't mean I want every reference to a raii object to release the instance! Only the OWNER reference: void foo() { owner File f = new File(); f.doSomething(); bar(f); } void bar(File f2) { ... } So the when the "f" reference is "lost", the instance is deleted. But not with the "f2" reference. Yes I realize that this is exactly the same that was suggested by Walter the first time, but with another keyword (raii). I also would like to point out that this is almost the same as constructing a wrapper struct with a destructor at "f", and passing the wrapped reference to "bar". See the same with desturcted struct : void foo { instance RaiiWrapper(File).RaiiWrapper fw; fw.f = new File(); fw.f.doSomething(); bar(fw.f); } Which one do you like better? What you see is unneccessary indirection in the source code and the binary too. Not talking about the wrapper contruct which is hard to read out. So please return to the original idea. Use a keyword to signal that a certain reference is owner of the instance. If operator overloading would be <Sigh> nice enough (dot operator, cast overloading, struct constructors), you could make a transparent wrapper struct here. That way source code complexity would match the first example. But binary code will be still a bit slower. Please don't say: that way referenced instances can be deleted. Yes, but with structs there are the same problem if you store the address of a struct. Or the wrapped reference. Either way this has to be left to the programmers good will. Future plans: 1) References stored inside class members can be owners too. This should mean, that after the finalyzer of a class runs, owned instances should be deleted. Seems easy to me. 2) Ownership can be made transferable. (implementation problems) 3) Asignment to an owner reference should delete the old instance. (implementation problems) For the 3) problem I propose a solution. If owner references are a separate "type" from normal references, the compiler could generate special code when assigment to an owner reference happens. For 2) you need a special field in each reference, and complex code for each assignment to any reference. I don't see an elegant solution for this. Yours, Sandor |
September 02, 2002 Re: RAII take 2 | ||||
---|---|---|---|---|
| ||||
Posted in reply to Sandor Hojtsy | "Sandor Hojtsy" <hojtsy@index.hu> wrote in news:akva29$2uju$1@digitaldaemon.com: > Now that distinction between raii and non-raii is not clear enough for me. > > The problem is that you want to pass raii objects into other functions > before releasing them. With structs that is quite uncomfortable. > I think we should pick the example of a File, find a solution, and > that solution will be OK for every other raii object. > > Struct destructors, (and constructors) are usefull on their own right, and they should be implemented, but using them for raii, means or requires workarounds. Please do not force workarounds in or with the language. As I have already written raii is the property of the class I wholeheartedly agree. Using structs for raii is not very practical, since we actually want _classes_ to be such. |
September 03, 2002 Re: RAII take 2 | ||||
---|---|---|---|---|
| ||||
Posted in reply to Walter | Guys It seems to be the case that this debate is trying to reconcile keeping the simpler semantics and implementation of Java, whilst bringing back the essential constructs of C++ which (IMO) make it a more powerful, robust and object-oriented (supporting) language than its weakened offspring. I have a couple of thoughts: 1. struct The struct keyword remains what most people assume it to imply, an "inactive" type definition that doesn't contain constructor/destructor. The programmer can specify the layout of the structure. Arrays of structs are valid. Structs can be on the heap or on the frame (see below). Structs can have static methods. If it is feasible (from a compiler Walter's perspective), they may contain instance methods. Structs can have contain other structs (as members), but cannot contain references to classes (since they don't have destructors). 2. class The class keyword remains what most people assume it to imply, an "active" type definition, that contains constructors and destructors (although they can be omitted, in which case the compiler can provide them, or simply omit them, as it chooses). The default nature of a class is that of the current D class, ie. it is on the heap, and is garbage collected. class Normal { }; Normal n = new Normal(... some params ...); The raii (or auto) keyword can be applied to a class instance declaration, in which case the instance is placed on the frame, and is doffed when it goes out of scope. The syntax for such declarations do not use the new keyword, further disambiguating the syntax. class ToDofOrNotToDof { }; auto ToDofOrNotToDof dof(... args ...); // Dof'ed at the end of this scope. ToDofOrNotToDof nodof = new ToDofOrNotToDof(... args ...); // Destructor called when garbage collected. The raii (or auto) keyword can also be applied to a class definition, in which case all instances of the class _must_ be placed on the frame, and are doffed when they go out of scope. All instances use the raii declaration syntax. auto class AlwaysDof { }; AlwaysDof adof1(... args ...); // Dof'ed at the end of this scope auto AlwaysDof adof2(... args ...); // Identical to previous line - auto is redundant on an auto class AlwaysDof adof3 = new AlwaysDof(... args ...); // ERROR! The question of whether a doffing instance can be passed to a function taking a non-doffing instance can be resolved by either (i) disallowing it for any doffed instance, or (ii) allowing this at the programmer's own risk. I prefer the latter, but can live with the former quite happily. 3. Function declarations are at global scope, whereas object default-construction is not. Nevetheless, if Walter wants to distinguish between the two, the former could bear the "function" keyword, or the latter can be required to omit (as in C++) the empty braces: AlwaysDof adof(); // invalid AlwaysDOf adof; // valid I'm not sure this isn't only a summary of all the points made on this topic, but thought I'd mention it. One important implication for all this, that I don't know if anyone's yet addressed, is the impact of generics/templates. Perhaps this needs to be discussed before a final DOF decision is made. Matthew "Walter" <walter@digitalmars.com> wrote in message news:akgsaf$aog$1@digitaldaemon.com... > The 'auto' idea was looking more and more like a way to simply put class objects on the stack. Ok, so I smack my forehead, and think why not allow destructors for structs? This would provide the automatic destruction of structs when they go out of scope. Some other properties talked about with auto are implicit with structs: > > 1) they cannot be derived from or inherited > 2) the 'auto' is implicit > 3) can't implicitly convert to Object > > Furthermore, to make things easier, the following can be disallowed: > > 1) arrays of structs with destructors > 2) structs with destructors as class or struct fields > 3) assignment of structs with destructors > > A class can be 'auto-ized' by wrapping it in a struct: > > struct AutoA > { A a; > ~this() { delete a; } > } > > and can even be templatized: > > template AutoClass(T) > { > T t; > ~this() { delete t; } > } > > > |
September 03, 2002 Re: RAII take 2 | ||||
---|---|---|---|---|
| ||||
Posted in reply to Matthew Wilson | Matthew Wilson wrote: > The raii (or auto) keyword can also be applied to a class definition, in > which case all instances of the class _must_ be placed on the frame, and are > doffed when they go out of scope. All instances use the raii declaration > syntax. > > auto class AlwaysDof > { > }; > > AlwaysDof adof1(... args ...); // Dof'ed at the end of this scope > auto AlwaysDof adof2(... args ...); // Identical to previous line - auto I think that "auto" should also be _required_. > The question of whether a doffing instance can be passed to a function > taking a non-doffing instance can be resolved by either (i) disallowing it > for any doffed instance, or (ii) allowing this at the programmer's own risk. > I prefer the latter, but can live with the former quite happily. I think the latter is much better... just to remind my example with Stream/File: class Stream; // not all Streams are auto, auto class File; // but File definitely is uint crc32(Stream str); // Stream is not auto! auto File f("foo.bar"); crc32(f); Now if you forbid passing auto objects to functions expecting non-auto reference, the last line is invalid. But I don't see any reasons why it shouldn't be allowed... > 3. Function declarations are at global scope, whereas object > default-construction is not. Nevetheless, if Walter wants to distinguish > between the two, the former could bear the "function" keyword, or the latter > can be required to omit (as in C++) the empty braces: > > AlwaysDof adof(); // invalid > AlwaysDOf adof; // valid As long as you require to use "auto", there is no ambiguity. Overall, I must say that I really like this proposal, and would vote for it should the voting take place. It seems like the best approach to me, enabling power of C++ while preserving D's simplicity |
September 04, 2002 Re: RAII take 2 | ||||
---|---|---|---|---|
| ||||
Posted in reply to Matthew Wilson | "Matthew Wilson" <matthew@thedjournal.com> wrote in message > Guys > > It seems to be the case that this debate is trying to reconcile keeping the > simpler semantics and implementation of Java, whilst bringing back the essential constructs of C++ which (IMO) make it a more powerful, robust and > object-oriented (supporting) language than its weakened offspring. I have a > couple of thoughts: > > 1. struct > > The struct keyword remains what most people assume it to imply, an "inactive" type definition that doesn't contain constructor/destructor. > > The programmer can specify the layout of the structure. > > Arrays of structs are valid. > > Structs can be on the heap or on the frame (see below). > > Structs can have static methods. If it is feasible (from a compiler Walter's > perspective), they may contain instance methods. > > Structs can have contain other structs (as members), but cannot contain > references to classes (since they don't have destructors). I don't think destructors and contained references are connected. > > 2. class > > The class keyword remains what most people assume it to imply, an "active" type definition, that contains constructors and destructors (although they can be omitted, in which case the compiler can provide them, or simply omit > them, as it chooses). > > The default nature of a class is that of the current D class, ie. it is on the heap, and is garbage collected. > > class Normal > { > }; > > Normal n = new Normal(... some params ...); > > The raii (or auto) keyword can be applied to a class instance declaration, in which case the instance is placed on the frame, and is doffed when it goes out of scope. The syntax for such declarations do not use the new keyword, further disambiguating the syntax. > > class ToDofOrNotToDof > { > }; > > auto ToDofOrNotToDof dof(... args ...); > // Dof'ed at the end of this scope. > ToDofOrNotToDof nodof = new ToDofOrNotToDof(... args ...); // > Destructor called when garbage collected. I like this syntax. Could you please remind me, what this DOF word stands for? > > The raii (or auto) keyword can also be applied to a class definition, in which case all instances of the class _must_ be placed on the frame, and are > doffed when they go out of scope. All instances use the raii declaration syntax. I don't think the language specification should say whether raii is on the stack, or the heap. The only important point is that it's lifetime is the scope. > auto class AlwaysDof > { > }; > > AlwaysDof adof1(... args ...); // Dof'ed at the end of this scope > auto AlwaysDof adof2(... args ...); // Identical to previous line - auto > is redundant on an auto class > AlwaysDof adof3 = new AlwaysDof(... args ...); // ERROR! > > The question of whether a doffing instance can be passed to a function taking a non-doffing instance can be resolved by either (i) disallowing it for any doffed instance, or (ii) allowing this at the programmer's own risk. > I prefer the latter, but can live with the former quite happily. Incorrect. The raii is not the property of the object instance! It is the property of the reference instance. This is very important. You cannot ever pass a class instance. Only copy the reference. Now, would you like the copied reference to destroy your original object before the caller function ends? In 99 cases out of 100, no. So the real problem is copying an raii reference to an other raii reference. There is simply no risk with copying a raii reference into a non-raii reference. Why would someone disallow it? Because I want functions which take a reference to a File object, and don't close the File, you cannot have File as a raii class. Only specific references to File-s can be raii. Can you please provide a class which requires all references to it, to be raii? Because that is what you are suggesting. So I agree with you in most points, but think there is no need for this "auto class", but only for the individual "auto"s. Yours, Sandor |
September 04, 2002 Re: RAII take 2 | ||||
---|---|---|---|---|
| ||||
Posted in reply to Sean L. Palmer | "Sean L. Palmer" wrote: > If I had a choice, I'd *much* rather have operator overloading for structs than for classes. I guess for both wouldn't hurt. > > And I'd prefer if the stack allocation policy be explicit in the spec. Although I suppose arrays of structs and very large structs can come from the heap if necessary. > > I don't know what everybody's problem with implicit code is. Every function ever compiled comes with hidden entry and exit code. A lot of stuff goes on behind the scenes that most people never know about, even in C. The chance to contribute to that general madness can make alot of programming tasks a lot easier. > > If I can't get the compiler to write my program for me, at least I should be able to get it to do a good portion of the redundant grunt work. ;) Amen. I'm not against having the compiler insert implicit code - in fact, I'm generally very much in favor of it. But if we make structs too much like classes, then why do we have two types at all? Either unify them in one program element, or give them noticable (useful) differences. -- The Villagers are Online! villagersonline.com .[ (the fox.(quick,brown)) jumped.over(the dog.lazy) ] .[ (a version.of(English).(precise.more)) is(possible) ] ?[ you want.to(help(develop(it))) ] |
September 04, 2002 Re: RAII take 2 | ||||
---|---|---|---|---|
| ||||
Posted in reply to Sandor Hojtsy | In article <al4j79$2s6p$1@digitaldaemon.com>, Sandor Hojtsy says... > >"Matthew Wilson" <matthew@thedjournal.com> wrote in message >Incorrect. The raii is not the property of the object instance! It is the >property of the reference instance. >This is very important. You cannot ever pass a class instance. Only copy the >reference. Now, would you like the copied reference to destroy your original >object before the caller function ends? In 99 cases out of 100, no. So the >real problem is copying an raii reference to an other raii reference. There >is simply no risk with copying a raii reference into a non-raii reference. >Why would someone disallow it? There is a risk if the non-raii reference will be holding on past the function invocation lifetime -- if it is storing it away in a cache or something. Of course, such a situation will blow up anything that isn't reference counted, so we'll ignore that case for the time being... >Because I want functions which take a reference to a File object, and don't >close the File, you cannot have File as a raii class. Only specific >references to File-s can be raii. >Can you please provide a class which requires all references to it, to be >raii? Because that is what you are suggesting. > >So I agree with you in most points, but think there is no need for this "auto class", but only for the individual "auto"s. > >Yours, >Sandor My personal preference is not to require the per-instance keyword, because it is easy to forget it and then lose all of the benefits of raii. If I make a Lock class, I know that in 99.99% of the cases I want it to be raii. Why require someone to type an extra keyword for all of those cases? Why add the debugging risk of forgetting to type it to the most common case? C++ requires you to flag each and every member function as virtual if you want polymorphism to work. How many class hierarchies are subtly broken because somebody forgot to add that word? 'virtual' is the most common case, so it should be what you get if you don't go out of your way to specify otherwise. Because C++ broke that rule, lots of C++ code is broken as well. I would prefer to avoid a similar mistake in D (which does do 'virtual' by default, by the way, because Walter realized this problem). Because there will always be the odd case of needing a non-raii instance of a class that is "almost always" raii, I recommend having a keyword that *stops* raii behavior for a particular instance. That way, the keyword is only used to flag the special cases, not the common cases. With this, you signal your class that all of its instances are raii unless otherwise noted, and then let people switch individual special cases back to normal GC'ed instances. Another argument for having the raii per-instance keyword is that it signifies at the local code that this instance is used differently. I'm not a big believer in this, but I see the validity of the issue. If that is a desirable property, then I would keep the raii for classes, and then whenever you make an instance of that class, you are then required to explicitly state whether you want raii or gc behavior. More typing than I prefer, but it is explicit about these classes and their use. Mac |
September 04, 2002 Re: RAII take 2 | ||||
---|---|---|---|---|
| ||||
Posted in reply to Mac Reiter | Mac Reiter wrote:
> Another argument for having the raii per-instance keyword is that it signifies
> at the local code that this instance is used differently. I'm not a big
> believer in this, but I see the validity of the issue. If that is a desirable
> property, then I would keep the raii for classes, and then whenever you make an
> instance of that class, you are then required to explicitly state whether you
> want raii or gc behavior. More typing than I prefer, but it is explicit about
> these classes and their use.
Yes, exactly. I think it would be the best approach.
|
September 05, 2002 Re: RAII take 2 | ||||
---|---|---|---|---|
| ||||
Posted in reply to Mac Reiter | > There is a risk if the non-raii reference will be holding on past the function > invocation lifetime -- if it is storing it away in a cache or something. Of > course, such a situation will blow up anything that isn't reference counted, so > we'll ignore that case for the time being... Yes that kind of trickery can also ruin the "struct destructors" concept. And of course you will need non-raii references to Lock and File objects, to enable function calls (including member functions of Lock and File). Would you disable member function calls of Lock and File? The secret parameter, the reference to itself will be non-raii, and can be stored anywhere you like. But let me start a new line of discussion here. You don't really need to *delete* the instance. Memory is not important. You only need to release other resources. Finalyzing or "disposing" would do. Then the old references may remain valid. Like references to an existing but closed File object. All i/o operations would produce exceptions. I think it is more elegant than possibly having "dangling references". > >Because I want functions which take a reference to a File object, and don't > >close the File, you cannot have File as a raii class. Only specific > >references to File-s can be raii. > >Can you please provide a class which requires all references to it, to be > >raii? Because that is what you are suggesting. > > My personal preference is not to require the per-instance keyword, because it is > easy to forget it and then lose all of the benefits of raii. If I make a Lock > class, I know that in 99.99% of the cases I want it to be raii. Why require > someone to type an extra keyword for all of those cases? Why add the debugging > risk of forgetting to type it to the most common case? C++ requires you to flag > each and every member function as virtual if you want polymorphism to work. How > many class hierarchies are subtly broken because somebody forgot to add that > word? 'virtual' is the most common case, so it should be what you get if you > don't go out of your way to specify otherwise. Because C++ broke that rule, > lots of C++ code is broken as well. I would prefer to avoid a similar mistake > in D (which does do 'virtual' by default, by the way, because Walter realized > this problem). > > Because there will always be the odd case of needing a non-raii instance of a > class that is "almost always" raii, I recommend having a keyword that *stops* > raii behavior for a particular instance. That way, the keyword is only used to > flag the special cases, not the common cases. With this, you signal your class > that all of its instances are raii unless otherwise noted, and then let people > switch individual special cases back to normal GC'ed instances. The *instances* of Lock and File can (<sigh> almost) always be raii. But you are associating the raii property with the *references* to Lock and File. In this discussion there was no mentioning of keywords to require or stop raii "for a particular instance". How would you accomplish that? > "you signal your class that all of its instances are raii unless otherwise noted" Put it this way: "you signal your class that all *references* to its instances are raii unless otherwise noted" And signaling that doesn't make sense. > Another argument for having the raii per-instance keyword is that it signifies > at the local code that this instance is used differently. The reference is used differently. Actually it is the *owner* reference of the instance, as compared to other references to the same instance. The owner reference deletes the instance, when itself is "scoped out". Other references to the same instance don't do it. Raii is the property of the class. Ownership is the property of the reference. Nothing special is property of the instance. Raii classes differ from non-raii classes that: - an instance of a raii class has exaclty one owner reference pointing to it, - an instance of a non-raii class has exaclty zero owner reference pointing to it. This can be easily achieved if - owner references can be assigned to non-owners without limitation, and - owner references can be overwritten by both kinds of reference, automagically deleting the old owned instance (if there is any). > I'm not a big > believer in this, but I see the validity of the issue. If that is a desirable > property, then I would keep the raii for classes, and then whenever you make an > instance of that class, You mean inside the "new" operator? > you are then required to explicitly state whether you > want raii or gc behavior. More typing than I prefer, but it is explicit about > these classes and their use. |
Copyright © 1999-2021 by the D Language Foundation