DIP44: scope(class) and scope(struct)

H. S. Teoh hsteoh at quickfur.ath.cx
Mon Aug 26 14:30:09 PDT 2013


On Sun, Aug 25, 2013 at 12:18:27AM +0200, Tobias Pankrath wrote:
> On Saturday, 24 August 2013 at 20:11:14 UTC, H. S. Teoh wrote:
> >How would you use RAII to solve this problem? If I have a class:
> >
> >	class C {
> >		Resource1 res1;
> >		Resource2 res2;
> >		Resource3 res3;
> >		this() {
> >			...
> >		}
> >	}
> >
> >How would you write the ctor with RAII such that if it successfully
> >inits res1 and res2, but throws before it inits res3, then only res1
> >and res2 will be cleaned up?
> 
> Like someone else already proposed: Using a wrapper type W that
> releases the resources in it's destructor. W.init wouldn't release
> anything. So by definition (if I recall the rules correctly) every
> new instance of C would have res3 = Resource3.init prior to it's
> constructor called.
> 
> Now make sure that a) res3's destructor gets called (check) b)
> res3's destructor may be called on Resource3.init. That would be a
> new rule similar to 'no internal aliasing' and c) that every
> constructor of C either sets res3 to a destructable value or does
> not touch it at all ('transactional programming').
[...]

But don't you still need to manually cleanup in the case of ctor
failure? AFAIK, the dtor is not invoked on the partially-constructed
object if the ctor throws. So you'd still have to use scope(failure) to
manually release the resource.

To prove my point, here is some sample code that (tries to) use RAII to
cleanup:

	import std.stdio;
	
	struct Resource {
		int x = 0;
		this(int id) {
			x = id;
			writefln("Acquired resource %d", x);
		}
		~this() {
			if (x == 0)
				writefln("Empty resource, no cleanup");
			else
				writefln("Cleanup resource %d", x);
		}
	}
	
	struct S {
		Resource res1;
		Resource res2;
		Resource res3;
	
		this(int) {
			res1 = Resource(1);
			res2 = Resource(2);
			assert(res1.x == 1);
			assert(res2.x == 2);
	
			throw new Exception("ctor failed!");
			res3 = Resource(3);	// not reached
			assert(res3.x == 3);	// not reached
		}
	}
	
	void main() {
		try {
			auto s = S(123);
		} catch(Exception e) {
			writeln(e.msg);
		}
	}

Here is the program output:

	Acquired resource 1
	Empty resource, no cleanup
	Acquired resource 2
	Empty resource, no cleanup
	ctor failed!

As you can see, the two resources acquired in the partially-constructed
object did NOT get cleaned up. So, RAII doesn't work in this case.

The two interspersed cleanups were presumably cause by Resource.init
being destructed when res1 and res2 were assigned to. But after being
assigned to, res1 and res2's dtors were NOT invoked.

So I think my scope(this) idea has some merit, since it will correctly
handle this case. :)


T

-- 
Many open minds should be closed for repairs. -- K5 user


More information about the Digitalmars-d mailing list