www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Destructor order

reply "eles" <eles eles.com> writes:
C++ versions:

	{ //displays ~C~B~A
	    A foo;
	    B bar;
	    C *caz = new C();
	    delete caz;
	}

	std::cout << std::endl;

	{ //displays ~C~B~A
	    std::unique_ptr<A> foo = std::make_unique<A>();
	    std::unique_ptr<B> bar = std::make_unique<B>();
	    C *caz = new C();
	    delete caz;
	}


D version:

	{ //displays ~A~B~C
	    A foo = scoped!(A)();
	    B bar = scoped!(B)();
	    C caz = new C();
	    destroy(caz);
	}

Why the objects are not destroyed in the inverse order of their 
creation? Case in point, destroying foo releases a lock for bar 
and caz.
Oct 22 2014
next sibling parent reply "eles" <eles eles.com> writes:
On Wednesday, 22 October 2014 at 15:45:02 UTC, eles wrote:

D version with structs:

	{ //display ~C~B~A
	    A foo;
	    B bar;
	    C *caz = new C();
	    delete caz;
	}

as expected.
Oct 22 2014
parent reply "Regan Heath" <regan netmail.co.nz> writes:
On Wed, 22 Oct 2014 16:49:20 +0100, eles <eles eles.com> wrote:

 On Wednesday, 22 October 2014 at 15:45:02 UTC, eles wrote:

 D version with structs:

 	{ //display ~C~B~A
 	    A foo;
 	    B bar;
 	    C *caz = new C();
 	    delete caz;
 	}

 as expected.
Structs are special, compare: http://dlang.org/struct.html#struct-destructor with: http://dlang.org/class.html#destructors Specifically: "The garbage collector is not guaranteed to run the destructor for all unreferenced objects. Furthermore, the order in which the garbage collector calls destructors for unreference objects is not specified. This means that when the garbage collector calls a destructor for an object of a class that has members that are references to garbage collected objects, those references may no longer be valid. This means that destructors cannot reference sub objects. This rule does not apply to auto objects or objects deleted with the DeleteExpression, as the destructor is not being run by the garbage collector, meaning all references are valid." Regan -- Using Opera's revolutionary email client: http://www.opera.com/mail/
Oct 22 2014
parent reply "Ola Fosheim =?UTF-8?B?R3LDuHN0YWQi?= writes:
On Wednesday, 22 October 2014 at 16:55:41 UTC, Regan Heath wrote:
 "The garbage collector is not guaranteed to run the destructor 
 for all unreferenced objects. Furthermore, the order in which 
 the garbage collector calls destructors for unreference objects 
 is not specified. This means that when the garbage collector 
 calls a destructor for an object of a class that has members 
 that are references to garbage collected objects, those 
 references may no longer be valid. This means that destructors 
 cannot reference sub objects. This rule does not apply to auto 
 objects or objects deleted with the DeleteExpression, as the 
 destructor is not being run by the garbage collector, meaning 
 all references are valid."
But Scoped!A is on the stack? So why wasn't the eles' destructor order in reverse if Scoped is a struct and calls explicit destroy(B) then destroy(A)? https://github.com/D-Programming-Language/phobos/blob/master/std/typecons.d#L4628 ~this() { .destroy(Scoped_payload); }
Oct 22 2014
next sibling parent "eles" <eles215 gzk.dot> writes:
On Wednesday, 22 October 2014 at 17:13:35 UTC, Ola Fosheim 
Grøstad wrote:
 On Wednesday, 22 October 2014 at 16:55:41 UTC, Regan Heath 
 wrote:
 But Scoped!A is on the stack?
Exactly. From Ali's book: "scoped() wraps the class object inside a struct and the destructor of that struct object destroys the class object when itself goes out of scope. " Destroy! ;) http://ddili.org/ders/d.en/destroy.html
Oct 22 2014
prev sibling parent "eles" <eles215 gzk.dot> writes:
On Wednesday, 22 October 2014 at 17:13:35 UTC, Ola Fosheim 
Grøstad wrote:
 On Wednesday, 22 October 2014 at 16:55:41 UTC, Regan Heath
 So why wasn't the eles' destructor order in reverse if Scoped 
 is a struct and calls explicit destroy(B) then destroy(A)?
Maybe it's the writeln() inside the destructor which behaves badly, albeit the same should have happened for structs too.
Oct 22 2014
prev sibling next sibling parent reply "anonymous" <anonymous example.com> writes:
On Wednesday, 22 October 2014 at 15:45:02 UTC, eles wrote:
 D version:

 	{ //displays ~A~B~C
 	    A foo = scoped!(A)();
 	    B bar = scoped!(B)();
 	    C caz = new C();
 	    destroy(caz);
 	}

 Why the objects are not destroyed in the inverse order of their 
 creation? Case in point, destroying foo releases a lock for bar 
 and caz.
`foo` should be a `Scoped!A`. When it's typed as `A`, the `Scoped!A` that is returned by `scoped`, is destructed immediately (and the reference leaks, I guess). Compare: import std.stdio; import std.typecons; class A {~this() {writeln("~A");}} class B {~this() {writeln("~B");}} class C {~this() {writeln("~C");}} void main() { { writeln("bad:"); A foo = scoped!(A)(); writeln("1"); B bar = scoped!(B)(); writeln("2"); } { writeln("good:"); auto foo = scoped!(A)(); writeln("1"); auto bar = scoped!(B)(); writeln("2"); } } prints: bad: ~A 1 ~B 2 good: 1 2 ~B ~A
Oct 22 2014
next sibling parent reply ketmar via Digitalmars-d-learn <digitalmars-d-learn puremagic.com> writes:
On Wed, 22 Oct 2014 18:03:44 +0000
anonymous via Digitalmars-d-learn <digitalmars-d-learn puremagic.com>
wrote:

 On Wednesday, 22 October 2014 at 15:45:02 UTC, eles wrote:
 D version:

 	{ //displays ~A~B~C
 	    A foo =3D scoped!(A)();
 	    B bar =3D scoped!(B)();
 	    C caz =3D new C();
 	    destroy(caz);
 	}

 Why the objects are not destroyed in the inverse order of their=20
 creation? Case in point, destroying foo releases a lock for bar=20
 and caz.
=20 `foo` should be a `Scoped!A`. When it's typed as `A`, the `Scoped!A` that is returned by `scoped`, is destructed immediately (and the reference leaks, I guess).
yes, this is the case. i believe that this should be explicitly documented in `scoped` ddocs. documentation examples using 'auto', but there is no mention that this is what *must* be used.
Oct 22 2014
parent reply "Ola Fosheim =?UTF-8?B?R3LDuHN0YWQi?= writes:
On Wednesday, 22 October 2014 at 18:14:54 UTC, ketmar via 
Digitalmars-d-learn wrote:
 yes, this is the case. i believe that this should be explicitly
 documented in `scoped` ddocs. documentation examples using 
 'auto', but there is no mention that this is what *must* be 
 used.
This is too dangerous to be used in real programs…
Oct 22 2014
parent ketmar via Digitalmars-d-learn <digitalmars-d-learn puremagic.com> writes:
On Wed, 22 Oct 2014 18:44:26 +0000
via Digitalmars-d-learn <digitalmars-d-learn puremagic.com> wrote:

 On Wednesday, 22 October 2014 at 18:14:54 UTC, ketmar via=20
 Digitalmars-d-learn wrote:
 yes, this is the case. i believe that this should be explicitly
 documented in `scoped` ddocs. documentation examples using=20
 'auto', but there is no mention that this is what *must* be=20
 used.
This is too dangerous to be used in real programs=E2=80=A6
there is nothing really dangerous if the feature is used properly. i'm using `scoped` in my software without any troubles. yes, there might be better ways to do what `scoped` does, but for now `scoped` is... satisfactory. it just need some care. ;-)
Oct 22 2014
prev sibling next sibling parent "eles" <eles215 gzk.dot> writes:
On Wednesday, 22 October 2014 at 18:03:44 UTC, anonymous wrote:
 On Wednesday, 22 October 2014 at 15:45:02 UTC, eles wrote:
 `foo` should be a `Scoped!A`. When it's typed as `A`, the
 `Scoped!A` that is returned by `scoped`, is destructed
 immediately (and the reference leaks, I guess).
Just tell me that's a feature, not a bug...
Oct 22 2014
prev sibling parent reply "eles" <eles215 gzk.dot> writes:
On Wednesday, 22 October 2014 at 18:03:44 UTC, anonymous wrote:
 On Wednesday, 22 October 2014 at 15:45:02 UTC, eles wrote:
 D version:
 `foo` should be a `Scoped!A`. When it's typed as `A`, the
 `Scoped!A` that is returned by `scoped`, is destructed
 immediately (and the reference leaks, I guess).
And the compiler swallows this without even barking? Why Scoped!A is convertible to A, then? And what the resulting A-typed variable contains if the object gets destroyed. And what use for that content?
Oct 22 2014
parent reply =?UTF-8?B?QWxpIMOHZWhyZWxp?= <acehreli yahoo.com> writes:
On 10/22/2014 04:05 PM, eles wrote:

 And the compiler swallows this without even barking?
The compiler must obey an alias this inside Scoped. I've thinking for a way do disallow this but haven't been able to spend much time on it today.
 Why Scoped!A is convertible to A, then?
So that Scoped!A can conveniently be used as an A.
 And what the resulting A-typed variable contains if the object
 gets destroyed.
Note that the A is not the object but the class reference to it. The 'alias this' hands out a reference to the object that is on the stack. The reference is valid when that occurs. Then, the compiler destroys the temporary Scoped!A object as it should, leaving behind a dangling reference. There must be a way to disallow this. Ali
Oct 22 2014
parent reply "Marc =?UTF-8?B?U2Now7x0eiI=?= <schuetzm gmx.net> writes:
On Wednesday, 22 October 2014 at 23:11:14 UTC, Ali Çehreli wrote:
 On 10/22/2014 04:05 PM, eles wrote:

 And the compiler swallows this without even barking?
The compiler must obey an alias this inside Scoped. I've thinking for a way do disallow this but haven't been able to spend much time on it today.
 Why Scoped!A is convertible to A, then?
So that Scoped!A can conveniently be used as an A.
 And what the resulting A-typed variable contains if the object
 gets destroyed.
Note that the A is not the object but the class reference to it. The 'alias this' hands out a reference to the object that is on the stack. The reference is valid when that occurs. Then, the compiler destroys the temporary Scoped!A object as it should, leaving behind a dangling reference. There must be a way to disallow this.
Yet another use case for borrowing.
Oct 23 2014
parent reply Marco Leise <Marco.Leise gmx.de> writes:
Am Thu, 23 Oct 2014 12:15:13 +0000
schrieb "Marc Sch=C3=BCtz" <schuetzm gmx.net>:

 Yet another use case for borrowing.
Cool, how does it keep the original struct alive though, if it isn't stored anywhere? Or will it error out when you attempt to use the dangling pointer to the object? --=20 Marco
Oct 23 2014
parent "Marc =?UTF-8?B?U2Now7x0eiI=?= <schuetzm gmx.net> writes:
On Thursday, 23 October 2014 at 13:03:08 UTC, Marco Leise wrote:
 Am Thu, 23 Oct 2014 12:15:13 +0000
 schrieb "Marc Schütz" <schuetzm gmx.net>:

 Yet another use case for borrowing.
Cool, how does it keep the original struct alive though, if it isn't stored anywhere? Or will it error out when you attempt to use the dangling pointer to the object?
It needs to error out. Artificial life support for the original object would be too much magic for my taste. struct Scoped(T) if(is(T == class)) { private T _payload; // <insert constructor and destructor here> scope!this(T) borrow() { return _payload; } alias borrow this; } void foo() { A a = scoped!A(); // ERROR: cannot assign Scope!A to A scope b = scoped!A(); // ERROR: local `b` outlives temporary }
Oct 23 2014
prev sibling parent reply =?UTF-8?B?QWxpIMOHZWhyZWxp?= <acehreli yahoo.com> writes:
On 10/22/2014 08:45 AM, eles wrote:> C++ versions:
      { //displays ~C~B~A
          A foo;
          B bar;
          C *caz = new C();
          delete caz;
      }

      std::cout << std::endl;

      { //displays ~C~B~A
          std::unique_ptr<A> foo = std::make_unique<A>();
          std::unique_ptr<B> bar = std::make_unique<B>();
          C *caz = new C();
          delete caz;
      }


 D version:

      { //displays ~A~B~C
          A foo = scoped!(A)();
          B bar = scoped!(B)();
          C caz = new C();
          destroy(caz);
      }

 Why the objects are not destroyed in the inverse order of their
 creation? Case in point, destroying foo releases a lock for bar and caz.
I confirm this. If you put writeln() expressions inside typecons.scoped, you will realize that the destroys are happening right after each scoped line: constructed A destroying ~A constructed B destroying ~B ~C Extremely dangerous and very tricky! The solution? Define the objects as 'auto' (or 'const', etc.), which you want anyway: auto foo = scoped!(A)(); auto bar = scoped!(B)(); You do not want to be left with A or B. You want Scoped!A and Scoped!B. Ali
Oct 22 2014
parent reply "eles" <eles215 gzk.dot> writes:
On Wednesday, 22 October 2014 at 18:03:59 UTC, Ali Çehreli wrote:
 On 10/22/2014 08:45 AM, eles wrote:> C++ versions:
 Extremely dangerous and very tricky! The solution? Define the 
 objects as 'auto' (or 'const', etc.), which you want anyway:

     auto foo = scoped!(A)();
     auto bar = scoped!(B)();

 You do not want to be left with A or B. You want Scoped!A and 
 Scoped!B.
Write it on this page http://ddili.org/ders/d.en/destroy.html with red big font, please.
Oct 22 2014
parent =?UTF-8?B?QWxpIMOHZWhyZWxp?= <acehreli yahoo.com> writes:
On 10/22/2014 03:59 PM, eles wrote:

 Write it on this page http://ddili.org/ders/d.en/destroy.html with red
 big font, please.
Don't worry, it's already in my short todo list. ;) Ali
Oct 22 2014