www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Recommended way to do RAII cleanly

reply Jonathan M Davis <jmdavisprog gmail.com> writes:
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
next sibling parent "Lars T. Kyllingstad" <public kyllingen.NOSPAMnet> writes:
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
prev sibling next sibling parent Jonathan M Davis <jmdavisprog gmail.com> writes:
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
Jul 12 2010
prev sibling next sibling parent Jonathan M Davis <jmdavisprog gmail.com> writes:
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
Jul 12 2010
prev sibling next sibling parent Jacob Carlborg <doob me.com> writes:
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
Jul 12 2010
prev sibling next sibling parent "Simen kjaeraas" <simen.kjaras gmail.com> writes:
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
prev sibling next sibling parent reply torhu <no spam.invalid> writes:
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.
Jul 12 2010
next sibling parent reply bearophile <bearophileHUGS lycos.com> writes:
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
parent reply torhu <no spam.invalid> writes:
On 13.07.2010 00:09, bearophile wrote:
 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

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.
Jul 12 2010
parent bearophile <bearophileHUGS lycos.com> writes:
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
prev sibling parent "Nick Sabalausky" <a a.a> writes:
"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
prev sibling next sibling parent Jonathan M Davis <jmdavisprog gmail.com> writes:
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
prev sibling next sibling parent Jonathan M Davis <jmdavisprog gmail.com> writes:
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
Jul 12 2010
prev sibling next sibling parent Jonathan M Davis <jmdavisprog gmail.com> writes:
On Monday, July 12, 2010 15:40:24 torhu wrote:
 On 13.07.2010 00:09, bearophile wrote:
 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

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.

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 Davis
Jul 12 2010
prev sibling parent Jonathan M Davis <jmdavisprog gmail.com> writes:
On Monday, July 12, 2010 18:15:04 Nick Sabalausky wrote:
 "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?

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 Davis
Jul 12 2010