www.digitalmars.com         C & C++   DMDScript  

D - RAII

reply "Walter" <walter digitalmars.com> writes:
 Resource Aquisition Is Initialization (RAII)

RAII is a programming paradigm where resources are automatically released
when an object is no longer referenced. In C++, this is accomplished by
putting the release code in a class destructor, and then the compiler will
automatically insert the destructor call when the object goes out of scope.

In D, automatic objects are structs, and structs do not have destructors.
Classes have destructors (finalizers), but are only allocated on the gc
heap,
so the finalizer gets run at arbitrary and unpredictable times. D does
support explicit finalization of objects:

 A a = new A();
 ...
 delete a;

but in the inevitable presence of exceptions, to make the code robust and
reliable:

 try
 {
     A a = new A();
     ...
 }
 finally
 {
     delete a;
 }

While this does work, it can get tedious and unsightly when dealing with
many such objects.

The solution is to create a new storage class, raii:

 raii A a = new A();

All raii references will get a finally block created for them that calls
delete on the reference. Of course, if the programmer stores a copy of a
somewhere
outside the scope, that reference will point to garbage once the scope is
exited.
Aug 25 2002
next sibling parent reply Pavel Minayev <evilone omen.ru> writes:
On Sun, 25 Aug 2002 12:18:09 -0700 "Walter" <walter digitalmars.com> wrote:

 The solution is to create a new storage class, raii:
 
  raii A a = new A();
 
 All raii references will get a finally block created for them that calls
 delete on the reference. Of course, if the programmer stores a copy of a
 somewhere
 outside the scope, that reference will point to garbage once the scope is
 exited.
Is it going to be part of D? Just a suggestion... "raii" doesn't sound good =). I'd suggest using "auto" instead.
Aug 25 2002
next sibling parent "Walter" <walter digitalmars.com> writes:
"Pavel Minayev" <evilone omen.ru> wrote in message
news:CFN374939933247569 news.digitalmars.com...
 Is it going to be part of D?
If no big problems with it show up.
 Just a suggestion... "raii" doesn't sound good =). I'd suggest using
"auto"
 instead.
Yeah, auto is probably better.
Aug 25 2002
prev sibling parent Russell Lewis <spamhole-2001-07-16 deming-os.org> writes:
Pavel Minayev wrote:
 On Sun, 25 Aug 2002 12:18:09 -0700 "Walter" <walter digitalmars.com> wrote:
 
 
The solution is to create a new storage class, raii:

 raii A a = new A();

All raii references will get a finally block created for them that calls
delete on the reference. Of course, if the programmer stores a copy of a
somewhere
outside the scope, that reference will point to garbage once the scope is
exited.
Is it going to be part of D? Just a suggestion... "raii" doesn't sound good =). I'd suggest using "auto" instead.
I get nightmares of "riaa" objects...
Aug 26 2002
prev sibling next sibling parent reply Patrick Down <pat codemoon.com> writes:
"Walter" <walter digitalmars.com> wrote in
news:akba6n$28tb$1 digitaldaemon.com: 

 
 The solution is to create a new storage class, raii:
 
  raii A a = new A();
 
 All raii references will get a finally block created for them that
 calls delete on the reference. Of course, if the programmer stores a
 copy of a somewhere
 outside the scope, that reference will point to garbage once the scope
 is exited.
What happens if a is reassigned? Can A be reassigned? raii A a = new A(); a = new A(); I like "auto" better too.
Aug 25 2002
parent reply "Walter" <walter digitalmars.com> writes:
"Patrick Down" <pat codemoon.com> wrote in message
news:Xns9275A0B6E350Cpatcodemooncom 63.105.9.61...
 "Walter" <walter digitalmars.com> wrote in
 What happens if a is reassigned? Can A be reassigned?

 raii A a = new A();

 a = new A();
In that case, the first a will get deleted by the gc when it gets around to it. Or you could explicitly do it via delete.
Aug 25 2002
parent Russell Lewis <spamhole-2001-07-16 deming-os.org> writes:
Walter wrote:
 "Patrick Down" <pat codemoon.com> wrote in message
 news:Xns9275A0B6E350Cpatcodemooncom 63.105.9.61...
 
"Walter" <walter digitalmars.com> wrote in
What happens if a is reassigned? Can A be reassigned?

raii A a = new A();

a = new A();
In that case, the first a will get deleted by the gc when it gets around to it. Or you could explicitly do it via delete.
IMHO,If the reference is raii, the old object should get deleted whenever you reassign the reference!
Aug 26 2002
prev sibling next sibling parent "Matthew Wilson" <matthew thedjournal.com> writes:
Excellent!

auto is a better keyword though



"Walter" <walter digitalmars.com> wrote in message
news:akba6n$28tb$1 digitaldaemon.com...
 Resource Aquisition Is Initialization (RAII)

 RAII is a programming paradigm where resources are automatically released
 when an object is no longer referenced. In C++, this is accomplished by
 putting the release code in a class destructor, and then the compiler will
 automatically insert the destructor call when the object goes out of
scope.
 In D, automatic objects are structs, and structs do not have destructors.
 Classes have destructors (finalizers), but are only allocated on the gc
 heap,
 so the finalizer gets run at arbitrary and unpredictable times. D does
 support explicit finalization of objects:

  A a = new A();
  ...
  delete a;

 but in the inevitable presence of exceptions, to make the code robust and
 reliable:

  try
  {
      A a = new A();
      ...
  }
  finally
  {
      delete a;
  }

 While this does work, it can get tedious and unsightly when dealing with
 many such objects.

 The solution is to create a new storage class, raii:

  raii A a = new A();

 All raii references will get a finally block created for them that calls
 delete on the reference. Of course, if the programmer stores a copy of a
 somewhere
 outside the scope, that reference will point to garbage once the scope is
 exited.
Aug 25 2002
prev sibling next sibling parent reply "Sandor Hojtsy" <hojtsy index.hu> writes:
"Walter" <walter digitalmars.com> wrote in message
news:akba6n$28tb$1 digitaldaemon.com...
 Resource Aquisition Is Initialization (RAII)

 RAII is a programming paradigm where resources are automatically released
 when an object is no longer referenced. In C++, this is accomplished by
 putting the release code in a class destructor, and then the compiler will
 automatically insert the destructor call when the object goes out of
scope.
 In D, automatic objects are structs, and structs do not have destructors.
 Classes have destructors (finalizers), but are only allocated on the gc
 heap,
 so the finalizer gets run at arbitrary and unpredictable times. D does
 support explicit finalization of objects:

  A a = new A();
  ...
  delete a;

 but in the inevitable presence of exceptions, to make the code robust and
 reliable:

  try
  {
      A a = new A();
      ...
  }
  finally
  {
      delete a;
  }

 While this does work, it can get tedious and unsightly when dealing with
 many such objects.

 The solution is to create a new storage class, raii:

  raii A a = new A();

 All raii references will get a finally block created for them that calls
 delete on the reference. Of course, if the programmer stores a copy of a
 somewhere
 outside the scope, that reference will point to garbage once the scope is
 exited.
Hey, isn't that exactly the same that we was suggesting a few months ago? I thought it was ignored. And the same problem arises: In practice, RAII is not the property of the instance, but of the class, isn't it? Then you need to specify this at the class declaration of "A", so it cannot be a storage class. Thoughts? Yours, Sandor
Aug 26 2002
parent reply Mac Reiter <Mac_member pathlink.com> writes:
In article <akd1sb$1ed6$1 digitaldaemon.com>, Sandor Hojtsy says...
"Walter" <walter digitalmars.com> wrote in message
news:akba6n$28tb$1 digitaldaemon.com...
 Resource Aquisition Is Initialization (RAII)

 RAII is a programming paradigm where resources are automatically released
 when an object is no longer referenced. In C++, this is accomplished by
[clip]
Hey, isn't that exactly the same that we was suggesting a few months ago?
I thought it was ignored.
And the same problem arises:
In practice, RAII is not the property of the instance, but of the class,
isn't it?
Then you need to specify this at the class declaration of "A", so it cannot
be a storage class.
Thoughts?

Yours,
Sandor
I don't know about the "suggesting a few months ago", since I am a very sporadic participant in this newsgroup (highly active for short periods, then nothing for quite awhile...). I do, however, agree that scoping is almost always going to be a property of the class, not the instances. Arguably, making it an instance is more flexible (if you have some reason to make a non-auto instance, you can), but you can now hose your program by forgetting to put the 'auto' in front of one of your instances. This could be fatal for something like a mutex -- if other threads got locked up before the GC ran, you could end up deadlocking the entire program over a mutex that should have been freed. Yes, it is a bug to forget the 'auto', but it would likewise be a bug to forget the 'try{}finally{}' that is being replaced by the auto. If it is doable, I would prefer that 'auto' be a class property ( auto class Lock{} ). If flexibility is a problem, and it becomes necessary for a particular instance of an 'auto class' to outlive its scope (unlikely, but it is possible), and if un-autoing the class is simply not an acceptable solution, perhaps a keyword like 'collected' or 'non_auto' could be added (it doesn't have to look nice -- it will be used VERY infrequently and should only be used by people who really know what they are doing) as a storage class so that it disabled 'auto'ness for a particular instance. Or, to flog a fatally wounded horse one last time, you could implement reference counting and get the RAII that you originally described : "RAII is a programming paradigm where resources are automatically released when an object is no longer referenced." C++'s method is a limited subset of what RAII really should be. If you can automatically implement the pseudo-finally to perform a delete for auto instances, you can automatically implement a pseudo-finally to decrement the reference count for a counted instance. The only reason for un-autoing an instance of an auto class (that I can think of) is to pass the instance on to someone else (it is occasionally necessary to transfer ownership of a synchronization lock). Reference counting handles this case without needing another keyword or any other special handling. The act of handing off the instance increases the refcount, keeping the instance alive. Reference counting also solves the: { auto A a = new A; // A1 a = new A; // A2 } where A2 gets deleted but A1 waits for the GC to get around to it (which is not RAII or deterministic). Refcounting would have to watch assignments, and modify the refcount for the old object as well as the new. But when it did that, it would notice that A1 was no longer used and could finalize it (I defer to the masses on whether it should be deleted or not, but it definitely should be finalized). As for whether the suggested 'counted' should be an instance or class property, I suspect the argument given above for class property still holds. Classes should be counted, not instances. Part of the reason for this is that a refcounted instance could be returned from a function and still be valid. If refcounting is an instance property, the external storage instance (the variable that stores the function return value) would also have to be refcounted to maintain the refcounting invariant. If the property belongs to the class, then all instances of the class are refcounted, and life is good. Also, by making it a class property, you can set a flag in the symbol table that says that this class: 1. is synchronized (refcounting must be thread safe) 2. contains a refcount field 3. has additional refcounting prolog or epilog in its assignment operator 4. <anything else that comes up as being necessary for refcounting> That way you don't have to track it on a per-instance basis, which should simplify processing. Because derived types can be assigned into base type references, it may be necessary to consider the interaction of inheritance and the 'counted' class specifier. Are all children of a counted class automatically counted? Can instances of counted classes be polymorphically assigned into non-counted base references? How about the other way around? It may also become necessary to make assignments across refcounting boundaries illegal, at least for current compilers, until the potential needs/problems are understood better. I realize that actually stopping *only* assignments across the refcounting border would require a runtime check. Perhaps a compile time restraint that simply says that counted classes are not allowed to participate in polymorphic assignments at all would be a better first solution... Yes, there are complications with 'counted' classes. But realistically, either compiler writers or programmers are going to experience similar complications with 'auto' classes (and worse problems with 'auto' instances). If the problems are roughly the same, I would vote for the more flexible and complete solution. Of course, I am somewhat biased on the issue... As a quick comment, if 'auto' is going to win out over 'counted', I would prefer 'scoped' over 'auto', because 'auto' means so many different things. 'scoped' (at least to me) is very explicit about exactly what behavior you are modifying. Of course, if you are worried about adding common keywords, 'scoped' is not currently a keyword in other languages, but I think 'auto' is already reserved in C/C++, which would make it a better choice from that standpoint. Mac
Aug 26 2002
parent reply "Walter" <walter digitalmars.com> writes:
"Mac Reiter" <Mac_member pathlink.com> wrote in message
news:akdgqc$218p$1 digitaldaemon.com...
 In article <akd1sb$1ed6$1 digitaldaemon.com>, Sandor Hojtsy says...
Hey, isn't that exactly the same that we was suggesting a few months ago?
I thought it was ignored.
I think it was suggested, though I can't recall who did. It might have been Pavel.
And the same problem arises:
In practice, RAII is not the property of the instance, but of the class,
isn't it?
Then you need to specify this at the class declaration of "A", so it
cannot
be a storage class.
Thoughts?
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.
 If it is doable, I would prefer that 'auto' be a class property ( auto
class
 Lock{} ).  If flexibility is a problem, and it becomes necessary for a
 particular instance of an 'auto class' to outlive its scope (unlikely, but
it is
 possible), and if un-autoing the class is simply not an acceptable
solution,
 perhaps a keyword like 'collected' or 'non_auto' could be added (it
doesn't have
 to look nice -- it will be used VERY infrequently and should only be used
by
 people who really know what they are doing) as a storage class so that it
 disabled 'auto'ness for a particular instance.
While in C++ it is common to have a destructor (to manage memory), in D having a resource that needs cleaning up should be the exception, not the rule.
 Or, to flog a fatally wounded horse one last time, you could implement
reference
 counting and get the RAII that you originally described : "RAII is a
programming
 paradigm where resources are automatically released when an object is no
longer
 referenced."  C++'s method is a limited subset of what RAII really should
be.
 If you can automatically implement the pseudo-finally to perform a delete
for
 auto instances, you can automatically implement a pseudo-finally to
decrement
 the reference count for a counted instance.  The only reason for
un-autoing an
 instance of an auto class (that I can think of) is to pass the instance on
to
 someone else (it is occasionally necessary to transfer ownership of a
 synchronization lock).  Reference counting handles this case without
needing
 another keyword or any other special handling.  The act of handing off the
 instance increases the refcount, keeping the instance alive.
Reference counting involves far, far more than just a pseudo-finally.
 Reference counting also solves the:

 {
 auto A a = new A; // A1
 a = new A;        // A2
 }

 where A2 gets deleted but A1 waits for the GC to get around to it (which
is not
 RAII or deterministic).  Refcounting would have to watch assignments, and
modify
 the refcount for the old object as well as the new.  But when it did that,
it
 would notice that A1 was no longer used and could finalize it (I defer to
the
 masses on whether it should be deleted or not, but it definitely should be
 finalized).
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.
 As for whether the suggested 'counted' should be an instance or class
property,
 I suspect the argument given above for class property still holds.
Classes
 should be counted, not instances.  Part of the reason for this is that a
 refcounted instance could be returned from a function and still be valid.
If
 refcounting is an instance property, the external storage instance (the
variable
 that stores the function return value) would also have to be refcounted to
 maintain the refcounting invariant.  If the property belongs to the class,
then
 all instances of the class are refcounted, and life is good.  Also, by
making it
 a class property, you can set a flag in the symbol table that says that
this
 class:
 1. is synchronized (refcounting must be thread safe)
 2. contains a refcount field
 3. has additional refcounting prolog or epilog in its assignment operator
 4. <anything else that comes up as being necessary for refcounting>
 That way you don't have to track it on a per-instance basis, which should
 simplify processing.

 Because derived types can be assigned into base type references, it may be
 necessary to consider the interaction of inheritance and the 'counted'
class
 specifier.  Are all children of a counted class automatically counted?
Can
 instances of counted classes be polymorphically assigned into non-counted
base
 references?  How about the other way around?  It may also become necessary
to
 make assignments across refcounting boundaries illegal, at least for
current
 compilers, until the potential needs/problems are understood better.  I
realize
 that actually stopping *only* assignments across the refcounting border
would
 require a runtime check.  Perhaps a compile time restraint that simply
says that
 counted classes are not allowed to participate in polymorphic assignments
at all
 would be a better first solution...
Suppose you have a printing function that takes an object of type Object. Object is not counted, so the counted class is cast to Object. To support this, it becomes necessary to 1) extend counting to every object 2) ignore the possibility of bugs from dangling references 3) disallow conversions to Object. 1) has too many penalties for D as whole 2) is a similar bug to handing off a reference to an 'auto' instance, but I think worse because it will happen more often 3) requires creation of two versions of most things in the library, one to handle counted and one for non-counted
 Yes, there are complications with 'counted' classes.  But realistically,
either
 compiler writers or programmers are going to experience similar
complications
 with 'auto' classes (and worse problems with 'auto' instances).  If the
problems
 are roughly the same, I would vote for the more flexible and complete
solution.
 Of course, I am somewhat biased on the issue...
No, I believe the 'auto' approach has an order of magnitude less implementation effort than ref counting. For example, it won't be necessary to handle arrays of counted objects, counted objects as members of structs, counted objects as members of non-counted objects, assignment overloading, etc.
 As a quick comment, if 'auto' is going to win out over 'counted', I would
prefer
 'scoped' over 'auto', because 'auto' means so many different things.
'scoped'
 (at least to me) is very explicit about exactly what behavior you are
modifying.
 Of course, if you are worried about adding common keywords, 'scoped' is
not
 currently a keyword in other languages, but I think 'auto' is already
reserved
 in C/C++, which would make it a better choice from that standpoint.
auto does have the nice advantage that people recognize it as a keyword.
Aug 26 2002
next sibling parent reply Pavel Minayev <evilone omen.ru> writes:
On Mon=2C 26 Aug 2002 10=3A23=3A14 -0700 =22Walter=22
=3Cwalter=40digitalmars=2Ecom=3E wrote=3A

=3E I think it was suggested=2C though I can't recall who did=2E It might have
been
=3E Pavel=2E

I think it was me=2C but can't tell it for sure=2E Okay=2C so you can blame
me=2E =3D=29
 
=3E Making it a property of the class=2C rather than the instance=2C leads to
much
=3E implementation grief=2E For example=2C pulling on that thread a bit
=3Cg=3E=2C it
=3E seems to lead to needing to implement two versions of each class - one
=3E counted=2C one not=2E

I wonder if you'd be able to emulate this proposed =22refcounted class=22 
behaviour by applying
attirubute to a typedef=3A

=09class =5FFile { =2E=2E=2E }
=09typedef auto =5FFile File=3B
 
Aug 26 2002
parent reply "Walter" <walter digitalmars.com> writes:
Wouldn't it be better to make it clear upon each use that this is an
auto-destruct instance?

"Pavel Minayev" <evilone omen.ru> wrote in message
news:CFN374949748311227 news.digitalmars.com...
I wonder if you'd be able to emulate this proposed "refcounted class"
behaviour by applying
attirubute to a typedef:
class _File { ... }
typedef auto _File File;
Aug 26 2002
parent reply Pavel Minayev <evilone omen.ru> writes:
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.
Aug 27 2002
parent reply "Walter" <walter digitalmars.com> writes:
"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'.
Aug 27 2002
next sibling parent reply Russell Lewis <spamhole-2001-07-16 deming-os.org> writes:
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?
Aug 27 2002
parent "Walter" <walter digitalmars.com> writes:
"Russell Lewis" <spamhole-2001-07-16 deming-os.org> wrote in message
news:3D6BBF0D.3010000 deming-os.org...
 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?
Since such errors could be detected at compile time, I think it would be better to do it that way.
Aug 27 2002
prev sibling parent reply Pavel Minayev <evilone omen.ru> writes:
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.
Aug 27 2002
parent Mac Reiter <Mac_member pathlink.com> writes:
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. 
Now that sounds like a plan. Explicitness is good. Mac
Aug 27 2002
prev sibling parent reply Mac Reiter <Mac_member pathlink.com> writes:
And the same problem arises:
In practice, RAII is not the property of the instance, but of the class,
isn't it?
Then you need to specify this at the class declaration of "A", so it
cannot
be a storage class.
Thoughts?
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.
To avoid constantly saying "scoped or reference counted", I'm just going to say dof'ed (deterministic object finalized, or something like that...) 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. 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. 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.
 If it is doable, I would prefer that 'auto' be a class property ( auto
class
 Lock{} ).  If flexibility is a problem, and it becomes necessary for a
 particular instance of an 'auto class' to outlive its scope (unlikely, but
it is
 possible), and if un-autoing the class is simply not an acceptable
solution,
 perhaps a keyword like 'collected' or 'non_auto' could be added (it
doesn't have
 to look nice -- it will be used VERY infrequently and should only be used
by
 people who really know what they are doing) as a storage class so that it
 disabled 'auto'ness for a particular instance.
While in C++ it is common to have a destructor (to manage memory), in D having a resource that needs cleaning up should be the exception, not the rule.
I think this comment is suggesting that dof'ing is an exception, so it's OK to require a keyword at each instance. 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. When using classes that have dof behavior, dof is the rule, and non-dof is either non-existent or a rare exception. This suggests that dof classes should not require a keyword for the common dof'ed instances, and possibly have a keyword for the exceptional non-dof'ed instances. Slightly off topic, since I know that you recognize the importance of dof (otherwise you would not have offered up an implementation, right?). The statement that the use of dof is the exceptional case bothers me. I suspect D specifically because of the lack of dof? 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.
 Or, to flog a fatally wounded horse one last time, you could implement
reference
 counting and get the RAII that you originally described : "RAII is a
programming
 paradigm where resources are automatically released when an object is no
longer
 referenced."  C++'s method is a limited subset of what RAII really should
be.
 If you can automatically implement the pseudo-finally to perform a delete
for
 auto instances, you can automatically implement a pseudo-finally to
decrement
 the reference count for a counted instance.  The only reason for
[clip]
Reference counting involves far, far more than just a pseudo-finally.
Granted. But in one of my previous refcounting posts it was mentioned that the primary difficulty was in maintaining the reference counting invariant in the presence of exceptions. Well, you have a mechanism for handling arbitrarily complicated stuff in the presence of exceptions, so it shouldn't be any harder to maintain the invariant with exceptions than it would have been without exceptions.
 Reference counting also solves the:

 {
 auto A a = new A; // A1
 a = new A;        // A2
 }

 where A2 gets deleted but A1 waits for the GC to get around to it (which
is not
 RAII or deterministic).  Refcounting would have to watch assignments, and
modify
 the refcount for the old object as well as the new.  But when it did that,
it
 would notice that A1 was no longer used and could finalize it (I defer to
the
 masses on whether it should be deleted or not, but it definitely should be
 finalized).
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. 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. _If_ the GC could ever get cycles to run, it would notice this object and finalize it. But since all of the threads are locked on this object, the GC never gets a chance to run, so the object never gets finalized, and the application sits there forever. Unlikely, but the first rule in multithreaded programming is that (to plagiarize shamelessly from Terry Pratchett's Discworld books) "1 in a million chances happen 9 times out of 10". "Should" isn't "will", and unless it "will" be collected, it isn't safe.
 As for whether the suggested 'counted' should be an instance or class
property,
 I suspect the argument given above for class property still holds.
Classes
 should be counted, not instances.  Part of the reason for this is that a
[clip]
 Because derived types can be assigned into base type references, it may be
 necessary to consider the interaction of inheritance and the 'counted'
class
 specifier.  Are all children of a counted class automatically counted?
[clip]
 require a runtime check.  Perhaps a compile time restraint that simply
says that
 counted classes are not allowed to participate in polymorphic assignments
at all
 would be a better first solution...
Suppose you have a printing function that takes an object of type Object. Object is not counted, so the counted class is cast to Object. To support this, it becomes necessary to 1) extend counting to every object 2) ignore the possibility of bugs from dangling references 3) disallow conversions to Object. 1) has too many penalties for D as whole 2) is a similar bug to handing off a reference to an 'auto' instance, but I think worse because it will happen more often 3) requires creation of two versions of most things in the library, one to handle counted and one for non-counted
This is why I suggested that 'counted' classes not be allowed to participate in polymorphic actions (conversion to Object, in your example). 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. 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.
 Yes, there are complications with 'counted' classes.  But realistically,
either
 compiler writers or programmers are going to experience similar
complications
 with 'auto' classes (and worse problems with 'auto' instances).  If the
problems
 are roughly the same, I would vote for the more flexible and complete
solution.
 Of course, I am somewhat biased on the issue...
No, I believe the 'auto' approach has an order of magnitude less implementation effort than ref counting. For example, it won't be necessary to handle arrays of counted objects, counted objects as members of structs, counted objects as members of non-counted objects, assignment overloading, etc.
1. arrays of counted objects Pragmatically, this means that the array is counted. The slightly tricky bit is that the array doesn't have a refcount of its own. Its refcount is the largest refcount of any of its elements. For pathological cases, this could be expensive to check, but you do get to stop as soon as you hit any non-zero refcount, so it's only a problem for large arrays of counted objects, where all but the last object have already been "freed". Because of that, I would recommend that when you construct the pseudo-finally, you decrement refcounts for the array elements starting at the back, so that each check will see the positive refcount in the first array element. Then when you finish by decrementing the refcount of the first element, the array refcount check will make one big sweep through and free up the memory. (Or, if you write the count checking loop as a back-to-front loop, then do the decrements from front-to-back.) Of course, static analysis could be used in more advanced implementations to notice when the refcount would never exceed 1 and just finalize the whole array, bypassing the whole refcounting proceedings, at scope exit. But it doesn't have to be that fancy to begin with. I also don't see really big arrays of counted objects, but just 'coz I don't see it doesn't mean somebody won't try to do it... 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. My simple answer for this is the same as it would be for (keep reading): 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. It would have to wait until the symbol table is available during actual compilation to detect, given the "property of a class" nature of the counted keyword. Since nobody but the compiler particularly needs to know about the counted/non-counted nature of classes, I don't think that's a problem. 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. 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. 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. 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. 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;"). Hoping that I'm not being too annoying, Mac
Aug 26 2002
next sibling parent Russell Lewis <spamhole-2001-07-16 deming-os.org> writes:
Part of the problem (that I see) is that classes may need to be dof if 
one of their members is.  If your class holds a reference to a File 
class (that is dof), then really your class that holds the reference 
should be dof as well.  That's not too hard, I suppose.  But what if you 
are using templates?

     instance Container(File) fileContainer;

Now, the container needs to be dof as well...but that probably isn't 
already declared in the container class's implementation.

I'm not saying that these things aren't solvable...but they need to be 
considered.

Russ
Aug 26 2002
prev sibling next sibling parent reply "Walter" <walter digitalmars.com> writes:
"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,
 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.
Aug 26 2002
parent "Sean L. Palmer" <seanpalmer earthlink.net> writes:
"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
Aug 27 2002
prev sibling next sibling parent Pavel Minayev <evilone omen.ru> writes:
On Mon=2C 26 Aug 2002 20=3A11=3A51 +0000 =28UTC=29 Mac Reiter
=3CMac=5Fmember=40pathlink=2Ecom=3E 
wrote=3A

=3E your 3=29=2C because I suggested disabling =5Fany=5F conversion=2C up or
down=2C to any
=3E level of the class hierarchy=2E  The compiler simply disallows casting a
counted
=3E class=2C implicitly or explicitly=2C to anything else=2E  Maybe someday=2C
when more
=3E experience is gained=2C some method of safely handling conversions may be
found=2E

I have to disagree here=2E Clearly=2C File should be a counted class=2E On the
other 
hand=2C it derives
from Stream =28which is not counted=29=2E Currently=2C you can write code like
this=3A

=09uint crc32=28Stream s=29 { =2E=2E=2E }

=09File f =3D new File=28=22foo=2Ebar=22=29=3B
=09crc =3D crc32=28f=29=3B

If casting is forbidden=2C you couldn't treat File as if it was Stream - which 
is the main feature of
the current streams implementation=2E 
Aug 27 2002
prev sibling parent "Sean L. Palmer" <seanpalmer earthlink.net> writes:
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
Aug 27 2002
prev sibling next sibling parent reply Patrick Down <pat codemoon.com> writes:
"Walter" <walter digitalmars.com> wrote in
news:akba6n$28tb$1 digitaldaemon.com: 

  Resource Aquisition Is Initialization (RAII)
 
I think this is a very pragmatic solution to a hard problem. I only have two suggestions. 1. Call it auto. 2. I think auto Foo a = new Foo(); is how this will be used 99% of the time. Why not get rid of the explict initialization and not allow reassignment. So: auto Foo a; // same as a = new Foo(); a = new Foo(); // error
Aug 26 2002
next sibling parent reply Russell Lewis <spamhole-2001-07-16 deming-os.org> writes:
Patrick Down wrote:
 "Walter" <walter digitalmars.com> wrote in
 news:akba6n$28tb$1 digitaldaemon.com: 
 
 
 Resource Aquisition Is Initialization (RAII)
I think this is a very pragmatic solution to a hard problem. I only have two suggestions. 1. Call it auto. 2. I think auto Foo a = new Foo(); is how this will be used 99% of the time. Why not get rid of the explict initialization and not allow reassignment. So: auto Foo a; // same as a = new Foo(); a = new Foo(); // error
I seem to remember that "auto" had some meaning back in the early C days. I wonder if it isn't a bad choice. Maybe, going off of Patrick's point 2 here, we could use the keyword "stack": stack Foo a; Which might make it clear that it is a stack variable, not an ordinary reference. I like Patrick's idea that you wouldn't need to "new" a copy.
Aug 26 2002
parent reply Pavel Minayev <evilone omen.ru> writes:
On Mon=2C 26 Aug 2002 11=3A39=3A00 -0700 Russell Lewis 
=3Cspamhole-2001-07-16=40deming-os=2Eorg=3E wrote=3A

=3E I seem to remember that =22auto=22 had some meaning back in the early C 

It is in ANSI C=2FC++ standard and means local non-static variable=2E Since 
variables are
non-static by default=2C it's never used=2C but still it is there =28and for
that 
reason I proposed it=29=2E

=3E days=2E  I wonder if it isn't a bad choice=2E  Maybe=2C going off of
Patrick's 
=3E point 2 here=2C we could use the keyword =22stack=22=3A
=3E 
=3E      stack Foo a=3B

I also like =22counted=22=2C since the object is not actually on stack - it can 
outlive
the function in which it was created if you pass it outside =28due to 
refcounting=29=2E
Maybe =22counted=22 is a better idea then=3F Still I like =22auto=22=2E=2E=2E
=3D=29
 
=3E Which might make it clear that it is a stack variable=2C not an ordinary 
=3E reference=2E  I like Patrick's idea that you wouldn't need to =22new=22 a
copy=2E
 
Alternatively=2C it could new it by defaullt=2C and where you don't want it=2C
you 
can initialize
it to null=3A

=09auto File a=3B=09=09=2F=2F default =3D new File=28=29=3B
=09auto File b =3D null=3B=09=2F=2F no object created=2E
Aug 26 2002
parent "Walter" <walter digitalmars.com> writes:
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.
Aug 26 2002
prev sibling parent reply "Walter" <walter digitalmars.com> writes:
"Patrick Down" <pat codemoon.com> wrote in message
news:Xns927689D889E24patcodemooncom 63.105.9.61...
 "Walter" <walter digitalmars.com> wrote in
 news:akba6n$28tb$1 digitaldaemon.com:

  Resource Aquisition Is Initialization (RAII)
I think this is a very pragmatic solution to a hard problem. I only have two suggestions. 1. Call it auto.
Ok, ok!
 2. I think auto Foo a = new Foo(); is how this will
 be used 99% of the time.  Why not get rid of
 the explict initialization and not allow reassignment.

 So:

 auto Foo a; // same as a = new Foo();
That would preclude initialization via out parameter: void func(out A a); void test() { auto A a; foo(a); }
 a = new Foo(); // error
This may be a very good idea, but I don't want to close the door on reassigning it just yet.
Aug 26 2002
parent Patrick Down <pat codemoon.com> writes:
"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.
Aug 26 2002
prev sibling parent reply Russell Lewis <spamhole-2001-07-16 deming-os.org> writes:
You probably should be able to declare certain members of a class to be 
"auto" as well, so that they are dof'ed whenever the class goes away. 
Otherwise, the programmer has to code an explicit 'delete' in a destructor.
Aug 26 2002
parent reply "Walter" <walter digitalmars.com> writes:
"Russell Lewis" <spamhole-2001-07-16 deming-os.org> wrote in message
news:3D6A8F2A.40902 deming-os.org...
 You probably should be able to declare certain members of a class to be
 "auto" as well, so that they are dof'ed whenever the class goes away.
 Otherwise, the programmer has to code an explicit 'delete' in a
destructor. That adds some significant complexity to the compiler (I know, I did it for C++!), so I'd like to defer that for some future variant of the language.
Aug 27 2002
parent reply Russ Lewis <spamhole-2001-07-16 deming-os.org> writes:
Walter wrote:

 "Russell Lewis" <spamhole-2001-07-16 deming-os.org> wrote in message
 news:3D6A8F2A.40902 deming-os.org...
 You probably should be able to declare certain members of a class to be
 "auto" as well, so that they are dof'ed whenever the class goes away.
 Otherwise, the programmer has to code an explicit 'delete' in a
destructor. That adds some significant complexity to the compiler (I know, I did it for C++!), so I'd like to defer that for some future variant of the language.
Details? I've learned to trust it when you say "too complex"...but I'm curious what happens... -- 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 "Walter" <walter digitalmars.com> writes:
"Russ Lewis" <spamhole-2001-07-16 deming-os.org> wrote in message
news:3D6D1A90.910A1A30 deming-os.org...
 Walter wrote:

 "Russell Lewis" <spamhole-2001-07-16 deming-os.org> wrote in message
 news:3D6A8F2A.40902 deming-os.org...
 You probably should be able to declare certain members of a class to
be
 "auto" as well, so that they are dof'ed whenever the class goes away.
 Otherwise, the programmer has to code an explicit 'delete' in a
destructor. That adds some significant complexity to the compiler (I know, I did it
for
 C++!), so I'd like to defer that for some future variant of the
language.
 Details?  I've learned to trust it when you say "too complex"...but I'm
curious
 what happens...
You have to add the member destructor code into existing destructors, and create destructors where there aren't any.
Aug 28 2002