Thread overview | |||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
July 12, 2010 Recommended way to do RAII cleanly | ||||
---|---|---|---|---|
| ||||
Okay. There are cases where you want a constructor to do something when the class/struct is created, and you want the destructor to do something when the class/struct goes out of scope. A classic example would be an autolock for a mutex. Another would be the hourglass in MFC - it's displayed when the object is created and disappears when the object is destroyed (so all you have to do is declare the object it at the beggining of the function and it automatically is displayed and then disappears). This is classic RAII. Obviously, because classes are reference types with infinite lifetime while structs are value types with their lifetime restricted to their scope, structs would be the better choice for RAII. I have noticed a bit of a snag however: structs can't have default constructors. After reading TDPL, I completely understand that structs can't have default constructors due to how the init property works. However, the classic case where you want to simply declare an object and have it do what it does through RAII then falls apart. Ideally, you'd have something like this struct S { this() { /* do something */ } ~this() { /* undo what you did before or do whatever clean up is required for it */ } } void main() { auto s = S(); /* whatever the rest of main() does */ } Thanks to the lack of default constructor, you can't do that. Therefore, I see 2 options: 1. Create a nonsensical constructor that takes an argument of _some_ kind which is totally ignored. 2. Create a factory function to create the struct, and it does whatever would have been in the default constructor. Out of those two options, the second seems the best, but it seems to me that there have got to be more options than that. So, the question is what would be the best option (be it one of those or another that I haven't though of) to do RAII in the general case? What would be "best practice" for D when dealing with structs intended for RAII without any arguments to their constructor when you can't have a default constructor? - Jonathan M Davis |
July 12, 2010 Re: Recommended way to do RAII cleanly | ||||
---|---|---|---|---|
| ||||
Posted in reply to Jonathan M Davis | On Sun, 11 Jul 2010 23:25:32 -0700, Jonathan M Davis wrote:
> Okay. There are cases where you want a constructor to do something when the class/struct is created, and you want the destructor to do something when the class/struct goes out of scope. A classic example would be an autolock for a mutex. Another would be the hourglass in MFC - it's displayed when the object is created and disappears when the object is destroyed (so all you have to do is declare the object it at the beggining of the function and it automatically is displayed and then disappears). This is classic RAII.
>
> Obviously, because classes are reference types with infinite lifetime while structs are value types with their lifetime restricted to their scope, structs would be the better choice for RAII. I have noticed a bit of a snag however: structs can't have default constructors.
>
> After reading TDPL, I completely understand that structs can't have default constructors due to how the init property works. However, the classic case where you want to simply declare an object and have it do what it does through RAII then falls apart. Ideally, you'd have something like this
>
> struct S
> {
> this()
> {
> /* do something */
> }
>
> ~this()
> {
> /* undo what you did before or do whatever clean up is required
> for it */
> }
> }
>
> void main()
> {
> auto s = S();
> /* whatever the rest of main() does */
> }
>
>
> Thanks to the lack of default constructor, you can't do that. Therefore, I see 2 options:
>
> 1. Create a nonsensical constructor that takes an argument of _some_ kind which is totally ignored.
>
> 2. Create a factory function to create the struct, and it does whatever would have been in the default constructor.
>
>
> Out of those two options, the second seems the best, but it seems to me that there have got to be more options than that. So, the question is what would be the best option (be it one of those or another that I haven't though of) to do RAII in the general case? What would be "best practice" for D when dealing with structs intended for RAII without any arguments to their constructor when you can't have a default constructor?
I'd say option 2 is your best bet. I don't know any other way to "fake" default construction.
That said, the recommended best practice for D is, if possible, to use scope guards:
void doStuffWith(string resourceName)
{
auto resource = acquire(resourceName);
scope(exit) release(resource);
... // Do stuff with resource here
}
-Lars
|
July 12, 2010 Re: Recommended way to do RAII cleanly | ||||
---|---|---|---|---|
| ||||
Posted in reply to Lars T. Kyllingstad | On Monday 12 July 2010 00:27:11 Rory McGuire wrote:
> Do you know about the scope storage class, or about scope classes?
>
> {
> scope tmp = new A();
>
> // use tmp;
>
> tmp destructor is called.
> }
>
> scope classes are similar: http://www.digitalmars.com/d/2.0/class.html
Except that as I understand it, scope as a storage class is being deprecated. So, while it will work now, it won't later. Otherwise, given the restrictions on default constructors with structs, I'd use scope that way to solve the problem. If nothing else though, I would think that this situation would be a good case against deprecating scope as a storage class.
- Jonathan M Davis
|
July 12, 2010 Re: Recommended way to do RAII cleanly | ||||
---|---|---|---|---|
| ||||
Posted in reply to Lars T. Kyllingstad | On Sunday 11 July 2010 23:41:21 Lars T. Kyllingstad wrote:
>
> That said, the recommended best practice for D is, if possible, to use scope guards:
>
> void doStuffWith(string resourceName)
> {
> auto resource = acquire(resourceName);
> scope(exit) release(resource);
>
> ... // Do stuff with resource here
> }
>
> -Lars
Except that that's two statements and it's no longer RAII. The beauty of doing it entirely in the constructor and destructor is that you only need one statement and the whole thing takes care of itself. With scope, you have to worry about remembering to use scope, and even if you do, that's two statements instead of one. Obviously, it works, but it's not as clean or elegant.
- Jonathan M Davis
|
July 12, 2010 Re: Recommended way to do RAII cleanly | ||||
---|---|---|---|---|
| ||||
Posted in reply to Jonathan M Davis | On 2010-07-12 08.25, Jonathan M Davis wrote: > Okay. There are cases where you want a constructor to do something when the > class/struct is created, and you want the destructor to do something when the > class/struct goes out of scope. A classic example would be an autolock for a > mutex. Another would be the hourglass in MFC - it's displayed when the object is > created and disappears when the object is destroyed (so all you have to do is > declare the object it at the beggining of the function and it automatically is > displayed and then disappears). This is classic RAII. > > Obviously, because classes are reference types with infinite lifetime while > structs are value types with their lifetime restricted to their scope, structs > would be the better choice for RAII. I have noticed a bit of a snag however: > structs can't have default constructors. > > After reading TDPL, I completely understand that structs can't have default > constructors due to how the init property works. However, the classic case where > you want to simply declare an object and have it do what it does through RAII > then falls apart. Ideally, you'd have something like this > > struct S > { > this() > { > /* do something */ > } > > ~this() > { > /* undo what you did before or do whatever clean up is required for it */ > } > } > > void main() > { > auto s = S(); > /* whatever the rest of main() does */ > } > > > Thanks to the lack of default constructor, you can't do that. Therefore, I see 2 > options: > > 1. Create a nonsensical constructor that takes an argument of _some_ kind which > is totally ignored. > > 2. Create a factory function to create the struct, and it does whatever would > have been in the default constructor. > > > Out of those two options, the second seems the best, but it seems to me that > there have got to be more options than that. So, the question is what would be > the best option (be it one of those or another that I haven't though of) to do > RAII in the general case? What would be "best practice" for D when dealing with > structs intended for RAII without any arguments to their constructor when you > can't have a default constructor? > > - Jonathan M Davis You can use a static opCall method (if that hasn't been deprecated as well), like this: struct S { static S opCall () { S s; // do something with s you would otherwise do in the constructor return s; } } auto s = S(); -- Jacob Carlborg |
July 12, 2010 Re: Recommended way to do RAII cleanly | ||||
---|---|---|---|---|
| ||||
Posted in reply to Jonathan M Davis | Jonathan M Davis <jmdavisprog@gmail.com> wrote: > Except that that's two statements and it's no longer RAII. The beauty of doing > it entirely in the constructor and destructor is that you only need one > statement and the whole thing takes care of itself. With scope, you have to > worry about remembering to use scope, and even if you do, that's two statements > instead of one. Obviously, it works, but it's not as clean or elegant. So use an enum and string mixins: import std.stdio; enum bar = q{ writeln( "The bar has opened." ); scope( exit ) { writeln( "The bar has closed." ); } }; void main( ) { writeln( "Entering..." ); mixin( bar ); writeln( "Exiting..." ); } Output: Entering... The bar has opened. Exiting... The bar has closed. -- Simen |
July 12, 2010 Re: Recommended way to do RAII cleanly | ||||
---|---|---|---|---|
| ||||
Posted in reply to Jonathan M Davis | On 12.07.2010 08:25, Jonathan M Davis wrote: > Okay. There are cases where you want a constructor to do something when the > class/struct is created, and you want the destructor to do something when the > class/struct goes out of scope. A classic example would be an autolock for a > mutex. Another would be the hourglass in MFC - it's displayed when the object is > created and disappears when the object is destroyed (so all you have to do is > declare the object it at the beggining of the function and it automatically is > displayed and then disappears). This is classic RAII. > > Obviously, because classes are reference types with infinite lifetime while > structs are value types with their lifetime restricted to their scope, structs > would be the better choice for RAII. I have noticed a bit of a snag however: > structs can't have default constructors. > > After reading TDPL, I completely understand that structs can't have default > constructors due to how the init property works. However, the classic case where > you want to simply declare an object and have it do what it does through RAII > then falls apart. Ideally, you'd have something like this > > struct S > { > this() > { > /* do something */ > } > > ~this() > { > /* undo what you did before or do whatever clean up is required for it */ > } > } > > void main() > { > auto s = S(); > /* whatever the rest of main() does */ > } > > > Thanks to the lack of default constructor, you can't do that. Therefore, I see 2 > options: > > 1. Create a nonsensical constructor that takes an argument of _some_ kind which > is totally ignored. > > 2. Create a factory function to create the struct, and it does whatever would > have been in the default constructor. > > > Out of those two options, the second seems the best, but it seems to me that > there have got to be more options than that. So, the question is what would be > the best option (be it one of those or another that I haven't though of) to do > RAII in the general case? What would be "best practice" for D when dealing with > structs intended for RAII without any arguments to their constructor when you > can't have a default constructor? > > - Jonathan M Davis Appender in std.array has the same issue, and solves it with a static assert and a factory function: http://www.dsource.org/projects/phobos/changeset/ Well, except that Appender doesn't have a destructor. But other than that, wouldn't most structs that use RAII have a constructor that at least requires some kind of handle as an argument? For you hourglass example, wouldn't you need to call two methods anyway? I googled for "MFC hourglass". Then it'd look like this: BeginWaitCursor(); scope (exit) EndWaitCursor(); The Mutex in Phobos is a class, so you'd have to do basically the same thing. But if you only need a local mutex, you'd probably use a synchronized statement instead. I think the conclusion is that RAII is less important in D than in C++. In D you use scope (exit), or even finally, like in Java or Python. The other use of scope, as a storage class, is supposed to go away, and I suspect I'm not the only one who's going to miss it. |
July 12, 2010 Re: Recommended way to do RAII cleanly | ||||
---|---|---|---|---|
| ||||
Posted in reply to torhu | On Monday, July 12, 2010 13:17:04 torhu wrote: > For you hourglass example, wouldn't you need to call two methods anyway? I googled for "MFC hourglass". Then it'd look like this: > > BeginWaitCursor(); > scope (exit) EndWaitCursor(); I haven't used MFC recently, I'm 99% certain that there's a class in MFC that wraps this for you so that all you have to do is declare a local variable of that type, and the hourglass is visible until that variable leaves scope and is destroyed. If it's not MFC, then the shop that I was working at had their own class that did that. > > The Mutex in Phobos is a class, so you'd have to do basically the same thing. But if you only need a local mutex, you'd probably use a synchronized statement instead. It's not the mutex but an autolock for a mutex which would use RAII. It would lock the mutex when it was declared and unlock when it left scope and was destroyed. However, since you'd have to give it the mutex as a parameter (as opposed to using a default constructor), the lack of default constructor wouldn't be an issue. For the most part synchronized deals with the issue though. My one concern with synchronized is how you'd unsynchronize in the middle of the synchronized block if you had to (but that's a separate issue and a likely cause for having to use mutexes instead of synchronized). > > I think the conclusion is that RAII is less important in D than in C++. > In D you use scope (exit), or even finally, like in Java or Python. > The other use of scope, as a storage class, is supposed to go away, and > I suspect I'm not the only one who's going to miss it. There are lots of cases where using scope(exit) makes sense, and it's a great construct. But there are also plenty of cases where using plain old RAII with a single declaration is better. It works fine in D as long as the struct in question doesn't need a default constructor. But if it does, then it becomes a problem. - Jonathan M Davis |
July 12, 2010 Re: Recommended way to do RAII cleanly | ||||
---|---|---|---|---|
| ||||
Posted in reply to Jonathan M Davis | Jonathan M Davis:
> There are lots of cases where using scope(exit) makes sense, and it's a great construct. But there are also plenty of cases where using plain old RAII with a single declaration is better. It works fine in D as long as the struct in question doesn't need a default constructor. But if it does, then it becomes a problem.
Can't you use a static opCall (also suggested by Jacob Carlborg)?
Bye,
bearophile
|
July 12, 2010 Re: Recommended way to do RAII cleanly | ||||
---|---|---|---|---|
| ||||
Posted in reply to Simen kjaeraas | On Monday, July 12, 2010 12:18:38 Simen kjaeraas wrote:
> Jonathan M Davis <jmdavisprog@gmail.com> wrote:
> > Except that that's two statements and it's no longer RAII. The beauty of
> > doing
> > it entirely in the constructor and destructor is that you only need one
> > statement and the whole thing takes care of itself. With scope, you have
> > to
> > worry about remembering to use scope, and even if you do, that's two
> > statements
> > instead of one. Obviously, it works, but it's not as clean or elegant.
>
> So use an enum and string mixins:
>
> import std.stdio;
>
> enum bar = q{
> writeln( "The bar has opened." );
> scope( exit ) {
> writeln( "The bar has closed." );
> }
> };
>
> void main( ) {
> writeln( "Entering..." );
> mixin( bar );
> writeln( "Exiting..." );
> }
>
> Output:
> Entering...
> The bar has opened.
> Exiting...
> The bar has closed.
That's a pretty good solution, though it does feel like you're fighting the language when you have to completely change your construct simply because the normal one isn't able to have a version without any arguments to it. I guess that if that's what I have to do, that's what I'll do, but it definitely feels ugly to me in comparison - particularly when I can still use RAII in cases where the constructor does take arguments.
- Jonathan M Davis
|
Copyright © 1999-2021 by the D Language Foundation