digitalmars.D.learn - Recommended way to do RAII cleanly
- Jonathan M Davis (44/44) Jul 11 2010 Okay. There are cases where you want a constructor to do something when ...
- Lars T. Kyllingstad (12/70) Jul 11 2010 I'd say option 2 is your best bet. I don't know any other way to "fake"...
- Jonathan M Davis (7/19) Jul 12 2010 Except that as I understand it, scope as a storage class is being deprec...
- Jonathan M Davis (7/20) Jul 12 2010 Except that that's two statements and it's no longer RAII. The beauty of...
- Simen kjaeraas (21/29) Jul 12 2010 So use an enum and string mixins:
- Jonathan M Davis (8/40) Jul 12 2010 That's a pretty good solution, though it does feel like you're fighting ...
- Jacob Carlborg (15/59) Jul 12 2010 You can use a static opCall method (if that hasn't been deprecated as
- torhu (18/62) Jul 12 2010 Appender in std.array has the same issue, and solves it with a static
- Jonathan M Davis (20/34) Jul 12 2010 I haven't used MFC recently, I'm 99% certain that there's a class in MFC...
- bearophile (4/9) Jul 12 2010 Can't you use a static opCall (also suggested by Jacob Carlborg)?
- torhu (4/13) Jul 12 2010 I think that's supposed to go away, but I'm not 100% sure. Would make
- Jonathan M Davis (6/22) Jul 12 2010 This would be why I like Berophile's suggestion on the main list of havi...
- bearophile (4/7) Jul 12 2010 Well, if you have found a use case for static opCall then it needs to be...
- Nick Sabalausky (8/12) Jul 12 2010 As good as scope guards are, anytime you have an object that has some so...
- Jonathan M Davis (12/27) Jul 12 2010 IIRC, Walter was of the opinion that you could do the same with structs ...
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
Jul 11 2010
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
Jul 11 2010
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.htmlExcept 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
Jul 12 2010
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 } -LarsExcept 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
Jul 12 2010
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
Jul 12 2010
On Monday, July 12, 2010 12:18:38 Simen kjaeraas wrote:Jonathan M Davis <jmdavisprog gmail.com> wrote: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 DavisExcept 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.
Jul 12 2010
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 DavisYou 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
Jul 12 2010
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 DavisAppender 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.
Jul 12 2010
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
Jul 12 2010
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
Jul 12 2010
On 13.07.2010 00:09, bearophile wrote:Jonathan M Davis:I think that's supposed to go away, but I'm not 100% sure. Would make sense, since it was added as a sort of stopgap measure when there were no struct literals.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
Jul 12 2010
On Monday, July 12, 2010 15:40:24 torhu wrote:On 13.07.2010 00:09, bearophile wrote:This would be why I like Berophile's suggestion on the main list of having a page which lists deprecated and intended to be deprecated constructs. opCall is a good solution and basically fulfills the requirements of a default constructor, but if it's going away, then it's in the same camp as using scope on a class. - Jonathan M DavisJonathan M Davis:I think that's supposed to go away, but I'm not 100% sure. Would make sense, since it was added as a sort of stopgap measure when there were no struct literals.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
Jul 12 2010
torhu:I think that's supposed to go away, but I'm not 100% sure. Would make sense, since it was added as a sort of stopgap measure when there were no struct literals.Well, if you have found a use case for static opCall then it needs to be shown to the people that want to deprecate the static opCall, to ask for ways to replace its functionality. Bye, bearophile
Jul 12 2010
"torhu" <no spam.invalid> wrote in message news:i1ft84$2h4j$1 digitalmars.com...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.As good as scope guards are, anytime you have an object that has some sort of cleanup function that needs to be called when you're done with it, it's absurd to think that requiring the *user* of the object to use scope guards *by convention* is ever a satisfactory substitute for real RAII. And I have to say, I'm rather disappointed to hear that scope objects are going away. What is the reason for that?
Jul 12 2010
On Monday, July 12, 2010 18:15:04 Nick Sabalausky wrote:"torhu" <no spam.invalid> wrote in message news:i1ft84$2h4j$1 digitalmars.com...IIRC, Walter was of the opinion that you could do the same with structs without needing scope. And as long as you can default construct them or your RAII object requires arguments to its constructor, that's more or less true (if arguably limiting). The question, therefore is whether there is a means to effectively default construct structs which is not going to be deprecated. I'd still like scope to stick around (even if all it did was put the class on the heap as normal and called clear() on it when it left scope rather than putting it on the stack like I believe it does at the moment), but if structs can totally take care of the RAII issue, then it's not as bad. But the situation with structs and default constructors is a bit frustrating. - Jonathan M DavisI 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.As good as scope guards are, anytime you have an object that has some sort of cleanup function that needs to be called when you're done with it, it's absurd to think that requiring the *user* of the object to use scope guards *by convention* is ever a satisfactory substitute for real RAII. And I have to say, I'm rather disappointed to hear that scope objects are going away. What is the reason for that?
Jul 12 2010