Partially constructed objects in various languages

H. S. Teoh hsteoh at quickfur.ath.cx
Fri Aug 23 14:58:13 PDT 2013


On Fri, Aug 23, 2013 at 01:39:05PM -0700, H. S. Teoh wrote:
> On Tue, Aug 20, 2013 at 04:55:21AM +0200, bearophile wrote:
> > Perhaps this thread deserves a D implementation with a small
> > explanation regarding D:
> > 
> > http://www.reddit.com/r/programming/comments/1kof0q/on_partiallyconstructed_objects/
> > 
> > (__ctWriteln is not yet available in D.)
> [...]
> 
> One issue he brought up that he didn't really address, was what to do if
> a ctor throws before fully initializing all fields. For example:
> 
> 	class A { ~this() { ... } }
> 	class B { ~this() { ... } }
> 	class C {
> 		A a;
> 		B b;
> 		this() {
> 			a = new A();
> 			if (someCondition)
> 				throw new Exception(...);
> 			b = new B();
> 		}
> 	}
> 
> The problem is, how would the compiler know to call A.~this, but not
> B.~this when the Exception is thrown?
[...]

Actually, a better example might be:

	class C {
		Resource1  res1;
		Resource2  res2;
		Resource3  res3;
		this() {
			res1 = acquireResource!1();
			res2 = acquireResource!2();
			if (someCondition)
				throw new Exception(...);
			res3 = acquireResource!3();
		}
		~this() {
			res3.release();
			res2.release();
			res1.release();
		}
	}

The problem is, once the Exception is thrown, what do you do? You can't
call the dtor (res3.release() is invalid because res3 hasn't been
acquired yet), but you do need to cleanup (res1 and res2 must be
released).

This is one area where the conventional ctor/dtor scheme does not work
very well. Essentially you have to uglify your code by making Resource1,
Resource2, Resource3 nullable, then in the dtor you check for null
before releasing it:

	class C {
		Nullable!Resource1 res1;
		... // etc.
		this() { /* as before */ }
		...
		~this() {
			if (res3 !is null) res3.release();
			if (res2 !is null) res2.release();
			if (res1 !is null) res1.release();
		}
	}

Or, as Adam Ruppe once did in his Terminal code, you implement a kind of
class-wide scope guard:

	class C {
		void delegate()[] cleanupFuncs;
		Resource1 res1;
		Resource2 res2;
		Resource3 res3;

		this() {
			res1 = acquireResource!1();
			cleanupFuncs ~= { res1.release(); };

			res2 = acquireResource!2();
			cleanupFuncs ~= { res2.release(); };

			res3 = acquireResource!3();
			cleanupFuncs ~= { res3.release(); };
		}

		~this {
			foreach_reverse(f; cleanupFuncs) {
				f();
			}
		}
	}

This has the nice property that, like scope guards, the cleanup sits
next to the initialization, so it's less prone to mistakes (e.g. ctor
gets changed to acquire a 4th resource, but the author forgot to add a
corresponding release() to the dtor). It also has the property that only
resources that are actually acquired will get cleaned up, making the
dtor safe to call if the ctor throws. So for example, if
acquireResource!3() throws an Exception, then only res1 and res2's
cleanup delegates are registered, so the dtor will only clean up res1
and res2 and leave res3 untouched, which is correct since res3 was never
initialized.

Hmm.

On second thoughts, maybe we should have language-support for this kind
of class-wide (or struct-wide) scope guard, and deprecate dtors, which
are unreliable anyways due to the GC. So maybe something like:

	class C {
		Resource1 res1;
		Resource2 res2;
		Resource3 res3;

		this() {
			res1 = acquireResource!1();
			scope(class) res1.release();

			res2 = acquireResource!2();
			scope(class) res2.release();

			res3 = acquireResource!3();
			scope(class) res3.release();
		}

		// no explicit dtor necessary, compiler supplies one for you
		// composed of all the scope(class) blocks encountered
		// in the ctor.
	}

I actually like this idea a lot! It addresses the partially-constructed
object problem in a nice way, and also extend scope guards to be
applicable in more situations. (It feels like such a pity that a clever
feature like scope guards would be used so little in D.)


T

-- 
Always remember that you are unique. Just like everybody else. -- despair.com


More information about the Digitalmars-d-learn mailing list