www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - resource structs and default constuction

reply "Erik Smith" <erik cruiserhouse.com> writes:
I have a struct that uses RefCounted internally to manage a 
database environment.    It’s basically in the same boat as 
RefCounted itself in that it requires runtime construction.  Two 
things set it apart:

1) No-arg construction is required
2) The potential prevalence of this type in application code and 
getting default initialized by mistake.

This issue has been discussed before (see link below).  It would 
be nice if this could be addressed in the language at some point, 
but as far as I can tell, the best option I have now is to put 
version(assert) checks in all of my member functions to check for 
proper construction.

For the default construction issue, a factory function was 
suggested as an option.  I came up with the following approach:

struct Database {

      static Database create() {
           return Database("");
      }

      static Database create(A...)(auto ref A args) {
           return Database(args);
      }

      this(string uri) {…}
      this(Config config) {…}
      ...
}

The first static function provides a way to default construct.  
The 2nd static function provide uniformity with all of the other 
constructors that might be present.

Might this be a good idiom to apply for resource managing structs 
in general?

erik


previous forum discussion on default constructors:
http://forum.dlang.org/thread/fgldbozuneoldxjrwxje forum.dlang.org?page=5
Jun 01 2015
parent reply "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Monday, 1 June 2015 at 16:00:20 UTC, Erik Smith wrote:
 I have a struct that uses RefCounted internally to manage a 
 database environment.    It’s basically in the same boat as 
 RefCounted itself in that it requires runtime construction.  
 Two things set it apart:

 1) No-arg construction is required
 2) The potential prevalence of this type in application code 
 and getting default initialized by mistake.

 This issue has been discussed before (see link below).  It 
 would be nice if this could be addressed in the language at 
 some point, but as far as I can tell, the best option I have 
 now is to put version(assert) checks in all of my member 
 functions to check for proper construction.

 For the default construction issue, a factory function was 
 suggested as an option.  I came up with the following approach:

 struct Database {

      static Database create() {
           return Database("");
      }

      static Database create(A...)(auto ref A args) {
           return Database(args);
      }

      this(string uri) {…}
      this(Config config) {…}
      ...
 }

 The first static function provides a way to default construct.  
 The 2nd static function provide uniformity with all of the 
 other constructors that might be present.

 Might this be a good idiom to apply for resource managing 
 structs in general?

 erik


 previous forum discussion on default constructors:
 http://forum.dlang.org/thread/fgldbozuneoldxjrwxje forum.dlang.org?page=5
If you want to force that a constructor be called rather than using the init value, then you need to make the init value unusable by disabling this(). Then the caller _has_ to explicitly call a constructor or factory function. Regardless, if you want a constructor that takes no arguments for a struct, you need a factory function, since you can't have struct constructors with no parameters. - Jonathan M Davis
Jun 01 2015
parent reply "Erik Smith" <erik cruiserhouse.com> writes:
 If you want to force that a constructor be called rather than 
 using the init value, then you need to make the init value 
 unusable by  disabling this(). Then the caller _has_ to 
 explicitly call a constructor or factory function. Regardless, 
 if you want a constructor that takes no arguments for a struct, 
 you need a factory function, since you can't have struct 
 constructors with no parameters.
Good to know, but disabling this() also prevents the object from being a struct member, which is probably too restrictive. RefCounted wouldn't be workable with this, for example. I could still use feedback on whether the variadic create is problem free. erik
Jun 01 2015
parent reply "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Monday, 1 June 2015 at 19:13:46 UTC, Erik Smith wrote:
 If you want to force that a constructor be called rather than 
 using the init value, then you need to make the init value 
 unusable by  disabling this(). Then the caller _has_ to 
 explicitly call a constructor or factory function. Regardless, 
 if you want a constructor that takes no arguments for a 
 struct, you need a factory function, since you can't have 
 struct constructors with no parameters.
Good to know, but disabling this() also prevents the object from being a struct member, which is probably too restrictive.
It shouldn't. It should just make it so that the struct it's in has a disabled init value as well - which is annoying in its own right, but it's not as restrictive. Regardless, if you want to guarantee that a struct is constructed rather than having its init value used, you have no choice. Asserting like you suggested could be done, but you'd have to put assertions in every function but opAssign (if you declare it) that tested whether the struct had been properly initialized or not, and that's going to be at least somewhat error-prone. If disabling the init value is too restrictive though and you _can't_ use the init value, then it could work. Though actually, if you can't use the init value and can't disable it, instead of asserting, you could always just set the struct to the default that you want if it's not initialized. That's a bit annoying, but it would be an option. Also, based on the name of your struct - Database - it sounds like a handle for talking to the database, which is the sort of thing that I'd expect to be very long lived and for which you likely would only have a few actual objects allocated. And since you're talking about reference-counting it, clearly, you intend to put it on the heap. And if you're dealing with a long-lived object on the heap where there won't be very many of them, you might as well just use a class and avoid the whole default-constructor issue (since classes have them). Using the GC to allocate such objects is usually fine, and if it isn't, std.allocator should make allocating them easy. Having them be reference-counted would just increase their overhead, and what you're doing clearly isn't interacting well with struct semantics anyway, because you basically want to require that a default constructor be used.
  RefCounted wouldn't be workable with this, for example.
It might not be. I don't know. I'd have to look at it. Certainly, if it were, it wouldn't have an init value and would have be explicitly initialized. But Walter and Andrei already seem to have come to the conclusion that RefCounted isn't going to cut it anyway (much as it comes close) and are looking at adding a ref-counting mechanism to the language, in which case, that's what you'd want to use rather than RefCounted for ref-counted objects anyway. I have no idea when that will actually be implemented though. Regardless, I'd suggest that you seriously consider using a class instead like I suggested above.
 I could still use feedback on whether the variadic create is 
 problem free.
I don't see anything wrong with it, though I'd add a template constraint that verified that the constructor could be called with the given arguments. - Jonathan M Davis
Jun 01 2015
parent "Erik Smith" <erik cruiserhouse.com> writes:
 It shouldn't. It should just make it so that the struct it's in 
 has a  disabled init value as well - which is annoying in its 
 own right, but it's not as restrictive. Regardless, if you want 
 to guarantee that a struct is constructed rather than having 
 its init value used, you have no choice. Asserting like you 
 suggested could be done, but you'd have to put assertions in 
 every function but opAssign (if you declare it) that tested 
 whether the struct had been properly initialized or not, and 
 that's going to be at least somewhat error-prone. If  disabling 
 the init value is too restrictive though and you _can't_ use 
 the init value, then it could work.
Thanks for clarifying. RefCounted takes the assert approach and clearly has a strong case for avoiding the restrictions. I tend to think Database should avoid the restrictions as well.
 Though actually, if you can't use the init value and can't 
 disable it, instead of asserting, you could always just set the 
 struct to the default that you want if it's not initialized. 
 That's a bit annoying, but it would be an option.
Yes, for this particular object, the cost is probably not an issue and I should consider just initializing in every member function (except opAssign).
 Also, based on the name of your struct - Database - it sounds 
 like a handle for talking to the database, which is the sort of 
 thing that I'd expect to be very long lived and for which you 
 likely would only have a few actual objects allocated. And 
 since you're talking about reference-counting it, clearly, you 
 intend to put it on the heap. And if you're dealing with a 
 long-lived object on the heap where there won't be very many of 
 them, you might as well just use a class and avoid the whole 
 default-constructor issue (since classes have them). Using the 
 GC to allocate such objects is usually fine, and if it isn't, 
 std.allocator should make allocating them easy. Having them be 
 reference-counted would just increase their overhead, and what 
 you're doing clearly isn't interacting well with struct 
 semantics anyway, because you basically want to require that a 
 default constructor be used.
Yes your observations on the Database type are accurate. I'm trying to generally avoid the GC in this library, but I think that the non-deterministic class destruction in terms of the timing and sequence is probably a deal breaker in general. Also, I have at least one other object, Statement, that also needs to be struct member compatible but that does not require default construction, although all other objects come from factories. In this case also, though, deterministic destruction is critical. I agree also that there is the runtime cost (which is mitigated perhaps by no collection) but it's fine for the outer types. For inner types, like Row and Value, I'm currently using proxies to avoid GC and RC.
 RefCounted wouldn't be workable with this, for example.
It might not be. I don't know. I'd have to look at it. Certainly, if it were, it wouldn't have an init value and would have be explicitly initialized. But Walter and Andrei already seem to have come to the conclusion that RefCounted isn't going to cut it anyway (much as it comes close) and are looking at adding a ref-counting mechanism to the language, in which case, that's what you'd want to use rather than RefCounted for ref-counted objects anyway. I have no idea when that will actually be implemented though. Regardless, I'd suggest that you seriously consider using a class instead like I suggested above.
It will be interesting to see how that progresses. erik
Jun 01 2015