www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - raii

reply Ellery Newcomer <ellery-newcomer utulsa.edu> writes:
Hello

The impetus:

 I agree, except I more and more think that scope classes were a mistake.
Structs with destructors are a much better solution, and wrapping a class
inside a struct would give it RAII semantics.
The problem: In designing a library (or rather touching up someone else's library), I have a class (interface?) Foo, and it has a method close (actually several methods). Almost invariably (although conceivably not always), usage involves manipulating Foo, and then remembering to call close. That last part is obnoxious. One obtains a Foo from function foo. What I'd like is for the result of foo to have RAII semantics by default, with the possibility of nixing it if the user actually cares. I want to take the burden of remembering that stupid close off the user. For example: void bar(){ auto a = foo(); a.doStuff(); } has an implicit call to close after doStuff. The impetus suggests I can do this by wrapping Foo inside a struct, but I'm not so sure how well this would work out. Also note that close can fail in a number of ways relating to file io. So I wrote up a simple prototype. Thoughts? import std.stdio; class Foo{ int k; this(int i){ writefln("bin %d",k); k = i; } void doStuff(){ writefln("do stuff %d",k); } void close(){ writefln("close %d",k); } } struct RAII(T){ T b; bool extracted = false; alias b this; this (T i){ assert(i !is null); writeln("in"); b = i; } ~this(){ writeln("~this"); if(!extracted) b.close(); } T extract(){ assert(!extracted); T t = b; b = null; extracted = true; return t; } } RAII!(Foo) foo(int i){ return RAII!(Foo)(new Foo(i)); } void main(){ auto a = foo(1); auto b = foo(2).extract(); a.doStuff(); b.doStuff(); }
Feb 28 2010
next sibling parent reply sybrandy <sybrandy gmail.com> writes:
<snip>

I have two questions for you:

1) Are class destructors not good enough?  If so, why?

2) Have you looked into static destructors?  I created a logging library 
and that worked perfect for me to ensure that if I killed the program 
via Ctrl-C, it would flush the output buffer and close the file handle.

Personally, I really like the use of "scope" vs. RAII.  Keeping the code 
to clean up a file handle or the like right by where it's declared seems 
very sensible to me.

Casey
Feb 28 2010
parent reply Ellery Newcomer <ellery-newcomer utulsa.edu> writes:
On 02/28/2010 02:34 PM, sybrandy wrote:
 <snip>

 I have two questions for you:

 1) Are class destructors not good enough? If so, why?
They might be good enough, I don't know. I thought they wouldn't be guaranteed to be called on scope exit, though.
 2) Have you looked into static destructors? I created a logging library
 and that worked perfect for me to ensure that if I killed the program
 via Ctrl-C, it would flush the output buffer and close the file handle.
I'm not really looking at unexpected terminations
 Personally, I really like the use of "scope" vs. RAII. Keeping the code
 to clean up a file handle or the like right by where it's declared seems
 very sensible to me.
I like scope too. But it's still something the user has to remember.
 Casey
Feb 28 2010
parent Jonathan M Davis <jmdavisProg gmail.com> writes:
Ellery Newcomer wrote:

 On 02/28/2010 02:34 PM, sybrandy wrote:
 <snip>

 I have two questions for you:

 1) Are class destructors not good enough? If so, why?
They might be good enough, I don't know. I thought they wouldn't be guaranteed to be called on scope exit, though.
 2) Have you looked into static destructors? I created a logging library
 and that worked perfect for me to ensure that if I killed the program
 via Ctrl-C, it would flush the output buffer and close the file handle.
I'm not really looking at unexpected terminations
 Personally, I really like the use of "scope" vs. RAII. Keeping the code
 to clean up a file handle or the like right by where it's declared seems
 very sensible to me.
I like scope too. But it's still something the user has to remember.
 Casey
Without RAII, it's likely something that they have to remember too. Unless they specifically call close or whatever it is that's supposed to be done when the object leaves scope, it won't happen. scope is just a better way to explicitly make it happen without worrying about things like exceptions. If you want to guarantee that something will be destroyed when it leaves scope, you're going to have to go with a struct, since that's guaranteed by the language - the user of your struct can't forget it like they can with scope and classes. I like scope, but it's something that the person using the class has to remember just like they'd have to remember to call the close function or its equivalent if there were no scope modifier. It's just the nature of classes. And really, it's not all that different from C++. It's just D's way of declaring classes on the stack but with the benefit that you're not passing objects around on the stack and incurring those copying costs. - Jonathan M Davis
Feb 28 2010
prev sibling next sibling parent Ellery Newcomer <ellery-newcomer utulsa.edu> writes:
On 02/28/2010 02:16 PM, Ellery Newcomer wrote:
 RAII!(Foo) foo(int i){
 return RAII!(Foo)(new Foo(i));
 }
One thing I'm curious about in particular is how to ensure that the destructor of Foo doesn't get called inside foo. With this particular code, it evidently doesn't, but I can't tell if it's a guarantee of the language, or a side effect of some optimization.
Feb 28 2010
prev sibling parent =?ISO-8859-1?Q?Pelle_M=E5nsson?= <pelle.mansson gmail.com> writes:
On 02/28/2010 09:16 PM, Ellery Newcomer wrote:
 Hello

 The impetus:

 I agree, except I more and more think that scope classes were a
 mistake. Structs with destructors are a much better solution, and
 wrapping a class inside a struct would give it RAII semantics.
The problem: In designing a library (or rather touching up someone else's library), I have a class (interface?) Foo, and it has a method close (actually several methods). Almost invariably (although conceivably not always), usage involves manipulating Foo, and then remembering to call close. That last part is obnoxious. One obtains a Foo from function foo. What I'd like is for the result of foo to have RAII semantics by default, with the possibility of nixing it if the user actually cares. I want to take the burden of remembering that stupid close off the user. For example: void bar(){ auto a = foo(); a.doStuff(); } has an implicit call to close after doStuff. The impetus suggests I can do this by wrapping Foo inside a struct, but I'm not so sure how well this would work out. Also note that close can fail in a number of ways relating to file io. So I wrote up a simple prototype. Thoughts? import std.stdio; class Foo{ int k; this(int i){ writefln("bin %d",k); k = i; } void doStuff(){ writefln("do stuff %d",k); } void close(){ writefln("close %d",k); } } struct RAII(T){ T b; bool extracted = false; alias b this; this (T i){ assert(i !is null); writeln("in"); b = i; } ~this(){ writeln("~this"); if(!extracted) b.close(); } T extract(){ assert(!extracted); T t = b; b = null; extracted = true; return t; } } RAII!(Foo) foo(int i){ return RAII!(Foo)(new Foo(i)); } void main(){ auto a = foo(1); auto b = foo(2).extract(); a.doStuff(); b.doStuff(); }
Maybe something along the lines of this struct RAII(T : Object){ T b; alias b this; void delegate(T) destroyer; this (T i, void delegate(T) called_at_exit){ b = i; destroyer = called_at_exit; } ~this(){ if (b) destroyer(b); } T extract(){ T t = b; b = null; return t; } } class Foo { this() { } void close() { writeln("closed."); } } RAII!Foo foo() { return RAII!Foo(new Foo, (Foo f) {f.close;}); } for a slightly more generalized RAII struct, which works on classes only.
Feb 28 2010