August 24, 2013 Re: DIP44: scope(class) and scope(struct) | ||||
---|---|---|---|---|
| ||||
Posted in reply to Simen Kjaeraas | On Saturday, 24 August 2013 at 08:34:10 UTC, Simen Kjaeraas wrote:
> On Sat, 24 Aug 2013 03:34:32 +0200, Ramon <spam@thanks.no> wrote:
>
>> What, for instance, if I aquire 10 resources in ctor and during normal programm execution cleanup 7 of them, so only some are left for dtor?
>
> Then they don't have the same lifetime as the object, and scope(this)
> would be the wrong tool for the job.
Understood. But there *are* objects with different lifetimes. If, for instance, I aquire some "nice" objects (GC'd, properly through D) - and - some "dirty" malloced stuff (not talking about externals).
Thinking about it I also dislike a special thing that also feels and shows a special thing (namely "this" rather than e.g. "failure").
"Holy rule": No special cases if any possible but everything in a consistent way. If there are special cases, somehow make them not look like it; try to make them look consistent. The scope mechanism deals with success, with failure and with exit. That's great, that's sufficient. That's how it should at least *look* like in ctors, too.
Another inconsistency: If this mechanism is to "guard" on class level, it should be there, at class level and not in a function like entity (albeit the rather special "function" this).
Maybe I'm overly shy but I feel that logical consistency is a major issue to be a good language and for safety, too.
|
August 24, 2013 Re: DIP44: scope(class) and scope(struct) | ||||
---|---|---|---|---|
| ||||
Posted in reply to H. S. Teoh | On Saturday, 24 August 2013 at 00:45:46 UTC, H. S. Teoh wrote:
> I've written up a proposal to solve the partially-constructed object
> problem[*] in D in a very nice way by extending scope guards:
[snip]
What about a language rule, that for every type T destroy(t) must be valid if t == T.init?
Couldn't this problem be solved by using RAII and destructors? You would (only) need to make sure that every member is either correctly initialised or T.init.
|
August 24, 2013 Re: DIP44: scope(class) and scope(struct) | ||||
---|---|---|---|---|
| ||||
Posted in reply to H. S. Teoh | 24-Aug-2013 04:44, H. S. Teoh пишет: > I've written up a proposal to solve the partially-constructed object > problem[*] in D in a very nice way by extending scope guards: > > http://wiki.dlang.org/DIP44 > > Destroy! ;-) > Instead of introducing extra mess into an already tricky ctor/dtor situation. (Just peek at past issues with dtors not being called, being called at wrong time, etc.) I'd say just go RAII in bits and pieces. Unlike scope, there it works just fine as it has the right kind of lifetime from the get go. In function scope (where scope(exit/success/failure) shines) RAII actually sucks as it may prolong the object lifetime I you are not careful to tightly wrap it into { }. Start with this: class C { Resource1 res1; Resource2 res2; Resource3 res3; this() { res1 = acquireResource!1(); res2 = acquireResource!2(); res3 = acquireResource!3(); } ~this() { res3.release(); res2.release(); res1.release(); } } Write a helper once: struct Handler(alias acquire, alias release) { alias Resource = typeof(acquire()); Resource resource; this(int dummy) //OMG when 0-argument ctor becomes usable? { resource = acquire(); } static auto acquire() { return Handler(0); //ditto } ~this() { release(resource); } alias this resource; } Then: class C{ Handler!(acquireResource!1, (r){ r.release(); }) res1; Handler!(acquireResource!2, (r){ r.release(); }) res2; Handler!(acquireResource!3, (r){ r.release(); }) res3; this(){ res1 = typeof(res1).acquire(); res2 = typeof(res2).acquire(); res3 = typeof(res3).acquire(); } } There are many more alternatives on how to auto-generate RAII helpers. The point is we can easily do so, after all our compile-time kung-fu is strong. -- Dmitry Olshansky |
August 24, 2013 Re: DIP44: scope(class) and scope(struct) | ||||
---|---|---|---|---|
| ||||
Posted in reply to H. S. Teoh | On 8/23/2013 5:44 PM, H. S. Teoh wrote:
> I've written up a proposal to solve the partially-constructed object
> problem[*] in D in a very nice way by extending scope guards:
>
> http://wiki.dlang.org/DIP44
Not a bad idea, but it has some issues:
1. scope(failure) takes care of most of it already
2. I'm not sure this is a problem that needs solving, as the DIP points out, these issues are already easily dealt with. We should be conservative about adding more syntax.
3. What if the destructor needs to do more than just unwind the transactions? Where does that code fit in?
4. The worst issue is the DIP assumes there is only one constructor, from which the destructor is inferred. What if there is more than one constructor?
|
August 24, 2013 Re: DIP44: scope(class) and scope(struct) | ||||
---|---|---|---|---|
| ||||
On Sat, Aug 24, 2013 at 01:09:44PM -0700, H. S. Teoh wrote: [...] > On Sat, Aug 24, 2013 at 12:27:37PM -0700, Walter Bright wrote: [...] > > 4. The worst issue is the DIP assumes there is only one constructor, from which the destructor is inferred. What if there is more than one constructor? > > This is not a problem. If there is more than one constructor, then only those scope(this) statements in the ctor that were actually encountered will trigger when the object reaches the end of its lifetime. You already have to do this anyway, since if the ctor throws an Exception before completely constructing the object, only those scope(this) statements that have been encountered up to that point will be triggered, not all of them. Otherwise, you'd still have the partially-initialized object problem. [...] Argh, that was poorly worded. What I mean is this: struct S { this(int) { scope(this) writeln("A"); } this(float) { scope(this) writeln("B"); } } void fun1() { auto s = S(1); } // prints "A" void fun2() { auto s = S(1.0); } // prints "B" T -- Dogs have owners ... cats have staff. -- Krista Casada |
August 25, 2013 Re: DIP44: scope(class) and scope(struct) | ||||
---|---|---|---|---|
| ||||
Posted in reply to Dmitry Olshansky | On 8/24/13 10:31 AM, Dmitry Olshansky wrote:
> 24-Aug-2013 04:44, H. S. Teoh пишет:
>> I've written up a proposal to solve the partially-constructed object
>> problem[*] in D in a very nice way by extending scope guards:
>>
>> http://wiki.dlang.org/DIP44
>
>>
>> Destroy! ;-)
>>
>
> Instead of introducing extra mess into an already tricky ctor/dtor
> situation. (Just peek at past issues with dtors not being called, being
> called at wrong time, etc.)
>
> I'd say just go RAII in bits and pieces.
Agreed. DIP44 supports an idiom of handling multiple unprotected resources within the same object, which should be infrequent and generally unrecommended.
Andrei
|
August 25, 2013 Re: DIP44: scope(class) and scope(struct) | ||||
---|---|---|---|---|
| ||||
Posted in reply to Walter Bright | On Sat, Aug 24, 2013 at 06:50:11PM -0700, Walter Bright wrote: > On 8/24/2013 1:09 PM, H. S. Teoh wrote: > >On Sat, Aug 24, 2013 at 12:27:37PM -0700, Walter Bright wrote: [...] > >>Not a bad idea, but it has some issues: > >> > >>1. scope(failure) takes care of most of it already > >> > >>2. I'm not sure this is a problem that needs solving, as the DIP points out, these issues are already easily dealt with. We should be conservative about adding more syntax. > >> > >>3. What if the destructor needs to do more than just unwind the transactions? Where does that code fit in? > > > >I think it's unhelpful to conflate scope(this) with dtors. They are > >related, but -- and I guess I was a bit too strong about saying dtors > >are redundant -- if we allow both, then scope(this) can be reserved > >for transactions, and you can still put code in ~this() to do > >non-trivial cleanups. > > If you take out automatic dtor generation, I see no difference > between scope(this) and scope(failure). > > > >>4. The worst issue is the DIP assumes there is only one constructor, from which the destructor is inferred. What if there is more than one constructor? > > > >This is not a problem. If there is more than one constructor, then only those scope(this) statements in the ctor that were actually encountered will trigger when the object reaches the end of its lifetime. > > Do you mean multiple dtors will be generated, one for each constructor? No. Given this code: class C { this(int) { scope(this) writeln("A1"); scope(this) writeln("A2"); } this(float) { scope(this) writeln("B1"); scope(this) writeln("B2"); } } The lowered code looks something like this: class C { this(int) { scope(failure) __cleanup(); // scope(this) writeln("A1"); // Translated into: __cleanups ~= { writeln("A1"); }; // scope(this) writeln("A2"); // Translated into: __cleanups ~= { writeln("A2"); }; } this(float) { scope(failure) __cleanup(); // scope(this) writeln("B1"); // Translated into: __cleanups ~= { writeln("B1"); }; // scope(this) writeln("B2"); // Translated into: __cleanups ~= { writeln("B2"); }; } void delegate()[] __cleanups; void __cleanup() { foreach_reverse (f; __cleanups) f(); } ~this() { __cleanup(); } } So, there is only one dtor. But it automatically takes handles triggering the scope(this) statements of only the ctor that was actually used for creating the object. And if the ctor didn't successfully complete, it only triggers the scope(this) statements that have been encountered up to that point. Of course, the above lowered code is just to illustrate how it works. The actual implementation can be optimized by the compiler. E.g., if there is only one ctor and the sequence of scope(this) can be statically determined, then the cleanup statements can be put into the dtor directly without incurring the overhead of allocating the __cleanups array. If the cleanups don't need closure over ctor local variables, then they can just be function ptrs, not delegates. Only when you're doing something complicated do you actually need an array of delegates, which should be relatively rare. T -- Political correctness: socially-sanctioned hypocrisy. |
August 25, 2013 Re: DIP44: scope(class) and scope(struct) | ||||
---|---|---|---|---|
| ||||
Posted in reply to H. S. Teoh | On 8/24/2013 10:35 PM, H. S. Teoh wrote:
> On Sat, Aug 24, 2013 at 06:50:11PM -0700, Walter Bright wrote:
>> On 8/24/2013 1:09 PM, H. S. Teoh wrote:
>>> On Sat, Aug 24, 2013 at 12:27:37PM -0700, Walter Bright wrote:
>>> [...]
>>>> Not a bad idea, but it has some issues:
>>>>
>>>> 1. scope(failure) takes care of most of it already
>>>>
>>>> 2. I'm not sure this is a problem that needs solving, as the DIP
>>>> points out, these issues are already easily dealt with. We should be
>>>> conservative about adding more syntax.
>>>>
>>>> 3. What if the destructor needs to do more than just unwind the
>>>> transactions? Where does that code fit in?
>>>
>>> I think it's unhelpful to conflate scope(this) with dtors. They are
>>> related, but -- and I guess I was a bit too strong about saying dtors
>>> are redundant -- if we allow both, then scope(this) can be reserved
>>> for transactions, and you can still put code in ~this() to do
>>> non-trivial cleanups.
>>
>> If you take out automatic dtor generation, I see no difference
>> between scope(this) and scope(failure).
>>
>>
>>>> 4. The worst issue is the DIP assumes there is only one constructor,
>>> >from which the destructor is inferred. What if there is more than
>>>> one constructor?
>>>
>>> This is not a problem. If there is more than one constructor, then
>>> only those scope(this) statements in the ctor that were actually
>>> encountered will trigger when the object reaches the end of its
>>> lifetime.
>>
>> Do you mean multiple dtors will be generated, one for each
>> constructor?
>
> No. Given this code:
>
> class C {
> this(int) {
> scope(this) writeln("A1");
> scope(this) writeln("A2");
> }
>
> this(float) {
> scope(this) writeln("B1");
> scope(this) writeln("B2");
> }
> }
>
> The lowered code looks something like this:
>
> class C {
> this(int) {
> scope(failure) __cleanup();
>
> // scope(this) writeln("A1");
> // Translated into:
> __cleanups ~= { writeln("A1"); };
>
> // scope(this) writeln("A2");
> // Translated into:
> __cleanups ~= { writeln("A2"); };
> }
>
> this(float) {
> scope(failure) __cleanup();
>
> // scope(this) writeln("B1");
> // Translated into:
> __cleanups ~= { writeln("B1"); };
>
> // scope(this) writeln("B2");
> // Translated into:
> __cleanups ~= { writeln("B2"); };
> }
>
> void delegate()[] __cleanups;
>
> void __cleanup() {
> foreach_reverse (f; __cleanups)
> f();
> }
>
> ~this() {
> __cleanup();
> }
> }
>
> So, there is only one dtor. But it automatically takes handles
> triggering the scope(this) statements of only the ctor that was actually
> used for creating the object. And if the ctor didn't successfully
> complete, it only triggers the scope(this) statements that have been
> encountered up to that point.
>
> Of course, the above lowered code is just to illustrate how it works.
> The actual implementation can be optimized by the compiler. E.g., if
> there is only one ctor and the sequence of scope(this) can be statically
> determined, then the cleanup statements can be put into the dtor
> directly without incurring the overhead of allocating the __cleanups
> array. If the cleanups don't need closure over ctor local variables,
> then they can just be function ptrs, not delegates. Only when you're
> doing something complicated do you actually need an array of delegates,
> which should be relatively rare.
Your example, again, is of an auto-generated dtor. But you said earlier that wasn't the point.
Without the auto-generated dtor, it is just scope(failure), which is already a D feature.
I don't get it.
|
August 25, 2013 Re: DIP44: scope(class) and scope(struct) | ||||
---|---|---|---|---|
| ||||
Posted in reply to Walter Bright | On Sat, Aug 24, 2013 at 11:30:26PM -0700, Walter Bright wrote: [...] > Your example, again, is of an auto-generated dtor. But you said earlier that wasn't the point. > > Without the auto-generated dtor, it is just scope(failure), which is > already a D feature. > > I don't get it. It's a way to avoid code duplication in the dtor. If you just had scope(failure), you have to factor out a __cleanup method in order to do cleanup on both a failed ctor call or a dtor call, as I did in the example. I don't know if it will clarify things, but if you already have a dtor, then scope(this) just adds to it: class C { this() { scope(this) writeln("A"); scope(this) writeln("B"); } ~this() { writeln("In dtor"); } } This would get lowered to the equivalent of: class C { this() { scope(failure) __cleanup(); __cleanups ~= { writeln("A"); }; __cleanups ~= { writeln("B"); }; } void delegate()[] __cleanups; void __cleanup() { foreach_reverse (f; __cleanups) f(); } ~this() { writeln("In dtor"); __cleanup(); } } T -- Skill without imagination is craftsmanship and gives us many useful objects such as wickerwork picnic baskets. Imagination without skill gives us modern art. -- Tom Stoppard |
August 25, 2013 Re: DIP44: scope(class) and scope(struct) | ||||
---|---|---|---|---|
| ||||
Posted in reply to H. S. Teoh | On 8/25/2013 12:14 AM, H. S. Teoh wrote: [...] Ok, you had thrown me off by saying it wasn't about dtors. It is all about dtors :-) I think it is an implementable proposal. However, I tend to agree with Andrei's comment on the utility of it. |
Copyright © 1999-2021 by the D Language Foundation