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