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