DIP44: scope(class) and scope(struct)

H. S. Teoh hsteoh at quickfur.ath.cx
Sat Aug 24 22:35:30 PDT 2013


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.


More information about the Digitalmars-d mailing list