Discussion:
Destructor order
eles via Digitalmars-d-learn
2014-10-22 15:45:01 UTC
Permalink
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.
eles via Digitalmars-d-learn
2014-10-22 15:49:20 UTC
Permalink
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.
Regan Heath via Digitalmars-d-learn
2014-10-22 16:55:40 UTC
Permalink
Post by eles via Digitalmars-d-learn
{ //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/
via Digitalmars-d-learn
2014-10-22 17:13:34 UTC
Permalink
Post by Regan Heath via Digitalmars-d-learn
"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);
}
eles via Digitalmars-d-learn
2014-10-22 17:40:28 UTC
Permalink
On Wednesday, 22 October 2014 at 17:13:35 UTC, Ola Fosheim
On Wednesday, 22 October 2014 at 16:55:41 UTC, Regan Heath
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
eles via Digitalmars-d-learn
2014-10-22 17:44:30 UTC
Permalink
On Wednesday, 22 October 2014 at 17:13:35 UTC, Ola Fosheim
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.
Ali Çehreli via Digitalmars-d-learn
2014-10-22 18:03:59 UTC
Permalink
Post by eles via Digitalmars-d-learn
{ //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;
}
{ //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
eles via Digitalmars-d-learn
2014-10-22 22:59:02 UTC
Permalink
Post by Ali Çehreli via Digitalmars-d-learn
Extremely dangerous and very tricky! The solution? Define the
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.
Ali Çehreli via Digitalmars-d-learn
2014-10-22 23:02:35 UTC
Permalink
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
anonymous via Digitalmars-d-learn
2014-10-22 18:03:44 UTC
Permalink
Post by eles via Digitalmars-d-learn
{ //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
ketmar via Digitalmars-d-learn
2014-10-22 18:14:44 UTC
Permalink
On Wed, 22 Oct 2014 18:03:44 +0000
Post by anonymous via Digitalmars-d-learn
Post by eles via Digitalmars-d-learn
{ //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).
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.
via Digitalmars-d-learn
2014-10-22 18:44:26 UTC
Permalink
On Wednesday, 22 October 2014 at 18:14:54 UTC, ketmar via
Post by ketmar via Digitalmars-d-learn
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…
ketmar via Digitalmars-d-learn
2014-10-22 18:52:56 UTC
Permalink
On Wed, 22 Oct 2014 18:44:26 +0000
Post by via Digitalmars-d-learn
On Wednesday, 22 October 2014 at 18:14:54 UTC, ketmar via
Post by ketmar via Digitalmars-d-learn
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

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. ;-)
eles via Digitalmars-d-learn
2014-10-22 22:56:53 UTC
Permalink
Post by anonymous via Digitalmars-d-learn
`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...
eles via Digitalmars-d-learn
2014-10-22 23:05:19 UTC
Permalink
Post by anonymous via Digitalmars-d-learn
`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?
Ali Çehreli via Digitalmars-d-learn
2014-10-22 23:11:14 UTC
Permalink
Post by eles via Digitalmars-d-learn
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.
Post by eles via Digitalmars-d-learn
Why Scoped!A is convertible to A, then?
So that Scoped!A can conveniently be used as an A.
Post by eles via Digitalmars-d-learn
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
via Digitalmars-d-learn
2014-10-23 12:15:13 UTC
Permalink
Post by Ali Çehreli via Digitalmars-d-learn
Post by eles via Digitalmars-d-learn
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.
Post by eles via Digitalmars-d-learn
Why Scoped!A is convertible to A, then?
So that Scoped!A can conveniently be used as an A.
Post by eles via Digitalmars-d-learn
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.
Marco Leise via Digitalmars-d-learn
2014-10-23 13:12:49 UTC
Permalink
Am Thu, 23 Oct 2014 12:15:13 +0000
Post by via Digitalmars-d-learn
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?
--
Marco
via Digitalmars-d-learn
2014-10-23 15:44:26 UTC
Permalink
Post by Marco Leise via Digitalmars-d-learn
Am Thu, 23 Oct 2014 12:15:13 +0000
Post by via Digitalmars-d-learn
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
}

Continue reading on narkive:
Loading...