DIP44: scope(class) and scope(struct)

Dmitry Olshansky dmitry.olsh at gmail.com
Tue Aug 27 05:01:57 PDT 2013


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.

-- 
Dmitry Olshansky


More information about the Digitalmars-d mailing list