www.digitalmars.com         C & C++   DMDScript  

D - RAII take 2

reply "Walter" <walter digitalmars.com> writes:
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; }
    }
Aug 27 2002
next sibling parent reply Patrick Down <pat codemoon.com> writes:
"Walter" <walter digitalmars.com> wrote in
news:akgsaf$aog$1 digitaldaemon.com: 


 3) assignment of structs with destructors
struct Foo { ~this() { } } void Bar(Foo a) { } Foo a; Bar(a); // Is this allowed?
Aug 27 2002
parent reply "Walter" <walter digitalmars.com> writes:
"Patrick Down" <pat codemoon.com> wrote in message
news:Xns9277B119511Bpatcodemooncom 63.105.9.61...
 "Walter" <walter digitalmars.com> wrote in
 news:akgsaf$aog$1 digitaldaemon.com:
 3) assignment of structs with destructors
struct Foo { ~this() { } } void Bar(Foo a) { } Foo a; Bar(a); // Is this allowed?
No. I should add that copy-initializers are not allowed, either.
Aug 27 2002
parent reply Patrick Down <pat codemoon.com> writes:
"Walter" <walter digitalmars.com> wrote in news:akh8id$p90$1
 digitaldaemon.com:

 
 "Patrick Down" <pat codemoon.com> wrote in message
 news:Xns9277B119511Bpatcodemooncom 63.105.9.61...
 "Walter" <walter digitalmars.com> wrote in
 news:akgsaf$aog$1 digitaldaemon.com:
 3) assignment of structs with destructors
struct Foo { ~this() { } } void Bar(Foo a) { } Foo a; Bar(a); // Is this allowed?
No. I should add that copy-initializers are not allowed, either.
With the previous method I could. auto fload = new File(name); LoadObjects(fload); Now it looks like I need to do this... instance AutoClass(File) AutoFile; AutoFile fload; fload.t = new File(name); LoadObjects(fload.t); If you put constructors on structs too then it could work like this. struct AutoFile { File f; this(char[] name) { f = new File(name); } ~this() { if(f) f.close(); } } Autofile fload(name); LoadObjects(fload.f);
Aug 27 2002
next sibling parent "Walter" <walter digitalmars.com> writes:
You're right. I know supporting struct constructors is an attractive notion,
but I'd like to try avoiding them for the moment.
Aug 28 2002
prev sibling parent "Sandor Hojtsy" <hojtsy index.hu> writes:
"Patrick Down" <pat codemoon.com> wrote in message
news:Xns927810D18195Dpatcodemooncom 63.105.9.61...
 "Walter" <walter digitalmars.com> wrote in news:akh8id$p90$1
  digitaldaemon.com:

 "Patrick Down" <pat codemoon.com> wrote in message
 news:Xns9277B119511Bpatcodemooncom 63.105.9.61...
 "Walter" <walter digitalmars.com> wrote in
 news:akgsaf$aog$1 digitaldaemon.com:
 3) assignment of structs with destructors
struct Foo { ~this() { } } void Bar(Foo a) { } Foo a; Bar(a); // Is this allowed?
No. I should add that copy-initializers are not allowed, either.
With the previous method I could. auto fload = new File(name); LoadObjects(fload);
Every problem can be solved by adding a level of indirection. auto fload = new File(name); LoadObjects(&fload); If File is a struct you should not pass it by value, but by address.
Aug 28 2002
prev sibling next sibling parent reply "Sean L. Palmer" <seanpalmer earthlink.net> writes:
That sounds like a fine plan.

I don't see how any of your second 3 points (to make things easier) would
seem daunting to someone like you who has implemented a C++ compiler or
three.  ;)

They seem like simple enough concepts.  What do you find so difficult about
it?  Seems templates and exceptions are far more of a challenge.

Sean

"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; }
     }
Aug 27 2002
parent "Walter" <walter digitalmars.com> writes:
Making them work is a substantial increase in the complexity of the
compiler, bugs in the compiler (!), attempting to explain to programmers all
the noise going on behind the scenes for something simple-appearing like
passing a struct value to a function, etc. It brings to the fore confusing
notions like the difference between assignment and initialization,
overloading assignment operators and copy constructors start becoming
necessary, etc.

In short, many things I was trying to leave behind <g>.

If the territory can be covered without needing that stuff, I sure want to
give it a try.
Aug 28 2002
prev sibling next sibling parent reply Patrick Down <pat codemoon.com> writes:
"Walter" <walter digitalmars.com> wrote in
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
I think I still like the auto idea better. With the struct solution it seems like you would end back at the old problem of having duplicate class and struct variants of the same objects. As you suggest we can wrap a class reference in a struct. However this just seems like a more complicated version of the auto keyword.
Aug 28 2002
parent Russ Lewis <spamhole-2001-07-16 deming-os.org> writes:
Yes, my gut feeling is that the struct solution causes more problems than
it solves.

--
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))) ]
Aug 28 2002
prev sibling next sibling parent reply Mac Reiter <Mac_member pathlink.com> writes:
Let me start by saying that the struct solution would work for me.  I can't
resist the urge to ramble on, however.

Just for comment:

1. Since structs don't participate in inheritance, D already has variable types
that cannot participate in Stream solutions or solutions that leverage the
Object interface.  Which suggests that arguments against non-convertible
auto/scoped/counted classes can also be made against the structs that have been
there since day one...

2. Presence of a destructor will tend to make people wonder why there isn't a
constructor.  I know that the answer has to do with reducing complexity, but the
question will continue to come up.

3. This kinda pushes an implementation detail up into user cognitive space.
Prior to this suggestion, the major benefits of structs were alignment control
and lightweight allocation (stack).  Now the second benefit (stack) turns into
two benefits - lightweight allocation and scoping/d.o.f.  This feels slightly
wrong, somehow.

4. I know that the impetus for using struct this way would be to minimize the
marginal added complexity of d.o.f. behavior.  However, looking at the end
result, I kinda feel the same way I do when I look at the 'static' keyword in
C++ and have to stop and wonder if it is referring to a static thing or to a
file-scope-restricted thing.  Or when I look at the 'const' keyword and have to
stop and figure out if it is referring to the thing being constant, or claiming
that it is legal for the thing to be called on a constant instance (For C++
programmers that may not have seen the usage I am talking about: 'const' after a
member function declaration means that it is legal for a user to call that
member function if they have a constant instance of that class.  Functions
without this decoration cannot be called on a constant instance because the
function might change the state of the class, which would violate the const-ness
of the instance.  Of course, const functions can also change the state of the
instance, but they're not *supposed* to...)  (Sorry for the tangential rambling)


Having said all of that, I have been thinking about how to use RAII.v2.  Since
all classes derive (directly or indirectly) from Object, you could make:

struct Scoped
{
Object obj;
~this() {delete obj;}
}

and put that somewhere in Phobos.  Then, to scope something, I can just:

void MyFunc()
{
Scoped s;
s.obj = new MyClass;
// proceed willy-nilly
} // s.obj gets deleted here.

Since the scoping struct doesn't need any interface of the scoped object other
than Object, this polymorphism works, and can be done once and for all inside
the standard library.

I would like some form of initializer.  It would be great if we could use the
"static initializer" syntax from struct.html for non-static structs:

void MyFunc()
{
Scoped s = {obj:new MyClass};
// proceed willy-nilly
} // s.obj gets deleted here.

Actually, there are nicer (or at least easier to type/read) syntaxes for this,
but this would be consistent with existing language.

How bad would it be to allow initializer-only constructors for structs?  In C++,
a constructor can use an initializer list to store incoming parameters in data
members during creation.  Since that would be the only use for a struct
constructor anyway, initializer-only constructors should be sufficient for
anything anyone needs in a struct (more complicated stuff gets done in a class,
like it should).  This would make the syntax something more like:

struct Scoped
{
Object obj;
this(Object o) : obj(o); // or pick a better syntax, this is "like" C++
~this() {delete obj;}
}

void MyFunc()
{
Scoped s(new MyClass);
// proceed willy-nilly
} // s.obj gets deleted here.

I don't know if limiting the nature of the constructor helps any or not.  An
initializer-based constructor may cause just as many problems as a full-fledged
constructor.

Mac
Aug 28 2002
parent reply "Walter" <walter digitalmars.com> writes:
"Mac Reiter" <Mac_member pathlink.com> wrote in message
news:akj7dl$nc$1 digitaldaemon.com...
 1. Since structs don't participate in inheritance, D already has variable
types
 that cannot participate in Stream solutions or solutions that leverage the
 Object interface.  Which suggests that arguments against non-convertible
 auto/scoped/counted classes can also be made against the structs that have
been
 there since day one...
Yes, but with this scheme there are only 2 kinds of objects instead of 3.
 2. Presence of a destructor will tend to make people wonder why there
isn't a
 constructor.  I know that the answer has to do with reducing complexity,
but the
 question will continue to come up.
You're right. We'll have to see how this goes.
 3. This kinda pushes an implementation detail up into user cognitive
space.
 Prior to this suggestion, the major benefits of structs were alignment
control
 and lightweight allocation (stack).  Now the second benefit (stack) turns
into
 two benefits - lightweight allocation and scoping/d.o.f.  This feels
slightly
 wrong, somehow.
It seems natural to me <g>.
 4. I know that the impetus for using struct this way would be to minimize
the
 marginal added complexity of d.o.f. behavior.  However, looking at the end
 result, I kinda feel the same way I do when I look at the 'static' keyword
in
 C++ and have to stop and wonder if it is referring to a static thing or to
a
 file-scope-restricted thing.  Or when I look at the 'const' keyword and
have to
 stop and figure out if it is referring to the thing being constant, or
claiming
 that it is legal for the thing to be called on a constant instance
Structs were already scoped, they just didn't have destructors.
 Having said all of that, I have been thinking about how to use RAII.v2.
Since
 all classes derive (directly or indirectly) from Object, you could make:

 struct Scoped
 {
 Object obj;
 ~this() {delete obj;}
 }

 and put that somewhere in Phobos.  Then, to scope something, I can just:

 void MyFunc()
 {
 Scoped s;
 s.obj = new MyClass;
 // proceed willy-nilly
 } // s.obj gets deleted here.

 Since the scoping struct doesn't need any interface of the scoped object
other
 than Object, this polymorphism works, and can be done once and for all
inside
 the standard library.
Yes, although Scoped can be made a template.
 I would like some form of initializer.  It would be great if we could use
the
 "static initializer" syntax from struct.html for non-static structs:

 void MyFunc()
 {
 Scoped s = {obj:new MyClass};
 // proceed willy-nilly
 } // s.obj gets deleted here.

 Actually, there are nicer (or at least easier to type/read) syntaxes for
this,
 but this would be consistent with existing language.
Yes.
 How bad would it be to allow initializer-only constructors for structs?
In C++,
 a constructor can use an initializer list to store incoming parameters in
data
 members during creation.  Since that would be the only use for a struct
 constructor anyway, initializer-only constructors should be sufficient for
 anything anyone needs in a struct (more complicated stuff gets done in a
class,
 like it should).  This would make the syntax something more like:

 struct Scoped
 {
 Object obj;
 this(Object o) : obj(o); // or pick a better syntax, this is "like" C++
 ~this() {delete obj;}
 }

 void MyFunc()
 {
 Scoped s(new MyClass);
 // proceed willy-nilly
 } // s.obj gets deleted here.

 I don't know if limiting the nature of the constructor helps any or not.
An
 initializer-based constructor may cause just as many problems as a
full-fledged
 constructor.
Initializer lists are a bug in C++ <g>.
Aug 28 2002
next sibling parent reply Russ Lewis <spamhole-2001-07-16 deming-os.org> writes:
Walter wrote:

 "Mac Reiter" <Mac_member pathlink.com> wrote in message
 news:akj7dl$nc$1 digitaldaemon.com...
 1. Since structs don't participate in inheritance, D already has variable
types
 that cannot participate in Stream solutions or solutions that leverage the
 Object interface.  Which suggests that arguments against non-convertible
 auto/scoped/counted classes can also be made against the structs that have
been
 there since day one...
Yes, but with this scheme there are only 2 kinds of objects instead of 3.
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. -- 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))) ]
Aug 28 2002
parent reply "Walter" <walter digitalmars.com> writes:
"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++.
Aug 28 2002
next sibling parent reply Russ Lewis <spamhole-2001-07-16 deming-os.org> writes:
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))) ]
Aug 29 2002
next sibling parent "Walter" <walter digitalmars.com> writes:
"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.
A struct also provides a mechanism for lightweight objects intended to be on the stack. (The absense of these is a large burden in Java.) Providing a destructor for raii is a natural extension of that.
Aug 29 2002
prev sibling parent reply "Sean L. Palmer" <seanpalmer earthlink.net> writes:
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))) ]
Aug 30 2002
parent Russ Lewis <spamhole-2001-07-16 deming-os.org> writes:
"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))) ]
Sep 04 2002
prev sibling parent reply "Sandor Hojtsy" <hojtsy index.hu> writes:
"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
Sep 02 2002
parent Pavel Minayev <evilone omen.ru> writes:
"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.
Sep 02 2002
prev sibling parent reply "Martin M. Pedersen" <mmp www.moeller-pedersen.dk> writes:
Hi,

"Walter" <walter digitalmars.com> wrote in message
news:akjh94$k4g$1 digitaldaemon.com...
 2. Presence of a destructor will tend to make people wonder why there
 isn't a constructor.
You're right. We'll have to see how this goes.
Constructors are nice, but not really that important. They do not offer functionality, that could not be realized using regular methods. Destructors, on the other hand, are a gift to man-kind. Deterministic destructors makes them even more so. Concentrating on destructors first seems very right to me. I have a thought about constructors, though: A new object is often created like this: SomeClass a = new SomeClass(args); You wrote yourself sometime ago, that it was clumsy. I agree. How about: SomeClass a(args); meaning that if an argument list was present, it was to be instantiated, and the constructor called. The argument list could be omitted like today like this: SomeClass a; // initialized to null The destinction between null initialization and initialization using a constructor without arguments would be: SomeClass a; // null SomeClass a(); // allocates new object and initializes it using this() The existing syntax would still have to be supported in cases like this: SomeClass a = new DerivedClass(args); Regards, Martin M. Pedersen
Aug 28 2002
next sibling parent reply "Martin M. Pedersen" <mmp www.moeller-pedersen.dk> writes:
Hi,

"Martin M. Pedersen" <mmp www.moeller-pedersen.dk> wrote in message
news:akjir7$ns6$1 digitaldaemon.com...
    SomeClass a(); // allocates new object and initializes it using this()
I might add, that this might conflict with prototypes as we know them from C. However, prototypes belongs at global scope, object instantiation do not. So, there does not need to be a conflict. Regards, Martin M. Pedersen
Aug 28 2002
parent "Walter" <walter digitalmars.com> writes:
"Martin M. Pedersen" <mmp www.moeller-pedersen.dk> wrote in message
news:akjj57$odk$1 digitaldaemon.com...
 "Martin M. Pedersen" <mmp www.moeller-pedersen.dk> wrote in message
 news:akjir7$ns6$1 digitaldaemon.com...
    SomeClass a(); // allocates new object and initializes it using
this()
 I might add, that this might conflict with prototypes as we know them from
 C. However, prototypes belongs at global scope, object instantiation do
not.
 So, there does not need to be a conflict.
You're right, but it would be better if the syntax for constructors was unique without relying on scope context. In C++, as things got added to the language, fairly simple ambiguity problems just got worse and worse. It's so bad now that even if the compiler parses it correctly, it is unable to generate a reasonable error message for any goofs. Syntactical redundancy is the key to being able to generate good error diagnostics.
Aug 28 2002
prev sibling parent "Walter" <walter digitalmars.com> writes:
"Martin M. Pedersen" <mmp www.moeller-pedersen.dk> wrote in message
news:akjir7$ns6$1 digitaldaemon.com...
 "Walter" <walter digitalmars.com> wrote in message
 news:akjh94$k4g$1 digitaldaemon.com...
 2. Presence of a destructor will tend to make people wonder why there
 isn't a constructor.
You're right. We'll have to see how this goes.
Constructors are nice, but not really that important. They do not offer functionality, that could not be realized using regular methods.
I agree. Also, constructors for stack objects have wretched parsing ambiguity problems. (Is it a declaration? Is it a function call? Is it a bird? Is it a plane? <g>)
 Destructors, on the other hand, are a gift to man-kind. Deterministic
 destructors makes them even more so. Concentrating on destructors first
 seems very right to me.
Yes.
 I have a thought about constructors, though:
 A new object is often created like this:
     SomeClass a = new SomeClass(args);
 You wrote yourself sometime ago, that it was clumsy. I agree. How about:
    SomeClass a(args);
 meaning that if an argument list was present, it was to be instantiated,
and
 the constructor called. The argument list could be omitted like today like
 this:
    SomeClass a; // initialized to null
 The destinction between null initialization and initialization using a
 constructor without arguments would be:
    SomeClass a; // null
    SomeClass a(); // allocates new object and initializes it using this()
 The existing syntax would still have to be supported in cases like this:
    SomeClass a = new DerivedClass(args);
That's the "is it a function declaration or a declaration with constructor".
Aug 28 2002
prev sibling next sibling parent reply "Matthew Wilson" <matthew thedjournal.com> writes:
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; }
     }
Sep 02 2002
next sibling parent Pavel Minayev <evilone omen.ru> writes:
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
Sep 02 2002
prev sibling parent reply "Sandor Hojtsy" <hojtsy index.hu> writes:
"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
Sep 04 2002
parent reply Mac Reiter <Mac_member pathlink.com> writes:
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
Sep 04 2002
next sibling parent Pavel Minayev <evilone omen.ru> writes:
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.
Sep 04 2002
prev sibling next sibling parent reply "Sandor Hojtsy" <hojtsy index.hu> writes:
 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.
Sep 05 2002
next sibling parent Mac Reiter <Mac_member pathlink.com> writes:
OK, there are two things that I need to point out that will help tremendously:

1. I apologize, I was sloppy with my terminology.  When reading my earlier
posts, please replace "instance" with "reference".  At no point was I actually
intentionally referring to the instance (chunk of memory, allocated with new, to
which there may be multiple references).  I was always referring to the
reference variable.  Please take my posts, put 'em in an editor, and do a global
search and replace, then see if they make any more sense.  This was an
incredibly bad mistake on my part, because without precision in our words, we
can't communicate effectively.

2. I am strongly in favor of reference counting instead of simple scoping.  I
think that the only reliable and safe deterministic finalization technique is
reference counting (assuming the presence of a GC that can come through and
gather up the cycles that refcounting leaves behind).  Scoping falls out as the
simplest case of reference counting.  However, I have beaten that horse until it
is not only dead, but has now been chopped up, processed, cured, canned, fed to
dogs around the country, and gone to its final (though ignoble and somewhat
disgusting) resting place(s).  It isn't going to happen, at least not in the
foreseeable future.  So, I have turned my attentions to trying to make the raii
system as safe as I can envision it.  

The safest form that I can imagine, because it requires the least continuous
attention and effort from the programmer, is one where classes are flagged so
that any references to their instances are raii by default.  Then two exceptions
are made: 

1. References that are function arguments are not raii -- the function isn't
creating the instance (I meant it that time :-), so the argument reference is
not the owning reference, so it must not exhibit raii destruction behavior.

2. A keyword is introduced to allow a particular owner reference (the one that
was used in the statement "MyRef = new MyRaiiClass") to not exhibit raii
behaviors.  Thus, if a function is going to create an instance (meant that one,
too) and attach a reference to it, but it also expects to hand that instance to
somewhere else and then exit (which would finalize the instance, normally), it
can declare its reference to be a nonraii reference.  The following is
contrived, but I'm trying to keep it short...

raii class Lock{/*...*/}

void func1()
{
Lock lockFromElsewhere; // reference is raii, because no keyword

lockFromElsewhere = func2();
} // lockFromElsewhere reference will finalize instance that is returned
// from func2 at this point

Lock func2()
{
nonraii Lock notMine = new Lock; // notMine is a nonraii reference
return notMine;
} // Since notMine is nonraii, nothing magical happens here.

Looking at that code, I'm not sure if a third exception needs to be made for
function return types or not.  I certainly don't want to have to type:

nonraii Lock func2() {/*...*/}

although that might not be a bad thing...  It would signify that the Lock that
is being returned does not have the normal raii semantics and will need to be
handled some other way.  Of course, any return value will "not have the normal
raii semantics", so the nonraii keyword there should not technically be
necessary.

Functions like those above are not common, but it is possible that a subfunction
could start doing some work for which it needed to acquire a resource, and then
reached a point where it needed to let the caller finish the work, but it
doesn't want to release the resource because that would open a window of
opportunity for other threads/processes/tasks/whatever to affect the resource.
Of the few cases where this comes up, most can probably be handled by
redesigning the two functions in question.  But there are still some cases that
simply need that design.

Just to forestall any comments, I wish to point out that Lock can usually be
fixed through other means.  If a function has to return a locked resource, the
programmer must take the responsibility for tracking who owns the lock and who
should finally unlock it.  Most Lock classes do not actually contain the locking
mechanism -- they usually just contain a reference to a Mutex or Semaphore.  The
purpose of the Lock class is to remove responsibility from the programmer for
remembering to unlock the Mutex/Semaphore.  If the programmer has taken that
responsibility back upon him/herself, then there is no need to use the Lock, and
the Mutex/Semaphore (which are non-raii classes anyway) can be directly managed.
I mention this to show that I understand that Lock is probably a poor choice for
demonstrating some of the more contorted cases that occasionally arise.  A
better example would probably be File or Printer or Port, where the raii'd class
also actually "is" the resource, from a programming standpoint, rather than a
simple convenience wrapper around another class.

In article <al7dih$2oaq$1 digitaldaemon.com>, Sandor Hojtsy says...
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.
OK, given the exceptions mentioned above (function parameter references and keyword marked references), does it now make sense to signal that all non-exceptional references to the raii class are raii references? Mac
Sep 05 2002
prev sibling parent reply Russ Lewis <spamhole-2001-07-16 deming-os.org> writes:
Sandor Hojtsy wrote:

 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".
Interesting idea. But maybe you could build it into the compile: There could be a "valid" bool variable (allocated along with the object?) that marked whether or not the object had been finalized. Each call on a member function would include an implicit "in" contract that "valid==true". So use after the object had been "finalized" would result in a contract failure exception. Of course, you could also code this individually into each class that needs it. Perhaps a special template for raii classes: template Raii(X : Object) { class Raii_Wrapper : X { X ref; bool raii_valid = true; void finalize() { ref.finalize(); raii_valid = false; }; invariant() { assert raii_valid; } } } Now, each time that somebody declares an raii object: void func() { auto MyClass raii_ref; // blah } the compiler turns it into an instance of the template, with the implicit finally clause: void func() { instance Raii(MyClass) raii_ref; try { // blah } finally { raii_ref.finalize(); } } Thoughts? -- 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))) ]
Sep 05 2002
parent reply "Walter" <walter digitalmars.com> writes:
"Russ Lewis" <spamhole-2001-07-16 deming-os.org> wrote in message
news:3D77A06B.ACD19741 deming-os.org...
 Interesting idea.  But maybe you could build it into the compile: There
could be
 a "valid" bool variable (allocated along with the object?) that marked
whether
 or not the object had been finalized.  Each call on a member function
would
 include an implicit "in" contract that "valid==true".  So use after the
object
 had been "finalized" would result in a contract failure exception.
That is handilly handled (!) by zeroing the vptr after finalization.
Sep 05 2002
next sibling parent Russell Lewis <spamhole-2001-07-16 deming-os.org> writes:
Walter wrote:
 "Russ Lewis" <spamhole-2001-07-16 deming-os.org> wrote in message
 news:3D77A06B.ACD19741 deming-os.org...
 
Interesting idea.  But maybe you could build it into the compile: There
could be
a "valid" bool variable (allocated along with the object?) that marked
whether
or not the object had been finalized.  Each call on a member function
would
include an implicit "in" contract that "valid==true".  So use after the
object
had been "finalized" would result in a contract failure exception.
That is handilly handled (!) by zeroing the vptr after finalization.
(Shudder) Well, I suppose any error is better than none. It's a good point, though, that raii finalization should NOT drop the object out of the garbage collection map. Finalize it, but don't mark it as garbage until you're sure there are no dangling references. It will make for easier-to-debug programs.
Sep 05 2002
prev sibling parent "Sandor Hojtsy" <hojtsy index.hu> writes:
"Walter" <walter digitalmars.com> wrote in message
news:al8nql$2ejr$1 digitaldaemon.com...
 "Russ Lewis" <spamhole-2001-07-16 deming-os.org> wrote in message
 news:3D77A06B.ACD19741 deming-os.org...
 Interesting idea.  But maybe you could build it into the compile: There
could be
 a "valid" bool variable (allocated along with the object?) that marked
whether
 or not the object had been finalized.  Each call on a member function
would
 include an implicit "in" contract that "valid==true".  So use after the
object
 had been "finalized" would result in a contract failure exception.
That is handilly handled (!) by zeroing the vptr after finalization.
Good. In that case, releasing raii objects should call finalyze, and not delete. Theoretically a wrapper object of a released resource may still have usable member functions, such as statistical data of usage, or filename an such. Calling these functions are still disabled, but at least dangling references are better handled. Sandor
Sep 09 2002
prev sibling parent "Walter" <walter digitalmars.com> writes:
"Mac Reiter" <Mac_member pathlink.com> wrote in message
news:al5kgm$1ud8$1 digitaldaemon.com...
 In article <al4j79$2s6p$1 digitaldaemon.com>, Sandor Hojtsy says...
 I would prefer to avoid a similar mistake
 in D (which does do 'virtual' by default, by the way, because Walter
realized
 this problem).
LOL. I suffered from subtle bugs caused by that exact issue over and over. When you have a large app, the tedium of trying to verify the correctness of the virtual use is boggling. There was just no way to prove your program was free of such. It's a maddening timewaster. The correct way is to have virtual by default, and allow no overrides of final functions.
Sep 05 2002
prev sibling parent Juarez Rudsatz <juarez nowhere.com> writes:
"Walter" <walter digitalmars.com> wrote in
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.
A example of code being converted for user RAII: http://sourceforge.net/docman/display_doc.php?docid=8673&group_id=9028 This is the Firebird SQL DB Server converted from C to C++
Sep 05 2002