DIP44: scope(class) and scope(struct)

H. S. Teoh hsteoh at quickfur.ath.cx
Tue Aug 27 07:25:23 PDT 2013


On Tue, Aug 27, 2013 at 04:01:57PM +0400, Dmitry Olshansky wrote:
> 27-Aug-2013 01:30, H. S. Teoh пишет:
> >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.
> 
> Fixed?
> 
> > 			auto r1 = Resource(1);
> > 			auto r2 = Resource(2);
> > 			assert(res1.x == 1);
> > 			assert(res2.x == 2);
> > 	
> > 			throw new Exception("ctor failed!");
> > 			auto r3 = Resource(3);	// not reached
> > 			assert(res3.x == 3);	// not reached
> 
> 			res1 = move(r1);
> 			res2 = move(r2);
> 			res3 = move(r3);
> 
> 
> Exception-safe code after all has to be exception safe.
> Unlike in C++ we have no explicit initialization of members (and
> strict order of this) hence no automatic partial cleanup.
[...]

What if move(r2) throws? Then res1 won't get cleaned up, because r1 has
already been nulled by the move.


T

-- 
Without outlines, life would be pointless.


More information about the Digitalmars-d mailing list