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.


Reply via email to