DIP44: scope(class) and scope(struct)

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


On Mon, Aug 26, 2013 at 02:30:09PM -0700, H. S. Teoh wrote:
> 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. :)
[...]

P.S. If you comment out the throw line, you will see that the resources
are released correctly. So, RAII lets us handle automatic cleanup
without needing an explicit dtor, but it (currently) does NOT correctly
handle cleanup for the partially-constructed object case.

To solve this problem, basically the compiler will have to keep track of
which fields have been initialized already, and insert scope(failure)
statements for them in the ctor. But once you have that, you have
basically already implemented scope(this), so might as well go all the
way and give it a nice syntax.


T

-- 
Let's not fight disease by killing the patient. -- Sean 'Shaleh' Perry


More information about the Digitalmars-d mailing list